diff options
Diffstat (limited to 'poezio/tabs/rostertab.py')
-rw-r--r-- | poezio/tabs/rostertab.py | 459 |
1 files changed, 180 insertions, 279 deletions
diff --git a/poezio/tabs/rostertab.py b/poezio/tabs/rostertab.py index 6f43cca1..18334c20 100644 --- a/poezio/tabs/rostertab.py +++ b/poezio/tabs/rostertab.py @@ -14,44 +14,36 @@ import ssl from functools import partial from os import getenv, path from pathlib import Path -from typing import Dict, Callable +from typing import Dict, Callable, Union + +from slixmpp import JID, InvalidJID +from slixmpp.exceptions import IqError, IqTimeout -from poezio import common from poezio import windows -from poezio.common import safeJID, shell_split +from poezio.common import shell_split from poezio.config import config from poezio.contact import Contact, Resource from poezio.decorators import refresh_wrapper from poezio.roster import RosterGroup, roster from poezio.theming import get_theme, dump_tuple -from poezio.decorators import command_args_parser +from poezio.decorators import command_args_parser, deny_anonymous from poezio.core.structs import Command, Completion from poezio.tabs import Tab +from poezio.ui.types import InfoMessage log = logging.getLogger(__name__) -def deny_anonymous(func: Callable) -> Callable: - def wrap(self: 'RosterInfoTab', *args, **kwargs): - if self.core.xmpp.anon: - return self.core.information( - 'This command is not available for anonymous accounts.', - 'Info' - ) - return func(self, *args, **kwargs) - return wrap - - class RosterInfoTab(Tab): """ A tab, split in two, containing the roster and infos """ - plugin_commands = {} # type: Dict[str, Command] - plugin_keys = {} # type: Dict[str, Callable] + plugin_commands: Dict[str, Command] = {} + plugin_keys: Dict[str, Callable] = {} def __init__(self, core): Tab.__init__(self, core) - self.name = "Roster" + self._name = "Roster" self.v_separator = windows.VerticalSeparator() self.information_win = windows.TextWin() self.core.information_buffer.add_window(self.information_win) @@ -83,15 +75,6 @@ class RosterInfoTab(Tab): self.key_func["S"] = self.start_search_slow self.key_func["n"] = self.change_contact_name self.register_command( - 'deny', - self.command_deny, - usage='[jid]', - desc='Deny your presence to the provided JID (or the ' - 'selected contact in your roster), who is asking' - 'you to be in their roster.', - shortdesc='Deny a user your presence.', - completion=self.completion_deny) - self.register_command( 'name', self.command_name, usage='<jid> [name]', @@ -119,16 +102,6 @@ class RosterInfoTab(Tab): shortdesc='Remove a user from a group.', completion=self.completion_groupremove) self.register_command( - 'remove', - self.command_remove, - usage='[jid]', - desc='Remove the specified JID from your roster. This ' - 'will unsubscribe you from its presence, cancel ' - 'its subscription to yours, and remove the item ' - 'from your roster.', - shortdesc='Remove a user from your roster.', - completion=self.completion_remove) - self.register_command( 'export', self.command_export, usage='[/path/to/file]', @@ -226,50 +199,40 @@ class RosterInfoTab(Tab): completion=self.completion_cert_fetch) @property - def selected_row(self): + def selected_row(self) -> Union[Contact, Resource]: return self.roster_win.get_selected_row() @command_args_parser.ignored - def command_certs(self): + async def command_certs(self): """ /certs """ - - def cb(iq): - if iq['type'] == 'error': - self.core.information( - 'Unable to retrieve the certificate list.', 'Error') - return - certs = [] - for item in iq['sasl_certs']['items']: - users = '\n'.join(item['users']) - certs.append((item['name'], users)) - - if not certs: - return self.core.information('No certificates found', 'Info') - msg = 'Certificates:\n' - msg += '\n'.join( - ((' %s%s' % (item[0] + (': ' if item[1] else ''), item[1])) - for item in certs)) - self.core.information(msg, 'Info') - - self.core.xmpp.plugin['xep_0257'].get_certs(callback=cb, timeout=3) + try: + iq = await self.core.xmpp.plugin['xep_0257'].get_certs(timeout=3) + except (IqError, IqTimeout): + self.core.information( + 'Unable to retrieve the certificate list.', 'Error') + return + certs = [] + for item in iq['sasl_certs']['items']: + users = '\n'.join(item['users']) + certs.append((item['name'], users)) + + if not certs: + return self.core.information('No certificates found', 'Info') + msg = 'Certificates:\n' + msg += '\n'.join( + ((' %s%s' % (item[0] + (': ' if item[1] else ''), item[1])) + for item in certs)) + self.core.information(msg, 'Info') @command_args_parser.quoted(2, 1) - def command_cert_add(self, args): + async def command_cert_add(self, args): """ /cert_add <name> <certfile> [cert-management] """ if not args or len(args) < 2: return self.core.command.help('cert_add') - - def cb(iq): - if iq['type'] == 'error': - self.core.information('Unable to add the certificate.', - 'Error') - else: - self.core.information('Certificate added.', 'Info') - name = args[0] try: @@ -295,8 +258,17 @@ class RosterInfoTab(Tab): else: management = True - self.core.xmpp.plugin['xep_0257'].add_cert( - name, crt, callback=cb, allow_management=management) + try: + await self.core.xmpp.plugin['xep_0257'].add_cert( + name, + crt, + allow_management=management + ) + self.core.information('Certificate added.', 'Info') + except (IqError, IqTimeout): + self.core.information('Unable to add the certificate.', + 'Error') + def completion_cert_add(self, the_input): """ @@ -312,76 +284,62 @@ class RosterInfoTab(Tab): return Completion(the_input.new_completion, ['true', 'false'], n) @command_args_parser.quoted(1) - def command_cert_disable(self, args): + async def command_cert_disable(self, args): """ /cert_disable <name> """ if not args: return self.core.command.help('cert_disable') - - def cb(iq): - if iq['type'] == 'error': - self.core.information('Unable to disable the certificate.', - 'Error') - else: - self.core.information('Certificate disabled.', 'Info') - name = args[0] - - self.core.xmpp.plugin['xep_0257'].disable_cert(name, callback=cb) + try: + await self.core.xmpp.plugin['xep_0257'].disable_cert(name) + self.core.information('Certificate disabled.', 'Info') + except (IqError, IqTimeout): + self.core.information('Unable to disable the certificate.', + 'Error') @command_args_parser.quoted(1) - def command_cert_revoke(self, args): + async def command_cert_revoke(self, args): """ /cert_revoke <name> """ if not args: return self.core.command.help('cert_revoke') - - def cb(iq): - if iq['type'] == 'error': - self.core.information('Unable to revoke the certificate.', - 'Error') - else: - self.core.information('Certificate revoked.', 'Info') - name = args[0] - - self.core.xmpp.plugin['xep_0257'].revoke_cert(name, callback=cb) + try: + await self.core.xmpp.plugin['xep_0257'].revoke_cert(name) + self.core.information('Certificate revoked.', 'Info') + except (IqError, IqTimeout): + self.core.information('Unable to revoke the certificate.', + 'Error') @command_args_parser.quoted(2) - def command_cert_fetch(self, args): + async def command_cert_fetch(self, args): """ /cert_fetch <name> <path> """ if not args or len(args) < 2: return self.core.command.help('cert_fetch') - - def cb(iq): - if iq['type'] == 'error': - self.core.information('Unable to fetch the certificate.', - 'Error') - return - - cert = None - for item in iq['sasl_certs']['items']: - if item['name'] == name: - cert = base64.b64decode(item['x509cert']) - break - - if not cert: - return self.core.information('Certificate not found.', 'Info') - - cert = ssl.DER_cert_to_PEM_cert(cert) - with open(path, 'w') as fd: - fd.write(cert) - - self.core.information('File stored at %s' % path, 'Info') - name = args[0] path = args[1] - self.core.xmpp.plugin['xep_0257'].get_certs(callback=cb) + try: + iq = await self.core.xmpp.plugin['xep_0257'].get_certs() + except (IqError, IqTimeout): + self.core.information('Unable to fetch the certificate.', + 'Error') + return + cert = None + for item in iq['sasl_certs']['items']: + if item['name'] == name: + cert = base64.b64decode(item['x509cert']) + break + if not cert: + return self.core.information('Certificate not found.', 'Info') + cert = ssl.DER_cert_to_PEM_cert(cert) + with open(path, 'w') as fd: + fd.write(cert) + self.core.information('File stored at %s' % path, 'Info') def completion_cert_fetch(self, the_input): """ @@ -402,32 +360,30 @@ class RosterInfoTab(Tab): 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' % { + message = 'Cannot send message to %(jid)s: contact blocked' % { 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'jid': message['from'], } - tab.add_message(message) + tab.add_message(InfoMessage(message)) @command_args_parser.ignored - def command_list_blocks(self): + async def command_list_blocks(self): """ /list_blocks """ - - def callback(iq): - if iq['type'] == 'error': - return self.core.information( - 'Could not retrieve the blocklist.', 'Error') - s = 'List of blocked JIDs:\n' - items = (str(item) for item in iq['blocklist']['items']) - jids = '\n'.join(items) - if jids: - s += jids - else: - s = 'No blocked JIDs.' - self.core.information(s, 'Info') - - self.core.xmpp.plugin['xep_0191'].get_blocked(callback=callback) + try: + iq = await self.core.xmpp.plugin['xep_0191'].get_blocked() + except (IqError, IqTimeout) as iq: + return self.core.information( + 'Could not retrieve the blocklist.', 'Error') + s = 'List of blocked JIDs:\n' + items = (str(item) for item in iq['blocklist']['items']) + jids = '\n'.join(items) + if jids: + s += jids + else: + s = 'No blocked JIDs.' + self.core.information(s, 'Info') @command_args_parser.ignored def command_disconnect(self): @@ -478,7 +434,9 @@ class RosterInfoTab(Tab): roster_width) self.information_win.resize( self.height - 1 - tab_win_height - contact_win_h, info_width, - 0, roster_width + 1, self.core.information_buffer) + 0, roster_width + 1, self.core.information_buffer, + force=self.ui_config_changed) + self.ui_config_changed = False if display_contact_win: y = self.height - tab_win_height - contact_win_h - 1 avatar_width = contact_win_h * 2 @@ -552,64 +510,34 @@ class RosterInfoTab(Tab): @deny_anonymous @command_args_parser.quoted(1) - def command_password(self, args): + async def command_password(self, args): """ /password <password> """ - - def callback(iq): - if iq['type'] == 'result': - self.core.information('Password updated', 'Account') - if config.get('password'): - config.silent_set('password', args[0]) - else: - self.core.information('Unable to change the password', - 'Account') - - self.core.xmpp.plugin['xep_0077'].change_password( - args[0], callback=callback) - - @deny_anonymous - @command_args_parser.quoted(0, 1) - def command_deny(self, args): - """ - /deny [jid] - Denies a JID from our roster - """ - if not args: - item = self.roster_win.selected_row - if isinstance(item, Contact): - jid = item.bare_jid - else: - self.core.information('No subscription to deny', 'Warning') - return - else: - jid = safeJID(args[0]).bare - if jid not in [jid for jid in roster.jids()]: - self.core.information('No subscription to deny', 'Warning') - return - - contact = roster[jid] - if contact: - contact.unauthorize() - self.core.information('Subscription to %s was revoked' % jid, - 'Roster') + try: + await self.core.xmpp.plugin['xep_0077'].change_password( + args[0] + ) + self.core.information('Password updated', 'Account') + if config.getstr('password'): + config.silent_set('password', args[0]) + except (IqError, IqTimeout): + self.core.information('Unable to change the password', + 'Account') @deny_anonymous @command_args_parser.quoted(1, 1) - def command_name(self, args): + async def command_name(self, args): """ Set a name for the specified JID in your roster """ - - def callback(iq): - if not iq: - self.core.information('The name could not be set.', 'Error') - log.debug('Error in /name:\n%s', iq) - if args is None: return self.core.command.help('name') - jid = safeJID(args[0]).bare + try: + jid = JID(args[0]).bare + except InvalidJID: + self.core.information(f'Invalid JID: {args[0]}', 'Error') + return name = args[1] if len(args) == 2 else '' contact = roster[jid] @@ -621,16 +549,19 @@ class RosterInfoTab(Tab): if 'none' in groups: groups.remove('none') subscription = contact.subscription - self.core.xmpp.update_roster( - jid, - name=name, - groups=groups, - subscription=subscription, - callback=callback) + try: + await self.core.xmpp.update_roster( + jid, + name=name, + groups=groups, + subscription=subscription + ) + except (IqError, IqTimeout): + self.core.information('The name could not be set.', 'Error') @deny_anonymous @command_args_parser.quoted(1, 1) - def command_groupadd(self, args): + async def command_groupadd(self, args): """ Add the specified JID to the specified group """ @@ -646,7 +577,11 @@ class RosterInfoTab(Tab): else: return self.core.command.help('groupadd') else: - jid = safeJID(args[0]).bare + try: + jid = JID(args[0]).bare + except InvalidJID: + self.core.information(f'Invalid JID: {args[0]}', 'Error') + return group = args[1] contact = roster[jid] @@ -669,29 +604,31 @@ class RosterInfoTab(Tab): name = contact.name subscription = contact.subscription - def callback(iq): - if iq: - roster.update_contact_groups(jid) - else: - self.core.information('The group could not be set.', 'Error') - log.debug('Error in groupadd:\n%s', iq) - self.core.xmpp.update_roster( - jid, - name=name, - groups=new_groups, - subscription=subscription, - callback=callback) + try: + await self.core.xmpp.update_roster( + jid, + name=name, + groups=new_groups, + subscription=subscription, + ) + roster.update_contact_groups(jid) + except (IqError, IqTimeout): + self.core.information('The group could not be set.', 'Error') @deny_anonymous @command_args_parser.quoted(3) - def command_groupmove(self, args): + async def command_groupmove(self, args): """ Remove the specified JID from the first specified group and add it to the second one """ if args is None: return self.core.command.help('groupmove') - jid = safeJID(args[0]).bare + try: + jid = JID(args[0]).bare + except InvalidJID: + self.core.information(f'Invalid JID: {args[0]}', 'Error') + return group_from = args[1] group_to = args[2] @@ -728,31 +665,31 @@ class RosterInfoTab(Tab): new_groups.remove(group_from) name = contact.name subscription = contact.subscription - - def callback(iq): - if iq: - roster.update_contact_groups(contact) - else: - self.core.information('The group could not be set', 'Error') - log.debug('Error in groupmove:\n%s', iq) - - self.core.xmpp.update_roster( - jid, - name=name, - groups=new_groups, - subscription=subscription, - callback=callback) + try: + await self.core.xmpp.update_roster( + jid, + name=name, + groups=new_groups, + subscription=subscription, + ) + roster.update_contact_groups(contact) + except (IqError, IqTimeout): + self.core.information('The group could not be set', 'Error') @deny_anonymous @command_args_parser.quoted(2) - def command_groupremove(self, args): + async def command_groupremove(self, args): """ Remove the specified JID from the specified group """ if args is None: return self.core.command.help('groupremove') - jid = safeJID(args[0]).bare + try: + jid = JID(args[0]).bare + except InvalidJID: + self.core.information(f'Invalid JID: {args[0]}', 'Error') + return group = args[1] contact = roster[jid] @@ -774,39 +711,16 @@ class RosterInfoTab(Tab): new_groups.remove(group) name = contact.name subscription = contact.subscription - - def callback(iq): - if iq: - roster.update_contact_groups(jid) - else: - self.core.information('The group could not be set') - log.debug('Error in groupremove:\n%s', iq) - - self.core.xmpp.update_roster( - jid, - name=name, - groups=new_groups, - subscription=subscription, - callback=callback) - - @deny_anonymous - @command_args_parser.quoted(0, 1) - def command_remove(self, args): - """ - Remove the specified JID from the roster. i.e.: unsubscribe - from its presence, and cancel its subscription to our. - """ - if args: - jid = safeJID(args[0]).bare - else: - item = self.roster_win.selected_row - if isinstance(item, Contact): - jid = item.bare_jid - else: - self.core.information('No roster item to remove', 'Error') - return - roster.remove(jid) - del roster[jid] + try: + self.core.xmpp.update_roster( + jid, + name=name, + groups=new_groups, + subscription=subscription, + ) + roster.update_contact_groups(jid) + except (IqError, IqTimeout): + self.core.information('The group could not be set') @deny_anonymous @command_args_parser.quoted(0, 1) @@ -932,16 +846,6 @@ class RosterInfoTab(Tab): the_input.new_completion, groups, n, '', quotify=True) return False - def completion_deny(self, the_input): - """ - Complete the first argument from the list of the - contact with ask=='subscribe' - """ - jids = sorted( - str(contact.bare_jid) for contact in roster.contacts.values() - if contact.pending_in) - return Completion(the_input.new_completion, jids, 1, '', quotify=False) - def refresh(self): if self.need_resize: self.resize() @@ -982,7 +886,7 @@ class RosterInfoTab(Tab): Show or hide offline contacts """ option = 'roster_show_offline' - value = config.get(option) + value = config.getbool(option) success = config.silent_set(option, str(not value)) roster.modified() if not success: @@ -1126,15 +1030,6 @@ class RosterInfoTab(Tab): '%s connected resource%s' % (len(cont), '' if len(cont) == 1 else 's')) acc.append('Current status: %s' % res.status) - if cont.tune: - acc.append('Tune: %s' % common.format_tune_string(cont.tune)) - if cont.mood: - acc.append('Mood: %s' % cont.mood) - if cont.activity: - acc.append('Activity: %s' % cont.activity) - if cont.gaming: - acc.append( - 'Game: %s' % (common.format_gaming_string(cont.gaming))) msg = '\n'.join(acc) elif isinstance(selected_row, Resource): res = selected_row @@ -1160,7 +1055,7 @@ class RosterInfoTab(Tab): if isinstance(selected_row, Contact): jid = selected_row.bare_jid elif isinstance(selected_row, Resource): - jid = safeJID(selected_row.jid).bare + jid = JID(selected_row.jid).bare else: return self.on_slash() @@ -1242,8 +1137,11 @@ def jid_and_name_match(contact, txt): if not txt: return True txt = txt.lower() - if txt in safeJID(contact.bare_jid).bare.lower(): - return True + try: + if txt in JID(contact.bare_jid).bare.lower(): + return True + except InvalidJID: + pass if txt in contact.name.lower(): return True return False @@ -1256,9 +1154,12 @@ def jid_and_name_match_slow(contact, txt): """ if not txt: return True # Everything matches when search is empty - user = safeJID(contact.bare_jid).bare - if diffmatch(txt, user): - return True + try: + user = JID(contact.bare_jid).bare + if diffmatch(txt, user): + return True + except InvalidJID: + pass if contact.name and diffmatch(txt, contact.name): return True return False |