def callback(iq): if iq['type'] != 'result': if iq['error']['type'] == 'auth': self.core.information( 'You are not allowed to see the activity of this contact.', 'Error') else: self.core.information('Error retrieving the activity', 'Error') return seconds = iq['last_activity']['seconds'] status = iq['last_activity']['status'] from_ = iq['from'] msg = '\x19%s}The last activity of %s was %s ago%s' if not safeJID(from_).user: msg = '\x19%s}The uptime of %s is %s.' % ( dump_tuple(get_theme().COLOR_INFORMATION_TEXT), from_, common.parse_secs_to_str(seconds)) else: msg = '\x19%s}The last activity of %s was %s ago%s' % ( dump_tuple(get_theme().COLOR_INFORMATION_TEXT), from_, common.parse_secs_to_str(seconds), (' and his/her last status was %s' % status) if status else '', ) self.add_message(msg) self.core.refresh_window()
def user_left(self, status_message, from_nick): """ The user left the associated MUC """ self.deactivate() if not status_message: self.add_message( '\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room' % { 'nick': from_nick, 'spec': get_theme().CHAR_QUIT, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) }, typ=2) else: self.add_message( '\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room (%(status)s)"' % { 'nick': from_nick, 'spec': get_theme().CHAR_QUIT, 'status': status_message, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) }, typ=2) return self.core.current_tab() is self
def on_groupchat_subject(self, message): """ Triggered when the topic is changed. """ nick_from = message['mucnick'] room_from = message.get_mucroom() tab = self.get_tab_by_name(room_from, tabs.MucTab) subject = message['subject'] if subject is None or not tab: return if subject != tab.topic: # Do not display the message if the subject did not change or if we # receive an empty topic when joining the room. if nick_from: tab.add_message(_("\x19%(info_col)s}%(nick)s set the subject to: %(subject)s") % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'nick':nick_from, 'subject':subject}, time=None, typ=2) else: tab.add_message(_("\x19%(info_col)s}The subject is: %(subject)s") % {'subject':subject, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, time=None, typ=2) tab.topic = subject tab.topic_from = nick_from if self.get_tab_by_name(room_from, tabs.MucTab) is self.current_tab(): self.refresh_window()
def command_info(self): contact = roster[self.get_dest_jid()] jid = safeJID(self.get_dest_jid()) if contact: if jid.resource: resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: resource = None if resource: status = (_('Status: %s') % resource.status) if resource.status else '' self._text_buffer.add_message( "\x19%(info_col)s}Show: %(show)s, %(status)s\x19o" % { 'show': resource.show or 'available', 'status': status, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) }) return True else: self._text_buffer.add_message( "\x19%(info_col)s}No information available\x19o" % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}) return True
def command_smp(self, args): """ /otrsmp <ask|answer|abort> [question] [secret] """ if args is None or not args: return self.core.command_help('otrsmp') length = len(args) action = args.pop(0) if length == 2: question = None secret = args.pop(0).encode('utf-8') elif length == 3: question = args.pop(0).encode('utf-8') secret = args.pop(0).encode('utf-8') else: question = secret = None tab = self.api.current_tab() name = tab.name if isinstance(tab, DynamicConversationTab) and tab.locked_resource: name = safeJID(tab.name) name.resource = tab.locked_resource name = name.full format_dict = { 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'jid': name, 'bare_jid': safeJID(name).bare } ctx = self.get_context(name) if ctx.state != STATE_ENCRYPTED: self.api.information('The current conversation is not encrypted', 'Error') return if action == 'ask': ctx.in_smp = True ctx.smp_own = True if question: ctx.smpInit(secret, question) else: ctx.smpInit(secret) tab.add_message(SMP_INITIATED % format_dict, typ=0) elif action == 'answer': ctx.smpGotSecret(secret) elif action == 'abort': if ctx.in_smp: ctx.smpAbort() tab.add_message(SMP_ABORTED % format_dict, typ=0) self.core.refresh_window()
def make_message(txt, time, nickname, nick_color, history, user, identifier, str_time=None, highlight=False, old_message=None, revisions=0, jid=None, ack=None): """ Create a new Message object with parameters, check for /me messages, and delayed messages """ time = time or datetime.now() if txt.startswith('/me '): me = True txt = '\x19%s}%s' % (dump_tuple( get_theme().COLOR_ME_MESSAGE), txt[4:]) else: me = False if history: txt = txt.replace( '\x19o', '\x19o\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG)) str_time = time.strftime("%Y-%m-%d %H:%M:%S") else: if str_time is None: str_time = time.strftime("%H:%M:%S") else: str_time = '' msg = Message(txt='%s\x19o' % (txt.replace('\t', ' '), ), nick_color=nick_color, time=time, str_time=str_time, nickname=nickname, user=user, identifier=identifier, highlight=highlight, me=me, old_message=old_message, revisions=revisions, jid=jid, ack=ack) log.debug('Set message %s with %s.', identifier, msg) return msg
def on_conversation_say(self, msg, tab): """ On message sent """ if isinstance(tab, DynamicConversationTab) and tab.locked_resource: jid = safeJID(tab.name) jid.resource = tab.locked_resource name = jid.full else: name = tab.name jid = safeJID(tab.name) format_dict = { 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'jid': name, } ctx = None default_ctx = self.get_context(name) if isinstance(tab, DynamicConversationTab) and not tab.locked_resource: log.debug('Unlocked tab %s found, falling back to the first encrypted chat we find.', name) ctx = self.find_encrypted_context_with_matching(jid.bare) if ctx is None: ctx = default_ctx if ctx and ctx.state == STATE_ENCRYPTED: ctx.sendMessage(0, msg['body'].encode('utf-8')) if not tab.send_chat_state('active'): tab.send_chat_state('inactive', always_send=True) tab.add_message(msg['body'], nickname=self.core.own_nick or tab.own_nick, nick_color=get_theme().COLOR_OWN_NICK, identifier=msg['id'], jid=self.core.xmpp.boundjid, typ=ctx.log) # remove everything from the message so that it doesn’t get sent del msg['body'] del msg['replace'] del msg['html'] elif ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'): tab.add_message(MESSAGE_NOT_SENT % format_dict, typ=0) del msg['body'] del msg['replace'] del msg['html'] self.otr_start(tab, name, format_dict)
def features_checked(self, iq): "Features check callback" features = iq['disco_info'].get_features() or [] before = ('correct' in self.commands, self.remote_supports_attention, self.remote_supports_receipts) correct = self._feature_correct(features) attention = self._feature_attention(features) receipts = self._feature_receipts(features) if (correct, attention, receipts) == before and self.__initial_disco: return else: self.__initial_disco = True if not (correct or attention or receipts): return # don’t display anything ok = get_theme().CHAR_OK nope = get_theme().CHAR_EMPTY correct = ok if correct else nope attention = ok if attention else nope receipts = ok if receipts else nope msg = ('\x19%s}Contact supports: correction [%s], ' 'attention [%s], receipts [%s].') color = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) msg = msg % (color, correct, attention, receipts) self.add_message(msg, typ=0) self.core.refresh_window()
def features_checked(self, iq): "Features check callback" features = iq['disco_info'].get_features() or [] before = ('correct' in self.commands, self.remote_supports_attention, self.remote_supports_receipts) correct = self._feature_correct(features) attention = self._feature_attention(features) receipts = self._feature_receipts(features) if (correct, attention, receipts) == before and self.__initial_disco: return else: self.__initial_disco = True if not (correct or attention or receipts): return # don’t display anything ok = get_theme().CHAR_OK nope = get_theme().CHAR_EMPTY correct = ok if correct else nope attention = ok if attention else nope receipts = ok if receipts else nope msg = _('\x19%s}Contact supports: correction [%s], ' 'attention [%s], receipts [%s].') color = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) msg = msg % (color, correct, attention, receipts) self.add_message(msg, typ=0) self.core.refresh_window()
def on_status_codes(self, message): """ Handle groupchat messages with status codes. Those are received when a room configuration change occurs. """ room_from = message['from'] tab = self.get_tab_by_name(room_from, tabs.MucTab) status_codes = set([s.attrib['code'] for s in message.findall('{%s}x/{%s}status' % (tabs.NS_MUC_USER, tabs.NS_MUC_USER))]) if '101' in status_codes: self.information('Your affiliation in the room %s changed' % room_from, 'Info') elif tab and status_codes: show_unavailable = '102' in status_codes hide_unavailable = '103' in status_codes non_priv = '104' in status_codes logging_on = '170' in status_codes logging_off = '171' in status_codes non_anon = '172' in status_codes semi_anon = '173' in status_codes full_anon = '174' in status_codes modif = False if show_unavailable or hide_unavailable or non_priv or logging_off\ or non_anon or semi_anon or full_anon: tab.add_message('\x19%(info_col)s}Info: A configuration change not privacy-related occured.' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) modif = True if show_unavailable: tab.add_message('\x19%(info_col)s}Info: The unavailable members are now shown.' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) elif hide_unavailable: tab.add_message('\x19%(info_col)s}Info: The unavailable members are now hidden.' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) if non_anon: tab.add_message('\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) elif semi_anon: tab.add_message('\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) elif full_anon: tab.add_message('\x19%(info_col)s}Info: The room is now fully anonymous.' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) if logging_on: tab.add_message('\x191}Warning: \x19%(info_col)s}This room is publicly logged' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) elif logging_off: tab.add_message('\x19%(info_col)s}Info: This room is not logged anymore.' % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) if modif: self.refresh_window()
def setState(self, newstate): format_dict = { 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'normal': '\x19%s}' % dump_tuple(get_theme().COLOR_NORMAL_TEXT), 'jid': self.peer, 'bare_jid': safeJID(self.peer).bare } tab = self.core.get_tab_by_name(self.peer) if not tab: tab = self.core.get_tab_by_name(safeJID(self.peer).bare, DynamicConversationTab) if tab and not tab.locked_resource == safeJID(self.peer).resource: tab = None if self.state == STATE_ENCRYPTED: if newstate == STATE_ENCRYPTED and tab: log.debug('OTR conversation with %s refreshed', self.peer) if self.getCurrentTrust(): msg = OTR_REFRESH_TRUSTED % format_dict tab.add_message(msg, typ=self.log) else: msg = OTR_REFRESH_UNTRUSTED % format_dict tab.add_message(msg, typ=self.log) hl(tab) elif newstate == STATE_FINISHED or newstate == STATE_PLAINTEXT: log.debug('OTR conversation with %s finished', self.peer) if tab: tab.add_message(OTR_END % format_dict, typ=self.log) hl(tab) elif newstate == STATE_ENCRYPTED and tab: if self.getCurrentTrust(): tab.add_message(OTR_START_TRUSTED % format_dict, typ=self.log) else: format_dict['our_fpr'] = self.user.getPrivkey() format_dict['remote_fpr'] = self.getCurrentKey() tab.add_message(OTR_TUTORIAL % format_dict, typ=0) tab.add_message(OTR_START_UNTRUSTED % format_dict, typ=self.log) hl(tab) log.debug('Set encryption state of %s to %s', self.peer, states[newstate]) super(PoezioContext, self).setState(newstate) if tab: self.core.refresh_window() self.core.doupdate()
def lock(self, resource): """ Lock the tab to the resource. """ assert(resource) if resource != self.locked_resource: self.locked_resource = resource info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) jid_c = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) message = ('%(info)sConversation locked to ' '%(jid_c)s%(jid)s/%(resource)s%(info)s.') % { 'info': info, 'jid_c': jid_c, 'jid': self.name, 'resource': resource} self.add_message(message, typ=0) self.check_features()
def build_message(self, message, timestamp=False, nick_size=10): """ Build a list of lines from a message, without adding it to a list """ if message is None: # line separator return [None] txt = message.txt if not txt: return [] if len(message.str_time) > 8: default_color = (FORMAT_CHAR + dump_tuple(get_theme().COLOR_LOG_MSG) + '}') else: default_color = None ret = [] nick = truncate_nick(message.nickname, nick_size) offset = 0 if message.ack: if message.ack > 0: offset += poopt.wcswidth(get_theme().CHAR_ACK_RECEIVED) + 1 else: offset += poopt.wcswidth(get_theme().CHAR_NACK) + 1 if nick: offset += poopt.wcswidth(nick) + 2 # + nick + '> ' length if message.revisions > 0: offset += ceil(log10(message.revisions + 1)) if message.me: offset += 1 # '* ' before and ' ' after if timestamp: if message.str_time: offset += 1 + len(message.str_time) if get_theme().CHAR_TIME_LEFT and message.str_time: offset += 1 if get_theme().CHAR_TIME_RIGHT and message.str_time: offset += 1 lines = poopt.cut_text(txt, self.width - offset - 1) prepend = default_color if default_color else '' attrs = [] for line in lines: saved = Line(msg=message, start_pos=line[0], end_pos=line[1], prepend=prepend) attrs = parse_attrs(message.txt[line[0]:line[1]], attrs) if attrs: prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs) else: if default_color: prepend = default_color else: prepend = '' ret.append(saved) return ret
def unlock(self, from_=None): """ Unlock the tab from a resource. It is now “associated” with the bare jid. """ self.remote_wants_chatstates = None if self.locked_resource != None: self.locked_resource = None info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) jid_c = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) if from_: message = ('%(info)sConversation unlocked (received activity' ' from %(jid_c)s%(jid)s%(info)s).') % { 'info': info, 'jid_c': jid_c, 'jid': from_} self.add_message(message, typ=0) else: message = '%sConversation unlocked.' % info self.add_message(message, typ=0)
def on_blocked_message(self, message): """ When we try to send a message to a blocked contact """ tab = self.core.get_conversation_by_jid(message['from'], False) if not tab: log.debug('Received message from nonexistent tab: %s', message['from']) message = '\x19%(info_col)s}Cannot send message to %(jid)s: contact blocked' % { 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'jid': message['from'], } tab.add_message(message)
def make_message(txt, time, nickname, nick_color, history, user, identifier, str_time=None, highlight=False, old_message=None, revisions=0, jid=None, ack=None): """ Create a new Message object with parameters, check for /me messages, and delayed messages """ time = time or datetime.now() if txt.startswith('/me '): me = True txt = '\x19%s}%s' % (dump_tuple(get_theme().COLOR_ME_MESSAGE), txt[4:]) else: me = False if history: txt = txt.replace('\x19o', '\x19o\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG)) str_time = time.strftime("%Y-%m-%d %H:%M:%S") else: if str_time is None: str_time = time.strftime("%H:%M:%S") else: str_time = '' msg = Message( txt='%s\x19o'%(txt.replace('\t', ' '),), nick_color=nick_color, time=time, str_time=str_time, nickname=nickname, user=user, identifier=identifier, highlight=highlight, me=me, old_message=old_message, revisions=revisions, jid=jid, ack=ack) log.debug('Set message %s with %s.', identifier, msg) return msg
def build_message(self, message, timestamp=False): """ Build a list of lines from a message, without adding it to a list """ if message is None: # line separator return [None] txt = message.txt if not txt: return [] if len(message.str_time) > 8: default_color = (FORMAT_CHAR + dump_tuple(get_theme().COLOR_LOG_MSG) + '}') else: default_color = None ret = [] nick = truncate_nick(message.nickname) offset = 0 if message.ack: if message.ack > 0: offset += poopt.wcswidth(get_theme().CHAR_ACK_RECEIVED) + 1 else: offset += poopt.wcswidth(get_theme().CHAR_NACK) + 1 if nick: offset += poopt.wcswidth(nick) + 2 # + nick + '> ' length if message.revisions > 0: offset += ceil(log10(message.revisions + 1)) if message.me: offset += 1 # '* ' before and ' ' after if timestamp: if message.str_time: offset += 1 + len(message.str_time) if get_theme().CHAR_TIME_LEFT and message.str_time: offset += 1 if get_theme().CHAR_TIME_RIGHT and message.str_time: offset += 1 lines = poopt.cut_text(txt, self.width-offset-1) prepend = default_color if default_color else '' attrs = [] for line in lines: saved = Line(msg=message, start_pos=line[0], end_pos=line[1], prepend=prepend) attrs = parse_attrs(message.txt[line[0]:line[1]], attrs) if attrs: prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs) else: if default_color: prepend = default_color else: prepend = '' ret.append(saved) return ret
def command_help(self, args): """ /help [command_name] """ if not args: color = dump_tuple(get_theme().COLOR_HELP_COMMANDS) acc = [] buff = ['Global commands:'] for command in self.commands: if isinstance(self.commands[command], Command): acc.append(' \x19%s}%s\x19o - %s' % ( color, command, self.commands[command].short)) else: acc.append(' \x19%s}%s\x19o' % (color, command)) acc = sorted(acc) buff.extend(acc) acc = [] buff.append('Tab-specific commands:') commands = self.current_tab().commands for command in commands: if isinstance(commands[command], Command): acc.append(' \x19%s}%s\x19o - %s' % ( color, command, commands[command].short)) else: acc.append(' \x19%s}%s\x19o' % (color, command)) acc = sorted(acc) buff.extend(acc) msg = '\n'.join(buff) msg += _("\nType /help <command_name> to know what each command does") else: command = args[0].lstrip('/').strip() if command in self.current_tab().commands: tup = self.current_tab().commands[command] elif command in self.commands: tup = self.commands[command] else: self.information(_('Unknown command: %s') % command, 'Error') return if isinstance(tup, Command): msg = _('Usage: /%s %s\n' % (command, tup.usage)) msg += tup.desc else: msg = tup[1] self.information(msg, 'Help')
def user_rejoined(self, nick): """ The user (or at least someone with the same nick) came back in the MUC """ self.activate() self.check_features() tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) color = 3 if tab and config.get_by_tabname('display_user_color_in_join_part', self.general_jid): user = tab.get_user_by_name(nick) if user: color = dump_tuple(user.color) self.add_message( '\x194}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} joined the room' % { 'nick': nick, 'color': color, 'spec': get_theme().CHAR_JOIN, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) }, typ=2) return self.core.current_tab() is self
def rename_user(self, old_nick, new_nick): """ The user changed her nick in the corresponding muc: update the tab’s name and display a message. """ self.add_message( '\x193}%(old)s\x19%(info_col)s} is now known as \x193}%(new)s' % { 'old': old_nick, 'new': new_nick, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) }, typ=2) new_jid = safeJID(self.name).bare + '/' + new_nick self.name = new_jid return self.core.current_tab() is self
def callback(iq): if iq['type'] != 'result': if iq['error']['type'] == 'auth': self.core.information('You are not allowed to see the activity of this contact.', 'Error') else: self.core.information('Error retrieving the activity', 'Error') return seconds = iq['last_activity']['seconds'] status = iq['last_activity']['status'] from_ = iq['from'] msg = '\x19%s}The last activity of %s was %s ago%s' if not safeJID(from_).user: msg = '\x19%s}The uptime of %s is %s.' % ( dump_tuple(get_theme().COLOR_INFORMATION_TEXT), from_, common.parse_secs_to_str(seconds)) else: msg = '\x19%s}The last activity of %s was %s ago%s' % ( dump_tuple(get_theme().COLOR_INFORMATION_TEXT), from_, common.parse_secs_to_str(seconds), (' and his/her last status was %s' % status) if status else '',) self.add_message(msg) self.core.refresh_window()
def user_rejoined(self, nick): """ The user (or at least someone with the same nick) came back in the MUC """ self.activate() self.check_features() tab = self.core.get_tab_by_name(safeJID(self.name).bare, MucTab) color = 3 if tab and config.get_by_tabname('display_user_color_in_join_part', self.general_jid): user = tab.get_user_by_name(nick) if user: color = dump_tuple(user.color) self.add_message('\x194}%(spec)s \x19%(color)s}%(nick)s\x19%(info_col)s} joined the room' % {'nick':nick, 'color': color, 'spec':get_theme().CHAR_JOIN, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) return self.core.current_tab() is self
def remote_wants_chatstates(self, value): old_value = self._remote_wants_chatstates self._remote_wants_chatstates = value if (old_value is None and value != None) or \ (old_value != value and value != None): ok = get_theme().CHAR_OK nope = get_theme().CHAR_EMPTY support = ok if value else nope if value: msg = _('\x19%s}Contact supports chat states [%s].') else: msg = _('\x19%s}Contact does not support chat states [%s].') color = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) msg = msg % (color, support) self.add_message(msg, typ=0) self.core.refresh_window()
def remote_wants_chatstates(self, value): old_value = self._remote_wants_chatstates self._remote_wants_chatstates = value if (old_value is None and value != None) or \ (old_value != value and value != None): ok = get_theme().CHAR_OK nope = get_theme().CHAR_EMPTY support = ok if value else nope if value: msg = '\x19%s}Contact supports chat states [%s].' else: msg = '\x19%s}Contact does not support chat states [%s].' color = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) msg = msg % (color, support) self.add_message(msg, typ=0) self.core.refresh_window()
def command_info(self): contact = roster[self.get_dest_jid()] jid = safeJID(self.get_dest_jid()) if contact: if jid.resource: resource = contact[jid.full] else: resource = contact.get_highest_priority_resource() else: resource = None if resource: status = ('Status: %s' % resource.status) if resource.status else '' self._text_buffer.add_message("\x19%(info_col)s}Show: %(show)s, %(status)s\x19o" % { 'show': resource.show or 'available', 'status': status, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}) return True else: self._text_buffer.add_message("\x19%(info_col)s}No information available\x19o" % {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}) return True
def on_conversation_msg(self, msg, tab): """ Message received """ format_dict = { 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'jid': msg['from'] } try: ctx = self.get_context(msg['from']) txt, tlvs = ctx.receiveMessage(msg["body"].encode('utf-8')) # SMP if tlvs: self.handle_tlvs(tlvs, ctx, tab, format_dict) except UnencryptedMessage as err: # received an unencrypted message inside an OTR session self.unencrypted_message_received(err, ctx, msg, tab, format_dict) self.otr_start(tab, tab.name, format_dict) return except NotOTRMessage as err: # ignore non-otr messages # if we expected an OTR message, we would have # got an UnencryptedMesssage # but do an additional check because of a bug with potr and py3k if ctx.state != STATE_PLAINTEXT or ctx.getPolicy('REQUIRE_ENCRYPTION'): self.unencrypted_message_received(err, ctx, msg, tab, format_dict) self.otr_start(tab, tab.name, format_dict) return except ErrorReceived as err: # Received an OTR error format_dict['err'] = err.args[0].error.decode('utf-8', errors='replace') tab.add_message(OTR_ERROR % format_dict, typ=0) del msg['body'] del msg['html'] hl(tab) self.core.refresh_window() return except NotEncryptedError as err: # Encrypted message received, but unreadable as we do not have # an OTR session in place. text = MESSAGE_UNREADABLE % format_dict tab.add_message(text, jid=msg['from'], typ=0) hl(tab) del msg['body'] del msg['html'] self.core.refresh_window() return except crypt.InvalidParameterError: # Malformed OTR payload and stuff text = MESSAGE_INVALID % format_dict tab.add_message(text, jid=msg['from'], typ=0) hl(tab) del msg['body'] del msg['html'] self.core.refresh_window() return except Exception: # Unexpected error import traceback exc = traceback.format_exc() format_dict['exc'] = exc tab.add_message(POTR_ERROR % format_dict, typ=0) log.error('Unspecified error in the OTR plugin', exc_info=True) return # No error, proceed with the message self.encrypted_message_received(msg, ctx, tab, txt)
def test_dump_tuple(): assert dump_tuple((1, 2)) == '1,2' assert dump_tuple((1, )) == '1' assert dump_tuple((1, 2, 'u')) == '1,2,u'
def setState(self, newstate): color_jid = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) color_info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) tab = self.core.get_tab_by_name(self.peer) if not tab: tab = self.core.get_tab_by_name( safeJID(self.peer).bare, DynamicConversationTab) if tab and not tab.locked_resource == safeJID(self.peer).resource: tab = None if self.state == STATE_ENCRYPTED: if newstate == STATE_ENCRYPTED: log.debug('OTR conversation with %s refreshed', self.peer) if tab: if self.getCurrentTrust(): msg = _('%(info)sRefreshed \x19btrusted\x19o%(info)s' ' OTR conversation with %(jid_c)s%(jid)s') % { 'info': color_info, 'jid_c': color_jid, 'jid': self.peer } tab.add_message(msg, typ=self.log) else: msg = _('%(info)sRefreshed \x19buntrusted\x19o%(info)s' ' OTR conversation with %(jid_c)s%(jid)s' '%(info)s, key: \x19o%(key)s') % { 'jid': self.peer, 'key': self.getCurrentKey(), 'info': color_info, 'jid_c': color_jid } tab.add_message(msg, typ=self.log) hl(tab) elif newstate == STATE_FINISHED or newstate == STATE_PLAINTEXT: log.debug('OTR conversation with %s finished', self.peer) if tab: tab.add_message('%sEnded OTR conversation with %s%s' % (color_info, color_jid, self.peer), typ=self.log) hl(tab) else: if newstate == STATE_ENCRYPTED: if tab: if self.getCurrentTrust(): msg = _('%(info)sStarted a \x19btrusted\x19o%(info)s ' 'OTR conversation with %(jid_c)s%(jid)s') % { 'jid': self.peer, 'info': color_info, 'jid_c': color_jid } tab.add_message(msg, typ=self.log) else: msg = _( '%(info)sStarted an \x19buntrusted\x19o%(info)s' ' OTR conversation with %(jid_c)s%(jid)s' '%(info)s, key: \x19o%(key)s') % { 'jid': self.peer, 'key': self.getCurrentKey(), 'info': color_info, 'jid_c': color_jid } tab.add_message(msg, typ=self.log) hl(tab) log.debug('Set encryption state of %s to %s', self.peer, states[newstate]) super(PoezioContext, self).setState(newstate) if tab: self.core.refresh_window() self.core.doupdate()
def on_conversation_msg(self, msg, tab): color_jid = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) color_info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) try: ctx = self.get_context(msg['from']) txt, tlvs = ctx.receiveMessage(msg["body"].encode('utf-8')) except UnencryptedMessage as err: # received an unencrypted message inside an OTR session text = _('%(info)sThe following message from %(jid_c)s%(jid)s' '%(info)s was \x19bnot\x19o%(info)s encrypted:' '\x19o\n%(msg)s') % { 'info': color_info, 'jid_c': color_jid, 'jid': msg['from'], 'msg': err.args[0].decode('utf-8') } tab.add_message(text, jid=msg['from'], typ=0) del msg['body'] del msg['html'] hl(tab) self.core.refresh_window() return except ErrorReceived as err: # Received an OTR error text = _('%(info)sReceived the following error from ' '%(jid_c)s%(jid)s%(info)s:\x19o %(err)s') % { 'jid': msg['from'], 'err': err.args[0], 'info': color_info, 'jid_c': color_jid } tab.add_message(text, typ=0) del msg['body'] del msg['html'] hl(tab) self.core.refresh_window() return except NotOTRMessage as err: # ignore non-otr messages # if we expected an OTR message, we would have # got an UnencryptedMesssage # but do an additional check because of a bug with py3k if ctx.state != STATE_PLAINTEXT or ctx.getPolicy( 'REQUIRE_ENCRYPTION'): text = _('%(info)sThe following message from ' '%(jid_c)s%(jid)s%(info)s was \x19b' 'not\x19o%(info)s encrypted:\x19o\n%(msg)s') % { 'jid': msg['from'], 'msg': err.args[0].decode('utf-8'), 'info': color_info, 'jid_c': color_jid } tab.add_message(text, jid=msg['from'], typ=ctx.log) del msg['body'] del msg['html'] hl(tab) self.core.refresh_window() return return except NotEncryptedError as err: text = _('%(info)sAn encrypted message from %(jid_c)s%(jid)s' '%(info)s was received but is unreadable, as you are' ' not currently communicating privately.') % { 'info': color_info, 'jid_c': color_jid, 'jid': msg['from'] } tab.add_message(text, jid=msg['from'], typ=0) hl(tab) del msg['body'] del msg['html'] self.core.refresh_window() return except crypt.InvalidParameterError: tab.add_message( '%sThe message from %s%s%s could not be decrypted.' % (color_info, color_jid, msg['from'], color_info), jid=msg['from'], typ=0) hl(tab) del msg['body'] del msg['html'] self.core.refresh_window() return except: tab.add_message( '%sAn unspecified error in the OTR plugin occured' % color_info, typ=0) log.error('Unspecified error in the OTR plugin', exc_info=True) return # remove xhtml del msg['html'] del msg['body'] if not txt: return if isinstance(tab, PrivateTab): user = tab.parent_muc.get_user_by_name(msg['from'].resource) nick_color = None else: user = None nick_color = get_theme().COLOR_REMOTE_USER body = txt.decode() decode_entities = self.config.get_by_tabname('decode_entities', msg['from'].bare, default=True) decode_newlines = self.config.get_by_tabname('decode_newlines', msg['from'].bare, default=True) if self.config.get_by_tabname('decode_xhtml', msg['from'].bare, default=True): try: body = xhtml.xhtml_to_poezio_colors(body, force=True) except Exception: if decode_entities: body = html.unescape(body) if decode_newlines: body = body.replace('<br/>', '\n').replace('<br>', '\n') else: if decode_entities: body = html.unescape(body) if decode_newlines: body = body.replace('<br/>', '\n').replace('<br>', '\n') tab.add_message(body, nickname=tab.nick, jid=msg['from'], forced_user=user, typ=ctx.log, nick_color=nick_color) hl(tab) self.core.refresh_window() del msg['body']
def command_otr(self, arg): """ /otr [start|refresh|end|fpr|ourfpr] """ arg = arg.strip() tab = self.api.current_tab() name = tab.name color_jid = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) color_info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) color_normal = '\x19%s}' % dump_tuple(get_theme().COLOR_NORMAL_TEXT) if isinstance(tab, DynamicConversationTab) and tab.locked_resource: name = safeJID(tab.name) name.resource = tab.locked_resource name = name.full if arg == 'end': # close the session context = self.get_context(name) context.disconnect() if isinstance(tab, DynamicConversationTab) and not tab.locked_resource: ctx = self.find_encrypted_context_with_matching( safeJID(name).bare) ctx.disconnect() elif arg == 'start' or arg == 'refresh': otr = self.get_context(name) secs = self.config.get('timeout', 3) if isinstance(tab, DynamicConversationTab) and tab.locked_resource: was_locked = True else: was_locked = False def notify_otr_timeout(): nonlocal otr if isinstance(tab, DynamicConversationTab) and not was_locked: if tab.locked_resource: name = safeJID(tab.name) name.resource = tab.locked_resource name = name.full otr = self.get_context(name) if otr.state != STATE_ENCRYPTED: text = _('%(jid_c)s%(jid)s%(info)s did not enable' ' OTR after %(sec)s seconds.') % { 'jid': tab.name, 'info': color_info, 'jid_c': color_jid, 'sec': secs } tab.add_message(text, typ=0) self.core.refresh_window() if secs > 0: event = self.api.create_delayed_event(secs, notify_otr_timeout) self.api.add_timed_event(event) self.core.xmpp.send_message(mto=name, mtype='chat', mbody=self.contexts[name].sendMessage( 0, b'?OTRv?').decode()) text = _( '%(info)sOTR request to %(jid_c)s%(jid)s%(info)s sent.') % { 'jid': tab.name, 'info': color_info, 'jid_c': color_jid } tab.add_message(text, typ=0) elif arg == 'ourfpr': fpr = self.account.getPrivkey() text = _( '%(info)sYour OTR key fingerprint is %(norm)s%(fpr)s.') % { 'jid': tab.name, 'info': color_info, 'norm': color_normal, 'fpr': fpr } tab.add_message(text, typ=0) elif arg == 'fpr': if name in self.contexts: ctx = self.contexts[name] if ctx.getCurrentKey() is not None: text = _('%(info)sThe key fingerprint for %(jid_c)s' '%(jid)s%(info)s is %(norm)s%(fpr)s%(info)s.') % { 'jid': tab.name, 'info': color_info, 'norm': color_normal, 'jid_c': color_jid, 'fpr': ctx.getCurrentKey() } tab.add_message(text, typ=0) else: text = _('%(jid_c)s%(jid)s%(info)s has no' ' key currently in use.') % { 'jid': tab.name, 'info': color_info, 'jid_c': color_jid } tab.add_message(text, typ=0) elif arg == 'drop': # drop the privkey (and obviously, end the current conversations before that) for context in self.contexts.values(): if context.state not in (STATE_FINISHED, STATE_PLAINTEXT): context.disconnect() self.account.drop_privkey() tab.add_message('%sPrivate key dropped.' % color_info, typ=0) elif arg == 'trust': ctx = self.get_context(name) key = ctx.getCurrentKey() if key: fpr = key.cfingerprint() else: return if not ctx.getCurrentTrust(): ctx.setTrust(fpr, 'verified') self.account.saveTrusts() text = _('%(info)sYou added %(jid_c)s%(jid)s%(info)s with key ' '\x19o%(key)s%(info)s to your trusted list.') % { 'jid': ctx.trustName, 'key': key, 'info': color_info, 'jid_c': color_jid } tab.add_message(text, typ=0) elif arg == 'untrust': ctx = self.get_context(name) key = ctx.getCurrentKey() if key: fpr = key.cfingerprint() else: return if ctx.getCurrentTrust(): ctx.setTrust(fpr, '') self.account.saveTrusts() text = _( '%(info)sYou removed %(jid_c)s%(jid)s%(info)s with ' 'key \x19o%(key)s%(info)s from your trusted list.') % { 'jid': ctx.trustName, 'key': key, 'info': color_info, 'jid_c': color_jid } tab.add_message(text, typ=0) self.core.refresh_window()
def get_logs(self, jid, nb=10): """ Get the nb last messages from the log history for the given jid. Note that a message may be more than one line in these files, so this function is a little bit more complicated than “read the last nb lines”. """ if config.get_by_tabname('load_log', jid) <= 0: return if not config.get_by_tabname('use_log', jid): return if nb <= 0: return self.check_and_create_log_dir(jid, open_fd=False) try: fd = open(os.path.join(log_dir, jid), 'rb') except: log.error('Unable to open the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return if not fd: return # read the needed data from the file, we just search nb messages by # searching "\nM" nb times from the end of the file. We use mmap to # do that efficiently, instead of seek()s and read()s which are costly. with fd: try: m = mmap.mmap(fd.fileno(), 0, prot=mmap.PROT_READ) except Exception: # file probably empty log.error('Unable to mmap the log file for (%s)', os.path.join(log_dir, jid), exc_info=True) return pos = m.rfind(b"\nM") # start of messages begin with MI or MR, # after a \n # number of message found so far count = 0 while pos != -1 and count < nb-1: count += 1 pos = m.rfind(b"\nM", 0, pos) if pos == -1: # If we don't have enough lines in the file pos = 1 # 1, because we do -1 just on the next line # to get 0 (start of the file) lines = m[pos-1:].decode(errors='replace').splitlines() messages = [] color = '\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG) # now convert that data into actual Message objects idx = 0 while idx < len(lines): if lines[idx].startswith(' '): # should not happen ; skip idx += 1 log.debug('fail?') continue tup = parse_message_line(lines[idx]) idx += 1 if not tup or 7 > len(tup) > 10: # skip log.debug('format? %s', tup) continue time = [int(i) for index, i in enumerate(tup) if index < 6] message = {'lines': [], 'history': True, 'time': common.get_local_time(datetime(*time))} size = int(tup[6]) if len(tup) == 8: #info line message['lines'].append(color+tup[7]) else: # message line message['nickname'] = tup[7] message['lines'].append(color+tup[8]) while size != 0 and idx < len(lines): message['lines'].append(lines[idx][1:]) size -= 1 idx += 1 message['txt'] = '\n'.join(message['lines']) del message['lines'] messages.append(message) return messages
def command_set(self, args): """ /set [module|][section] <option> [value] """ if args is None or len(args) == 0: config_dict = config.to_dict() lines = [] theme = get_theme() for section_name, section in config_dict.items(): lines.append('\x19%(section_col)s}[%(section)s]\x19o' % { 'section': section_name, 'section_col': dump_tuple(theme.COLOR_INFORMATION_TEXT), }) for option_name, option_value in section.items(): lines.append('%s\x19%s}=\x19o%s' % (option_name, dump_tuple(theme.COLOR_REVISIONS_MESSAGE), option_value)) info = ('Current options:\n%s' % '\n'.join(lines), 'Info') elif len(args) == 1: option = args[0] value = config.get(option) if value is None and '=' in option: args = option.split('=', 1) info = ('%s=%s' % (option, value), 'Info') if len(args) == 2: if '|' in args[0]: plugin_name, section = args[0].split('|')[:2] if not section: section = plugin_name option = args[1] if not plugin_name in self.plugin_manager.plugins: file_name = self.plugin_manager.plugins_conf_dir file_name = os.path.join(file_name, plugin_name + '.cfg') plugin_config = PluginConfig(file_name, plugin_name) else: plugin_config = self.plugin_manager.plugins[plugin_name].config value = plugin_config.get(option, default='', section=section) info = ('%s=%s' % (option, value), 'Info') else: possible_section = args[0] if config.has_section(possible_section): section = possible_section option = args[1] value = config.get(option, section=section) info = ('%s=%s' % (option, value), 'Info') else: option = args[0] value = args[1] info = config.set_and_save(option, value) self.trigger_configuration_change(option, value) elif len(args) == 3: if '|' in args[0]: plugin_name, section = args[0].split('|')[:2] if not section: section = plugin_name option = args[1] value = args[2] if not plugin_name in self.plugin_manager.plugins: file_name = self.plugin_manager.plugins_conf_dir file_name = os.path.join(file_name, plugin_name + '.cfg') plugin_config = PluginConfig(file_name, plugin_name) else: plugin_config = self.plugin_manager.plugins[plugin_name].config info = plugin_config.set_and_save(option, value, section) else: if args[0] == '.': name = safeJID(self.current_tab().name).bare if not name: self.information(_('Invalid tab to use the "." argument.'), _('Error')) return section = name else: section = args[0] option = args[1] value = args[2] info = config.set_and_save(option, value, section) self.trigger_configuration_change(option, value) elif len(args) > 3: return self.command_help('set') self.call_for_resize() self.information(*info)
def user_left(self, status_message, from_nick): """ The user left the associated MUC """ self.deactivate() if not status_message: self.add_message(_('\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room') % {'nick':from_nick, 'spec':get_theme().CHAR_QUIT, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) else: self.add_message(_('\x191}%(spec)s \x193}%(nick)s\x19%(info_col)s} has left the room (%(status)s)"') % {'nick':from_nick, 'spec':get_theme().CHAR_QUIT, 'status': status_message, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) return self.core.current_tab() is self
def command_set(self, args): """ /set [module|][section] <option> [value] """ if args is None or len(args) == 0: config_dict = config.to_dict() lines = [] theme = get_theme() for section_name, section in config_dict.items(): lines.append( '\x19%(section_col)s}[%(section)s]\x19o' % { 'section': section_name, 'section_col': dump_tuple(theme.COLOR_INFORMATION_TEXT), }) for option_name, option_value in section.items(): lines.append( '%s\x19%s}=\x19o%s' % (option_name, dump_tuple( theme.COLOR_REVISIONS_MESSAGE), option_value)) info = ('Current options:\n%s' % '\n'.join(lines), 'Info') elif len(args) == 1: option = args[0] value = config.get(option) if value is None and '=' in option: args = option.split('=', 1) info = ('%s=%s' % (option, value), 'Info') if len(args) == 2: if '|' in args[0]: plugin_name, section = args[0].split('|')[:2] if not section: section = plugin_name option = args[1] if not plugin_name in self.plugin_manager.plugins: file_name = self.plugin_manager.plugins_conf_dir file_name = os.path.join(file_name, plugin_name + '.cfg') plugin_config = PluginConfig(file_name, plugin_name) else: plugin_config = self.plugin_manager.plugins[plugin_name].config value = plugin_config.get(option, default='', section=section) info = ('%s=%s' % (option, value), 'Info') else: possible_section = args[0] if config.has_section(possible_section): section = possible_section option = args[1] value = config.get(option, section=section) info = ('%s=%s' % (option, value), 'Info') else: option = args[0] value = args[1] info = config.set_and_save(option, value) self.trigger_configuration_change(option, value) elif len(args) == 3: if '|' in args[0]: plugin_name, section = args[0].split('|')[:2] if not section: section = plugin_name option = args[1] value = args[2] if not plugin_name in self.plugin_manager.plugins: file_name = self.plugin_manager.plugins_conf_dir file_name = os.path.join(file_name, plugin_name + '.cfg') plugin_config = PluginConfig(file_name, plugin_name) else: plugin_config = self.plugin_manager.plugins[plugin_name].config info = plugin_config.set_and_save(option, value, section) else: if args[0] == '.': name = safeJID(self.current_tab().name).bare if not name: self.information('Invalid tab to use the "." argument.', 'Error') return section = name else: section = args[0] option = args[1] value = args[2] info = config.set_and_save(option, value, section) self.trigger_configuration_change(option, value) elif len(args) > 3: return self.command_help('set') self.information(*info)
def command_otr(self, arg): """ /otr [start|refresh|end|fpr|ourfpr] """ args = common.shell_split(arg) if not args: return self.core.command_help('otr') action = args.pop(0) tab = self.api.current_tab() name = tab.name if isinstance(tab, DynamicConversationTab) and tab.locked_resource: name = safeJID(tab.name) name.resource = tab.locked_resource name = name.full format_dict = { 'jid_c': '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID), 'info': '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'normal': '\x19%s}' % dump_tuple(get_theme().COLOR_NORMAL_TEXT), 'jid': name, 'bare_jid': safeJID(name).bare } if action == 'end': # close the session context = self.get_context(name) context.disconnect() if isinstance(tab, DynamicConversationTab): ctx = self.find_encrypted_context_with_matching(safeJID(name).bare) while ctx is not None: ctx.disconnect() ctx = self.find_encrypted_context_with_matching(safeJID(name).bare) elif action == 'start' or action == 'refresh': self.otr_start(tab, name, format_dict) elif action == 'ourfpr': format_dict['fpr'] = self.account.getPrivkey() tab.add_message(OTR_OWN_FPR % format_dict, typ=0) elif action == 'fpr': if name in self.contexts: ctx = self.contexts[name] if ctx.getCurrentKey() is not None: format_dict['fpr'] = ctx.getCurrentKey() tab.add_message(OTR_REMOTE_FPR % format_dict, typ=0) else: tab.add_message(OTR_NO_FPR % format_dict, typ=0) elif action == 'drop': # drop the privkey (and obviously, end the current conversations before that) for context in self.contexts.values(): if context.state not in (STATE_FINISHED, STATE_PLAINTEXT): context.disconnect() self.account.drop_privkey() tab.add_message(KEY_DROPPED % format_dict, typ=0) elif action == 'trust': ctx = self.get_context(name) key = ctx.getCurrentKey() if key: fpr = key.cfingerprint() else: return if not ctx.getCurrentTrust(): format_dict['key'] = key ctx.setTrust(fpr, 'verified') self.account.saveTrusts() tab.add_message(TRUST_ADDED % format_dict, typ=0) elif action == 'untrust': ctx = self.get_context(name) key = ctx.getCurrentKey() if key: fpr = key.cfingerprint() else: return if ctx.getCurrentTrust(): format_dict['key'] = key ctx.setTrust(fpr, '') self.account.saveTrusts() tab.add_message(TRUST_REMOVED % format_dict, typ=0) self.core.refresh_window()
def get_logs(self, jid, nb=10): """ Get the nb last messages from the log history for the given jid. Note that a message may be more than one line in these files, so this function is a little bit more complicated than “read the last nb lines”. """ if config.get_by_tabname('load_log', jid) <= 0: return if not config.get_by_tabname('use_log', jid): return if nb <= 0: return self.check_and_create_log_dir(jid, open_fd=False) try: fd = open(os.path.join(log_dir, jid), 'rb') except FileNotFoundError: log.info('Non-existing log file (%s)', os.path.join(log_dir, jid), exc_info=True) return except OSError: log.error('Unable to open the log file (%s)', os.path.join(log_dir, jid), exc_info=True) return if not fd: return # read the needed data from the file, we just search nb messages by # searching "\nM" nb times from the end of the file. We use mmap to # do that efficiently, instead of seek()s and read()s which are costly. with fd: try: m = mmap.mmap(fd.fileno(), 0, prot=mmap.PROT_READ) except Exception: # file probably empty log.error('Unable to mmap the log file for (%s)', os.path.join(log_dir, jid), exc_info=True) return pos = m.rfind(b"\nM") # start of messages begin with MI or MR, # after a \n # number of message found so far count = 0 while pos != -1 and count < nb - 1: count += 1 pos = m.rfind(b"\nM", 0, pos) if pos == -1: # If we don't have enough lines in the file pos = 1 # 1, because we do -1 just on the next line # to get 0 (start of the file) lines = m[pos - 1:].decode(errors='replace').splitlines() messages = [] color = '\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG) # now convert that data into actual Message objects idx = 0 while idx < len(lines): if lines[idx].startswith(' '): # should not happen ; skip idx += 1 log.debug('fail?') continue tup = parse_message_line(lines[idx]) idx += 1 if not tup or 7 > len(tup) > 10: # skip log.debug('format? %s', tup) continue time = [int(i) for index, i in enumerate(tup) if index < 6] message = { 'lines': [], 'history': True, 'time': common.get_local_time(datetime(*time)) } size = int(tup[6]) if len(tup) == 8: #info line message['lines'].append(color + tup[7]) else: # message line message['nickname'] = tup[7] message['lines'].append(color + tup[8]) while size != 0 and idx < len(lines): message['lines'].append(lines[idx][1:]) size -= 1 idx += 1 message['txt'] = '\n'.join(message['lines']) del message['lines'] messages.append(message) return messages
def command_otr(self, arg): """ /otr [start|refresh|end|fpr|ourfpr] """ arg = arg.strip() tab = self.api.current_tab() name = tab.name color_jid = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) color_info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) color_normal = '\x19%s}' % dump_tuple(get_theme().COLOR_NORMAL_TEXT) if isinstance(tab, DynamicConversationTab) and tab.locked_resource: name = safeJID(tab.name) name.resource = tab.locked_resource name = name.full if arg == 'end': # close the session context = self.get_context(name) context.disconnect() if isinstance(tab, DynamicConversationTab) and not tab.locked_resource: ctx = self.find_encrypted_context_with_matching(safeJID(name).bare) ctx.disconnect() elif arg == 'start' or arg == 'refresh': otr = self.get_context(name) secs = self.config.get('timeout', 3) if isinstance(tab, DynamicConversationTab) and tab.locked_resource: was_locked = True else: was_locked = False def notify_otr_timeout(): nonlocal otr if isinstance(tab, DynamicConversationTab) and not was_locked: if tab.locked_resource: name = safeJID(tab.name) name.resource = tab.locked_resource name = name.full otr = self.get_context(name) if otr.state != STATE_ENCRYPTED: text = _('%(jid_c)s%(jid)s%(info)s did not enable' ' OTR after %(sec)s seconds.') % { 'jid': tab.name, 'info': color_info, 'jid_c': color_jid, 'sec': secs} tab.add_message(text, typ=0) self.core.refresh_window() if secs > 0: event = self.api.create_delayed_event(secs, notify_otr_timeout) self.api.add_timed_event(event) self.core.xmpp.send_message(mto=name, mtype='chat', mbody=self.contexts[name].sendMessage(0, b'?OTRv?').decode()) text = _('%(info)sOTR request to %(jid_c)s%(jid)s%(info)s sent.') % { 'jid': tab.name, 'info': color_info, 'jid_c': color_jid} tab.add_message(text, typ=0) elif arg == 'ourfpr': fpr = self.account.getPrivkey() text = _('%(info)sYour OTR key fingerprint is %(norm)s%(fpr)s.') % { 'jid': tab.name, 'info': color_info, 'norm': color_normal, 'fpr': fpr} tab.add_message(text, typ=0) elif arg == 'fpr': if name in self.contexts: ctx = self.contexts[name] if ctx.getCurrentKey() is not None: text = _('%(info)sThe key fingerprint for %(jid_c)s' '%(jid)s%(info)s is %(norm)s%(fpr)s%(info)s.') % { 'jid': tab.name, 'info': color_info, 'norm': color_normal, 'jid_c': color_jid, 'fpr': ctx.getCurrentKey()} tab.add_message(text, typ=0) else: text = _('%(jid_c)s%(jid)s%(info)s has no' ' key currently in use.') % { 'jid': tab.name, 'info': color_info, 'jid_c': color_jid} tab.add_message(text, typ=0) elif arg == 'drop': # drop the privkey (and obviously, end the current conversations before that) for context in self.contexts.values(): if context.state not in (STATE_FINISHED, STATE_PLAINTEXT): context.disconnect() self.account.drop_privkey() tab.add_message('%sPrivate key dropped.' % color_info, typ=0) elif arg == 'trust': ctx = self.get_context(name) key = ctx.getCurrentKey() if key: fpr = key.cfingerprint() else: return if not ctx.getCurrentTrust(): ctx.setTrust(fpr, 'verified') self.account.saveTrusts() text = _('%(info)sYou added %(jid_c)s%(jid)s%(info)s with key ' '\x19o%(key)s%(info)s to your trusted list.') % { 'jid': ctx.trustName, 'key': key, 'info': color_info, 'jid_c': color_jid} tab.add_message(text, typ=0) elif arg == 'untrust': ctx = self.get_context(name) key = ctx.getCurrentKey() if key: fpr = key.cfingerprint() else: return if ctx.getCurrentTrust(): ctx.setTrust(fpr, '') self.account.saveTrusts() text = _('%(info)sYou removed %(jid_c)s%(jid)s%(info)s with ' 'key \x19o%(key)s%(info)s from your trusted list.') % { 'jid': ctx.trustName, 'key': key, 'info': color_info, 'jid_c': color_jid} tab.add_message(text, typ=0) self.core.refresh_window()
def on_conversation_msg(self, msg, tab): color_jid = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) color_info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) try: ctx = self.get_context(msg['from']) txt, tlvs = ctx.receiveMessage(msg["body"].encode('utf-8')) except UnencryptedMessage as err: # received an unencrypted message inside an OTR session text = _('%(info)sThe following message from %(jid_c)s%(jid)s' '%(info)s was \x19bnot\x19o%(info)s encrypted:' '\x19o\n%(msg)s') % { 'info': color_info, 'jid_c': color_jid, 'jid': msg['from'], 'msg': err.args[0].decode('utf-8')} tab.add_message(text, jid=msg['from'], typ=0) del msg['body'] del msg['html'] hl(tab) self.core.refresh_window() return except ErrorReceived as err: # Received an OTR error text = _('%(info)sReceived the following error from ' '%(jid_c)s%(jid)s%(info)s:\x19o %(err)s') % { 'jid': msg['from'], 'err': err.args[0], 'info': color_info, 'jid_c': color_jid} tab.add_message(text, typ=0) del msg['body'] del msg['html'] hl(tab) self.core.refresh_window() return except NotOTRMessage as err: # ignore non-otr messages # if we expected an OTR message, we would have # got an UnencryptedMesssage # but do an additional check because of a bug with py3k if ctx.state != STATE_PLAINTEXT or ctx.getPolicy('REQUIRE_ENCRYPTION'): text = _('%(info)sThe following message from ' '%(jid_c)s%(jid)s%(info)s was \x19b' 'not\x19o%(info)s encrypted:\x19o\n%(msg)s') % { 'jid': msg['from'], 'msg': err.args[0].decode('utf-8'), 'info': color_info, 'jid_c': color_jid} tab.add_message(text, jid=msg['from'], typ=ctx.log) del msg['body'] del msg['html'] hl(tab) self.core.refresh_window() return return except NotEncryptedError as err: text = _('%(info)sAn encrypted message from %(jid_c)s%(jid)s' '%(info)s was received but is unreadable, as you are' ' not currently communicating privately.') % { 'info': color_info, 'jid_c': color_jid, 'jid': msg['from']} tab.add_message(text, jid=msg['from'], typ=0) hl(tab) del msg['body'] del msg['html'] self.core.refresh_window() return except crypt.InvalidParameterError: tab.add_message('%sThe message from %s%s%s could not be decrypted.' % (color_info, color_jid, msg['from'], color_info), jid=msg['from'], typ=0) hl(tab) del msg['body'] del msg['html'] self.core.refresh_window() return except: tab.add_message('%sAn unspecified error in the OTR plugin occured' % color_info, typ=0) log.error('Unspecified error in the OTR plugin', exc_info=True) return # remove xhtml del msg['html'] del msg['body'] if not txt: return if isinstance(tab, PrivateTab): user = tab.parent_muc.get_user_by_name(msg['from'].resource) nick_color = None else: user = None nick_color = get_theme().COLOR_REMOTE_USER body = txt.decode() decode_entities = self.config.get_by_tabname('decode_entities', msg['from'].bare, default=True) decode_newlines = self.config.get_by_tabname('decode_newlines', msg['from'].bare, default=True) if self.config.get_by_tabname('decode_xhtml', msg['from'].bare, default=True): try: body = xhtml.xhtml_to_poezio_colors(body, force=True) except Exception: if decode_entities: body = html.unescape(body) if decode_newlines: body = body.replace('<br/>', '\n').replace('<br>', '\n') else: if decode_entities: body = html.unescape(body) if decode_newlines: body = body.replace('<br/>', '\n').replace('<br>', '\n') tab.add_message(body, nickname=tab.nick, jid=msg['from'], forced_user=user, typ=ctx.log, nick_color=nick_color) hl(tab) self.core.refresh_window() del msg['body']
def setState(self, newstate): color_jid = '\x19%s}' % dump_tuple(get_theme().COLOR_MUC_JID) color_info = '\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT) tab = self.core.get_tab_by_name(self.peer) if not tab: tab = self.core.get_tab_by_name(safeJID(self.peer).bare, DynamicConversationTab) if tab and not tab.locked_resource == safeJID(self.peer).resource: tab = None if self.state == STATE_ENCRYPTED: if newstate == STATE_ENCRYPTED: log.debug('OTR conversation with %s refreshed', self.peer) if tab: if self.getCurrentTrust(): msg = _('%(info)sRefreshed \x19btrusted\x19o%(info)s' ' OTR conversation with %(jid_c)s%(jid)s') % { 'info': color_info, 'jid_c': color_jid, 'jid': self.peer } tab.add_message(msg, typ=self.log) else: msg = _('%(info)sRefreshed \x19buntrusted\x19o%(info)s' ' OTR conversation with %(jid_c)s%(jid)s' '%(info)s, key: \x19o%(key)s') % { 'jid': self.peer, 'key': self.getCurrentKey(), 'info': color_info, 'jid_c': color_jid} tab.add_message(msg, typ=self.log) hl(tab) elif newstate == STATE_FINISHED or newstate == STATE_PLAINTEXT: log.debug('OTR conversation with %s finished', self.peer) if tab: tab.add_message('%sEnded OTR conversation with %s%s' % ( color_info, color_jid, self.peer), typ=self.log) hl(tab) else: if newstate == STATE_ENCRYPTED: if tab: if self.getCurrentTrust(): msg = _('%(info)sStarted a \x19btrusted\x19o%(info)s ' 'OTR conversation with %(jid_c)s%(jid)s') % { 'jid': self.peer, 'info': color_info, 'jid_c': color_jid} tab.add_message(msg, typ=self.log) else: msg = _('%(info)sStarted an \x19buntrusted\x19o%(info)s' ' OTR conversation with %(jid_c)s%(jid)s' '%(info)s, key: \x19o%(key)s') % { 'jid': self.peer, 'key': self.getCurrentKey(), 'info': color_info, 'jid_c': color_jid} tab.add_message(msg, typ=self.log) hl(tab) log.debug('Set encryption state of %s to %s', self.peer, states[newstate]) super(PoezioContext, self).setState(newstate) if tab: self.core.refresh_window() self.core.doupdate()
def rename_user(self, old_nick, new_nick): """ The user changed her nick in the corresponding muc: update the tab’s name and display a message. """ self.add_message('\x193}%(old)s\x19%(info_col)s} is now known as \x193}%(new)s' % {'old':old_nick, 'new':new_nick, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, typ=2) new_jid = safeJID(self.name).bare+'/'+new_nick self.name = new_jid return self.core.current_tab() is self