diff options
Diffstat (limited to 'src/core/handlers.py')
-rw-r--r-- | src/core/handlers.py | 242 |
1 files changed, 160 insertions, 82 deletions
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): """ |