diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/commands.py | 650 | ||||
-rw-r--r-- | src/core/completions.py | 35 | ||||
-rw-r--r-- | src/core/core.py | 400 | ||||
-rw-r--r-- | src/core/handlers.py | 242 | ||||
-rw-r--r-- | src/core/structs.py | 51 |
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, |