Пример #1
0
 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()
Пример #2
0
 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
Пример #3
0
 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
Пример #4
0
    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']
Пример #5
0
    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()
Пример #6
0
    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)
Пример #7
0
    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
Пример #8
0
    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()
Пример #9
0
 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
Пример #10
0
    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()
Пример #11
0
    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()
Пример #12
0
 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
Пример #13
0
 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()
Пример #14
0
    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)
Пример #15
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)
Пример #16
0
    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')
Пример #17
0
 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()
Пример #18
0
    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)
Пример #19
0
    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)
Пример #20
0
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
Пример #21
0
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
Пример #22
0
 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
Пример #23
0
    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
Пример #24
0
    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
Пример #25
0
    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()
Пример #26
0
    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
Пример #27
0
    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)
Пример #28
0
 def remote_user_color(self):
     return dump_tuple(get_theme().COLOR_REMOTE_USER)
Пример #29
0
    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
Пример #30
0
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'
Пример #31
0
 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()
Пример #32
0
 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)
Пример #33
0
 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
Пример #34
0
 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)
Пример #35
0
 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)
Пример #36
0
 def remote_user_color(self):
     return dump_tuple(get_theme().COLOR_REMOTE_USER)
Пример #37
0
 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()
Пример #38
0
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'
Пример #39
0
 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()