summaryrefslogtreecommitdiff
path: root/poezio/core/commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'poezio/core/commands.py')
-rw-r--r--poezio/core/commands.py857
1 files changed, 616 insertions, 241 deletions
diff --git a/poezio/core/commands.py b/poezio/core/commands.py
index 86df9a93..fe91ca67 100644
--- a/poezio/core/commands.py
+++ b/poezio/core/commands.py
@@ -2,38 +2,46 @@
Global commands which are to be linked to the Core class
"""
-import logging
-
-log = logging.getLogger(__name__)
-
import asyncio
-from xml.etree import cElementTree as ET
+from urllib.parse import unquote
+from xml.etree import ElementTree as ET
+from typing import List, Optional, Tuple
+import logging
-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
from poezio.decorators import command_args_parser
-
from poezio.core.structs import Command, POSSIBLE_SHOW
+log = logging.getLogger(__name__)
+
+
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):
"""
@@ -132,7 +140,7 @@ class CommandCore:
current.send_chat_state('inactive')
for tab in self.core.tabs:
if isinstance(tab, tabs.MucTab) and tab.joined:
- muc.change_show(self.core.xmpp, tab.name, tab.own_nick, show,
+ muc.change_show(self.core.xmpp, tab.jid, tab.own_nick, show,
msg)
if hasattr(tab, 'directed_presence'):
del tab.directed_presence
@@ -150,7 +158,7 @@ class CommandCore:
jid, ptype, status = args[0], args[1], args[2]
if jid == '.' and isinstance(self.core.tabs.current_tab, tabs.ChatTab):
- jid = self.core.tabs.current_tab.name
+ jid = self.core.tabs.current_tab.jid
if ptype == 'available':
ptype = None
try:
@@ -216,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):
"""
@@ -257,7 +279,7 @@ class CommandCore:
self.core.refresh_window()
@command_args_parser.quoted(0, 1)
- def list(self, args):
+ def list(self, args: List[str]) -> None:
"""
/list [server]
Opens a MucListTab containing the list of the room in the specified server
@@ -265,51 +287,76 @@ class CommandCore:
if args is None:
return self.help('list')
elif args:
- jid = safeJID(args[0])
+ try:
+ jid = JID(args[0])
+ except InvalidJID:
+ return self.core.information('Invalid server %r' % jid, 'Error')
else:
if not isinstance(self.core.tabs.current_tab, tabs.MucTab):
return self.core.information('Please provide a server',
'Error')
- jid = safeJID(self.core.tabs.current_tab.name)
+ 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
if not isinstance(tab, (tabs.MucTab, tabs.PrivateTab)):
return (None, None)
- room = safeJID(tab.name).bare
+ room = tab.jid.bare
nick = tab.own_nick
return (room, nick)
- def _parse_join_jid(self, jid_string):
+ def _parse_join_jid(self, jid_string: str) -> Tuple[Optional[str], Optional[str]]:
# we try to join a server directly
- if jid_string.startswith('@'):
- server_root = True
- info = safeJID(jid_string[1:])
- else:
- info = safeJID(jid_string)
- server_root = False
+ 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
+ info = JID(jid_string[1:])
+ else:
+ info = JID(jid_string)
+ server_root = False
+ except InvalidJID:
+ info = JID('')
- set_nick = ''
+ set_nick: Optional[str] = ''
if len(jid_string) > 1 and jid_string.startswith('/'):
set_nick = jid_string[1:]
elif info.resource:
@@ -321,7 +368,7 @@ class CommandCore:
if not isinstance(tab, tabs.MucTab):
room, set_nick = (None, None)
else:
- room = tab.name
+ room = tab.jid.bare
if not set_nick:
set_nick = tab.own_nick
else:
@@ -331,14 +378,12 @@ class CommandCore:
# check if the current room's name has a server
if room.find('@') == -1 and not server_root:
tab = self.core.tabs.current_tab
- if isinstance(tab, tabs.MucTab):
- if tab.name.find('@') != -1:
- domain = safeJID(tab.name).domain
- room += '@%s' % domain
+ if isinstance(tab, tabs.MucTab) and tab.jid.domain:
+ room += '@%s' % tab.jid.domain
return (room, set_nick)
@command_args_parser.quoted(0, 2)
- def join(self, args):
+ async def join(self, args):
"""
/join [room][/nick] [password]
"""
@@ -350,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
@@ -377,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()
@@ -391,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.name
- 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.name
- 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
@@ -451,15 +548,20 @@ class CommandCore:
bookmark.password = password
self.core.bookmarks.save_local()
- self.core.bookmarks.save_remote(self.core.xmpp,
- self.core.handler.on_bookmark_result)
-
- def _add_wildcard_bookmarks(self, method):
+ 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)
+
+ async def _add_wildcard_bookmarks(self, method):
new_bookmarks = []
for tab in self.core.get_tabs(tabs.MucTab):
- bookmark = self.core.bookmarks[tab.name]
+ bookmark = self.core.bookmarks[tab.jid.bare]
if not bookmark:
- bookmark = Bookmark(tab.name, autojoin=True, method=method)
+ bookmark = Bookmark(tab.jid.bare, autojoin=True, method=method)
new_bookmarks.append(bookmark)
else:
bookmark.method = method
@@ -468,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):
@@ -486,33 +591,173 @@ 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]
+
+ asyncio.create_task(
+ self._remove_bookmark_routine(jid)
+ )
- def cb(success):
- if success:
+ 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')
+ else:
+ self.core.information('No bookmark to remove', 'Info')
+ @deny_anonymous
+ @command_args_parser.quoted(0, 1)
+ def accept(self, args):
+ """
+ Accept a JID. Authorize it AND subscribe to it
+ """
if not args:
tab = self.core.tabs.current_tab
- if isinstance(tab, tabs.MucTab) and self.core.bookmarks[tab.name]:
- self.core.bookmarks.remove(tab.name)
- self.core.bookmarks.save(self.core.xmpp, callback=cb)
+ RosterInfoTab = tabs.RosterInfoTab
+ if not isinstance(tab, RosterInfoTab):
+ return self.core.information('No JID specified', 'Error')
else:
- self.core.information('No bookmark to remove', 'Info')
+ item = tab.selected_row
+ if isinstance(item, Contact):
+ jid = item.bare_jid
+ else:
+ return self.core.information('No subscription to accept', 'Warning')
else:
- if self.core.bookmarks[args[0]]:
- self.core.bookmarks.remove(args[0])
- self.core.bookmarks.save(self.core.xmpp, callback=cb)
+ 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]
+ contact = roster[jid]
+ if contact is None:
+ return self.core.information('No subscription to accept', 'Warning')
+ contact.pending_in = False
+ roster.modified()
+ self.core.xmpp.send_presence(pto=jid, ptype='subscribed')
+ self.core.xmpp.client_roster.send_last_presence()
+ if contact.subscription in ('from',
+ 'none') and not contact.pending_out:
+ self.core.xmpp.send_presence(
+ 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 add(self, args):
+ """
+ Add the specified JID to the roster, and automatically
+ accept the reverse subscription
+ """
+ if args is None:
+ tab = self.core.tabs.current_tab
+ ConversationTab = tabs.ConversationTab
+ if isinstance(tab, ConversationTab):
+ jid = tab.general_jid
+ if jid in roster and roster[jid].subscription in ('to', 'both'):
+ return self.core.information('Already subscribed.', 'Roster')
+ roster.add(jid)
+ roster.modified()
+ return self.core.information('%s was added to the roster' % jid, 'Roster')
else:
- self.core.information('No bookmark to remove', 'Info')
+ return self.core.information('No JID specified', 'Error')
+ 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):
+ """
+ /reconnect
+ """
+ if self.core.xmpp.is_connected():
+ self.core.disconnect(reconnect=True)
+ else:
+ self.core.xmpp.start()
@command_args_parser.quoted(0, 3)
def set(self, args):
"""
/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 = []
@@ -525,6 +770,9 @@ class CommandCore:
theme.COLOR_INFORMATION_TEXT),
})
for option_name, option_value in section.items():
+ if isinstance(option_name, str) and \
+ 'password' in option_name and 'eval_password' not in option_name:
+ option_value = '********'
lines.append(
'%s\x19%s}=\x19o%s' %
(option_name, dump_tuple(
@@ -533,6 +781,9 @@ class CommandCore:
elif len(args) == 1:
option = args[0]
value = config.get(option)
+ if isinstance(option, str) and \
+ 'password' in option and 'eval_password' not in option and value is not None:
+ value = '********'
if value is None and '=' in option:
args = option.split('=', 1)
info = ('%s=%s' % (option, value), 'Info')
@@ -553,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)
@@ -580,7 +832,7 @@ class CommandCore:
info = plugin_config.set_and_save(option, value, section)
else:
if args[0] == '.':
- name = safeJID(self.core.tabs.current_tab.name).bare
+ name = self.core.tabs.current_tab.jid.bare
if not name:
self.core.information(
'Invalid tab to use the "." argument.', 'Error')
@@ -632,137 +884,88 @@ class CommandCore:
def server_cycle(self, args):
"""
Do a /cycle on each room of the given server.
- If none, do it on the current tab
+ If none, do it on the server of the current tab
"""
tab = self.core.tabs.current_tab
message = ""
if args:
- domain = args[0]
+ try:
+ domain = JID(args[0]).domain
+ except InvalidJID:
+ return self.core.information(
+ "Invalid server domain: %s" % args[0],
+ "Error"
+ )
if len(args) == 2:
message = args[1]
else:
if isinstance(tab, tabs.MucTab):
- domain = safeJID(tab.name).domain
+ domain = tab.jid.domain
else:
return self.core.information("No server specified", "Error")
for tab in self.core.get_tabs(tabs.MucTab):
- if tab.name.endswith(domain):
+ if tab.jid.domain == domain:
tab.leave_room(message)
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 his/her 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:
return self.help('invite')
reason = args[2]
- to = safeJID(args[0])
- room = safeJID(args[1]).bare
- self.core.invite(to.full, room, reason=reason)
- self.core.information('Invited %s to %s' % (to.bare, room), 'Info')
+ try:
+ to = JID(args[0])
+ except InvalidJID:
+ self.core.information('Invalid JID specified for invite: %s' % args[0], 'Error')
+ return None
+ try:
+ room = JID(args[1]).bare
+ except InvalidJID:
+ self.core.information('Invalid room JID specified to invite: %s' % args[1], 'Error')
+ return None
+ 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:
@@ -777,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]
@@ -795,21 +1004,135 @@ class CommandCore:
self.core.xmpp.plugin['xep_0045'].decline_invite(
jid.bare, self.core.pending_invites[jid.bare], reason)
+ @command_args_parser.quoted(0, 1)
+ def block(self, args: List[str]) -> None:
+ """
+ /block [jid]
+
+ If a JID is specified, use it. Otherwise if in RosterInfoTab, use the
+ selected JID, if in ConversationsTab use the Tab's JID.
+ """
+
+ jid = None
+ if args:
+ try:
+ 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:
+ if isinstance(current_tab, tabs.RosterInfoTab):
+ roster_win = self.core.tabs.by_name_and_class(
+ 'Roster',
+ tabs.RosterInfoTab,
+ )
+ item = roster_win.selected_row
+ if isinstance(item, Contact):
+ jid = item.bare_jid
+ elif isinstance(item, Resource):
+ jid = JID(item.jid)
+
+ chattabs = (
+ tabs.ConversationTab,
+ tabs.StaticConversationTab,
+ tabs.DynamicConversationTab,
+ )
+ if isinstance(current_tab, chattabs):
+ jid = JID(current_tab.jid.bare)
+
+ 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:
+ """
+ /unblock [jid]
+ """
+
+ item = self.core.tabs.by_name_and_class(
+ 'Roster',
+ tabs.RosterInfoTab,
+ ).selected_row
+
+ jid = None
+ if args:
+ try:
+ 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:
+ if isinstance(current_tab, tabs.RosterInfoTab):
+ roster_win = self.core.tabs.by_name_and_class(
+ 'Roster',
+ tabs.RosterInfoTab,
+ )
+ item = roster_win.selected_row
+ if isinstance(item, Contact):
+ jid = item.bare_jid
+ elif isinstance(item, Resource):
+ jid = JID(item.jid)
+
+ chattabs = (
+ tabs.ConversationTab,
+ tabs.StaticConversationTab,
+ tabs.DynamicConversationTab,
+ )
+ if isinstance(current_tab, chattabs):
+ jid = JID(current_tab.jid.bare)
+
+ if jid is not None:
+ 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):
@@ -821,32 +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.disconnect(msg)
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):
+ @command_args_parser.quoted(0, 3, ['', '', ''])
+ def destroy_room(self, args: List[str]):
"""
- /destroy_room [JID]
+ /destroy_room [JID [reason [alternative room JID]]]
"""
- room = safeJID(args[0]).bare
- if room:
- muc.destroy_room(self.core.xmpp, room)
- elif isinstance(self.core.tabs.current_tab,
- tabs.MucTab) and not args[0]:
- muc.destroy_room(self.core.xmpp,
- self.core.tabs.current_tab.general_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):
+ room = self.core.tabs.current_tab.general_jid
else:
- self.core.information('Invalid JID: "%s"' % args[0], 'Error')
+ try:
+ room = JID(args[0])
+ except InvalidJID:
+ room = None
+ else:
+ if room.resource:
+ room = None
+
+ 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
+
+ asyncio.create_task(do_destroy(room, reason, altroom))
@command_args_parser.quoted(1, 1, [''])
def bind(self, args):
@@ -903,11 +1245,17 @@ class CommandCore:
exc_info=True)
@command_args_parser.quoted(1, 256)
- def load(self, args):
+ def load(self, args: List[str]) -> None:
"""
/load <plugin> [<otherplugin> …]
# TODO: being able to load more than 256 plugins at once, hihi.
"""
+
+ usage = '/load <plugin> [<otherplugin> …]'
+ if not args:
+ self.core.information(usage, 'Error')
+ return
+
for plugin in args:
self.core.plugin_manager.load(plugin)
@@ -916,6 +1264,12 @@ class CommandCore:
"""
/unload <plugin> [<otherplugin> …]
"""
+
+ usage = '/unload <plugin> [<otherplugin> …]'
+ if not args:
+ self.core.information(usage, 'Error')
+ return
+
for plugin in args:
self.core.plugin_manager.unload(plugin)
@@ -929,20 +1283,23 @@ 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(
jid.full, False, fallback_barejid=False)
muc = self.core.tabs.by_name_and_class(jid.bare, tabs.MucTab)
if not tab and not muc:
- tab = self.core.open_conversation_window(jid.full, focus=True)
+ tab = self.core.open_conversation_window(JID(jid.full), focus=True)
elif muc:
if jid.resource:
tab = self.core.tabs.by_name_and_class(jid.full,
@@ -956,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):
@@ -968,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):
@@ -990,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.version))
+ if show else 'available', nick, self.core.custom_version))
self.core.information(info, 'Info')
@command_args_parser.ignored
@@ -1000,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"