diff options
Diffstat (limited to 'poezio/core/commands.py')
-rw-r--r-- | poezio/core/commands.py | 655 |
1 files changed, 403 insertions, 252 deletions
diff --git a/poezio/core/commands.py b/poezio/core/commands.py index b00cf24a..fe91ca67 100644 --- a/poezio/core/commands.py +++ b/poezio/core/commands.py @@ -3,25 +3,23 @@ Global commands which are to be linked to the Core class """ import asyncio +from urllib.parse import unquote from xml.etree import ElementTree as ET from typing import List, Optional, Tuple import logging -from slixmpp import Iq, JID, InvalidJID -from slixmpp.exceptions import XMPPError +from slixmpp import JID, InvalidJID +from slixmpp.exceptions import XMPPError, IqError, IqTimeout from slixmpp.xmlstream.xmlstream import NotConnectedError from slixmpp.xmlstream.stanzabase import StanzaBase from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.matcher import StanzaPath -from poezio import common -from poezio import pep -from poezio import tabs +from poezio import common, config as config_module, tabs, multiuserchat as muc from poezio.bookmarks import Bookmark -from poezio.common import safeJID -from poezio.config import config, DEFAULT_CONFIG, options as config_opts -from poezio import multiuserchat as muc +from poezio.config import config, DEFAULT_CONFIG from poezio.contact import Contact, Resource +from poezio.decorators import deny_anonymous from poezio.plugin import PluginConfig from poezio.roster import roster from poezio.theming import dump_tuple, get_theme @@ -36,6 +34,14 @@ class CommandCore: def __init__(self, core): self.core = core + @command_args_parser.ignored + def rotate_rooms_left(self, args=None): + self.core.rotate_rooms_left() + + @command_args_parser.ignored + def rotate_rooms_right(self, args=None): + self.core.rotate_rooms_right() + @command_args_parser.quoted(0, 1) def help(self, args): """ @@ -218,6 +224,20 @@ class CommandCore: return self.core.tabs.set_current_tab(match) + @command_args_parser.quoted(1) + def wup(self, args): + """ + /wup <prefix of name> + """ + if args is None: + return self.help('wup') + + prefix = args[0] + _, match = self.core.tabs.find_by_unique_prefix(prefix) + if match is None: + return + self.core.tabs.set_current_tab(match) + @command_args_parser.quoted(2) def move_tab(self, args): """ @@ -278,27 +298,40 @@ class CommandCore: jid = self.core.tabs.current_tab.jid if jid is None or not jid.domain: return None + asyncio.create_task( + self._list_async(jid) + ) + + async def _list_async(self, jid: JID): jid = JID(jid.domain) list_tab = tabs.MucListTab(self.core, jid) self.core.add_tab(list_tab, True) - cb = list_tab.on_muc_list_item_received - self.core.xmpp.plugin['xep_0030'].get_items(jid=jid, callback=cb) + iq = await self.core.xmpp.plugin['xep_0030'].get_items(jid=jid) + list_tab.on_muc_list_item_received(iq) @command_args_parser.quoted(1) - def version(self, args): + async def version(self, args): """ /version <jid> """ if args is None: return self.help('version') - jid = safeJID(args[0]) + try: + jid = JID(args[0]) + except InvalidJID: + return self.core.information( + 'Invalid JID for /version: %s' % args[0], + 'Error' + ) if jid.resource or jid not in roster or not roster[jid].resources: - self.core.xmpp.plugin['xep_0092'].get_version( - jid, callback=self.core.handler.on_version_result) + iq = await self.core.xmpp.plugin['xep_0092'].get_version(jid) + self.core.handler.on_version_result(iq) elif jid in roster: for resource in roster[jid].resources: - self.core.xmpp.plugin['xep_0092'].get_version( - resource.jid, callback=self.core.handler.on_version_result) + iq = await self.core.xmpp.plugin['xep_0092'].get_version( + resource.jid + ) + self.core.handler.on_version_result(iq) def _empty_join(self): tab = self.core.tabs.current_tab @@ -310,6 +343,9 @@ class CommandCore: def _parse_join_jid(self, jid_string: str) -> Tuple[Optional[str], Optional[str]]: # we try to join a server directly + server_root = False + if jid_string.startswith('xmpp:') and jid_string.endswith('?join'): + jid_string = unquote(jid_string[5:-5]) try: if jid_string.startswith('@'): server_root = True @@ -318,9 +354,9 @@ class CommandCore: info = JID(jid_string) server_root = False except InvalidJID: - return (None, None) + info = JID('') - set_nick = '' # type: Optional[str] + set_nick: Optional[str] = '' if len(jid_string) > 1 and jid_string.startswith('/'): set_nick = jid_string[1:] elif info.resource: @@ -347,7 +383,7 @@ class CommandCore: return (room, set_nick) @command_args_parser.quoted(0, 2) - def join(self, args): + async def join(self, args): """ /join [room][/nick] [password] """ @@ -359,7 +395,11 @@ class CommandCore: return # nothing was parsed room = room.lower() + + # Has the nick been specified explicitely when joining + config_nick = False if nick == '': + config_nick = True nick = self.core.own_nick # a password is provided @@ -386,10 +426,16 @@ class CommandCore: tab.password = password tab.join() - if config.get('bookmark_on_join'): - method = 'remote' if config.get( + if config.getbool('synchronise_open_rooms') and room not in self.core.bookmarks: + method = 'remote' if config.getbool( 'use_remote_bookmarks') else 'local' - self._add_bookmark('%s/%s' % (room, nick), True, password, method) + await self._add_bookmark( + room=room, + nick=nick if not config_nick else None, + autojoin=True, + password=password, + method=method, + ) if tab == self.core.tabs.current_tab: tab.refresh() @@ -400,57 +446,99 @@ class CommandCore: """ /bookmark_local [room][/nick] [password] """ - if not args and not isinstance(self.core.tabs.current_tab, - tabs.MucTab): + tab = self.core.tabs.current_tab + if not args and not isinstance(tab, tabs.MucTab): return + + room, nick = self._parse_join_jid(args[0] if args else '') password = args[1] if len(args) > 1 else None - jid = args[0] if args else None - self._add_bookmark(jid, True, password, 'local') + if not room: + room = tab.jid.bare + if password is None and tab.password is not None: + password = tab.password + + asyncio.create_task( + self._add_bookmark( + room=room, + nick=nick, + autojoin=True, + password=password, + method='local', + ) + ) @command_args_parser.quoted(0, 3) def bookmark(self, args): """ /bookmark [room][/nick] [autojoin] [password] """ - if not args and not isinstance(self.core.tabs.current_tab, - tabs.MucTab): + tab = self.core.tabs.current_tab + if not args and not isinstance(tab, tabs.MucTab): return - jid = args[0] if args else '' + room, nick = self._parse_join_jid(args[0] if args else '') password = args[2] if len(args) > 2 else None - if not config.get('use_remote_bookmarks'): - return self._add_bookmark(jid, True, password, 'local') - - if len(args) > 1: - autojoin = False if args[1].lower() != 'true' else True - else: - autojoin = True + method = 'remote' if config.getbool('use_remote_bookmarks') else 'local' + autojoin = (method == 'local' or + (len(args) > 1 and args[1].lower() == 'true')) + + if not room: + room = tab.jid.bare + if password is None and tab.password is not None: + password = tab.password + + asyncio.create_task( + self._add_bookmark(room, nick, autojoin, password, method) + ) + + async def _add_bookmark( + self, + room: str, + nick: Optional[str], + autojoin: bool, + password: str, + method: str, + ) -> None: + ''' + Adds a bookmark. + + Args: + room: room Jid. + nick: optional nick. Will always be added to the bookmark if + specified. This takes precedence over tab.own_nick which takes + precedence over core.own_nick (global config). + autojoin: set the bookmark to join automatically. + password: room password. + method: 'local' or 'remote'. + ''' + + + if room == '*': + return await self._add_wildcard_bookmarks(method) + + # Once we found which room to bookmark, find corresponding tab if it + # exists and fill nickname if none was specified and not default. + tab = self.core.tabs.by_name_and_class(room, tabs.MucTab) + if tab and isinstance(tab, tabs.MucTab) and \ + tab.joined and tab.own_nick != self.core.own_nick: + nick = nick or tab.own_nick - self._add_bookmark(jid, autojoin, password, 'remote') + # Validate / Normalize + try: + if not nick: + jid = JID(room) + else: + jid = JID('{}/{}'.format(room, nick)) + room = jid.bare + nick = jid.resource or None + except InvalidJID: + self.core.information(f'Invalid address for bookmark: {room}/{nick}', 'Error') + return - def _add_bookmark(self, jid, autojoin, password, method): - nick = None - if not jid: - tab = self.core.tabs.current_tab - roomname = tab.jid.bare - if tab.joined and tab.own_nick != self.core.own_nick: - nick = tab.own_nick - if password is None and tab.password is not None: - password = tab.password - elif jid == '*': - return self._add_wildcard_bookmarks(method) - else: - info = safeJID(jid) - roomname, nick = info.bare, info.resource - if roomname == '': - tab = self.core.tabs.current_tab - if not isinstance(tab, tabs.MucTab): - return - roomname = tab.jid.bare - bookmark = self.core.bookmarks[roomname] + bookmark = self.core.bookmarks[room] if bookmark is None: - bookmark = Bookmark(roomname) + bookmark = Bookmark(room) self.core.bookmarks.append(bookmark) bookmark.method = method bookmark.autojoin = autojoin @@ -460,10 +548,15 @@ class CommandCore: bookmark.password = password self.core.bookmarks.save_local() - self.core.bookmarks.save_remote(self.core.xmpp, - self.core.handler.on_bookmark_result) + try: + result = await self.core.bookmarks.save_remote( + self.core.xmpp, + ) + self.core.handler.on_bookmark_result(result) + except (IqError, IqTimeout) as iq: + self.core.handler.on_bookmark_result(iq) - def _add_wildcard_bookmarks(self, method): + async def _add_wildcard_bookmarks(self, method): new_bookmarks = [] for tab in self.core.get_tabs(tabs.MucTab): bookmark = self.core.bookmarks[tab.jid.bare] @@ -477,8 +570,11 @@ class CommandCore: new_bookmarks.extend(self.core.bookmarks.bookmarks) self.core.bookmarks.set(new_bookmarks) self.core.bookmarks.save_local() - self.core.bookmarks.save_remote(self.core.xmpp, - self.core.handler.on_bookmark_result) + try: + iq = await self.core.bookmarks.save_remote(self.core.xmpp) + self.core.handler.on_bookmark_result(iq) + except IqError as iq: + self.core.handler.on_bookmark_result(iq) @command_args_parser.ignored def bookmarks(self): @@ -495,30 +591,34 @@ class CommandCore: @command_args_parser.quoted(0, 1) def remove_bookmark(self, args): """/remove_bookmark [jid]""" + jid = None + if not args: + tab = self.core.tabs.current_tab + if isinstance(tab, tabs.MucTab): + jid = tab.jid.bare + else: + jid = args[0] - def cb(success): - if success: + asyncio.create_task( + self._remove_bookmark_routine(jid) + ) + + async def _remove_bookmark_routine(self, jid: str): + """Asynchronously remove a bookmark""" + if self.core.bookmarks[jid]: + self.core.bookmarks.remove(jid) + try: + await self.core.bookmarks.save(self.core.xmpp) self.core.information('Bookmark deleted', 'Info') - else: + except (IqError, IqTimeout): self.core.information('Error while deleting the bookmark', 'Error') - - if not args: - tab = self.core.tabs.current_tab - if isinstance(tab, tabs.MucTab) and self.core.bookmarks[tab.jid.bare]: - self.core.bookmarks.remove(tab.jid.bare) - self.core.bookmarks.save(self.core.xmpp, callback=cb) - else: - self.core.information('No bookmark to remove', 'Info') else: - if self.core.bookmarks[args[0]]: - self.core.bookmarks.remove(args[0]) - self.core.bookmarks.save(self.core.xmpp, callback=cb) - else: - self.core.information('No bookmark to remove', 'Info') + self.core.information('No bookmark to remove', 'Info') + @deny_anonymous @command_args_parser.quoted(0, 1) - def command_accept(self, args): + def accept(self, args): """ Accept a JID. Authorize it AND subscribe to it """ @@ -534,9 +634,12 @@ class CommandCore: else: return self.core.information('No subscription to accept', 'Warning') else: - jid = safeJID(args[0]).bare - nodepart = safeJID(jid).user - jid = safeJID(jid) + try: + jid = JID(args[0]).bare + except InvalidJID: + return self.core.information('Invalid JID for /accept: %s' % args[0], 'Error') + jid = JID(jid) + nodepart = jid.user # crappy transports putting resources inside the node part if '\\2f' in nodepart: jid.user = nodepart.split('\\2f')[0] @@ -553,8 +656,9 @@ class CommandCore: pto=jid, ptype='subscribe', pnick=self.core.own_nick) self.core.information('%s is now authorized' % jid, 'Roster') + @deny_anonymous @command_args_parser.quoted(1) - def command_add(self, args): + def add(self, args): """ Add the specified JID to the roster, and automatically accept the reverse subscription @@ -571,17 +675,72 @@ class CommandCore: return self.core.information('%s was added to the roster' % jid, 'Roster') else: return self.core.information('No JID specified', 'Error') - jid = safeJID(safeJID(args[0]).bare) - if not str(jid): - self.core.information( - 'The provided JID (%s) is not valid' % (args[0], ), 'Error') - return + try: + jid = JID(args[0]).bare + except InvalidJID: + return self.core.information('Invalid JID for /add: %s' % args[0], 'Error') if jid in roster and roster[jid].subscription in ('to', 'both'): return self.core.information('Already subscribed.', 'Roster') roster.add(jid) roster.modified() self.core.information('%s was added to the roster' % jid, 'Roster') + @deny_anonymous + @command_args_parser.quoted(0, 1) + def deny(self, args): + """ + /deny [jid] + Denies a JID from our roster + """ + jid = None + if not args: + tab = self.core.tabs.current_tab + if isinstance(tab, tabs.RosterInfoTab): + item = tab.roster_win.selected_row + if isinstance(item, Contact): + jid = item.bare_jid + else: + try: + jid = JID(args[0]).bare + except InvalidJID: + return self.core.information('Invalid JID for /deny: %s' % args[0], 'Error') + if jid not in [jid for jid in roster.jids()]: + jid = None + if jid is None: + 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') + + @deny_anonymous + @command_args_parser.quoted(0, 1) + def remove(self, args): + """ + Remove the specified JID from the roster. i.e.: unsubscribe + from its presence, and cancel its subscription to our. + """ + jid = None + if args: + try: + jid = JID(args[0]).bare + except InvalidJID: + return self.core.information('Invalid JID for /remove: %s' % args[0], 'Error') + else: + tab = self.core.tabs.current_tab + if isinstance(tab, tabs.RosterInfoTab): + item = tab.roster_win.selected_row + if isinstance(item, Contact): + jid = item.bare_jid + if jid is None: + self.core.information('No roster item to remove', 'Error') + return + roster.remove(jid) + del roster[jid] + @command_args_parser.ignored def command_reconnect(self): """ @@ -597,6 +756,8 @@ class CommandCore: """ /set [module|][section] <option> [value] """ + if len(args) == 3 and args[1] == '=': + args = [args[0], args[2]] if args is None or len(args) == 0: config_dict = config.to_dict() lines = [] @@ -643,7 +804,8 @@ class CommandCore: info = ('%s=%s' % (option, value), 'Info') else: possible_section = args[0] - if config.has_section(possible_section): + if (not config.has_option(section='Poezio', option=possible_section) + and config.has_section(possible_section)): section = possible_section option = args[1] value = config.get(option, section=section) @@ -747,108 +909,44 @@ class CommandCore: tab.join() @command_args_parser.quoted(1) - def last_activity(self, args): + async def last_activity(self, args): """ /last_activity <jid> """ - def callback(iq): - "Callback for the last activity" - 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'] - if not safeJID(from_).user: - msg = 'The uptime of %s is %s.' % ( - from_, common.parse_secs_to_str(seconds)) - else: - msg = 'The last activity of %s was %s ago%s' % ( - from_, common.parse_secs_to_str(seconds), - (' and their last status was %s' % status) - if status else '') - self.core.information(msg, 'Info') - if args is None: return self.help('last_activity') - jid = safeJID(args[0]) - self.core.xmpp.plugin['xep_0012'].get_last_activity( - jid, callback=callback) - - @command_args_parser.quoted(0, 2) - def mood(self, args): - """ - /mood [<mood> [text]] - """ - if not args: - return self.core.xmpp.plugin['xep_0107'].stop() - - mood = args[0] - if mood not in pep.MOODS: - return self.core.information( - '%s is not a correct value for a mood.' % mood, 'Error') - if len(args) == 2: - text = args[1] - else: - text = None - self.core.xmpp.plugin['xep_0107'].publish_mood( - mood, text, callback=dumb_callback) - - @command_args_parser.quoted(0, 3) - def activity(self, args): - """ - /activity [<general> [specific] [text]] - """ - length = len(args) - if not length: - return self.core.xmpp.plugin['xep_0108'].stop() + try: + jid = JID(args[0]) + except InvalidJID: + return self.core.information('Invalid JID for /last_activity: %s' % args[0], 'Error') - general = args[0] - if general not in pep.ACTIVITIES: - return self.core.information( - '%s is not a correct value for an activity' % general, 'Error') - specific = None - text = None - if length == 2: - if args[1] in pep.ACTIVITIES[general]: - specific = args[1] + try: + iq = await self.core.xmpp.plugin['xep_0012'].get_last_activity(jid) + except IqError as error: + if error.etype == 'auth': + msg = 'You are not allowed to see the activity of %s' % jid else: - text = args[1] - elif length == 3: - specific = args[1] - text = args[2] - if specific and specific not in pep.ACTIVITIES[general]: - return self.core.information( - '%s is not a correct value ' - 'for an activity' % specific, 'Error') - self.core.xmpp.plugin['xep_0108'].publish_activity( - general, specific, text, callback=dumb_callback) - - @command_args_parser.quoted(0, 2) - def gaming(self, args): - """ - /gaming [<game name> [server address]] - """ - if not args: - return self.core.xmpp.plugin['xep_0196'].stop() - - name = args[0] - if len(args) > 1: - address = args[1] + msg = 'Error retrieving the activity of %s: %s' % (jid, error) + return self.core.information(msg, 'Error') + except IqTimeout: + return self.core.information('Timeout while retrieving the last activity of %s' % jid, 'Error') + + seconds = iq['last_activity']['seconds'] + status = iq['last_activity']['status'] + from_ = iq['from'] + if not from_.user: + msg = 'The uptime of %s is %s.' % ( + from_, common.parse_secs_to_str(seconds)) else: - address = None - return self.core.xmpp.plugin['xep_0196'].publish_gaming( - name=name, server_address=address, callback=dumb_callback) + msg = 'The last activity of %s was %s ago%s' % ( + from_, common.parse_secs_to_str(seconds), + (' and their last status was %s' % status) + if status else '') + self.core.information(msg, 'Info') @command_args_parser.quoted(2, 1, [None]) - def invite(self, args): + async def invite(self, args): """/invite <to> <room> [reason]""" if args is None: @@ -865,8 +963,9 @@ class CommandCore: except InvalidJID: self.core.information('Invalid room JID specified to invite: %s' % args[1], 'Error') return None - self.core.invite(to.full, room, reason=reason) - self.core.information('Invited %s to %s' % (to.bare, room), 'Info') + result = await self.core.invite(to.full, room, reason=reason) + if result: + self.core.information('Invited %s to %s' % (to.bare, room), 'Info') @command_args_parser.quoted(1, 0) def impromptu(self, args: str) -> None: @@ -881,17 +980,23 @@ class CommandCore: jids.add(current_tab.general_jid) for jid in common.shell_split(' '.join(args)): - jids.add(safeJID(jid).bare) + try: + bare = JID(jid).bare + except InvalidJID: + return self.core.information('Invalid JID for /impromptu: %s' % args[0], 'Error') + jids.add(JID(bare)) - asyncio.ensure_future(self.core.impromptu(jids)) - self.core.information('Invited %s to a random room' % (', '.join(jids)), 'Info') + asyncio.create_task(self.core.impromptu(jids)) @command_args_parser.quoted(1, 1, ['']) def decline(self, args): """/decline <room@server.tld> [reason]""" if args is None: return self.help('decline') - jid = safeJID(args[0]) + try: + jid = JID(args[0]) + except InvalidJID: + return self.core.information('Invalid JID for /decline: %s' % args[0], 'Error') if jid.bare not in self.core.pending_invites: return reason = args[1] @@ -911,9 +1016,10 @@ class CommandCore: jid = None if args: try: - jid = JID(args[0]).full + jid = JID(args[0]) except InvalidJID: self.core.information('Invalid JID %s' % args, 'Error') + return current_tab = self.core.tabs.current_tab if jid is None: @@ -926,7 +1032,7 @@ class CommandCore: if isinstance(item, Contact): jid = item.bare_jid elif isinstance(item, Resource): - jid = item.jid + jid = JID(item.jid) chattabs = ( tabs.ConversationTab, @@ -934,22 +1040,22 @@ class CommandCore: tabs.DynamicConversationTab, ) if isinstance(current_tab, chattabs): - jid = current_tab.jid.bare - - def callback(iq: Iq) -> None: - if iq['type'] == 'error': - return self.core.information( - 'Could not block %s.' % jid, 'Error', - ) - if iq['type'] == 'result': - return self.core.information('Blocked %s.' % jid, 'Info') - return None - + jid = JID(current_tab.jid.bare) - if jid is not None: - self.core.xmpp.plugin['xep_0191'].block(jid, callback=callback) - else: + if jid is None: self.core.information('No specified JID to block', 'Error') + else: + asyncio.create_task(self._block_async(jid)) + + async def _block_async(self, jid: JID): + """Block a JID, asynchronously""" + try: + await self.core.xmpp.plugin['xep_0191'].block(jid) + return self.core.information('Blocked %s.' % jid, 'Info') + except (IqError, IqTimeout): + return self.core.information( + 'Could not block %s.' % jid, 'Error', + ) @command_args_parser.quoted(0, 1) def unblock(self, args: List[str]) -> None: @@ -965,9 +1071,10 @@ class CommandCore: jid = None if args: try: - jid = JID(args[0]).full + jid = JID(args[0]) except InvalidJID: self.core.information('Invalid JID %s' % args, 'Error') + return current_tab = self.core.tabs.current_tab if jid is None: @@ -980,7 +1087,7 @@ class CommandCore: if isinstance(item, Contact): jid = item.bare_jid elif isinstance(item, Resource): - jid = item.jid + jid = JID(item.jid) chattabs = ( tabs.ConversationTab, @@ -988,34 +1095,44 @@ class CommandCore: tabs.DynamicConversationTab, ) if isinstance(current_tab, chattabs): - jid = current_tab.jid.bare + jid = JID(current_tab.jid.bare) if jid is not None: - def callback(iq: Iq): - if iq['type'] == 'error': - return self.core.information('Could not unblock the contact.', - 'Error') - elif iq['type'] == 'result': - return self.core.information('Unblocked %s.' % jid, 'Info') - - self.core.xmpp.plugin['xep_0191'].unblock(jid, callback=callback) + asyncio.create_task( + self._unblock_async(jid) + ) else: self.core.information('No specified JID to unblock', 'Error') + async def _unblock_async(self, jid: JID): + """Unblock a JID, asynchrously""" + try: + await self.core.xmpp.plugin['xep_0191'].unblock(jid) + return self.core.information('Unblocked %s.' % jid, 'Info') + except (IqError, IqTimeout): + return self.core.information('Could not unblock the contact.', + 'Error') + ### Commands without a completion in this class ### @command_args_parser.ignored def invitations(self): """/invitations""" - build = "" - for invite in self.core.pending_invites: - build += "%s by %s" % ( - invite, safeJID(self.core.pending_invites[invite]).bare) - if self.core.pending_invites: - build = "You are invited to the following rooms:\n" + build + build = [] + for room, inviter in self.core.pending_invites.items(): + try: + bare = JID(inviter).bare + except InvalidJID: + self.core.information( + f'Invalid JID found in /invitations: {inviter}', + 'Error' + ) + build.append(f'{room} by {bare}') + if build: + message = 'You are invited to the following rooms:\n' + ','.join(build) else: - build = "You do not have any pending invitations." - self.core.information(build, 'Info') + message = 'You do not have any pending invitations.' + self.core.information(message, 'Info') @command_args_parser.quoted(0, 1, [None]) def quit(self, args): @@ -1027,38 +1144,51 @@ class CommandCore: return msg = args[0] - if config.get('enable_user_mood'): - self.core.xmpp.plugin['xep_0107'].stop() - if config.get('enable_user_activity'): - self.core.xmpp.plugin['xep_0108'].stop() - if config.get('enable_user_gaming'): - self.core.xmpp.plugin['xep_0196'].stop() self.core.save_config() self.core.plugin_manager.disable_plugins() self.core.xmpp.add_event_handler( "disconnected", self.core.exit, disposable=True) self.core.disconnect(msg) - @command_args_parser.quoted(0, 1, ['']) - def destroy_room(self, args: List[str]) -> None: + @command_args_parser.quoted(0, 3, ['', '', '']) + def destroy_room(self, args: List[str]): """ - /destroy_room [JID] + /destroy_room [JID [reason [alternative room JID]]] """ + async def do_destroy(room: JID, reason: str, altroom: Optional[JID]): + try: + await self.core.xmpp['xep_0045'].destroy(room, reason, altroom) + except (IqError, IqTimeout) as e: + self.core.information('Unable to destroy room %s: %s' % (room, e), 'Info') + else: + self.core.information('Room %s destroyed' % room, 'Info') + + room: Optional[JID] if not args[0] and isinstance(self.core.tabs.current_tab, tabs.MucTab): - muc.destroy_room(self.core.xmpp, - self.core.tabs.current_tab.general_jid) - return None + room = self.core.tabs.current_tab.general_jid + else: + try: + room = JID(args[0]) + except InvalidJID: + room = None + else: + if room.resource: + room = None - try: - room = JID(args[0]).bare - if room: - muc.destroy_room(self.core.xmpp, room) - return None - except InvalidJID: - pass + if room is None: + self.core.information('Invalid room JID: "%s"' % args[0], 'Error') + return + + reason = args[1] + altroom = None + if args[2]: + try: + altroom = JID(args[2]) + except InvalidJID: + self.core.information('Invalid alternative room JID: "%s"' % args[2], 'Error') + return - self.core.information('Invalid JID: "%s"' % args[0], 'Error') - return None + asyncio.create_task(do_destroy(room, reason, altroom)) @command_args_parser.quoted(1, 1, ['']) def bind(self, args): @@ -1153,13 +1283,16 @@ class CommandCore: list(self.core.plugin_manager.plugins.keys())), 'Info') @command_args_parser.quoted(1, 1) - def message(self, args): + async def message(self, args): """ /message <jid> [message] """ if args is None: return self.help('message') - jid = safeJID(args[0]) + try: + jid = JID(args[0]) + except InvalidJID: + return self.core.information('Invalid JID for /message: %s' % args[0], 'Error') if not jid.user and not jid.domain and not jid.resource: return self.core.information('Invalid JID.', 'Error') tab = self.core.get_conversation_by_jid( @@ -1180,7 +1313,7 @@ class CommandCore: else: self.core.focus_tab(tab) if len(args) == 2: - tab.command_say(args[1]) + await tab.command_say(args[1]) @command_args_parser.ignored def xml_tab(self): @@ -1192,15 +1325,23 @@ class CommandCore: self.core.xml_tab = tab @command_args_parser.quoted(1) - def adhoc(self, args): + async def adhoc(self, args): if not args: return self.help('ad-hoc') - jid = safeJID(args[0]) + try: + jid = JID(args[0]) + except InvalidJID: + return self.core.information( + 'Invalid JID for ad-hoc command: %s' % args[0], + 'Error', + ) list_tab = tabs.AdhocCommandsListTab(self.core, jid) self.core.add_tab(list_tab, True) - cb = list_tab.on_list_received - self.core.xmpp.plugin['xep_0050'].get_commands( - jid=jid, local=False, callback=cb) + iq = await self.core.xmpp.plugin['xep_0050'].get_commands( + jid=jid, + local=False + ) + list_tab.on_list_received(iq) @command_args_parser.ignored def self_(self): @@ -1214,7 +1355,7 @@ class CommandCore: info = ('Your JID is %s\nYour current status is "%s" (%s)' '\nYour default nickname is %s\nYou are running poezio %s' % (jid, message if message else '', show - if show else 'available', nick, config_opts.custom_version)) + if show else 'available', nick, self.core.custom_version)) self.core.information(info, 'Info') @command_args_parser.ignored @@ -1224,6 +1365,16 @@ class CommandCore: """ self.core.reload_config() + @command_args_parser.raw + def debug(self, args): + """/debug [filename]""" + if not args.strip(): + config_module.setup_logging('') + self.core.information('Debug logging disabled!', 'Info') + elif args: + config_module.setup_logging(args) + self.core.information(f'Debug logging to {args} enabled!', 'Info') + def dumb_callback(*args, **kwargs): "mock callback" |