summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/commands.py650
-rw-r--r--src/core/completions.py35
-rw-r--r--src/core/core.py400
-rw-r--r--src/core/handlers.py242
-rw-r--r--src/core/structs.py51
5 files changed, 771 insertions, 607 deletions
diff --git a/src/core/commands.py b/src/core/commands.py
index 4a8f7f19..3830d72a 100644
--- a/src/core/commands.py
+++ b/src/core/commands.py
@@ -6,37 +6,35 @@ import logging
log = logging.getLogger(__name__)
-import functools
import os
-import sys
from datetime import datetime
-from gettext import gettext as _
from xml.etree import cElementTree as ET
from slixmpp.xmlstream.stanzabase import StanzaBase
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
-import bookmark
import common
import fixes
import pep
import tabs
+from bookmarks import Bookmark
from common import safeJID
-from config import config, options as config_opts
+from config import config, DEFAULT_CONFIG, options as config_opts
import multiuserchat as muc
from plugin import PluginConfig
from roster import roster
from theming import dump_tuple, get_theme
+from decorators import command_args_parser
from . structs import Command, possible_show
-def command_help(self, arg):
+@command_args_parser.quoted(0, 1)
+def command_help(self, args):
"""
- /help <command_name>
+ /help [command_name]
"""
- args = arg.split()
if not args:
color = dump_tuple(get_theme().COLOR_HELP_COMMANDS)
acc = []
@@ -66,8 +64,8 @@ def command_help(self, arg):
buff.extend(acc)
msg = '\n'.join(buff)
- msg += _("\nType /help <command_name> to know what each command does")
- if args:
+ msg += "\nType /help <command_name> to know what each command does"
+ else:
command = args[0].lstrip('/').strip()
if command in self.current_tab().commands:
@@ -75,16 +73,17 @@ def command_help(self, arg):
elif command in self.commands:
tup = self.commands[command]
else:
- self.information(_('Unknown command: %s') % command, 'Error')
+ self.information('Unknown command: %s' % command, 'Error')
return
if isinstance(tup, Command):
- msg = _('Usage: /%s %s\n' % (command, tup.usage))
+ msg = 'Usage: /%s %s\n' % (command, tup.usage)
msg += tup.desc
else:
msg = tup[1]
self.information(msg, 'Help')
-def command_runkey(self, arg):
+@command_args_parser.quoted(1)
+def command_runkey(self, args):
"""
/runkey <key>
"""
@@ -93,7 +92,9 @@ def command_runkey(self, arg):
if key == '^J':
return '\n'
return key
- char = arg.strip()
+ if args is None:
+ return self.command_help('runkey')
+ char = args[0]
func = self.key_func.get(char, None)
if func:
func()
@@ -102,21 +103,20 @@ def command_runkey(self, arg):
if res:
self.refresh_window()
-def command_status(self, arg):
+@command_args_parser.quoted(1, 1, [None])
+def command_status(self, args):
"""
/status <status> [msg]
"""
- args = common.shell_split(arg)
- if len(args) < 1:
- return
+ if args is None:
+ return self.command_help('status')
+
if not args[0] in possible_show.keys():
- self.command_help('status')
- return
+ return self.command_help('status')
+
show = possible_show[args[0]]
- if len(args) == 2:
- msg = args[1]
- else:
- msg = None
+ msg = args[1]
+
pres = self.xmpp.make_presence()
if msg:
pres['status'] = msg
@@ -136,19 +136,15 @@ def command_status(self, arg):
if is_muctab and current.joined and show not in ('away', 'xa'):
current.send_chat_state('active')
-def command_presence(self, arg):
+@command_args_parser.quoted(1, 2, [None, None])
+def command_presence(self, args):
"""
/presence <JID> [type] [status]
"""
- args = common.shell_split(arg)
- if len(args) == 1:
- jid, type, status = args[0], None, None
- elif len(args) == 2:
- jid, type, status = args[0], args[1], None
- elif len(args) == 3:
- jid, type, status = args[0], args[1], args[2]
- else:
- return
+ if args is None:
+ return self.command_help('presence')
+
+ jid, type, status = args[0], args[1], args[2]
if jid == '.' and isinstance(self.current_tab(), tabs.ChatTab):
jid = self.current_tab().name
if type == 'available':
@@ -158,7 +154,7 @@ def command_presence(self, arg):
self.events.trigger('send_normal_presence', pres)
pres.send()
except:
- self.information(_('Could not send directed presence'), 'Error')
+ self.information('Could not send directed presence', 'Error')
log.debug('Could not send directed presence to %s', jid, exc_info=True)
return
tab = self.get_tab_by_name(jid)
@@ -177,24 +173,26 @@ def command_presence(self, arg):
if self.current_tab() in tab.privates:
self.current_tab().send_chat_state(chatstate, True)
-def command_theme(self, arg=''):
+@command_args_parser.quoted(1)
+def command_theme(self, args=None):
"""/theme <theme name>"""
- args = arg.split()
- if args:
- self.command_set('theme %s' % (args[0],))
+ if args is None:
+ return self.command_help('theme')
+ self.command_set('theme %s' % (args[0],))
-def command_win(self, arg):
+@command_args_parser.quoted(1)
+def command_win(self, args):
"""
/win <number>
"""
- arg = arg.strip()
- if not arg:
- self.command_help('win')
- return
+ if args is None:
+ return self.command_help('win')
+
+ nb = args[0]
try:
- nb = int(arg.split()[0])
+ nb = int(nb)
except ValueError:
- nb = arg
+ pass
if self.current_tab_nb == nb:
return
self.previous_tab_nb = self.current_tab_nb
@@ -219,15 +217,15 @@ def command_win(self, arg):
self.current_tab().on_gain_focus()
self.refresh_window()
-def command_move_tab(self, arg):
+@command_args_parser.quoted(2)
+def command_move_tab(self, args):
"""
/move_tab old_pos new_pos
"""
- args = common.shell_split(arg)
- current_tab = self.current_tab()
- if len(args) != 2:
+ if args is None:
return self.command_help('move_tab')
+ current_tab = self.current_tab()
if args[0] == '.':
args[0] = current_tab.nb
if args[1] == '.':
@@ -259,16 +257,16 @@ def command_move_tab(self, arg):
self.current_tab_nb = self.tabs.index(current_tab)
self.refresh_window()
-def command_list(self, arg):
+@command_args_parser.quoted(0, 1)
+def command_list(self, args):
"""
- /list <server>
+ /list [server]
Opens a MucListTab containing the list of the room in the specified server
"""
- arg = arg.split()
- if len(arg) > 1:
+ if args is None:
return self.command_help('list')
- elif arg:
- server = safeJID(arg[0]).server
+ elif args:
+ server = safeJID(args[0])
else:
if not isinstance(self.current_tab(), tabs.MucTab):
return self.information('Please provide a server', 'Error')
@@ -279,26 +277,27 @@ def command_list(self, arg):
self.xmpp.plugin['xep_0030'].get_items(jid=server,
callback=cb)
-def command_version(self, arg):
+@command_args_parser.quoted(1)
+def command_version(self, args):
"""
/version <jid>
"""
def callback(res):
"Callback for /version"
if not res:
- return self.information(_('Could not get the software'
- ' version from %s') % jid,
- _('Warning'))
- version = _('%s is running %s version %s on %s') % (
+ return self.information('Could not get the software'
+ ' version from %s' % jid,
+ 'Warning')
+ version = '%s is running %s version %s on %s' % (
jid,
- res.get('name') or _('an unknown software'),
- res.get('version') or _('unknown'),
- res.get('os') or _('an unknown platform'))
+ res.get('name') or 'an unknown software',
+ res.get('version') or 'unknown',
+ res.get('os') or 'an unknown platform')
self.information(version, 'Info')
- args = common.shell_split(arg)
- if len(args) < 1:
+ if args is None:
return self.command_help('version')
+
jid = safeJID(args[0])
if jid.resource or jid not in roster:
fixes.get_version(self.xmpp, jid, callback=callback)
@@ -308,11 +307,11 @@ def command_version(self, arg):
else:
fixes.get_version(self.xmpp, jid, callback=callback)
-def command_join(self, arg, histo_length=None):
+@command_args_parser.quoted(0, 2)
+def command_join(self, args, histo_length=None):
"""
/join [room][/nick] [password]
"""
- args = common.shell_split(arg)
password = None
if len(args) == 0:
tab = self.current_tab()
@@ -388,13 +387,15 @@ def command_join(self, arg, histo_length=None):
seconds = int(seconds)
else:
seconds = 0
+ if password:
+ tab.password = password
muc.join_groupchat(self, room, nick, password,
histo_length,
current_status.message,
current_status.show,
seconds=seconds)
if not tab:
- self.open_new_room(room, nick)
+ self.open_new_room(room, nick, password=password)
muc.join_groupchat(self, room, nick, password,
histo_length,
current_status.message,
@@ -409,196 +410,162 @@ def command_join(self, arg, histo_length=None):
tab.refresh()
self.doupdate()
-def command_bookmark_local(self, arg=''):
+@command_args_parser.quoted(0, 2)
+def command_bookmark_local(self, args):
"""
/bookmark_local [room][/nick] [password]
"""
- args = common.shell_split(arg)
- nick = None
- password = None
if not args and not isinstance(self.current_tab(), tabs.MucTab):
return
- if not args:
- tab = self.current_tab()
- roomname = tab.name
- if tab.joined and tab.own_nick != self.own_nick:
- nick = tab.own_nick
- elif args[0] == '*':
- new_bookmarks = []
- for tab in self.get_tabs(tabs.MucTab):
- b = bookmark.get_by_jid(tab.name)
- if not b:
- b = bookmark.Bookmark(tab.name,
- autojoin=True,
- method="local")
- new_bookmarks.append(b)
- else:
- b.method = "local"
- new_bookmarks.append(b)
- bookmark.bookmarks.remove(b)
- new_bookmarks.extend(bookmark.bookmarks)
- bookmark.bookmarks = new_bookmarks
- bookmark.save_local()
- bookmark.save_remote(self.xmpp, None)
- self.information('Bookmarks added and saved.', 'Info')
- return
- else:
- info = safeJID(args[0])
- if info.resource != '':
- nick = info.resource
- roomname = info.bare
- if not roomname:
- if not isinstance(self.current_tab(), tabs.MucTab):
- return
- roomname = self.current_tab().name
- if len(args) > 1:
- password = args[1]
-
- bm = bookmark.get_by_jid(roomname)
- if not bm:
- bm = bookmark.Bookmark(jid=roomname)
- bookmark.bookmarks.append(bm)
- self.information('Bookmark added.', 'Info')
- else:
- self.information('Bookmark updated.', 'Info')
- if nick:
- bm.nick = nick
- bm.autojoin = True
- bm.password = password
- bm.method = "local"
- bookmark.save_local()
- self.information(_('Your local bookmarks are now: %s') %
- [b for b in bookmark.bookmarks if b.method == 'local'], 'Info')
+ password = args[1] if len(args) > 1 else None
+ jid = args[0] if args else None
+
+ _add_bookmark(self, jid, True, password, 'local')
-def command_bookmark(self, arg=''):
+@command_args_parser.quoted(0, 3)
+def command_bookmark(self, args):
"""
/bookmark [room][/nick] [autojoin] [password]
"""
+ if not args and not isinstance(self.current_tab(), tabs.MucTab):
+ return
+ jid = args[0] if args else ''
+ password = args[2] if len(args) > 2 else None
if not config.get('use_remote_bookmarks'):
- self.command_bookmark_local(arg)
- return
- args = common.shell_split(arg)
+ return _add_bookmark(self, jid, True, password, 'local')
+
+ if len(args) > 1:
+ autojoin = False if args[1].lower() != 'true' else True
+ else:
+ autojoin = True
+
+ _add_bookmark(self, jid, autojoin, password, 'remote')
+
+def _add_bookmark(self, jid, autojoin, password, method):
nick = None
- if not args and not isinstance(self.current_tab(), tabs.MucTab):
- return
- if not args:
+ if not jid:
tab = self.current_tab()
roomname = tab.name
- if tab.joined:
+ if tab.joined and tab.own_nick != self.own_nick:
nick = tab.own_nick
- autojoin = True
- password = None
- elif args[0] == '*':
- if len(args) > 1:
- autojoin = False if args[1].lower() != 'true' else True
- else:
- autojoin = True
- new_bookmarks = []
- for tab in self.get_tabs(tabs.MucTab):
- b = bookmark.get_by_jid(tab.name)
- if not b:
- b = bookmark.Bookmark(tab.name, autojoin=autojoin,
- method=bookmark.preferred)
- new_bookmarks.append(b)
- else:
- b.method = bookmark.preferred
- bookmark.bookmarks.remove(b)
- new_bookmarks.append(b)
- new_bookmarks.extend(bookmark.bookmarks)
- bookmark.bookmarks = new_bookmarks
- def _cb(self, iq):
- if iq["type"] != "error":
- bookmark.save_local()
- self.information("Bookmarks added.", "Info")
- else:
- self.information("Could not add the bookmarks.", "Info")
- bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
- return
+ if password is None and tab.password is not None:
+ password = tab.password
+ elif jid == '*':
+ return _add_wildcard_bookmarks(self, method)
else:
- info = safeJID(args[0])
- if info.resource != '':
- nick = info.resource
- roomname = info.bare
+ info = safeJID(jid)
+ roomname, nick = info.bare, info.resource
if roomname == '':
if not isinstance(self.current_tab(), tabs.MucTab):
return
roomname = self.current_tab().name
- if len(args) > 1:
- autojoin = False if args[1].lower() != 'true' else True
- else:
- autojoin = True
- if len(args) > 2:
- password = args[2]
- else:
- password = None
- bm = bookmark.get_by_jid(roomname)
- if not bm:
- bm = bookmark.Bookmark(roomname)
- bookmark.bookmarks.append(bm)
- bm.method = config.get('use_bookmarks_method')
+ bookmark = self.bookmarks[roomname]
+ if bookmark is None:
+ bookmark = Bookmark(roomname)
+ self.bookmarks.append(bookmark)
+ bookmark.method = method
+ bookmark.autojoin = autojoin
if nick:
- bm.nick = nick
+ bookmark.nick = nick
if password:
- bm.password = password
- bm.autojoin = autojoin
- def _cb(self, iq):
+ bookmark.password = password
+ def callback(iq):
if iq["type"] != "error":
self.information('Bookmark added.', 'Info')
else:
self.information("Could not add the bookmarks.", "Info")
- bookmark.save_remote(self.xmpp, functools.partial(_cb, self))
- remote = []
- for each in bookmark.bookmarks:
- if each.method in ('pep', 'privatexml'):
- remote.append(each)
+ self.bookmarks.save_local()
+ self.bookmarks.save_remote(self.xmpp, callback)
+
+def _add_wildcard_bookmarks(self, method):
+ new_bookmarks = []
+ for tab in self.get_tabs(tabs.MucTab):
+ bookmark = self.bookmarks[tab.name]
+ if not bookmark:
+ bookmark = Bookmark(tab.name, autojoin=True,
+ method=method)
+ new_bookmarks.append(bookmark)
+ else:
+ bookmark.method = method
+ new_bookmarks.append(bookmark)
+ self.bookmarks.remove(bookmark)
+ new_bookmarks.extend(self.bookmarks.bookmarks)
+ self.bookmarks.set(new_bookmarks)
+ def _cb(iq):
+ if iq["type"] != "error":
+ self.information("Bookmarks saved.", "Info")
+ else:
+ self.information("Could not save the remote bookmarks.", "Info")
+ self.bookmarks.save_local()
+ self.bookmarks.save_remote(self.xmpp, _cb)
-def command_bookmarks(self, arg=''):
+@command_args_parser.ignored
+def command_bookmarks(self):
"""/bookmarks"""
- local = []
- remote = []
- for each in bookmark.bookmarks:
- if each.method in ('pep', 'privatexml'):
- remote.append(each)
- elif each.method == 'local':
- local.append(each)
-
- self.information(_('Your remote bookmarks are: %s') % remote,
- _('Info'))
- self.information(_('Your local bookmarks are: %s') % local,
- _('Info'))
-
-def command_remove_bookmark(self, arg=''):
+ tab = self.get_tab_by_name('Bookmarks', tabs.BookmarksTab)
+ old_tab = self.current_tab()
+ if tab:
+ self.current_tab_nb = tab.nb
+ else:
+ tab = tabs.BookmarksTab(self.bookmarks)
+ self.tabs.append(tab)
+ self.current_tab_nb = tab.nb
+ old_tab.on_lose_focus()
+ tab.on_gain_focus()
+ self.refresh_window()
+
+@command_args_parser.quoted(0, 1)
+def command_remove_bookmark(self, args):
"""/remove_bookmark [jid]"""
- args = common.shell_split(arg)
+
+ def cb(success):
+ if success:
+ self.information('Bookmark deleted', 'Info')
+ else:
+ self.information('Error while deleting the bookmark', 'Error')
+
if not args:
tab = self.current_tab()
- if isinstance(tab, tabs.MucTab) and bookmark.get_by_jid(tab.name):
- bookmark.remove(tab.name)
- bookmark.save(self.xmpp)
- if bookmark.save(self.xmpp):
- self.information('Bookmark deleted', 'Info')
+ if isinstance(tab, tabs.MucTab) and self.bookmarks[tab.name]:
+ self.bookmarks.remove(tab.name)
+ self.bookmarks.save(self.xmpp, callback=cb)
else:
self.information('No bookmark to remove', 'Info')
else:
- if bookmark.get_by_jid(args[0]):
- bookmark.remove(args[0])
- if bookmark.save(self.xmpp):
- self.information('Bookmark deleted', 'Info')
-
+ if self.bookmarks[args[0]]:
+ self.bookmarks.remove(args[0])
+ self.bookmarks.save(self.xmpp, callback=cb)
else:
self.information('No bookmark to remove', 'Info')
-def command_set(self, arg):
+@command_args_parser.quoted(0, 3)
+def command_set(self, args):
"""
/set [module|][section] <option> [value]
"""
- args = common.shell_split(arg)
- if len(args) == 1:
+ if args is None or len(args) == 0:
+ config_dict = config.to_dict()
+ lines = []
+ theme = get_theme()
+ for section_name, section in config_dict.items():
+ lines.append('\x19%(section_col)s}[%(section)s]\x19o' %
+ {
+ 'section': section_name,
+ 'section_col': dump_tuple(theme.COLOR_INFORMATION_TEXT),
+ })
+ for option_name, option_value in section.items():
+ lines.append('%s\x19%s}=\x19o%s' % (option_name,
+ dump_tuple(theme.COLOR_REVISIONS_MESSAGE),
+ option_value))
+ info = ('Current options:\n%s' % '\n'.join(lines), 'Info')
+ elif len(args) == 1:
option = args[0]
value = config.get(option)
+ if value is None and '=' in option:
+ args = option.split('=', 1)
info = ('%s=%s' % (option, value), 'Info')
- elif len(args) == 2:
+ if len(args) == 2:
if '|' in args[0]:
plugin_name, section = args[0].split('|')[:2]
if not section:
@@ -639,44 +606,72 @@ def command_set(self, arg):
plugin_config = self.plugin_manager.plugins[plugin_name].config
info = plugin_config.set_and_save(option, value, section)
else:
- section = args[0]
+ if args[0] == '.':
+ name = safeJID(self.current_tab().name).bare
+ if not name:
+ self.information('Invalid tab to use the "." argument.',
+ 'Error')
+ return
+ section = name
+ else:
+ section = args[0]
option = args[1]
value = args[2]
info = config.set_and_save(option, value, section)
self.trigger_configuration_change(option, value)
- else:
- self.command_help('set')
- return
- self.call_for_resize()
+ elif len(args) > 3:
+ return self.command_help('set')
self.information(*info)
-def command_toggle(self, arg):
+@command_args_parser.quoted(1, 2)
+def command_set_default(self, args):
+ """
+ /set_default [section] <option>
+ """
+ if len(args) == 1:
+ option = args[0]
+ section = 'Poezio'
+ elif len(args) == 2:
+ section = args[0]
+ option = args[1]
+ else:
+ return self.command_help('set_default')
+
+ default_config = DEFAULT_CONFIG.get(section, tuple())
+ if option not in default_config:
+ info = ("Option %s has no default value" % (option), "Error")
+ return self.information(*info)
+ self.command_set('%s %s %s' % (section, option, default_config[option]))
+
+@command_args_parser.quoted(1)
+def command_toggle(self, args):
"""
/toggle <option>
shortcut for /set <option> toggle
"""
- arg = arg.split()
- if arg and arg[0]:
- self.command_set('%s toggle' % arg[0])
+ if args is None:
+ return self.command_help('toggle')
-def command_server_cycle(self, arg=''):
+ if args[0]:
+ self.command_set('%s toggle' % args[0])
+
+@command_args_parser.quoted(1, 1)
+def command_server_cycle(self, args):
"""
Do a /cycle on each room of the given server.
If none, do it on the current tab
"""
- args = common.shell_split(arg)
tab = self.current_tab()
message = ""
- if len(args):
+ if args:
domain = args[0]
- if len(args) > 1:
+ if len(args) == 2:
message = args[1]
else:
if isinstance(tab, tabs.MucTab):
domain = safeJID(tab.name).domain
else:
- self.information(_("No server specified"), "Error")
- return
+ return self.information("No server specified", "Error")
for tab in self.get_tabs(tabs.MucTab):
if tab.name.endswith(domain):
if tab.joined:
@@ -690,7 +685,8 @@ def command_server_cycle(self, arg=''):
else:
self.command_join('"%s/%s"' %(tab.name, tab.own_nick))
-def command_last_activity(self, arg):
+@command_args_parser.quoted(1)
+def command_last_activity(self, args):
"""
/last_activity <jid>
"""
@@ -698,11 +694,11 @@ def command_last_activity(self, arg):
"Callback for the last activity"
if iq['type'] != 'result':
if iq['error']['type'] == 'auth':
- self.information(_('You are not allowed to see the '
- 'activity of this contact.'),
- _('Error'))
+ self.information('You are not allowed to see the '
+ 'activity of this contact.',
+ 'Error')
else:
- self.information(_('Error retrieving the activity'), 'Error')
+ self.information('Error retrieving the activity', 'Error')
return
seconds = iq['last_activity']['seconds']
status = iq['last_activity']['status']
@@ -717,46 +713,47 @@ def command_last_activity(self, arg):
common.parse_secs_to_str(seconds),
(' and his/her last status was %s' % status) if status else '')
self.information(msg, 'Info')
- jid = safeJID(arg)
- if jid == '':
+
+ if args is None:
return self.command_help('last_activity')
+ jid = safeJID(args[0])
self.xmpp.plugin['xep_0012'].get_last_activity(jid,
callback=callback)
-def command_mood(self, arg):
+@command_args_parser.quoted(0, 2)
+def command_mood(self, args):
"""
/mood [<mood> [text]]
"""
- args = common.shell_split(arg)
if not args:
- self.xmpp.plugin['xep_0107'].stop()
- return
+ return self.xmpp.plugin['xep_0107'].stop()
+
mood = args[0]
if mood not in pep.MOODS:
- return self.information(_('%s is not a correct value for a mood.')
- % mood,
- _('Error'))
- if len(args) > 1:
+ return self.information('%s is not a correct value for a mood.'
+ % mood,
+ 'Error')
+ if len(args) == 2:
text = args[1]
else:
text = None
self.xmpp.plugin['xep_0107'].publish_mood(mood, text,
callback=dumb_callback)
-def command_activity(self, arg):
+@command_args_parser.quoted(0, 3)
+def command_activity(self, args):
"""
/activity [<general> [specific] [text]]
"""
- args = common.shell_split(arg)
length = len(args)
if not length:
- self.xmpp.plugin['xep_0108'].stop()
- return
+ return self.xmpp.plugin['xep_0108'].stop()
+
general = args[0]
if general not in pep.ACTIVITIES:
- return self.information(_('%s is not a correct value for an activity')
+ return self.information('%s is not a correct value for an activity'
% general,
- _('Error'))
+ 'Error')
specific = None
text = None
if length == 2:
@@ -768,20 +765,20 @@ def command_activity(self, arg):
specific = args[1]
text = args[2]
if specific and specific not in pep.ACTIVITIES[general]:
- return self.information(_('%s is not a correct value '
- 'for an activity') % specific,
- _('Error'))
+ return self.information('%s is not a correct value '
+ 'for an activity' % specific,
+ 'Error')
self.xmpp.plugin['xep_0108'].publish_activity(general, specific, text,
callback=dumb_callback)
-def command_gaming(self, arg):
+@command_args_parser.quoted(0, 2)
+def command_gaming(self, args):
"""
/gaming [<game name> [server address]]
"""
- args = common.shell_split(arg)
if not args:
- self.xmpp.plugin['xep_0196'].stop()
- return
+ return self.xmpp.plugin['xep_0196'].stop()
+
name = args[0]
if len(args) > 1:
address = args[1]
@@ -791,25 +788,27 @@ def command_gaming(self, arg):
server_address=address,
callback=dumb_callback)
-def command_invite(self, arg):
+@command_args_parser.quoted(2, 1, [None])
+def command_invite(self, args):
"""/invite <to> <room> [reason]"""
- args = common.shell_split(arg)
- if len(args) < 2:
- return
- reason = args[2] if len(args) > 2 else None
+
+ if args is None:
+ return self.command_help('invite')
+
+ reason = args[2]
to = safeJID(args[0])
room = safeJID(args[1]).bare
self.invite(to.full, room, reason=reason)
-def command_decline(self, arg):
+@command_args_parser.quoted(1, 1, [''])
+def command_decline(self, args):
"""/decline <room@server.tld> [reason]"""
- args = common.shell_split(arg)
- if not len(args):
- return
+ if args is None:
+ return self.command_help('decline')
jid = safeJID(args[0])
if jid.bare not in self.pending_invites:
return
- reason = args[1] if len(args) > 1 else ''
+ reason = args[1]
del self.pending_invites[jid.bare]
self.xmpp.plugin['xep_0045'].decline_invite(jid.bare,
self.pending_invites[jid.bare],
@@ -817,7 +816,8 @@ def command_decline(self, arg):
### Commands without a completion in this class ###
-def command_invitations(self, arg=''):
+@command_args_parser.ignored
+def command_invitations(self):
"""/invitations"""
build = ""
for invite in self.pending_invites:
@@ -829,17 +829,16 @@ def command_invitations(self, arg=''):
build = "You do not have any pending invitations."
self.information(build, 'Info')
-def command_quit(self, arg=''):
+@command_args_parser.quoted(0, 1, [None])
+def command_quit(self, args):
"""
- /quit
+ /quit [message]
"""
if not self.xmpp.is_connected():
self.exit()
return
- if len(arg.strip()) != 0:
- msg = arg
- else:
- msg = None
+
+ msg = args[0]
if config.get('enable_user_mood'):
self.xmpp.plugin['xep_0107'].stop()
if config.get('enable_user_activity'):
@@ -851,44 +850,47 @@ def command_quit(self, arg=''):
self.disconnect(msg)
self.xmpp.add_event_handler("disconnected", self.exit, disposable=True)
-def command_destroy_room(self, arg=''):
+@command_args_parser.quoted(0, 1, [''])
+def command_destroy_room(self, args):
"""
/destroy_room [JID]
"""
- room = safeJID(arg).bare
+ room = safeJID(args[0]).bare
if room:
muc.destroy_room(self.xmpp, room)
- elif isinstance(self.current_tab(), tabs.MucTab) and not arg:
+ elif isinstance(self.current_tab(), tabs.MucTab) and not args[0]:
muc.destroy_room(self.xmpp, self.current_tab().general_jid)
else:
- self.information(_('Invalid JID: "%s"') % arg, _('Error'))
+ self.information('Invalid JID: "%s"' % args[0], 'Error')
-def command_bind(self, arg):
+@command_args_parser.quoted(1, 1, [''])
+def command_bind(self, args):
"""
Bind a key.
"""
- args = common.shell_split(arg)
- if len(args) < 1:
+ if args is None:
return self.command_help('bind')
- elif len(args) < 2:
- args.append("")
+
if not config.silent_set(args[0], args[1], section='bindings'):
- self.information(_('Unable to write in the config file'), 'Error')
+ self.information('Unable to write in the config file', 'Error')
+
if args[1]:
self.information('%s is now bound to %s' % (args[0], args[1]), 'Info')
else:
self.information('%s is now unbound' % args[0], 'Info')
-def command_rawxml(self, arg):
+@command_args_parser.raw
+def command_rawxml(self, args):
"""
/rawxml <xml stanza>
"""
- if not arg:
- return
+ if not args:
+ return
+ stanza = args
try:
- stanza = StanzaBase(self.xmpp, xml=ET.fromstring(arg))
+ stanza = StanzaBase(self.xmpp, xml=ET.fromstring(stanza))
if stanza.xml.tag == 'iq' and \
stanza.xml.attrib.get('type') in ('get', 'set') and \
stanza.xml.attrib.get('id'):
@@ -910,78 +912,85 @@ def command_rawxml(self, arg):
stanza.send()
except:
- self.information(_('Could not send custom stanza'), 'Error')
+ self.information('Could not send custom stanza', 'Error')
log.debug('/rawxml: Could not send custom stanza (%s)',
- repr(arg),
+ repr(stanza),
exc_info=True)
-def command_load(self, arg):
+@command_args_parser.quoted(1, 256)
+def command_load(self, args):
"""
/load <plugin> [<otherplugin> …]
+ # TODO: being able to load more than 256 plugins at once, hihi.
"""
- args = arg.split()
for plugin in args:
self.plugin_manager.load(plugin)
-def command_unload(self, arg):
+@command_args_parser.quoted(1, 256)
+def command_unload(self, args):
"""
/unload <plugin> [<otherplugin> …]
"""
- args = arg.split()
for plugin in args:
self.plugin_manager.unload(plugin)
-def command_plugins(self, arg=''):
+@command_args_parser.ignored
+def command_plugins(self):
"""
/plugins
"""
- self.information(_("Plugins currently in use: %s") %
+ self.information("Plugins currently in use: %s" %
repr(list(self.plugin_manager.plugins.keys())),
- _('Info'))
+ 'Info')
-def command_message(self, arg):
+@command_args_parser.quoted(1, 1)
+def command_message(self, args):
"""
/message <jid> [message]
"""
- args = common.shell_split(arg)
- if len(args) < 1:
- self.command_help('message')
- return
+ if args is None:
+ return self.command_help('message')
jid = safeJID(args[0])
if not jid.user and not jid.domain and not jid.resource:
return self.information('Invalid JID.', 'Error')
tab = self.get_conversation_by_jid(jid.full, False, fallback_barejid=False)
- if not tab:
+ muc = self.get_tab_by_name(jid.bare, typ=tabs.MucTab)
+ if not tab and not muc:
tab = self.open_conversation_window(jid.full, focus=True)
+ elif muc:
+ tab = self.get_tab_by_name(jid.full, typ=tabs.PrivateTab)
+ if tab:
+ self.focus_tab_named(tab.name)
+ else:
+ tab = self.open_private_window(jid.bare, jid.resource)
else:
self.focus_tab_named(tab.name)
- if len(args) > 1:
+ if len(args) == 2:
tab.command_say(args[1])
-def command_xml_tab(self, arg=''):
+@command_args_parser.ignored
+def command_xml_tab(self):
"""/xml_tab"""
- self.xml_tab = True
xml_tab = self.focus_tab_named('XMLTab', tabs.XMLTab)
if not xml_tab:
tab = tabs.XMLTab()
self.add_tab(tab, True)
+ self.xml_tab = tab
-def command_adhoc(self, arg):
- arg = arg.split()
- if len(arg) > 1:
+@command_args_parser.quoted(1)
+def command_adhoc(self, args):
+ if not args:
return self.command_help('ad-hoc')
- elif arg:
- jid = safeJID(arg[0])
- else:
- return self.information('Please provide a jid', 'Error')
+ jid = safeJID(args[0])
list_tab = tabs.AdhocCommandsListTab(jid)
self.add_tab(list_tab, True)
cb = list_tab.on_list_received
self.xmpp.plugin['xep_0050'].get_commands(jid=jid, local=False,
callback=cb)
-def command_self(self, arg=None):
+@command_args_parser.ignored
+def command_self(self):
"""
/self
"""
@@ -998,5 +1007,14 @@ def command_self(self, arg=None):
config_opts.version))
self.information(info, 'Info')
+
+@command_args_parser.ignored
+def command_reload(self):
+ """
+ /reload
+ """
+ self.reload_config()
+
def dumb_callback(*args, **kwargs):
"mock callback"
+
diff --git a/src/core/completions.py b/src/core/completions.py
index 7d95321b..f17e916c 100644
--- a/src/core/completions.py
+++ b/src/core/completions.py
@@ -8,7 +8,6 @@ log = logging.getLogger(__name__)
import os
from functools import reduce
-import bookmark
import common
import pep
import tabs
@@ -57,7 +56,7 @@ def completion_theme(self, the_input):
except OSError as e:
log.error('Completion for /theme failed', exc_info=True)
return
- theme_files = [name[:-3] for name in names if name.endswith('.py')]
+ theme_files = [name[:-3] for name in names if name.endswith('.py') and name != '__init__.py']
if not 'default' in theme_files:
theme_files.append('default')
return the_input.new_completion(theme_files, 1, '', quotify=False)
@@ -96,7 +95,7 @@ def completion_join(self, the_input):
relevant_rooms = []
relevant_rooms.extend(sorted(self.pending_invites.keys()))
- bookmarks = {str(elem.jid): False for elem in bookmark.bookmarks}
+ bookmarks = {str(elem.jid): False for elem in self.bookmarks}
for tab in self.get_tabs(tabs.MucTab):
name = tab.name
if name in bookmarks and not tab.joined:
@@ -119,7 +118,6 @@ def completion_join(self, the_input):
return the_input.new_completion(['/%s' % self.own_nick], 1, quotify=True)
else:
return the_input.new_completion(relevant_rooms, 1, quotify=True)
- return True
def completion_version(self, the_input):
@@ -192,7 +190,7 @@ def completion_bookmark(self, the_input):
def completion_remove_bookmark(self, the_input):
"""Completion for /remove_bookmark"""
- return the_input.new_completion([bm.jid for bm in bookmark.bookmarks], 1, quotify=False)
+ return the_input.new_completion([bm.jid for bm in self.bookmarks], 1, quotify=False)
def completion_decline(self, the_input):
@@ -214,9 +212,6 @@ def completion_bind(self, the_input):
return the_input.new_completion(args, n, '', quotify=False)
- return the_input
-
-
def completion_message(self, the_input):
"""Completion for /message"""
n = the_input.get_argument_position(quoted=True)
@@ -304,14 +299,21 @@ def completion_set(self, the_input):
plugin = self.plugin_manager.plugins[plugin_name]
end_list = ['%s|%s' % (plugin_name, section) for section in plugin.config.sections()]
else:
- end_list = config.options('Poezio')
+ end_list = set(config.options('Poezio'))
+ end_list.update(config.default.get('Poezio', {}))
+ end_list = list(end_list)
+ end_list.sort()
elif n == 2:
if '|' in args[1]:
plugin_name, section = args[1].split('|')[:2]
if not plugin_name in self.plugin_manager.plugins:
return the_input.new_completion([''], n, quotify=True)
plugin = self.plugin_manager.plugins[plugin_name]
- end_list = plugin.config.options(section or plugin_name)
+ end_list = set(plugin.config.options(section or plugin_name))
+ if plugin.config.default:
+ end_list.update(plugin.config.default.get(section or plugin_name, {}))
+ end_list = list(end_list)
+ end_list.sort()
elif not config.has_option('Poezio', args[1]):
if config.has_section(args[1]):
end_list = config.options(args[1])
@@ -336,6 +338,19 @@ def completion_set(self, the_input):
return
return the_input.new_completion(end_list, n, quotify=True)
+
+def completion_set_default(self, the_input):
+ """ Completion for /set_default
+ """
+ args = common.shell_split(the_input.text)
+ n = the_input.get_argument_position(quoted=True)
+ if n >= len(args):
+ args.append('')
+ if n == 1 or (n == 2 and config.has_section(args[1])):
+ return self.completion_set(the_input)
+ return []
+
+
def completion_toggle(self, the_input):
"Completion for /toggle"
return the_input.new_completion(config.options('Poezio'), 1, quotify=False)
diff --git a/src/core/core.py b/src/core/core.py
index 4daeed6c..92c9f987 100644
--- a/src/core/core.py
+++ b/src/core/core.py
@@ -10,7 +10,6 @@ import logging
log = logging.getLogger(__name__)
import asyncio
-import collections
import shutil
import curses
import os
@@ -18,22 +17,19 @@ import pipes
import sys
import time
from threading import Event
-from datetime import datetime
-from gettext import gettext as _
from slixmpp.xmlstream.handler import Callback
-import bookmark
import connection
import decorators
import events
-import fixes
import singleton
import tabs
import theming
import timed_events
import windows
+from bookmarks import BookmarkList
from common import safeJID
from config import config, firstrun
from contact import Contact, Resource
@@ -75,6 +71,7 @@ class Core(object):
self.keyboard = keyboard.Keyboard()
roster.set_node(self.xmpp.client_roster)
decorators.refresh_wrapper.core = self
+ self.bookmarks = BookmarkList()
self.paused = False
self.event = Event()
self.debug = False
@@ -90,7 +87,7 @@ class Core(object):
self.tab_win = windows.GlobalInfoBar()
# Whether the XML tab is opened
- self.xml_tab = False
+ self.xml_tab = None
self.xml_buffer = TextBuffer()
self.tabs = []
@@ -226,6 +223,7 @@ class Core(object):
self.xmpp.add_event_handler("groupchat_subject",
self.on_groupchat_subject)
self.xmpp.add_event_handler("message", self.on_message)
+ self.xmpp.add_event_handler("message_error", self.on_error_message)
self.xmpp.add_event_handler("receipt_received", self.on_receipt)
self.xmpp.add_event_handler("got_online", self.on_got_online)
self.xmpp.add_event_handler("got_offline", self.on_got_offline)
@@ -253,6 +251,7 @@ class Core(object):
self.on_chatstate_inactive)
self.xmpp.add_event_handler("attention", self.on_attention)
self.xmpp.add_event_handler("ssl_cert", self.validate_ssl)
+ self.xmpp.add_event_handler("ssl_invalid_chain", self.ssl_invalid_chain)
self.all_stanzas = Callback('custom matcher',
connection.MatchAll(None),
self.incoming_stanza)
@@ -310,8 +309,14 @@ class Core(object):
theming.update_themes_dir)
self.add_configuration_handler("theme",
self.on_theme_config_change)
+ self.add_configuration_handler("use_bookmarks_method",
+ self.on_bookmarks_method_config_change)
self.add_configuration_handler("password",
self.on_password_change)
+ self.add_configuration_handler("enable_vertical_tab_list",
+ self.on_vertical_tab_list_config_change)
+ self.add_configuration_handler("deterministic_nick_colors",
+ self.on_nick_determinism_changed)
self.add_configuration_handler("", self.on_any_config_change)
@@ -346,6 +351,15 @@ class Core(object):
for callback in self.configuration_change_handlers[option]:
callback(option, value)
+ def on_bookmarks_method_config_change(self, option, value):
+ """
+ Called when the use_bookmarks_method option changes
+ """
+ if value not in ('pep', 'privatexml'):
+ return
+ self.bookmarks.preferred = value
+ self.bookmarks.save(self.xmpp, core=self)
+
def on_gaps_config_change(self, option, value):
"""
Called when the option create_gaps is changed.
@@ -374,6 +388,12 @@ class Core(object):
path = os.path.expanduser(value)
self.plugin_manager.on_plugins_dir_change(path)
+ def on_vertical_tab_list_config_change(self, option, value):
+ """
+ Called when the enable_vertical_tab_list option is changed
+ """
+ self.call_for_resize()
+
def on_plugins_conf_dir_config_change(self, option, value):
"""
Called when the plugins_conf_dir option is changed
@@ -396,19 +416,23 @@ class Core(object):
"""
self.xmpp.password = value
- def sigusr_handler(self, num, stack):
- """
- Handle SIGUSR1 (10)
- When caught, reload all the possible files.
+
+ def on_nick_determinism_changed(self, option, value):
+ """If we change the value to true, we call /recolor on all the MucTabs, to
+ make the current nick colors reflect their deterministic value.
"""
- log.debug("SIGUSR1 caught, reloading the files…")
+ if value.lower() == "true":
+ for tab in self.get_tabs(tabs.MucTab):
+ tab.command_recolor('')
+
+ def reload_config(self):
# reload all log files
log.debug("Reloading the log files…")
logger.reload_all()
log.debug("Log files reloaded.")
# reload the theme
log.debug("Reloading the theme…")
- self.command_theme("")
+ theming.reload_theme()
log.debug("Theme reloaded.")
# reload the config from the disk
log.debug("Reloading the config…")
@@ -428,6 +452,14 @@ class Core(object):
# in case some roster options have changed
roster.modified()
+ def sigusr_handler(self, num, stack):
+ """
+ Handle SIGUSR1 (10)
+ When caught, reload all the possible files.
+ """
+ log.debug("SIGUSR1 caught, reloading the files…")
+ self.reload_config()
+
def exit_from_signal(self, *args, **kwargs):
"""
Quit when receiving SIGHUP or SIGTERM or SIGPIPE
@@ -476,15 +508,15 @@ class Core(object):
default_tab = tabs.RosterInfoTab()
default_tab.on_gain_focus()
self.tabs.append(default_tab)
- self.information(_('Welcome to poezio!'), _('Info'))
+ self.information('Welcome to poezio!', 'Info')
if firstrun:
- self.information(_(
+ self.information(
'It seems that it is the first time you start poezio.\n'
'The online help is here http://doc.poez.io/\n'
'No room is joined by default, but you can join poezio’s'
'chatroom (with /join poezio@muc.poez.io), where you can'
- ' ask for help or tell us how great it is.'),
- _('Help'))
+ ' ask for help or tell us how great it is.',
+ 'Help')
self.refresh_window()
self.xmpp.plugin['xep_0012'].begin_idle(jid=self.xmpp.boundjid)
@@ -592,7 +624,7 @@ class Core(object):
except ValueError:
pass
else:
- if self.current_tab().nb == nb:
+ if self.current_tab().nb == nb and config.get('go_to_previous_tab_on_alt_number'):
self.go_to_previous_tab()
else:
self.command_win('%d' % nb)
@@ -617,9 +649,9 @@ class Core(object):
self.information_win_size,
'var')
if not ok:
- self.information(_('Unable to save runtime preferences'
- ' in the config file'),
- _('Error'))
+ self.information('Unable to save runtime preferences'
+ ' in the config file',
+ 'Error')
def on_roster_enter_key(self, roster_row):
"""
@@ -675,8 +707,8 @@ class Core(object):
func(arg)
return
else:
- self.information(_("Unknown command (%s)") % (command),
- _('Error'))
+ self.information("Unknown command (%s)" % (command),
+ 'Error')
def exec_command(self, command):
"""
@@ -809,15 +841,15 @@ class Core(object):
msg = msg.replace('\n', '|') if msg else ''
ok = ok and config.silent_set('status_message', msg)
if not ok:
- self.information(_('Unable to save the status in '
- 'the config file'), 'Error')
+ self.information('Unable to save the status in '
+ 'the config file', 'Error')
def get_bookmark_nickname(self, room_name):
"""
Returns the nickname associated with a bookmark
or the default nickname
"""
- bm = bookmark.get_by_jid(room_name)
+ bm = self.bookmarks[room_name]
if bm:
return bm.nick
return self.own_nick
@@ -884,18 +916,18 @@ class Core(object):
if code in DEPRECATED_ERRORS:
body = DEPRECATED_ERRORS[code]
else:
- body = condition or _('Unknown error')
+ body = condition or 'Unknown error'
else:
if code in ERROR_AND_STATUS_CODES:
body = ERROR_AND_STATUS_CODES[code]
else:
- body = condition or _('Unknown error')
+ body = condition or 'Unknown error'
if code:
- message = _('%(from)s: %(code)s - %(msg)s: %(body)s') % {
- 'from': sender, 'msg': msg, 'body': body, 'code': code}
+ message = '%(from)s: %(code)s - %(msg)s: %(body)s' % {
+ 'from': sender, 'msg': msg, 'body': body, 'code': code}
else:
- message = _('%(from)s: %(msg)s: %(body)s') % {
- 'from': sender, 'msg': msg, 'body': body}
+ message = '%(from)s: %(msg)s: %(body)s' % {
+ 'from': sender, 'msg': msg, 'body': body}
return message
@@ -1163,7 +1195,7 @@ class Core(object):
self._current_tab_nb = len(self.tabs) - 1
else:
self._current_tab_nb = value
- if old != self._current_tab_nb:
+ if old != self._current_tab_nb and self.tabs[self._current_tab_nb]:
self.events.trigger('tab_change', old, self._current_tab_nb)
### Opening actions ###
@@ -1209,11 +1241,11 @@ class Core(object):
tab.privates.append(new_tab)
return new_tab
- def open_new_room(self, room, nick, focus=True):
+ def open_new_room(self, room, nick, *, password=None, focus=True):
"""
Open a new tab.MucTab containing a muc Room, using the specified nick
"""
- new_tab = tabs.MucTab(room, nick)
+ new_tab = tabs.MucTab(room, nick, password=password)
self.add_tab(new_tab, focus)
self.refresh_window()
@@ -1262,7 +1294,7 @@ class Core(object):
Disable private tabs when leaving a room
"""
if reason is None:
- reason = _('\x195}You left the chatroom\x193}')
+ reason = '\x195}You left the chatroom\x193}'
for tab in self.get_tabs(tabs.PrivateTab):
if tab.name.startswith(room_name):
tab.deactivate(reason=reason)
@@ -1272,7 +1304,7 @@ class Core(object):
Enable private tabs when joining a room
"""
if reason is None:
- reason = _('\x195}You joined the chatroom\x193}')
+ reason = '\x195}You joined the chatroom\x193}'
for tab in self.get_tabs(tabs.PrivateTab):
if tab.name.startswith(room_name):
tab.activate(reason=reason)
@@ -1286,6 +1318,7 @@ class Core(object):
"""
Close the given tab. If None, close the current one
"""
+ was_current = tab is None
tab = tab or self.current_tab()
if isinstance(tab, tabs.RosterInfoTab):
return # The tab 0 should NEVER be closed
@@ -1293,9 +1326,10 @@ class Core(object):
del tab.commands # and make the object collectable
tab.on_close()
nb = tab.nb
- if self.previous_tab_nb != nb:
- self.current_tab_nb = self.previous_tab_nb
- self.previous_tab_nb = 0
+ if was_current:
+ if self.previous_tab_nb != nb:
+ self.current_tab_nb = self.previous_tab_nb
+ self.previous_tab_nb = 0
if config.get('create_gaps'):
if nb >= len(self.tabs) - 1:
self.tabs.remove(tab)
@@ -1315,7 +1349,8 @@ class Core(object):
self.current_tab_nb = len(self.tabs) - 1
while not self.tabs[self.current_tab_nb]:
self.current_tab_nb -= 1
- self.current_tab().on_gain_focus()
+ if was_current:
+ self.current_tab().on_gain_focus()
self.refresh_window()
import gc
gc.collect()
@@ -1403,9 +1438,11 @@ class Core(object):
"""
Refresh everything
"""
+ nocursor = curses.curs_set(0)
self.current_tab().state = 'current'
self.current_tab().refresh()
self.doupdate()
+ curses.curs_set(nocursor)
def refresh_tab_win(self):
"""
@@ -1556,7 +1593,7 @@ class Core(object):
"""
enabled = config.get('enable_vertical_tab_list')
if not config.silent_set('enable_vertical_tab_list', str(not enabled)):
- self.information(_('Unable to write in the config file'), 'Error')
+ self.information('Unable to write in the config file', 'Error')
self.call_for_resize()
def resize_global_information_win(self):
@@ -1681,214 +1718,225 @@ class Core(object):
Register the commands when poezio starts
"""
self.register_command('help', self.command_help,
- usage=_('[command]'),
+ usage='[command]',
shortdesc='\\_o< KOIN KOIN KOIN',
completion=self.completion_help)
self.register_command('join', self.command_join,
- usage=_("[room_name][@server][/nick] [password]"),
- desc=_("Join the specified room. You can specify a nickname "
- "after a slash (/). If no nickname is specified, you will"
- " use the default_nick in the configuration file. You can"
- " omit the room name: you will then join the room you\'re"
- " looking at (useful if you were kicked). You can also "
- "provide a room_name without specifying a server, the "
- "server of the room you're currently in will be used. You"
- " can also provide a password to join the room.\nExamples"
- ":\n/join room@server.tld\n/join room@server.tld/John\n"
- "/join room2\n/join /me_again\n/join\n/join room@server"
- ".tld/my_nick password\n/join / password"),
- shortdesc=_('Join a room'),
+ usage="[room_name][@server][/nick] [password]",
+ desc="Join the specified room. You can specify a nickname "
+ "after a slash (/). If no nickname is specified, you will"
+ " use the default_nick in the configuration file. You can"
+ " omit the room name: you will then join the room you\'re"
+ " looking at (useful if you were kicked). You can also "
+ "provide a room_name without specifying a server, the "
+ "server of the room you're currently in will be used. You"
+ " can also provide a password to join the room.\nExamples"
+ ":\n/join room@server.tld\n/join room@server.tld/John\n"
+ "/join room2\n/join /me_again\n/join\n/join room@server"
+ ".tld/my_nick password\n/join / password",
+ shortdesc='Join a room',
completion=self.completion_join)
self.register_command('exit', self.command_quit,
- desc=_('Just disconnect from the server and exit poezio.'),
- shortdesc=_('Exit poezio.'))
+ desc='Just disconnect from the server and exit poezio.',
+ shortdesc='Exit poezio.')
self.register_command('quit', self.command_quit,
- desc=_('Just disconnect from the server and exit poezio.'),
- shortdesc=_('Exit poezio.'))
+ desc='Just disconnect from the server and exit poezio.',
+ shortdesc='Exit poezio.')
self.register_command('next', self.rotate_rooms_right,
- shortdesc=_('Go to the next room.'))
+ shortdesc='Go to the next room.')
self.register_command('prev', self.rotate_rooms_left,
- shortdesc=_('Go to the previous room.'))
+ shortdesc='Go to the previous room.')
self.register_command('win', self.command_win,
- usage=_('<number or name>'),
- shortdesc=_('Go to the specified room'),
+ usage='<number or name>',
+ shortdesc='Go to the specified room',
completion=self.completion_win)
self.commands['w'] = self.commands['win']
self.register_command('move_tab', self.command_move_tab,
- usage=_('<source> <destination>'),
- desc=_("Insert the <source> tab at the position of "
- "<destination>. This will make the following tabs shift in"
- " some cases (refer to the documentation). A tab can be "
- "designated by its number or by the beginning of its "
- "address. You can use \".\" as a shortcut for the current "
- "tab."),
- shortdesc=_('Move a tab.'),
+ usage='<source> <destination>',
+ desc="Insert the <source> tab at the position of "
+ "<destination>. This will make the following tabs shift in"
+ " some cases (refer to the documentation). A tab can be "
+ "designated by its number or by the beginning of its "
+ "address. You can use \".\" as a shortcut for the current "
+ "tab.",
+ shortdesc='Move a tab.',
completion=self.completion_move_tab)
self.register_command('destroy_room', self.command_destroy_room,
- usage=_('[room JID]'),
- desc=_('Try to destroy the room [room JID], or the current'
- ' tab if it is a multi-user chat and [room JID] is '
- 'not given.'),
- shortdesc=_('Destroy a room.'),
+ usage='[room JID]',
+ desc='Try to destroy the room [room JID], or the current'
+ ' tab if it is a multi-user chat and [room JID] is '
+ 'not given.',
+ shortdesc='Destroy a room.',
completion=None)
self.register_command('show', self.command_status,
- usage=_('<availability> [status message]'),
- desc=_("Sets your availability and (optionally) your status "
- "message. The <availability> argument is one of \"available"
- ", chat, away, afk, dnd, busy, xa\" and the optional "
- "[status message] argument will be your status message."),
- shortdesc=_('Change your availability.'),
+ usage='<availability> [status message]',
+ desc="Sets your availability and (optionally) your status "
+ "message. The <availability> argument is one of \"available"
+ ", chat, away, afk, dnd, busy, xa\" and the optional "
+ "[status message] argument will be your status message.",
+ shortdesc='Change your availability.',
completion=self.completion_status)
self.commands['status'] = self.commands['show']
self.register_command('bookmark_local', self.command_bookmark_local,
- usage=_("[roomname][/nick] [password]"),
- desc=_("Bookmark Local: Bookmark locally the specified room "
- "(you will then auto-join it on each poezio start). This"
- " commands uses almost the same syntaxe as /join. Type "
- "/help join for syntax examples. Note that when typing "
- "\"/bookmark\" on its own, the room will be bookmarked "
- "with the nickname you\'re currently using in this room "
- "(instead of default_nick)"),
- shortdesc=_('Bookmark a room locally.'),
+ usage="[roomname][/nick] [password]",
+ desc="Bookmark Local: Bookmark locally the specified room "
+ "(you will then auto-join it on each poezio start). This"
+ " commands uses almost the same syntaxe as /join. Type "
+ "/help join for syntax examples. Note that when typing "
+ "\"/bookmark\" on its own, the room will be bookmarked "
+ "with the nickname you\'re currently using in this room "
+ "(instead of default_nick)",
+ shortdesc='Bookmark a room locally.',
completion=self.completion_bookmark_local)
self.register_command('bookmark', self.command_bookmark,
- usage=_("[roomname][/nick] [autojoin] [password]"),
- desc=_("Bookmark: Bookmark online the specified room (you "
- "will then auto-join it on each poezio start if autojoin"
- " is specified and is 'true'). This commands uses almost"
- " the same syntax as /join. Type /help join for syntax "
- "examples. Note that when typing \"/bookmark\" alone, the"
- " room will be bookmarked with the nickname you\'re "
- "currently using in this room (instead of default_nick)."),
- shortdesc=_("Bookmark a room online."),
+ usage="[roomname][/nick] [autojoin] [password]",
+ desc="Bookmark: Bookmark online the specified room (you "
+ "will then auto-join it on each poezio start if autojoin"
+ " is specified and is 'true'). This commands uses almost"
+ " the same syntax as /join. Type /help join for syntax "
+ "examples. Note that when typing \"/bookmark\" alone, the"
+ " room will be bookmarked with the nickname you\'re "
+ "currently using in this room (instead of default_nick).",
+ shortdesc="Bookmark a room online.",
completion=self.completion_bookmark)
self.register_command('set', self.command_set,
- usage=_("[plugin|][section] <option> [value]"),
- desc=_("Set the value of an option in your configuration file."
- " You can, for example, change your default nickname by "
- "doing `/set default_nick toto` or your resource with `/set"
- "resource blabla`. You can also set options in specific "
- "sections with `/set bindings M-i ^i` or in specific plugin"
- " with `/set mpd_client| host 127.0.0.1`. `toggle` can be "
- "used as a special value to toggle a boolean option."),
- shortdesc=_("Set the value of an option"),
+ usage="[plugin|][section] <option> [value]",
+ desc="Set the value of an option in your configuration file."
+ " You can, for example, change your default nickname by "
+ "doing `/set default_nick toto` or your resource with `/set"
+ " resource blabla`. You can also set options in specific "
+ "sections with `/set bindings M-i ^i` or in specific plugin"
+ " with `/set mpd_client| host 127.0.0.1`. `toggle` can be "
+ "used as a special value to toggle a boolean option.",
+ shortdesc="Set the value of an option",
completion=self.completion_set)
+ self.register_command('set_default', self.command_set_default,
+ usage="[section] <option>",
+ desc="Set the default value of an option. For example, "
+ "`/set_default resource` will reset the resource "
+ "option. You can also reset options in specific "
+ "sections by doing `/set_default section option`.",
+ shortdesc="Set the default value of an option",
+ completion=self.completion_set_default)
self.register_command('toggle', self.command_toggle,
- usage=_('<option>'),
- desc=_('Shortcut for /set <option> toggle'),
- shortdesc=_('Toggle an option'),
+ usage='<option>',
+ desc='Shortcut for /set <option> toggle',
+ shortdesc='Toggle an option',
completion=self.completion_toggle)
self.register_command('theme', self.command_theme,
- usage=_('[theme name]'),
- desc=_("Reload the theme defined in the config file. If theme"
- "_name is provided, set that theme before reloading it."),
- shortdesc=_('Load a theme'),
+ usage='[theme name]',
+ desc="Reload the theme defined in the config file. If theme"
+ "_name is provided, set that theme before reloading it.",
+ shortdesc='Load a theme',
completion=self.completion_theme)
self.register_command('list', self.command_list,
- usage=_('[server]'),
- desc=_("Get the list of public chatrooms"
- " on the specified server."),
- shortdesc=_('List the rooms.'),
+ usage='[server]',
+ desc="Get the list of public chatrooms"
+ " on the specified server.",
+ shortdesc='List the rooms.',
completion=self.completion_list)
self.register_command('message', self.command_message,
- usage=_('<jid> [optional message]'),
- desc=_("Open a conversation with the specified JID (even if it"
- " is not in our roster), and send a message to it, if the "
- "message is specified."),
- shortdesc=_('Send a message'),
+ usage='<jid> [optional message]',
+ desc="Open a conversation with the specified JID (even if it"
+ " is not in our roster), and send a message to it, if the "
+ "message is specified.",
+ shortdesc='Send a message',
completion=self.completion_message)
self.register_command('version', self.command_version,
usage='<jid>',
- desc=_("Get the software version of the given JID (usually its"
- " XMPP client and Operating System)."),
- shortdesc=_('Get the software version of a JID.'),
+ desc="Get the software version of the given JID (usually its"
+ " XMPP client and Operating System).",
+ shortdesc='Get the software version of a JID.',
completion=self.completion_version)
self.register_command('server_cycle', self.command_server_cycle,
- usage=_('[domain] [message]'),
- desc=_('Disconnect and reconnect in all the rooms in domain.'),
- shortdesc=_('Cycle a range of rooms'),
+ usage='[domain] [message]',
+ desc='Disconnect and reconnect in all the rooms in domain.',
+ shortdesc='Cycle a range of rooms',
completion=self.completion_server_cycle)
self.register_command('bind', self.command_bind,
- usage=_('<key> <equ>'),
- desc=_("Bind a key to another key or to a “command”. For "
- "example \"/bind ^H KEY_UP\" makes Control + h do the"
- " same same as the Up key."),
+ usage='<key> <equ>',
+ desc="Bind a key to another key or to a “command”. For "
+ "example \"/bind ^H KEY_UP\" makes Control + h do the"
+ " same same as the Up key.",
completion=self.completion_bind,
- shortdesc=_('Bind a key to another key.'))
+ shortdesc='Bind a key to another key.')
self.register_command('load', self.command_load,
- usage=_('<plugin> [<otherplugin> …]'),
- shortdesc=_('Load the specified plugin(s)'),
+ usage='<plugin> [<otherplugin> …]',
+ shortdesc='Load the specified plugin(s)',
completion=self.plugin_manager.completion_load)
self.register_command('unload', self.command_unload,
- usage=_('<plugin> [<otherplugin> …]'),
- shortdesc=_('Unload the specified plugin(s)'),
+ usage='<plugin> [<otherplugin> …]',
+ shortdesc='Unload the specified plugin(s)',
completion=self.plugin_manager.completion_unload)
self.register_command('plugins', self.command_plugins,
- shortdesc=_('Show the plugins in use.'))
+ shortdesc='Show the plugins in use.')
self.register_command('presence', self.command_presence,
- usage=_('<JID> [type] [status]'),
- desc=_("Send a directed presence to <JID> and using"
- " [type] and [status] if provided."),
- shortdesc=_('Send a directed presence.'),
+ usage='<JID> [type] [status]',
+ desc="Send a directed presence to <JID> and using"
+ " [type] and [status] if provided.",
+ shortdesc='Send a directed presence.',
completion=self.completion_presence)
self.register_command('rawxml', self.command_rawxml,
usage='<xml>',
- shortdesc=_('Send a custom xml stanza.'))
+ shortdesc='Send a custom xml stanza.')
self.register_command('invite', self.command_invite,
- usage=_('<jid> <room> [reason]'),
- desc=_('Invite jid in room with reason.'),
- shortdesc=_('Invite someone in a room.'),
+ usage='<jid> <room> [reason]',
+ desc='Invite jid in room with reason.',
+ shortdesc='Invite someone in a room.',
completion=self.completion_invite)
self.register_command('invitations', self.command_invitations,
- shortdesc=_('Show the pending invitations.'))
+ shortdesc='Show the pending invitations.')
self.register_command('bookmarks', self.command_bookmarks,
- shortdesc=_('Show the current bookmarks.'))
+ shortdesc='Show the current bookmarks.')
self.register_command('remove_bookmark', self.command_remove_bookmark,
usage='[jid]',
- desc=_("Remove the specified bookmark, or the "
- "bookmark on the current tab, if any."),
- shortdesc=_('Remove a bookmark'),
+ desc="Remove the specified bookmark, or the "
+ "bookmark on the current tab, if any.",
+ shortdesc='Remove a bookmark',
completion=self.completion_remove_bookmark)
self.register_command('xml_tab', self.command_xml_tab,
- shortdesc=_('Open an XML tab.'))
+ shortdesc='Open an XML tab.')
self.register_command('runkey', self.command_runkey,
- usage=_('<key>'),
- shortdesc=_('Execute the action defined for <key>.'),
+ usage='<key>',
+ shortdesc='Execute the action defined for <key>.',
completion=self.completion_runkey)
self.register_command('self', self.command_self,
- shortdesc=_('Remind you of who you are.'))
+ shortdesc='Remind you of who you are.')
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.'),
+ desc='Informs you of the last activity of a JID.',
+ shortdesc='Get the activity of someone.',
completion=self.completion_last_activity)
self.register_command('ad-hoc', self.command_adhoc,
usage='<jid>',
- shortdesc=_('List available ad-hoc commands on the given jid'))
+ shortdesc='List available ad-hoc commands on the given jid')
+ self.register_command('reload', self.command_reload,
+ shortdesc='Reload the config. You can achieve the same by '
+ 'sending SIGUSR1 to poezio.')
if config.get('enable_user_activity'):
self.register_command('activity', self.command_activity,
usage='[<general> [specific] [text]]',
- desc=_('Send your current activity to your contacts '
- '(use the completion). Nothing means '
- '"stop broadcasting an activity".'),
- shortdesc=_('Send your activity.'),
+ desc='Send your current activity to your contacts '
+ '(use the completion). Nothing means '
+ '"stop broadcasting an activity".',
+ shortdesc='Send your activity.',
completion=self.completion_activity)
if config.get('enable_user_mood'):
self.register_command('mood', self.command_mood,
usage='[<mood> [text]]',
- desc=_('Send your current mood to your contacts '
- '(use the completion). Nothing means '
- '"stop broadcasting a mood".'),
- shortdesc=_('Send your mood.'),
+ desc='Send your current mood to your contacts '
+ '(use the completion). Nothing means '
+ '"stop broadcasting a mood".',
+ shortdesc='Send your mood.',
completion=self.completion_mood)
if config.get('enable_user_gaming'):
self.register_command('gaming', self.command_gaming,
usage='[<game name> [server address]]',
- desc=_('Send your current gaming activity to '
- 'your contacts. Nothing means "stop '
- 'broadcasting a gaming activity".'),
- shortdesc=_('Send your gaming activity.'),
+ desc='Send your current gaming activity to '
+ 'your contacts. Nothing means "stop '
+ 'broadcasting a gaming activity".',
+ shortdesc='Send your gaming activity.',
completion=None)
####################### XMPP Event Handlers ##################################
@@ -1899,6 +1947,7 @@ class Core(object):
on_groupchat_direct_invitation = handlers.on_groupchat_direct_invitation
on_groupchat_decline = handlers.on_groupchat_decline
on_message = handlers.on_message
+ on_error_message = handlers.on_error_message
on_normal_message = handlers.on_normal_message
on_nick_received = handlers.on_nick_received
on_gaming_event = handlers.on_gaming_event
@@ -1943,9 +1992,11 @@ class Core(object):
on_receipt = handlers.on_receipt
on_attention = handlers.on_attention
room_error = handlers.room_error
+ check_bookmark_storage = handlers.check_bookmark_storage
outgoing_stanza = handlers.outgoing_stanza
incoming_stanza = handlers.incoming_stanza
validate_ssl = handlers.validate_ssl
+ ssl_invalid_chain = handlers.ssl_invalid_chain
on_next_adhoc_step = handlers.on_next_adhoc_step
on_adhoc_error = handlers.on_adhoc_error
cancel_adhoc_command = handlers.cancel_adhoc_command
@@ -1967,6 +2018,7 @@ class Core(object):
command_destroy_room = commands.command_destroy_room
command_remove_bookmark = commands.command_remove_bookmark
command_set = commands.command_set
+ command_set_default = commands.command_set_default
command_toggle = commands.command_toggle
command_server_cycle = commands.command_server_cycle
command_last_activity = commands.command_last_activity
@@ -1986,6 +2038,7 @@ class Core(object):
command_xml_tab = commands.command_xml_tab
command_adhoc = commands.command_adhoc
command_self = commands.command_self
+ command_reload = commands.command_reload
completion_help = completions.completion_help
completion_status = completions.completion_status
completion_presence = completions.completion_presence
@@ -2007,6 +2060,7 @@ class Core(object):
completion_last_activity = completions.completion_last_activity
completion_server_cycle = completions.completion_server_cycle
completion_set = completions.completion_set
+ completion_set_default = completions.completion_set_default
completion_toggle = completions.completion_toggle
completion_bookmark_local = completions.completion_bookmark_local
diff --git a/src/core/handlers.py b/src/core/handlers.py
index 50dca216..828c39d1 100644
--- a/src/core/handlers.py
+++ b/src/core/handlers.py
@@ -12,14 +12,12 @@ import ssl
import sys
import time
from hashlib import sha1, sha512
-from gettext import gettext as _
from os import path
from slixmpp import InvalidJID
-from slixmpp.stanza import Message
-from slixmpp.xmlstream.stanzabase import StanzaBase
+from slixmpp.xmlstream.stanzabase import StanzaBase, ElementBase
+from xml.etree import ElementTree as ET
-import bookmark
import common
import fixes
import pep
@@ -32,11 +30,64 @@ from config import config, CACHE_DIR
from contact import Resource
from logger import logger
from roster import roster
-from text_buffer import CorrectionError
+from text_buffer import CorrectionError, AckError
from theming import dump_tuple, get_theme
from . commands import dumb_callback
+try:
+ from pygments import highlight
+ from pygments.lexers import get_lexer_by_name
+ from pygments.formatters import HtmlFormatter
+ LEXER = get_lexer_by_name('xml')
+ FORMATTER = HtmlFormatter(noclasses=True)
+ PYGMENTS = True
+except ImportError:
+ PYGMENTS = False
+
+def _join_initial_rooms(self, bookmarks):
+ """Join all rooms given in the iterator `bookmarks`"""
+ for bm in bookmarks:
+ if not (bm.autojoin or config.get('open_all_bookmarks')):
+ continue
+ tab = self.get_tab_by_name(bm.jid, tabs.MucTab)
+ nick = bm.nick if bm.nick else self.own_nick
+ if not tab:
+ self.open_new_room(bm.jid, nick, focus=False)
+ self.initial_joins.append(bm.jid)
+ histo_length = config.get('muc_history_length')
+ if histo_length == -1:
+ histo_length = None
+ if histo_length is not None:
+ histo_length = str(histo_length)
+ # do not join rooms that do not have autojoin
+ # but display them anyway
+ if bm.autojoin:
+ muc.join_groupchat(self, bm.jid, nick,
+ passwd=bm.password,
+ maxhistory=histo_length,
+ status=self.status.message,
+ show=self.status.show)
+
+def check_bookmark_storage(self, features):
+ private = 'jabber:iq:private' in features
+ pep_ = 'http://jabber.org/protocol/pubsub#publish' in features
+ self.bookmarks.available_storage['private'] = private
+ self.bookmarks.available_storage['pep'] = pep_
+ def _join_remote_only(iq):
+ if iq['type'] == 'error':
+ type_ = iq['error']['type']
+ condition = iq['error']['condition']
+ if not (type_ == 'cancel' and condition == 'item-not-found'):
+ self.information('Unable to fetch the remote'
+ ' bookmarks; %s: %s' % (type_, condition),
+ 'Error')
+ return
+ remote_bookmarks = self.bookmarks.remote()
+ _join_initial_rooms(self, remote_bookmarks)
+ if not self.xmpp.anon and config.get('use_remote_bookmarks'):
+ self.bookmarks.get_remote(self.xmpp, self.information, _join_remote_only)
+
def on_session_start_features(self, _):
"""
Enable carbons & blocking on session start if wanted and possible
@@ -47,11 +98,13 @@ def on_session_start_features(self, _):
features = iq['disco_info']['features']
rostertab = self.get_tab_by_name('Roster', tabs.RosterInfoTab)
rostertab.check_blocking(features)
+ rostertab.check_saslexternal(features)
if (config.get('enable_carbons') and
'urn:xmpp:carbons:2' in features):
self.xmpp.plugin['xep_0280'].enable()
self.xmpp.add_event_handler('carbon_received', self.on_carbon_received)
self.xmpp.add_event_handler('carbon_sent', self.on_carbon_sent)
+ self.check_bookmark_storage(features)
self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain,
callback=callback)
@@ -173,11 +226,31 @@ def on_message(self, message):
jid_from = message['from']
for tab in self.get_tabs(tabs.MucTab):
if tab.name == jid_from.bare:
+ if message['type'] == 'chat':
+ return self.on_groupchat_private_message(message)
+ return self.on_normal_message(message)
+
+def on_error_message(self, message):
+ """
+ When receiving any message with type="error"
+ """
+ jid_from = message['from']
+ for tab in self.get_tabs(tabs.MucTab):
+ if tab.name == jid_from.bare:
if message['type'] == 'error':
- return self.room_error(message, jid_from)
+ return self.room_error(message, jid_from.bare)
else:
return self.on_groupchat_private_message(message)
- return self.on_normal_message(message)
+ tab = self.get_conversation_by_jid(message['from'], create=False)
+ error_msg = self.get_error_message(message, deprecated=True)
+ if not tab:
+ return self.information(error_msg, 'Error')
+ error = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_CHAR_NACK),
+ error_msg)
+ if not tab.nack_message('\n' + error, message['id'], message['to']):
+ tab.add_message(error, typ=0)
+ self.refresh_window()
+
def on_normal_message(self, message):
"""
@@ -185,7 +258,7 @@ def on_normal_message(self, message):
muc participant)
"""
if message['type'] == 'error':
- return self.information(self.get_error_message(message, deprecated=True), 'Error')
+ return
elif message['type'] == 'headline' and message['body']:
return self.information('%s says: %s' % (message['from'], message['body']), 'Headline')
@@ -448,7 +521,7 @@ def on_groupchat_message(self, message):
tab = self.get_tab_by_name(room_from, tabs.MucTab)
if not tab:
- self.information(_("message received for a non-existing room: %s") % (room_from))
+ self.information("message received for a non-existing room: %s" % (room_from))
muc.leave_groupchat(self.xmpp, room_from, self.own_nick, msg='')
return
@@ -689,7 +762,10 @@ def on_subscription_request(self, presence):
contact = roster.get_and_set(jid)
roster.update_contact_groups(contact)
contact.pending_in = True
- self.information('%s wants to subscribe to your presence' % jid, 'Roster')
+ self.information('%s wants to subscribe to your presence, '
+ 'use /accept <jid> or /deny <jid> to accept '
+ 'or reject the query.' % jid,
+ 'Roster')
self.get_tab_by_number(0).state = 'highlight'
roster.modified()
if isinstance(self.current_tab(), tabs.RosterInfoTab):
@@ -782,7 +858,7 @@ def on_got_offline(self, presence):
return
jid = presence['from']
if not logger.log_roster_change(jid.bare, 'got offline'):
- self.information(_('Unable to write in the log file'), 'Error')
+ self.information('Unable to write in the log file', 'Error')
# If a resource got offline, display the message in the conversation with this
# precise resource.
if jid.resource:
@@ -806,7 +882,7 @@ def on_got_online(self, presence):
return
roster.modified()
if not logger.log_roster_change(jid.bare, 'got online'):
- self.information(_('Unable to write in the log file'), 'Error')
+ self.information('Unable to write in the log file', 'Error')
resource = Resource(jid.full, {
'priority': presence.get_priority() or 0,
'status': presence['status'],
@@ -843,7 +919,7 @@ def on_failed_connection(self, error):
"""
We cannot contact the remote server
"""
- self.information(_("Connection to remote server failed: %s" % (error,)), _('Error'))
+ self.information("Connection to remote server failed: %s" % (error,), 'Error')
def on_disconnected(self, event):
"""
@@ -854,9 +930,10 @@ def on_disconnected(self, event):
roster.modified()
for tab in self.get_tabs(tabs.MucTab):
tab.disconnect()
- self.information(_("Disconnected from server."), _('Error'))
- if not self.legitimate_disconnect and config.get('auto_reconnect', False):
- self.information(_("Auto-reconnecting."), _('Info'))
+ msg_typ = 'Error' if not self.legitimate_disconnect else 'Info'
+ self.information("Disconnected from server.", msg_typ)
+ if not self.legitimate_disconnect and config.get('auto_reconnect', True):
+ self.information("Auto-reconnecting.", 'Info')
self.xmpp.connect()
def on_stream_error(self, event):
@@ -864,29 +941,29 @@ def on_stream_error(self, event):
When we receive a stream error
"""
if event and event['text']:
- self.information(_('Stream error: %s') % event['text'], _('Error'))
+ self.information('Stream error: %s' % event['text'], 'Error')
def on_failed_all_auth(self, event):
"""
Authentication failed
"""
- self.information(_("Authentication failed (bad credentials?)."),
- _('Error'))
+ self.information("Authentication failed (bad credentials?).",
+ 'Error')
self.legitimate_disconnect = True
def on_no_auth(self, event):
"""
Authentication failed (no mech)
"""
- self.information(_("Authentication failed, no login method available."),
- _('Error'))
+ self.information("Authentication failed, no login method available.",
+ 'Error')
self.legitimate_disconnect = True
def on_connected(self, event):
"""
Remote host responded, but we are not yet authenticated
"""
- self.information(_("Connected to server."), 'Info')
+ self.information("Connected to server.", 'Info')
def on_connecting(self, event):
"""
@@ -901,11 +978,12 @@ def on_session_start(self, event):
self.connection_time = time.time()
if not self.plugins_autoloaded: # Do not reload plugins on reconnection
self.autoload_plugins()
- self.information(_("Authentication success."), 'Info')
- self.information(_("Your JID is %s") % self.xmpp.boundjid.full, 'Info')
+ self.information("Authentication success.", 'Info')
+ self.information("Your JID is %s" % self.xmpp.boundjid.full, 'Info')
if not self.xmpp.anon:
# request the roster
self.xmpp.get_roster()
+ roster.update_contact_groups(self.xmpp.boundjid.bare)
# send initial presence
if config.get('send_initial_presence'):
pres = self.xmpp.make_presence()
@@ -913,37 +991,9 @@ def on_session_start(self, event):
pres['status'] = self.status.message
self.events.trigger('send_normal_presence', pres)
pres.send()
- bookmark.get_local()
- def _join_initial_rooms(bookmarks):
- """Join all rooms given in the iterator `bookmarks`"""
- for bm in bookmarks:
- if bm.autojoin or config.get('open_all_bookmarks'):
- tab = self.get_tab_by_name(bm.jid, tabs.MucTab)
- nick = bm.nick if bm.nick else self.own_nick
- if not tab:
- self.open_new_room(bm.jid, nick, False)
- self.initial_joins.append(bm.jid)
- histo_length = config.get('muc_history_length')
- if histo_length == -1:
- histo_length = None
- if histo_length is not None:
- histo_length = str(histo_length)
- # do not join rooms that do not have autojoin
- # but display them anyway
- if bm.autojoin:
- muc.join_groupchat(self, bm.jid, nick,
- passwd=bm.password,
- maxhistory=histo_length,
- status=self.status.message,
- show=self.status.show)
- def _join_remote_only():
- remote_bookmarks = (bm for bm in bookmark.bookmarks if (bm.method in ("pep", "privatexml")))
- _join_initial_rooms(remote_bookmarks)
- if not self.xmpp.anon and config.get('use_remote_bookmarks'):
- bookmark.get_remote(self.xmpp, _join_remote_only)
- # join all the available bookmarks. As of yet, this is just the local
- # ones
- _join_initial_rooms(bookmark.bookmarks)
+ self.bookmarks.get_local()
+ # join all the available bookmarks. As of yet, this is just the local ones
+ _join_initial_rooms(self, self.bookmarks)
if config.get('enable_user_nick'):
self.xmpp.plugin['xep_0172'].publish_nick(nick=self.own_nick, callback=dumb_callback)
@@ -1024,12 +1074,12 @@ def on_groupchat_subject(self, message):
# Do not display the message if the subject did not change or if we
# receive an empty topic when joining the room.
if nick_from:
- tab.add_message(_("\x19%(info_col)s}%(nick)s set the subject to: %(subject)s") %
+ tab.add_message("\x19%(info_col)s}%(nick)s set the subject to: %(subject)s" %
{'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'nick':nick_from, 'subject':subject},
time=None,
typ=2)
else:
- tab.add_message(_("\x19%(info_col)s}The subject is: %(subject)s") %
+ tab.add_message("\x19%(info_col)s}The subject is: %(subject)s" %
{'subject':subject, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)},
time=None,
typ=2)
@@ -1052,7 +1102,10 @@ def on_receipt(self, message):
if not conversation:
return
- conversation.ack_message(msg_id)
+ try:
+ conversation.ack_message(msg_id, self.xmpp.boundjid)
+ except AckError:
+ log.debug('Error while receiving an ack', exc_info=True)
def on_data_form(self, message):
"""
@@ -1083,19 +1136,21 @@ def room_error(self, error, room_name):
Display the error in the tab
"""
tab = self.get_tab_by_name(room_name, tabs.MucTab)
+ if not tab:
+ return
error_message = self.get_error_message(error)
tab.add_message(error_message, highlight=True, nickname='Error',
nick_color=get_theme().COLOR_ERROR_MSG, typ=2)
code = error['error']['code']
if code == '401':
- msg = _('To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)')
+ msg = 'To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)'
tab.add_message(msg, typ=2)
if code == '409':
if config.get('alternative_nickname') != '':
self.command_join('%s/%s'% (tab.name, tab.own_nick+config.get('alternative_nickname')))
else:
if not tab.joined:
- tab.add_message(_('You can join the room with an other nick, by typing "/join /other_nick"'), typ=2)
+ tab.add_message('You can join the room with an other nick, by typing "/join /other_nick"', typ=2)
self.refresh_window()
def outgoing_stanza(self, stanza):
@@ -1103,7 +1158,20 @@ def outgoing_stanza(self, stanza):
We are sending a new stanza, write it in the xml buffer if needed.
"""
if self.xml_tab:
- self.add_message_to_text_buffer(self.xml_buffer, '\x191}<--\x19o %s' % stanza)
+ if PYGMENTS:
+ xhtml_text = highlight('%s' % stanza, LEXER, FORMATTER)
+ poezio_colored = xhtml.xhtml_to_poezio_colors(xhtml_text, force=True).rstrip('\x19o').strip()
+ else:
+ poezio_colored = '%s' % stanza
+ self.add_message_to_text_buffer(self.xml_buffer, poezio_colored,
+ nickname=get_theme().CHAR_XML_OUT)
+ try:
+ if self.xml_tab.match_stanza(ElementBase(ET.fromstring(stanza))):
+ self.add_message_to_text_buffer(self.xml_tab.filtered_buffer, poezio_colored,
+ nickname=get_theme().CHAR_XML_OUT)
+ except:
+ log.debug('', exc_info=True)
+
if isinstance(self.current_tab(), tabs.XMLTab):
self.current_tab().refresh()
self.doupdate()
@@ -1113,11 +1181,27 @@ def incoming_stanza(self, stanza):
We are receiving a new stanza, write it in the xml buffer if needed.
"""
if self.xml_tab:
- self.add_message_to_text_buffer(self.xml_buffer, '\x192}-->\x19o %s' % stanza)
+ if PYGMENTS:
+ xhtml_text = highlight('%s' % stanza, LEXER, FORMATTER)
+ poezio_colored = xhtml.xhtml_to_poezio_colors(xhtml_text, force=True).rstrip('\x19o').strip()
+ else:
+ poezio_colored = '%s' % stanza
+ self.add_message_to_text_buffer(self.xml_buffer, poezio_colored,
+ nickname=get_theme().CHAR_XML_IN)
+ try:
+ if self.xml_tab.match_stanza(stanza):
+ self.add_message_to_text_buffer(self.xml_tab.filtered_buffer, poezio_colored,
+ nickname=get_theme().CHAR_XML_IN)
+ except:
+ log.debug('', exc_info=True)
if isinstance(self.current_tab(), tabs.XMLTab):
self.current_tab().refresh()
self.doupdate()
+def ssl_invalid_chain(self, tb):
+ self.information('The certificate sent by the server is invalid.', 'Error')
+ self.disconnect()
+
def validate_ssl(self, pem):
"""
Check the server certificate using the slixmpp ssl_cert event
@@ -1151,40 +1235,34 @@ def validate_ssl(self, pem):
self.information('New certificate found (sha-2 hash:'
' %s)\nPlease validate or abort' % sha2_found_cert,
'Warning')
- input = windows.YesNoInput(text="WARNING! Server certificate has changed, accept? (y/n)")
- self.current_tab().input = input
- input.resize(1, self.current_tab().width, self.current_tab().height-1, 0)
- input.refresh()
- self.doupdate()
- old_loop = asyncio.get_event_loop()
- new_loop = asyncio.new_event_loop()
- asyncio.set_event_loop(new_loop)
- new_loop.add_reader(sys.stdin, self.on_input_readable)
- future = asyncio.Future()
- @asyncio.coroutine
- def check_input(future):
- while input.value is None:
- yield from asyncio.sleep(0.01)
+ def check_input():
self.current_tab().input = saved_input
self.paused = False
if input.value:
self.information('Setting new certificate: old: %s, new: %s' % (cert, sha2_found_cert), 'Info')
log.debug('Setting certificate to %s', sha2_found_cert)
if not config.silent_set('certificate', sha2_found_cert):
- self.information(_('Unable to write in the config file'), 'Error')
+ self.information('Unable to write in the config file', 'Error')
else:
self.information('You refused to validate the certificate. You are now disconnected', 'Info')
- self.xmpp.disconnect()
+ self.disconnect()
new_loop.stop()
asyncio.set_event_loop(old_loop)
- asyncio.async(check_input(future))
+ input = windows.YesNoInput(text="WARNING! Server certificate has changed, accept? (y/n)", callback=check_input)
+ self.current_tab().input = input
+ input.resize(1, self.current_tab().width, self.current_tab().height-1, 0)
+ input.refresh()
+ self.doupdate()
+ old_loop = asyncio.get_event_loop()
+ new_loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(new_loop)
+ new_loop.add_reader(sys.stdin, self.on_input_readable)
+ curses.beep()
new_loop.run_forever()
-
-
else:
log.debug('First time. Setting certificate to %s', sha2_found_cert)
if not config.silent_set('certificate', sha2_found_cert):
- self.information(_('Unable to write in the config file'), 'Error')
+ self.information('Unable to write in the config file', 'Error')
def _composing_tab_state(tab, state):
"""
diff --git a/src/core/structs.py b/src/core/structs.py
index d97acd9f..4ce0ef43 100644
--- a/src/core/structs.py
+++ b/src/core/structs.py
@@ -2,39 +2,38 @@
Module defining structures useful to the core class and related methods
"""
import collections
-from gettext import gettext as _
# http://xmpp.org/extensions/xep-0045.html#errorstatus
ERROR_AND_STATUS_CODES = {
- '401': _('A password is required'),
- '403': _('Permission denied'),
- '404': _('The room doesn’t exist'),
- '405': _('Your are not allowed to create a new room'),
- '406': _('A reserved nick must be used'),
- '407': _('You are not in the member list'),
- '409': _('This nickname is already in use or has been reserved'),
- '503': _('The maximum number of users has been reached'),
+ '401': 'A password is required',
+ '403': 'Permission denied',
+ '404': 'The room doesn’t exist',
+ '405': 'Your are not allowed to create a new room',
+ '406': 'A reserved nick must be used',
+ '407': 'You are not in the member list',
+ '409': 'This nickname is already in use or has been reserved',
+ '503': 'The maximum number of users has been reached',
}
# http://xmpp.org/extensions/xep-0086.html
DEPRECATED_ERRORS = {
- '302': _('Redirect'),
- '400': _('Bad request'),
- '401': _('Not authorized'),
- '402': _('Payment required'),
- '403': _('Forbidden'),
- '404': _('Not found'),
- '405': _('Not allowed'),
- '406': _('Not acceptable'),
- '407': _('Registration required'),
- '408': _('Request timeout'),
- '409': _('Conflict'),
- '500': _('Internal server error'),
- '501': _('Feature not implemented'),
- '502': _('Remote server error'),
- '503': _('Service unavailable'),
- '504': _('Remote server timeout'),
- '510': _('Disconnected'),
+ '302': 'Redirect',
+ '400': 'Bad request',
+ '401': 'Not authorized',
+ '402': 'Payment required',
+ '403': 'Forbidden',
+ '404': 'Not found',
+ '405': 'Not allowed',
+ '406': 'Not acceptable',
+ '407': 'Registration required',
+ '408': 'Request timeout',
+ '409': 'Conflict',
+ '500': 'Internal server error',
+ '501': 'Feature not implemented',
+ '502': 'Remote server error',
+ '503': 'Service unavailable',
+ '504': 'Remote server timeout',
+ '510': 'Disconnected',
}
possible_show = {'available':None,