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.parent_muc theme = get_theme() color = dump_tuple(theme.COLOR_REMOTE_USER) 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( '\x19%(join_col)s}%(spec)s \x19%(color)s}%(nick)s\x19' '%(info_col)s} joined the room' % { 'nick': nick, 'color': color, 'spec': theme.CHAR_JOIN, 'join_col': dump_tuple(theme.COLOR_JOIN_CHAR), 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) }, typ=2) return self.core.tabs.current_tab is self
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.presence 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_say(self, msg, tab): """ On message sent """ 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 = self.find_encrypted_context_with_matching(jid) default_ctx = self.get_context(name) if ctx is None: ctx = default_ctx if is_relevant(tab) and 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 is_relevant(tab) and ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'): warning_msg = MESSAGE_NOT_SENT % format_dict tab.add_message(warning_msg, typ=0) del msg['body'] del msg['replace'] del msg['html'] self.otr_start(tab, name, format_dict) elif not is_relevant(tab) and ctx and ( ctx.state == STATE_ENCRYPTED or ctx.getPolicy('REQUIRE_ENCRYPTION')): contact = roster[tab.name] res = [] if contact: res = [resource.jid for resource in contact.resources] help_msg = '' if res: help_msg = TAB_HELP_RESOURCE % ''.join( ('\n - /message %s' % jid) for jid in res) format_dict['help'] = help_msg warning_msg = INCOMPATIBLE_TAB % format_dict tab.add_message(warning_msg, typ=0) del msg['body'] del msg['replace'] del msg['html']
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 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 __init__(self, txt: str, time: Optional[datetime], nickname: Optional[str], nick_color: Optional[Tuple], history: bool, user: Optional[str], identifier: Optional[str], str_time: Optional[str] = None, highlight: bool = False, old_message: Optional['Message'] = None, revisions: int = 0, jid: Optional[str] = None, ack: int = 0) -> None: """ Create a new Message object with parameters, check for /me messages, and delayed messages """ time = time if time is not None else datetime.now() if txt.startswith('/me '): me = True txt = '\x19%s}%s\x19o' % (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 = '' self.txt = txt.replace('\t', ' ') + '\x19o' self.nick_color = nick_color self.time = time self.str_time = str_time self.nickname = nickname self.user = user self.identifier = identifier self.highlight = highlight self.me = me self.old_message = old_message self.revisions = revisions self.jid = jid self.ack = ack
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 rename_user(self, old_nick, user): """ The user changed her nick in the corresponding muc: update the tab’s name and display a message. """ self.add_message( '\x19%(nick_col)s}%(old)s\x19%(info_col)s} is now ' 'known as \x19%(nick_col)s}%(new)s' % { 'old': old_nick, 'new': user.nick, 'nick_col': dump_tuple(user.color), 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT) }, typ=2) new_jid = safeJID(self.name).bare + '/' + user.nick self.name = new_jid return self.core.tabs.current_tab is self
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: Optional[Message], timestamp: bool = False, nick_size: int = 10) -> List[Union[None, Line]]: """ 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 [] theme = get_theme() if len(message.str_time) > 8: default_color = ( FORMAT_CHAR + dump_tuple(theme.COLOR_LOG_MSG) + '}') # type: Optional[str] else: default_color = None ret = [] # type: List[Union[None, Line]] nick = truncate_nick(message.nickname, nick_size) offset = 0 if message.ack: if message.ack > 0: offset += poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1 else: offset += poopt.wcswidth(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 theme.CHAR_TIME_LEFT and message.str_time: offset += 1 if 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 = [] # type: List[str] 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 add_error(self, error_message): error = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_CHAR_NACK), error_message) self.add_message( error, highlight=True, nickname='Error', nick_color=get_theme().COLOR_ERROR_MSG, typ=2) self.core.refresh_window()
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 help(self, args): """ /help [command_name] """ if not args: color = dump_tuple(get_theme().COLOR_HELP_COMMANDS) acc = [] buff = ['Global commands:'] for name, command in enumerate(self.core.commands): if isinstance(command, Command): acc.append(' \x19%s}%s\x19o - %s' % ( color, name, command.short_desc)) else: acc.append(' \x19%s}%s\x19o' % (color, name)) acc = sorted(acc) buff.extend(acc) acc = [] buff.append('Tab-specific commands:') tab_commands = self.core.current_tab().commands for name, command in enumerate(tab_commands): if isinstance(command, Command): acc.append(' \x19%s}%s\x19o - %s' % ( color, name, command.short_desc)) else: acc.append(' \x19%s}%s\x19o' % (color, name)) 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() tab_commands = self.core.current_tab().commands if command in tab_commands: tup = tab_commands[command] elif command in self.core.commands: tup = self.core.commands[command] else: self.core.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.core.information(msg, 'Help')
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 update_status(self, status): old_status = self.__status if not (old_status.show != status.show or old_status.message != status.message): return self.__status = status hide_status_change = config.get_by_tabname('hide_status_change', safeJID(self.name).bare) now = datetime.now() dff = now - self.last_remote_message if hide_status_change > -1 and dff.total_seconds() > hide_status_change: return info_c = dump_tuple(get_theme().COLOR_INFORMATION_TEXT) nick = self.get_nick() remote = self.remote_user_color() msg = '\x19%(color)s}%(nick)s\x19%(info)s} changed: ' msg %= {'color': remote, 'nick': nick, 'info': info_c} if status.message != old_status.message and status.message: msg += 'status: %s, ' % status.message if status.show in SHOW_NAME: msg += 'show: %s, ' % SHOW_NAME[status.show] self.add_message(msg[:-2], typ=2)
def parse_log_lines(lines: List[str], jid: str) -> List[Dict[str, Any]]: """ Parse raw log lines into poezio log objects """ 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 log_item = parse_log_line(lines[idx], jid) idx += 1 if not isinstance(log_item, LogItem): log.debug('wrong log format? %s', log_item) continue message_lines = [] message = { 'history': True, 'time': common.get_local_time(log_item.time) } size = log_item.nb_lines if isinstance(log_item, LogInfo): message_lines.append(color + log_item.text) elif isinstance(log_item, LogMessage): message['nickname'] = log_item.nick message_lines.append(color + log_item.text) while size != 0 and idx < len(lines): message_lines.append(lines[idx][1:]) size -= 1 idx += 1 message['txt'] = '\n'.join(message_lines) messages.append(message) return messages
def parse_log_lines(lines: List[str]) -> List[Dict[str, Any]]: """ Parse raw log lines into poezio log objects """ 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 log_item = parse_log_line(lines[idx]) idx += 1 if not isinstance(log_item, LogItem): log.debug('wrong log format? %s', log_item) continue message_lines = [] message = { 'history': True, 'time': common.get_local_time(log_item.time) } size = log_item.nb_lines if isinstance(log_item, LogInfo): message_lines.append(color + log_item.text) elif isinstance(log_item, LogMessage): message['nickname'] = log_item.nick message_lines.append(color + log_item.text) while size != 0 and idx < len(lines): message_lines.append(lines[idx][1:]) size -= 1 idx += 1 message['txt'] = '\n'.join(message_lines) messages.append(message) return messages
async def _encrypt_wrapper(self, stanza: StanzaBase) -> Optional[StanzaBase]: """ Wrapper around _encrypt() to handle errors and display the message after encryption. """ try: # pylint: disable=unexpected-keyword-arg result = await self._encrypt(stanza, passthrough=True) except NothingToEncrypt: return stanza except Exception as exc: jid = stanza['to'] tab = self.core.tabs.by_name_and_class(jid, ChatTab) msg = ' \n\x19%s}Could not send message: %s' % ( dump_tuple(get_theme().COLOR_CHAR_NACK), exc, ) # XXX: check before commit. Do we not nack in MUCs? if not isinstance(tab, MucTab): tab.nack_message(msg, stanza['id'], stanza['from']) # TODO: display exceptions to the user properly log.error('Exception in encrypt:', exc_info=True) return None return result
def user_left(self, status_message, user): """ The user left the associated MUC """ self.deactivate() theme = get_theme() if config.get_by_tabname('display_user_color_in_join_part', self.general_jid): color = dump_tuple(user.color) else: color = dump_tuple(theme.COLOR_REMOTE_USER) if not status_message: self.add_message( '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' '%(nick)s\x19%(info_col)s} has left the room' % { 'nick': user.nick, 'spec': theme.CHAR_QUIT, 'nick_col': color, 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) }, typ=2) else: self.add_message( '\x19%(quit_col)s}%(spec)s \x19%(nick_col)s}' '%(nick)s\x19%(info_col)s} has left the room' ' (%(status)s)' % { 'status': status_message, 'nick': user.nick, 'spec': theme.CHAR_QUIT, 'nick_col': color, 'quit_col': dump_tuple(theme.COLOR_QUIT_CHAR), 'info_col': dump_tuple(theme.COLOR_INFORMATION_TEXT) }, typ=2) return self.core.tabs.current_tab is self
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 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() 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 log_item = parse_message_line(lines[idx]) idx += 1 if not isinstance(log_item, LogItem): log.debug('wrong log format? %s', log_item) continue message = { 'lines': [], 'history': True, 'time': common.get_local_time(log_item.time) } size = log_item.nb_lines if isinstance(log_item, LogInfo): message['lines'].append(color + log_item.text) elif isinstance(log_item, LogMessage): message['nickname'] = log_item.nick message['lines'].append(color + log_item.text) 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 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 proto_error = err.args[0].error # pylint: disable=no-member format_dict['err'] = proto_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 remote_user_color(self): return dump_tuple(get_theme().COLOR_REMOTE_USER)
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 len(tup) not in (8, 9): # skip log.debug('format? %s', tup) continue time = [int(i) for i in tup[: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 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 remote_user_color(self): user = self.parent_muc.get_user_by_name(safeJID(self.name).resource) if user: return dump_tuple(user.color) return super().remote_user_color()
def 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 plugin_name not in self.core.plugin_manager.plugins: file_name = self.core.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.core.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.core.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 plugin_name not in self.core.plugin_manager.plugins: file_name = self.core.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.core.plugin_manager.plugins[ plugin_name].config info = plugin_config.set_and_save(option, value, section) else: if args[0] == '.': name = safeJID(self.core.current_tab().name).bare if not name: self.core.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.core.trigger_configuration_change(option, value) elif len(args) > 3: return self.help('set') self.core.information(*info)
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.presence 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 __init__(self, txt: str, identifier: str = '', time: Optional[datetime] = None): txt = ('\x19%s}' % dump_tuple(get_theme().COLOR_INFORMATION_TEXT)) + txt super().__init__(txt=txt, identifier=identifier, time=time)
def 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 plugin_name not in self.core.plugin_manager.plugins: file_name = self.core.plugin_manager.plugins_conf_dir / ( plugin_name + '.cfg') plugin_config = PluginConfig(file_name, plugin_name) else: plugin_config = self.core.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.core.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 plugin_name not in self.core.plugin_manager.plugins: file_name = self.core.plugin_manager.plugins_conf_dir / ( plugin_name + '.cfg') plugin_config = PluginConfig(file_name, plugin_name) else: plugin_config = self.core.plugin_manager.plugins[ plugin_name].config info = plugin_config.set_and_save(option, value, section) else: if args[0] == '.': name = safeJID(self.core.tabs.current_tab.name).bare if not name: self.core.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.core.trigger_configuration_change(option, value) elif len(args) > 3: return self.help('set') self.core.information(*info)
def add_error(self, error_message): error = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_CHAR_NACK), error_message) self.add_message(error, highlight=True, nickname='Error', nick_color=get_theme().COLOR_ERROR_MSG, typ=2) self.core.refresh_window()