summaryrefslogtreecommitdiff
path: root/src/tabs/rostertab.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/tabs/rostertab.py')
-rw-r--r--src/tabs/rostertab.py546
1 files changed, 393 insertions, 153 deletions
diff --git a/src/tabs/rostertab.py b/src/tabs/rostertab.py
index 878e89ed..aaff7de3 100644
--- a/src/tabs/rostertab.py
+++ b/src/tabs/rostertab.py
@@ -5,15 +5,16 @@ rectangle shows the current contact info.
This module also includes functions to match users in the roster.
"""
-from gettext import gettext as _
-
import logging
log = logging.getLogger(__name__)
+import base64
import curses
import difflib
import os
+import ssl
from os import getenv, path
+from functools import partial
from . import Tab
@@ -25,6 +26,7 @@ from contact import Contact, Resource
from decorators import refresh_wrapper
from roster import RosterGroup, roster
from theming import get_theme, dump_tuple
+from decorators import command_args_parser
class RosterInfoTab(Tab):
"""
@@ -44,107 +46,315 @@ class RosterInfoTab(Tab):
self.input = self.default_help_message
self.state = 'normal'
self.key_func['^I'] = self.completion
- self.key_func[' '] = self.on_space
self.key_func["/"] = self.on_slash
- self.key_func["KEY_UP"] = self.move_cursor_up
- self.key_func["KEY_DOWN"] = self.move_cursor_down
- self.key_func["M-u"] = self.move_cursor_to_next_contact
- self.key_func["M-y"] = self.move_cursor_to_prev_contact
- self.key_func["M-U"] = self.move_cursor_to_next_group
- self.key_func["M-Y"] = self.move_cursor_to_prev_group
- self.key_func["M-[1;5B"] = self.move_cursor_to_next_group
- self.key_func["M-[1;5A"] = self.move_cursor_to_prev_group
- self.key_func["l"] = self.command_last_activity
- self.key_func["o"] = self.toggle_offline_show
- self.key_func["v"] = self.get_contact_version
- self.key_func["i"] = self.show_contact_info
- self.key_func["n"] = self.change_contact_name
- self.key_func["s"] = self.start_search
- self.key_func["S"] = self.start_search_slow
- 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 his/here roster.'),
- shortdesc=_('Deny an user your presence.'),
- completion=self.completion_deny)
- self.register_command('accept', self.command_accept,
- usage=_('[jid]'),
- desc=_('Allow the provided JID (or the selected contact in your roster), to see your presence.'),
- shortdesc=_('Allow an user your presence.'),
- completion=self.completion_deny)
- self.register_command('add', self.command_add,
- usage=_('<jid>'),
- desc=_('Add the specified JID to your roster, ask him to allow you to see his presence, and allow him to see your presence.'),
- shortdesc=_('Add an user to your roster.'))
- self.register_command('name', self.command_name,
- usage=_('<jid> <name>'),
- shortdesc=_('Set the given JID\'s name.'),
- completion=self.completion_name)
- self.register_command('groupadd', self.command_groupadd,
- usage=_('<jid> <group>'),
- desc=_('Add the given JID to the given group.'),
- shortdesc=_('Add an user to a group'),
- completion=self.completion_groupadd)
- self.register_command('groupmove', self.command_groupmove,
- usage=_('<jid> <old group> <new group>'),
- desc=_('Move the given JID from the old group to the new group.'),
- shortdesc=_('Move an user to another group.'),
- completion=self.completion_groupmove)
- self.register_command('groupremove', self.command_groupremove,
- usage=_('<jid> <group>'),
- desc=_('Remove the given JID from the given group.'),
- shortdesc=_('Remove an 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 wil unsubscribe you from its presence, cancel its subscription to yours, and remove the item from your roster.'),
- shortdesc=_('Remove an user from your roster.'),
- completion=self.completion_remove)
+ # disable most of the roster features when in anonymous mode
+ if not self.core.xmpp.anon:
+ self.key_func[' '] = self.on_space
+ self.key_func["KEY_UP"] = self.move_cursor_up
+ self.key_func["KEY_DOWN"] = self.move_cursor_down
+ self.key_func["M-u"] = self.move_cursor_to_next_contact
+ self.key_func["M-y"] = self.move_cursor_to_prev_contact
+ self.key_func["M-U"] = self.move_cursor_to_next_group
+ self.key_func["M-Y"] = self.move_cursor_to_prev_group
+ self.key_func["M-[1;5B"] = self.move_cursor_to_next_group
+ self.key_func["M-[1;5A"] = self.move_cursor_to_prev_group
+ self.key_func["l"] = self.command_last_activity
+ self.key_func["o"] = self.toggle_offline_show
+ self.key_func["v"] = self.get_contact_version
+ self.key_func["i"] = self.show_contact_info
+ self.key_func["s"] = self.start_search
+ 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 his/here roster.',
+ shortdesc='Deny an user your presence.',
+ completion=self.completion_deny)
+ self.register_command('accept', self.command_accept,
+ usage='[jid]',
+ desc='Allow the provided JID (or the selected contact '
+ 'in your roster), to see your presence.',
+ shortdesc='Allow an user your presence.',
+ completion=self.completion_deny)
+ self.register_command('add', self.command_add,
+ usage='<jid>',
+ desc='Add the specified JID to your roster, ask him to'
+ ' allow you to see his presence, and allow him to'
+ ' see your presence.',
+ shortdesc='Add an user to your roster.')
+ self.register_command('name', self.command_name,
+ usage='<jid> [name]',
+ shortdesc='Set the given JID\'s name.',
+ completion=self.completion_name)
+ self.register_command('groupadd', self.command_groupadd,
+ usage='<jid> <group>',
+ desc='Add the given JID to the given group.',
+ shortdesc='Add an user to a group',
+ completion=self.completion_groupadd)
+ self.register_command('groupmove', self.command_groupmove,
+ usage='<jid> <old group> <new group>',
+ desc='Move the given JID from the old group to the new group.',
+ shortdesc='Move an user to another group.',
+ completion=self.completion_groupmove)
+ self.register_command('groupremove', self.command_groupremove,
+ usage='<jid> <group>',
+ desc='Remove the given JID from the given group.',
+ shortdesc='Remove an 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 an user from your roster.',
+ completion=self.completion_remove)
+ self.register_command('export', self.command_export,
+ usage='[/path/to/file]',
+ desc='Export your contacts into /path/to/file if '
+ 'specified, or $HOME/poezio_contacts if not.',
+ shortdesc='Export your roster to a file.',
+ completion=partial(self.completion_file, 1))
+ self.register_command('import', self.command_import,
+ usage='[/path/to/file]',
+ desc='Import your contacts from /path/to/file if '
+ 'specified, or $HOME/poezio_contacts if not.',
+ shortdesc='Import your roster from a file.',
+ completion=partial(self.completion_file, 1))
+ self.register_command('password', self.command_password,
+ usage='<password>',
+ shortdesc='Change your password')
+
self.register_command('reconnect', self.command_reconnect,
- desc=_('Disconnect from the remote server if you are currently connected and then connect to it again.'),
- shortdesc=_('Disconnect and reconnect to the server.'))
+ desc='Disconnect from the remote server if you are '
+ 'currently connected and then connect to it again.',
+ shortdesc='Disconnect and reconnect to the server.')
self.register_command('disconnect', self.command_disconnect,
- desc=_('Disconnect from the remote server.'),
- shortdesc=_('Disconnect from the server.'))
- self.register_command('export', self.command_export,
- usage=_('[/path/to/file]'),
- desc=_('Export your contacts into /path/to/file if specified, or $HOME/poezio_contacts if not.'),
- shortdesc=_('Export your roster to a file.'),
- completion=self.completion_file)
- self.register_command('import', self.command_import,
- usage=_('[/path/to/file]'),
- desc=_('Import your contacts from /path/to/file if specified, or $HOME/poezio_contacts if not.'),
- shortdesc=_('Import your roster from a file.'),
- completion=self.completion_file)
+ desc='Disconnect from the remote server.',
+ shortdesc='Disconnect from the server.')
self.register_command('clear', self.command_clear,
- shortdesc=_('Clear the info buffer.'))
+ shortdesc='Clear the info buffer.')
self.register_command('last_activity', self.command_last_activity,
- usage=_('<jid>'),
- desc=_('Informs you of the last activity of a JID.'),
- shortdesc=_('Get the activity of someone.'),
+ usage='<jid>',
+ desc='Informs you of the last activity of a JID.',
+ shortdesc='Get the activity of someone.',
completion=self.core.completion_last_activity)
- self.register_command('password', self.command_password,
- usage='<password>',
- shortdesc=_('Change your password'))
self.resize()
self.update_commands()
self.update_keys()
def check_blocking(self, features):
- if 'urn:xmpp:blocking' in features:
+ if 'urn:xmpp:blocking' in features and not self.core.xmpp.anon:
self.register_command('block', self.command_block,
- usage=_('[jid]'),
- shortdesc=_('Prevent a JID from talking to you.'),
+ usage='[jid]',
+ shortdesc='Prevent a JID from talking to you.',
completion=self.completion_block)
self.register_command('unblock', self.command_unblock,
- usage=_('[jid]'),
- shortdesc=_('Allow a JID to talk to you.'),
+ usage='[jid]',
+ shortdesc='Allow a JID to talk to you.',
completion=self.completion_unblock)
self.register_command('list_blocks', self.command_list_blocks,
- shortdesc=_('Show the blocked contacts.'))
+ shortdesc='Show the blocked contacts.')
self.core.xmpp.del_event_handler('session_start', self.check_blocking)
self.core.xmpp.add_event_handler('blocked_message', self.on_blocked_message)
+ def check_saslexternal(self, features):
+ if 'urn:xmpp:saslcert:1' in features and not self.core.xmpp.anon:
+ self.register_command('certs', self.command_certs,
+ desc='List the fingerprints of certificates'
+ ' which can connect to your account.',
+ shortdesc='List allowed client certs.')
+ self.register_command('cert_add', self.command_cert_add,
+ desc='Add a client certificate to the authorized ones. '
+ 'It must have an unique name and be contained in '
+ 'a PEM file. [management] is a boolean indicating'
+ ' if a client connected using this certificate can'
+ ' manage the certificates itself.',
+ shortdesc='Add a client certificate.',
+ usage='<name> <certificate path> [management]',
+ completion=self.completion_cert_add)
+ self.register_command('cert_disable', self.command_cert_disable,
+ desc='Remove a certificate from the list '
+ 'of allowed ones. Clients currently '
+ 'using this certificate will not be '
+ 'forcefully disconnected.',
+ shortdesc='Disable a certificate',
+ usage='<name>')
+ self.register_command('cert_revoke', self.command_cert_revoke,
+ desc='Remove a certificate from the list '
+ 'of allowed ones. Clients currently '
+ 'using this certificate will be '
+ 'forcefully disconnected.',
+ shortdesc='Revoke a certificate',
+ usage='<name>')
+ self.register_command('cert_fetch', self.command_cert_fetch,
+ desc='Retrieve a certificate with its '
+ 'name. It will be stored in <path>.',
+ shortdesc='Fetch a certificate',
+ usage='<name> <path>',
+ completion=self.completion_cert_fetch)
+
+ @command_args_parser.ignored
+ 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)
+
+ @command_args_parser.quoted(2, 1)
+ 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:
+ with open(args[1]) as fd:
+ crt = fd.read()
+ crt = crt.replace(ssl.PEM_FOOTER, '').replace(ssl.PEM_HEADER, '').replace(' ', '').replace('\n', '')
+ except Exception as e:
+ self.core.information('Unable to read the certificate: %s' % e, 'Error')
+ return
+
+ if len(args) > 2:
+ management = args[2]
+ if management:
+ management = management.lower()
+ if management not in ('false', '0'):
+ management = True
+ else:
+ management = False
+ else:
+ management = False
+ else:
+ management = True
+
+ self.core.xmpp.plugin['xep_0257'].add_cert(name, crt, callback=cb,
+ allow_management=management)
+
+ def completion_cert_add(self, the_input):
+ """
+ completion for /cert_add <name> <path> [management]
+ """
+ text = the_input.get_text()
+ args = common.shell_split(text)
+ n = the_input.get_argument_position()
+ log.debug('%s %s %s', the_input.text, n, the_input.pos)
+ if n == 1:
+ return
+ elif n == 2:
+ return self.completion_file(2, the_input)
+ elif n == 3:
+ return the_input.new_completion(['true', 'false'], n)
+
+ @command_args_parser.quoted(1)
+ 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)
+
+ @command_args_parser.quoted(1)
+ 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)
+
+
+ @command_args_parser.quoted(2)
+ 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)
+
+ def completion_cert_fetch(self, the_input):
+ """
+ completion for /cert_fetch <name> <path>
+ """
+ text = the_input.get_text()
+ args = common.shell_split(text)
+ n = the_input.get_argument_position()
+ log.debug('%s %s %s', the_input.text, n, the_input.pos)
+ if n == 1:
+ return
+ elif n == 2:
+ return self.completion_file(2, the_input)
+
def on_blocked_message(self, message):
"""
When we try to send a message to a blocked contact
@@ -158,7 +368,8 @@ class RosterInfoTab(Tab):
}
tab.add_message(message)
- def command_block(self, arg):
+ @command_args_parser.quoted(0, 1)
+ def command_block(self, args):
"""
/block [jid]
"""
@@ -169,8 +380,8 @@ class RosterInfoTab(Tab):
return self.core.information('Contact blocked.', 'Info')
item = self.roster_win.selected_row
- if arg:
- jid = safeJID(arg)
+ if args:
+ jid = safeJID(args[0])
elif isinstance(item, Contact):
jid = item.bare_jid
elif isinstance(item, Resource):
@@ -185,7 +396,8 @@ class RosterInfoTab(Tab):
jids = roster.jids()
return the_input.new_completion(jids, 1, '', quotify=False)
- def command_unblock(self, arg):
+ @command_args_parser.quoted(0, 1)
+ def command_unblock(self, args):
"""
/unblock [jid]
"""
@@ -196,8 +408,8 @@ class RosterInfoTab(Tab):
return self.core.information('Contact unblocked.', 'Info')
item = self.roster_win.selected_row
- if arg:
- jid = safeJID(arg)
+ if args:
+ jid = safeJID(args[0])
elif isinstance(item, Contact):
jid = item.bare_jid
elif isinstance(item, Resource):
@@ -218,7 +430,8 @@ class RosterInfoTab(Tab):
self.core.xmpp.plugin['xep_0191'].get_blocked(callback=on_result)
return True
- def command_list_blocks(self, arg=None):
+ @command_args_parser.ignored
+ def command_list_blocks(self):
"""
/list_blocks
"""
@@ -236,7 +449,8 @@ class RosterInfoTab(Tab):
self.core.xmpp.plugin['xep_0191'].get_blocked(callback=callback)
- def command_reconnect(self, args=None):
+ @command_args_parser.ignored
+ def command_reconnect(self):
"""
/reconnect
"""
@@ -245,19 +459,21 @@ class RosterInfoTab(Tab):
else:
self.core.xmpp.connect()
- def command_disconnect(self, args=None):
+ @command_args_parser.ignored
+ def command_disconnect(self):
"""
/disconnect
"""
self.core.disconnect()
- def command_last_activity(self, arg=None):
+ @command_args_parser.quoted(0, 1)
+ def command_last_activity(self, args):
"""
/activity [jid]
"""
item = self.roster_win.selected_row
- if arg:
- jid = arg
+ if args:
+ jid = args[0]
elif isinstance(item, Contact):
jid = item.bare_jid
elif isinstance(item, Resource):
@@ -311,31 +527,45 @@ class RosterInfoTab(Tab):
not self.input.help_message:
self.complete_commands(self.input)
- def completion_file(self, the_input):
+ def completion_file(self, complete_number, the_input):
"""
- Completion for /import and /export
+ Generic quoted completion for files/paths
+ (use functools.partial to use directly as a completion
+ for a command)
"""
text = the_input.get_text()
- args = text.split()
- n = len(args)
- if n == 1:
- home = os.getenv('HOME') or '/'
- return the_input.auto_completion([home, '/tmp'], '')
- else:
- the_path = text[text.index(' ')+1:]
+ args = common.shell_split(text)
+ n = the_input.get_argument_position()
+ if n == complete_number:
+ if args[n-1] == '' or len(args) < n+1:
+ home = os.getenv('HOME') or '/'
+ return the_input.new_completion([home, '/tmp'], n, quotify=True)
+ path_ = args[n]
+ if path.isdir(path_):
+ dir_ = path_
+ base = ''
+ else:
+ dir_ = path.dirname(path_)
+ base = path.basename(path_)
try:
- names = os.listdir(the_path)
- except:
+ names = os.listdir(dir_)
+ except OSError:
names = []
+ names_filtered = [name for name in names if name.startswith(base)]
+ if names_filtered:
+ names = names_filtered
+ if not names:
+ names = [path_]
end_list = []
for name in names:
- value = os.path.join(the_path, name)
+ value = os.path.join(dir_, name)
if not name.startswith('.'):
end_list.append(value)
- return the_input.auto_completion(end_list, '')
+ return the_input.new_completion(end_list, n, quotify=True)
- def command_clear(self, arg=''):
+ @command_args_parser.ignored
+ def command_clear(self):
"""
/clear
"""
@@ -344,7 +574,8 @@ class RosterInfoTab(Tab):
self.core.information_win.rebuild_everything(self.core.information_buffer)
self.refresh()
- def command_password(self, arg):
+ @command_args_parser.quoted(1)
+ def command_password(self, args):
"""
/password <password>
"""
@@ -352,19 +583,18 @@ class RosterInfoTab(Tab):
if iq['type'] == 'result':
self.core.information('Password updated', 'Account')
if config.get('password'):
- config.silent_set('password', arg)
+ config.silent_set('password', args[0])
else:
self.core.information('Unable to change the password', 'Account')
- self.core.xmpp.plugin['xep_0077'].change_password(arg, callback=callback)
-
+ self.core.xmpp.plugin['xep_0077'].change_password(args[0], callback=callback)
-
- def command_deny(self, arg):
+ @command_args_parser.quoted(0, 1)
+ def command_deny(self, args):
"""
/deny [jid]
Denies a JID from our roster
"""
- if not arg:
+ if not args:
item = self.roster_win.selected_row
if isinstance(item, Contact):
jid = item.bare_jid
@@ -372,7 +602,7 @@ class RosterInfoTab(Tab):
self.core.information('No subscription to deny')
return
else:
- jid = safeJID(arg).bare
+ jid = safeJID(args[0]).bare
if not jid in [jid for jid in roster.jids()]:
self.core.information('No subscription to deny')
return
@@ -383,14 +613,15 @@ class RosterInfoTab(Tab):
self.core.information('Subscription to %s was revoked' % jid,
'Roster')
+ @command_args_parser.quoted(1)
def command_add(self, args):
"""
Add the specified JID to the roster, and set automatically
accept the reverse subscription
"""
- jid = safeJID(safeJID(args.strip()).bare)
+ jid = safeJID(safeJID(args[0]).bare)
if not jid:
- self.core.information(_('No JID specified'), 'Error')
+ self.core.information('No JID specified', 'Error')
return
if jid in roster and roster[jid].subscription in ('to', 'both'):
return self.core.information('Already subscribed.', 'Roster')
@@ -398,7 +629,8 @@ class RosterInfoTab(Tab):
roster.modified()
self.core.information('%s was added to the roster' % jid, 'Roster')
- def command_name(self, arg):
+ @command_args_parser.quoted(1, 1)
+ def command_name(self, args):
"""
Set a name for the specified JID in your roster
"""
@@ -406,15 +638,14 @@ class RosterInfoTab(Tab):
if not iq:
self.core.information('The name could not be set.', 'Error')
log.debug('Error in /name:\n%s', iq)
- args = common.shell_split(arg)
- if not args:
+ if args is None:
return self.core.command_help('name')
jid = safeJID(args[0]).bare
name = args[1] if len(args) == 2 else ''
contact = roster[jid]
if contact is None:
- self.core.information(_('No such JID in roster'), 'Error')
+ self.core.information('No such JID in roster', 'Error')
return
groups = set(contact.groups)
@@ -424,24 +655,24 @@ class RosterInfoTab(Tab):
self.core.xmpp.update_roster(jid, name=name, groups=groups,
subscription=subscription, callback=callback)
+ @command_args_parser.quoted(2)
def command_groupadd(self, args):
"""
Add the specified JID to the specified group
"""
- args = common.shell_split(args)
- if len(args) != 2:
- return
+ if args is None:
+ return self.core.command_help('groupadd')
jid = safeJID(args[0]).bare
group = args[1]
contact = roster[jid]
if contact is None:
- self.core.information(_('No such JID in roster'), 'Error')
+ self.core.information('No such JID in roster', 'Error')
return
new_groups = set(contact.groups)
if group in new_groups:
- self.core.information(_('JID already in group'), 'Error')
+ self.core.information('JID already in group', 'Error')
return
roster.modified()
@@ -464,12 +695,12 @@ class RosterInfoTab(Tab):
self.core.xmpp.update_roster(jid, name=name, groups=new_groups,
subscription=subscription, callback=callback)
- def command_groupmove(self, arg):
+ @command_args_parser.quoted(3)
+ def command_groupmove(self, args):
"""
Remove the specified JID from the first specified group and add it to the second one
"""
- args = common.shell_split(arg)
- if len(args) != 3:
+ if args is None:
return self.core.command_help('groupmove')
jid = safeJID(args[0]).bare
group_from = args[1]
@@ -477,7 +708,7 @@ class RosterInfoTab(Tab):
contact = roster[jid]
if not contact:
- self.core.information(_('No such JID in roster'), 'Error')
+ self.core.information('No such JID in roster', 'Error')
return
new_groups = set(contact.groups)
@@ -485,19 +716,19 @@ class RosterInfoTab(Tab):
new_groups.remove('none')
if group_to == 'none' or group_from == 'none':
- self.core.information(_('"none" is not a group.'), 'Error')
+ self.core.information('"none" is not a group.', 'Error')
return
if group_from not in new_groups:
- self.core.information(_('JID not in first group'), 'Error')
+ self.core.information('JID not in first group', 'Error')
return
if group_to in new_groups:
- self.core.information(_('JID already in second group'), 'Error')
+ self.core.information('JID already in second group', 'Error')
return
if group_to == group_from:
- self.core.information(_('The groups are the same.'), 'Error')
+ self.core.information('The groups are the same.', 'Error')
return
roster.modified()
@@ -519,19 +750,20 @@ class RosterInfoTab(Tab):
self.core.xmpp.update_roster(jid, name=name, groups=new_groups,
subscription=subscription, callback=callback)
+ @command_args_parser.quoted(2)
def command_groupremove(self, args):
"""
Remove the specified JID from the specified group
"""
- args = common.shell_split(args)
- if len(args) != 2:
- return
+ if args is None:
+ return self.core.command_help('groupremove')
+
jid = safeJID(args[0]).bare
group = args[1]
contact = roster[jid]
if contact is None:
- self.core.information(_('No such JID in roster'), 'Error')
+ self.core.information('No such JID in roster', 'Error')
return
new_groups = set(contact.groups)
@@ -540,7 +772,7 @@ class RosterInfoTab(Tab):
except KeyError:
pass
if group not in new_groups:
- self.core.information(_('JID not in group'), 'Error')
+ self.core.information('JID not in group', 'Error')
return
roster.modified()
@@ -559,13 +791,14 @@ class RosterInfoTab(Tab):
self.core.xmpp.update_roster(jid, name=name, groups=new_groups,
subscription=subscription, callback=callback)
+ @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.strip():
- jid = safeJID(args.strip()).bare
+ if args:
+ jid = safeJID(args[0]).bare
else:
item = self.roster_win.selected_row
if isinstance(item, Contact):
@@ -576,12 +809,12 @@ class RosterInfoTab(Tab):
roster.remove(jid)
del roster[jid]
- def command_import(self, arg):
+ @command_args_parser.quoted(0, 1)
+ def command_import(self, args):
"""
Import the contacts
"""
- args = common.shell_split(arg)
- if len(args):
+ if args:
if args[0].startswith('/'):
filepath = args[0]
else:
@@ -603,12 +836,12 @@ class RosterInfoTab(Tab):
self.command_add(jid.lstrip('\n'))
self.core.information('Contacts imported from %s' % filepath, 'Info')
- def command_export(self, arg):
+ @command_args_parser.quoted(0, 1)
+ def command_export(self, args):
"""
Export the contacts
"""
- args = common.shell_split(arg)
- if len(args):
+ if args:
if args[0].startswith('/'):
filepath = args[0]
else:
@@ -697,11 +930,12 @@ class RosterInfoTab(Tab):
if contact.pending_in)
return the_input.new_completion(jids, 1, '', quotify=False)
- def command_accept(self, arg):
+ @command_args_parser.quoted(0, 1)
+ def command_accept(self, args):
"""
Accept a JID from in roster. Authorize it AND subscribe to it
"""
- if not arg:
+ if not args:
item = self.roster_win.selected_row
if isinstance(item, Contact):
jid = item.bare_jid
@@ -709,7 +943,7 @@ class RosterInfoTab(Tab):
self.core.information('No subscription to accept')
return
else:
- jid = safeJID(arg).bare
+ jid = safeJID(args[0]).bare
nodepart = safeJID(jid).user
jid = safeJID(jid)
# crappy transports putting resources inside the node part
@@ -769,13 +1003,15 @@ class RosterInfoTab(Tab):
success = config.silent_set(option, str(not value))
roster.modified()
if not success:
- self.core.information(_('Unable to write in the config file'), 'Error')
+ self.core.information('Unable to write in the config file', 'Error')
return True
def on_slash(self):
"""
'/' is pressed, we enter "input mode"
"""
+ if isinstance(self.input, windows.YesNoInput):
+ return
curses.curs_set(1)
self.input = windows.CommandInput("", self.reset_help_message, self.execute_slash_command)
self.input.resize(1, self.width, self.height-1, 0)
@@ -951,6 +1187,8 @@ class RosterInfoTab(Tab):
Start the search. The input should appear with a short instruction
in it.
"""
+ if isinstance(self.input, windows.YesNoInput):
+ return
curses.curs_set(1)
self.input = windows.CommandInput("[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter)
self.input.resize(1, self.width, self.height-1, 0)
@@ -961,6 +1199,8 @@ class RosterInfoTab(Tab):
@refresh_wrapper.always
def start_search_slow(self):
+ if isinstance(self.input, windows.YesNoInput):
+ return
curses.curs_set(1)
self.input = windows.CommandInput("[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter_slow)
self.input.resize(1, self.width, self.height-1, 0)