From 34d110e643824ee5bc48705e5fdf4024af6dd82b Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 12 Jun 2016 13:57:42 +0100 Subject: Split core handlers into its own (broken) class. --- poezio/core/handlers.py | 2509 ++++++++++++++++++++++++----------------------- 1 file changed, 1257 insertions(+), 1252 deletions(-) (limited to 'poezio/core/handlers.py') diff --git a/poezio/core/handlers.py b/poezio/core/handlers.py index 8cc08179..fb83327f 100644 --- a/poezio/core/handlers.py +++ b/poezio/core/handlers.py @@ -46,1309 +46,1314 @@ try: 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, - password=bm.password) - self.initial_joins.append(bm.jid) - # 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, - 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') + +class HandlerCore: + def __init__(self, core): + self.core = core + + 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, + password=bm.password) + self.initial_joins.append(bm.jid) + # 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, + 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 + """ + def callback(iq): + if not iq: + return + 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.check_bookmark_storage(features) + + self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain, + callback=callback) + + def on_carbon_received(self, message): + """ + Carbon received + """ + def ignore_message(recv): + log.debug('%s has category conference, ignoring carbon', + recv['from'].server) + def receive_message(recv): + recv['to'] = self.xmpp.boundjid.full + if recv['receipt']: + return self.on_receipt(recv) + self.on_normal_message(recv) + + recv = message['carbon_received'] + if (recv['from'].bare not in roster or + roster[recv['from'].bare].subscription == 'none'): + fixes.has_identity(self.xmpp, recv['from'].server, + identity='conference', + on_true=functools.partial(ignore_message, recv), + on_false=functools.partial(receive_message, recv)) 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 - """ - def callback(iq): - if not iq: + else: + receive_message(recv) + + def on_carbon_sent(self, message): + """ + Carbon received + """ + def ignore_message(sent): + log.debug('%s has category conference, ignoring carbon', + sent['to'].server) + def send_message(sent): + sent['from'] = self.xmpp.boundjid.full + self.on_normal_message(sent) + + sent = message['carbon_sent'] + if (sent['to'].bare not in roster or + roster[sent['to'].bare].subscription == 'none'): + fixes.has_identity(self.xmpp, sent['to'].server, + identity='conference', + on_true=functools.partial(ignore_message, sent), + on_false=functools.partial(send_message, sent)) + else: + send_message(sent) + + ### Invites ### + + def on_groupchat_invitation(self, message): + """ + Mediated invitation received + """ + jid = message['from'] + if jid.bare in self.pending_invites: + return + # there are 2 'x' tags in the messages, making message['x'] useless + invite = StanzaBase(self.xmpp, xml=message.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite')) + inviter = invite['from'] + reason = invite['reason'] + password = invite['password'] + msg = "You are invited to the room %s by %s" % (jid.full, inviter.full) + if reason: + msg += "because: %s" % reason + if password: + msg += ". The password is \"%s\"." % password + self.information(msg, 'Info') + if 'invite' in config.get('beep_on').split(): + curses.beep() + logger.log_roster_change(inviter.full, 'invited you to %s' % jid.full) + self.pending_invites[jid.bare] = inviter.full + + def on_groupchat_decline(self, decline): + "Mediated invitation declined; skip for now" + pass + + def on_groupchat_direct_invitation(self, message): + """ + Direct invitation received + """ + room = safeJID(message['groupchat_invite']['jid']) + if room.bare in self.pending_invites: return - 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.check_bookmark_storage(features) - - self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain, - callback=callback) - -def on_carbon_received(self, message): - """ - Carbon received - """ - def ignore_message(recv): - log.debug('%s has category conference, ignoring carbon', - recv['from'].server) - def receive_message(recv): - recv['to'] = self.xmpp.boundjid.full - if recv['receipt']: - return self.on_receipt(recv) - self.on_normal_message(recv) - - recv = message['carbon_received'] - if (recv['from'].bare not in roster or - roster[recv['from'].bare].subscription == 'none'): - fixes.has_identity(self.xmpp, recv['from'].server, - identity='conference', - on_true=functools.partial(ignore_message, recv), - on_false=functools.partial(receive_message, recv)) - return - else: - receive_message(recv) - -def on_carbon_sent(self, message): - """ - Carbon received - """ - def ignore_message(sent): - log.debug('%s has category conference, ignoring carbon', - sent['to'].server) - def send_message(sent): - sent['from'] = self.xmpp.boundjid.full - self.on_normal_message(sent) - - sent = message['carbon_sent'] - if (sent['to'].bare not in roster or - roster[sent['to'].bare].subscription == 'none'): - fixes.has_identity(self.xmpp, sent['to'].server, - identity='conference', - on_true=functools.partial(ignore_message, sent), - on_false=functools.partial(send_message, sent)) - else: - send_message(sent) - -### Invites ### - -def on_groupchat_invitation(self, message): - """ - Mediated invitation received - """ - jid = message['from'] - if jid.bare in self.pending_invites: - return - # there are 2 'x' tags in the messages, making message['x'] useless - invite = StanzaBase(self.xmpp, xml=message.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite')) - inviter = invite['from'] - reason = invite['reason'] - password = invite['password'] - msg = "You are invited to the room %s by %s" % (jid.full, inviter.full) - if reason: - msg += "because: %s" % reason - if password: - msg += ". The password is \"%s\"." % password - self.information(msg, 'Info') - if 'invite' in config.get('beep_on').split(): - curses.beep() - logger.log_roster_change(inviter.full, 'invited you to %s' % jid.full) - self.pending_invites[jid.bare] = inviter.full - -def on_groupchat_decline(self, decline): - "Mediated invitation declined; skip for now" - pass - -def on_groupchat_direct_invitation(self, message): - """ - Direct invitation received - """ - room = safeJID(message['groupchat_invite']['jid']) - if room.bare in self.pending_invites: - return - - inviter = message['from'] - reason = message['groupchat_invite']['reason'] - password = message['groupchat_invite']['password'] - continue_ = message['groupchat_invite']['continue'] - msg = "You are invited to the room %s by %s" % (room, inviter.full) - - if password: - msg += ' (password: "%s")' % password - if continue_: - msg += '\nto continue the discussion' - if reason: - msg += "\nreason: %s" % reason - - self.information(msg, 'Info') - if 'invite' in config.get('beep_on').split(): - curses.beep() - - self.pending_invites[room.bare] = inviter.full - logger.log_roster_change(inviter.full, 'invited you to %s' % room.bare) - -### "classic" messages ### - -def on_message(self, message): - """ - When receiving private message from a muc OR a normal message - (from one of our contacts) - """ - if message.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite') != None: - return - if message['type'] == 'groupchat': - return - # Differentiate both type of messages, and call the appropriate handler. - 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.bare) - else: - return self.on_groupchat_private_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() + inviter = message['from'] + reason = message['groupchat_invite']['reason'] + password = message['groupchat_invite']['password'] + continue_ = message['groupchat_invite']['continue'] + msg = "You are invited to the room %s by %s" % (room, inviter.full) + + if password: + msg += ' (password: "%s")' % password + if continue_: + msg += '\nto continue the discussion' + if reason: + msg += "\nreason: %s" % reason + + self.information(msg, 'Info') + if 'invite' in config.get('beep_on').split(): + curses.beep() + + self.pending_invites[room.bare] = inviter.full + logger.log_roster_change(inviter.full, 'invited you to %s' % room.bare) + + ### "classic" messages ### + + def on_message(self, message): + """ + When receiving private message from a muc OR a normal message + (from one of our contacts) + """ + if message.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}invite') != None: + return + if message['type'] == 'groupchat': + return + # Differentiate both type of messages, and call the appropriate handler. + 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.bare) + else: + return self.on_groupchat_private_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): + """ + When receiving "normal" messages (not a private message from a + muc participant) + """ + if message['type'] == 'error': + return + elif message['type'] == 'headline' and message['body']: + return self.information('%s says: %s' % (message['from'], message['body']), 'Headline') + + use_xhtml = config.get('enable_xhtml_im') + tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') + extract_images = config.get('extract_inline_images') + body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, + tmp_dir=tmp_dir, + extract_images=extract_images) + if not body: + return -def on_normal_message(self, message): - """ - When receiving "normal" messages (not a private message from a - muc participant) - """ - if message['type'] == 'error': - return - elif message['type'] == 'headline' and message['body']: - return self.information('%s says: %s' % (message['from'], message['body']), 'Headline') - - use_xhtml = config.get('enable_xhtml_im') - tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') - extract_images = config.get('extract_inline_images') - body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, - tmp_dir=tmp_dir, - extract_images=extract_images) - if not body: - return - - remote_nick = '' - # normal message, we are the recipient - if message['to'].bare == self.xmpp.boundjid.bare: - conv_jid = message['from'] - jid = conv_jid - color = get_theme().COLOR_REMOTE_USER - # check for a name - if conv_jid.bare in roster: - remote_nick = roster[conv_jid.bare].name - # check for a received nick - if not remote_nick and config.get('enable_user_nick'): - if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None: - remote_nick = message['nick']['nick'] - if not remote_nick: - remote_nick = conv_jid.user + remote_nick = '' + # normal message, we are the recipient + if message['to'].bare == self.xmpp.boundjid.bare: + conv_jid = message['from'] + jid = conv_jid + color = get_theme().COLOR_REMOTE_USER + # check for a name + if conv_jid.bare in roster: + remote_nick = roster[conv_jid.bare].name + # check for a received nick + if not remote_nick and config.get('enable_user_nick'): + if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None: + remote_nick = message['nick']['nick'] if not remote_nick: - remote_nick = conv_jid.full - own = False - # we wrote the message (happens with carbons) - elif message['from'].bare == self.xmpp.boundjid.bare: - conv_jid = message['to'] - jid = self.xmpp.boundjid - color = get_theme().COLOR_OWN_NICK - remote_nick = self.own_nick - own = True - # we are not part of that message, drop it - else: - return - - conversation = self.get_conversation_by_jid(conv_jid, create=True) - if isinstance(conversation, tabs.DynamicConversationTab) and conv_jid.resource: - conversation.lock(conv_jid.resource) - - if not own and not conversation.nick: - conversation.nick = remote_nick - elif not own: # keep a fixed nick during the whole conversation - remote_nick = conversation.nick - - self.events.trigger('conversation_msg', message, conversation) - if not message['body']: - return - body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, - tmp_dir=tmp_dir, - extract_images=extract_images) - delayed, date = common.find_delayed_tag(message) - - def try_modify(): - replaced_id = message['replace']['id'] - if replaced_id and config.get_by_tabname('group_corrections', - conv_jid.bare): - try: - conversation.modify_message(body, replaced_id, message['id'], jid=jid, - nickname=remote_nick) - return True - except CorrectionError: - log.debug('Unable to correct a message', exc_info=True) - return False - - if not try_modify(): - conversation.add_message(body, date, - nickname=remote_nick, - nick_color=color, - history=delayed, - identifier=message['id'], - jid=jid, - typ=1) - - if conversation.remote_wants_chatstates is None and not delayed: - if message['chat_state']: - conversation.remote_wants_chatstates = True + remote_nick = conv_jid.user + if not remote_nick: + remote_nick = conv_jid.full + own = False + # we wrote the message (happens with carbons) + elif message['from'].bare == self.xmpp.boundjid.bare: + conv_jid = message['to'] + jid = self.xmpp.boundjid + color = get_theme().COLOR_OWN_NICK + remote_nick = self.own_nick + own = True + # we are not part of that message, drop it else: - conversation.remote_wants_chatstates = False - if not own and 'private' in config.get('beep_on').split(): - if not config.get_by_tabname('disable_beep', conv_jid.bare): - curses.beep() - if self.current_tab() is not conversation: - if not own: - conversation.state = 'private' - self.refresh_tab_win() + return + + conversation = self.get_conversation_by_jid(conv_jid, create=True) + if isinstance(conversation, tabs.DynamicConversationTab) and conv_jid.resource: + conversation.lock(conv_jid.resource) + + if not own and not conversation.nick: + conversation.nick = remote_nick + elif not own: # keep a fixed nick during the whole conversation + remote_nick = conversation.nick + + self.events.trigger('conversation_msg', message, conversation) + if not message['body']: + return + body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, + tmp_dir=tmp_dir, + extract_images=extract_images) + delayed, date = common.find_delayed_tag(message) + + def try_modify(): + replaced_id = message['replace']['id'] + if replaced_id and config.get_by_tabname('group_corrections', + conv_jid.bare): + try: + conversation.modify_message(body, replaced_id, message['id'], jid=jid, + nickname=remote_nick) + return True + except CorrectionError: + log.debug('Unable to correct a message', exc_info=True) + return False + + if not try_modify(): + conversation.add_message(body, date, + nickname=remote_nick, + nick_color=color, + history=delayed, + identifier=message['id'], + jid=jid, + typ=1) + + if conversation.remote_wants_chatstates is None and not delayed: + if message['chat_state']: + conversation.remote_wants_chatstates = True + else: + conversation.remote_wants_chatstates = False + if not own and 'private' in config.get('beep_on').split(): + if not config.get_by_tabname('disable_beep', conv_jid.bare): + curses.beep() + if self.current_tab() is not conversation: + if not own: + conversation.state = 'private' + self.refresh_tab_win() + else: + conversation.set_state('normal') + self.refresh_tab_win() else: - conversation.set_state('normal') - self.refresh_tab_win() - else: - self.refresh_window() + self.refresh_window() -def on_nick_received(self, message): - """ - Called when a pep notification for an user nickname - is received - """ - contact = roster[message['from'].bare] - if not contact: - return - item = message['pubsub_event']['items']['item'] - if item.xml.find('{http://jabber.org/protocol/nick}nick'): - contact.name = item['nick']['nick'] - else: - contact.name = '' - -def on_gaming_event(self, message): - """ - Called when a pep notification for user gaming - is received - """ - contact = roster[message['from'].bare] - if not contact: - return - item = message['pubsub_event']['items']['item'] - old_gaming = contact.gaming - if item.xml.find('{urn:xmpp:gaming:0}gaming'): - item = item['gaming'] - # only name and server_address are used for now - contact.gaming = { - 'character_name': item['character_name'], - 'character_profile': item['character_profile'], - 'name': item['name'], - 'level': item['level'], - 'uri': item['uri'], - 'server_name': item['server_name'], - 'server_address': item['server_address'], - } - else: - contact.gaming = {} - - if contact.gaming: - logger.log_roster_change(contact.bare_jid, 'is playing %s' % (common.format_gaming_string(contact.gaming))) - - if old_gaming != contact.gaming and config.get_by_tabname('display_gaming_notifications', contact.bare_jid): - if contact.gaming: - self.information('%s is playing %s' % (contact.bare_jid, common.format_gaming_string(contact.gaming)), 'Gaming') + def on_nick_received(self, message): + """ + Called when a pep notification for an user nickname + is received + """ + contact = roster[message['from'].bare] + if not contact: + return + item = message['pubsub_event']['items']['item'] + if item.xml.find('{http://jabber.org/protocol/nick}nick'): + contact.name = item['nick']['nick'] else: - self.information(contact.bare_jid + ' stopped playing.', 'Gaming') - -def on_mood_event(self, message): - """ - Called when a pep notification for an user mood - is received. - """ - contact = roster[message['from'].bare] - if not contact: - return - roster.modified() - item = message['pubsub_event']['items']['item'] - old_mood = contact.mood - if item.xml.find('{http://jabber.org/protocol/mood}mood'): - mood = item['mood']['value'] - if mood: - mood = pep.MOODS.get(mood, mood) - text = item['mood']['text'] - if text: - mood = '%s (%s)' % (mood, text) - contact.mood = mood + contact.name = '' + + def on_gaming_event(self, message): + """ + Called when a pep notification for user gaming + is received + """ + contact = roster[message['from'].bare] + if not contact: + return + item = message['pubsub_event']['items']['item'] + old_gaming = contact.gaming + if item.xml.find('{urn:xmpp:gaming:0}gaming'): + item = item['gaming'] + # only name and server_address are used for now + contact.gaming = { + 'character_name': item['character_name'], + 'character_profile': item['character_profile'], + 'name': item['name'], + 'level': item['level'], + 'uri': item['uri'], + 'server_name': item['server_name'], + 'server_address': item['server_address'], + } else: - contact.mood = '' - else: - contact.mood = '' + contact.gaming = {} - if contact.mood: - logger.log_roster_change(contact.bare_jid, 'has now the mood: %s' % contact.mood) + if contact.gaming: + logger.log_roster_change(contact.bare_jid, 'is playing %s' % (common.format_gaming_string(contact.gaming))) - if old_mood != contact.mood and config.get_by_tabname('display_mood_notifications', contact.bare_jid): - if contact.mood: - self.information('Mood from '+ contact.bare_jid + ': ' + contact.mood, 'Mood') + if old_gaming != contact.gaming and config.get_by_tabname('display_gaming_notifications', contact.bare_jid): + if contact.gaming: + self.information('%s is playing %s' % (contact.bare_jid, common.format_gaming_string(contact.gaming)), 'Gaming') + else: + self.information(contact.bare_jid + ' stopped playing.', 'Gaming') + + def on_mood_event(self, message): + """ + Called when a pep notification for an user mood + is received. + """ + contact = roster[message['from'].bare] + if not contact: + return + roster.modified() + item = message['pubsub_event']['items']['item'] + old_mood = contact.mood + if item.xml.find('{http://jabber.org/protocol/mood}mood'): + mood = item['mood']['value'] + if mood: + mood = pep.MOODS.get(mood, mood) + text = item['mood']['text'] + if text: + mood = '%s (%s)' % (mood, text) + contact.mood = mood + else: + contact.mood = '' else: - self.information(contact.bare_jid + ' stopped having his/her mood.', 'Mood') - -def on_activity_event(self, message): - """ - Called when a pep notification for an user activity - is received. - """ - contact = roster[message['from'].bare] - if not contact: - return - roster.modified() - item = message['pubsub_event']['items']['item'] - old_activity = contact.activity - if item.xml.find('{http://jabber.org/protocol/activity}activity'): - try: - activity = item['activity']['value'] - except ValueError: + contact.mood = '' + + if contact.mood: + logger.log_roster_change(contact.bare_jid, 'has now the mood: %s' % contact.mood) + + if old_mood != contact.mood and config.get_by_tabname('display_mood_notifications', contact.bare_jid): + if contact.mood: + self.information('Mood from '+ contact.bare_jid + ': ' + contact.mood, 'Mood') + else: + self.information(contact.bare_jid + ' stopped having his/her mood.', 'Mood') + + def on_activity_event(self, message): + """ + Called when a pep notification for an user activity + is received. + """ + contact = roster[message['from'].bare] + if not contact: return - if activity[0]: - general = pep.ACTIVITIES.get(activity[0]) - s = general['category'] - if activity[1]: - s = s + '/' + general.get(activity[1], 'other') - text = item['activity']['text'] - if text: - s = '%s (%s)' % (s, text) - contact.activity = s + roster.modified() + item = message['pubsub_event']['items']['item'] + old_activity = contact.activity + if item.xml.find('{http://jabber.org/protocol/activity}activity'): + try: + activity = item['activity']['value'] + except ValueError: + return + if activity[0]: + general = pep.ACTIVITIES.get(activity[0]) + s = general['category'] + if activity[1]: + s = s + '/' + general.get(activity[1], 'other') + text = item['activity']['text'] + if text: + s = '%s (%s)' % (s, text) + contact.activity = s + else: + contact.activity = '' else: contact.activity = '' - else: - contact.activity = '' - - if contact.activity: - logger.log_roster_change(contact.bare_jid, 'has now the activity %s' % contact.activity) - if old_activity != contact.activity and config.get_by_tabname('display_activity_notifications', contact.bare_jid): if contact.activity: - self.information('Activity from '+ contact.bare_jid + ': ' + contact.activity, 'Activity') + logger.log_roster_change(contact.bare_jid, 'has now the activity %s' % contact.activity) + + if old_activity != contact.activity and config.get_by_tabname('display_activity_notifications', contact.bare_jid): + if contact.activity: + self.information('Activity from '+ contact.bare_jid + ': ' + contact.activity, 'Activity') + else: + self.information(contact.bare_jid + ' stopped doing his/her activity.', 'Activity') + + def on_tune_event(self, message): + """ + Called when a pep notification for an user tune + is received + """ + contact = roster[message['from'].bare] + if not contact: + return + roster.modified() + item = message['pubsub_event']['items']['item'] + old_tune = contact.tune + if item.xml.find('{http://jabber.org/protocol/tune}tune'): + item = item['tune'] + contact.tune = { + 'artist': item['artist'], + 'length': item['length'], + 'rating': item['rating'], + 'source': item['source'], + 'title': item['title'], + 'track': item['track'], + 'uri': item['uri'] + } else: - self.information(contact.bare_jid + ' stopped doing his/her activity.', 'Activity') - -def on_tune_event(self, message): - """ - Called when a pep notification for an user tune - is received - """ - contact = roster[message['from'].bare] - if not contact: - return - roster.modified() - item = message['pubsub_event']['items']['item'] - old_tune = contact.tune - if item.xml.find('{http://jabber.org/protocol/tune}tune'): - item = item['tune'] - contact.tune = { - 'artist': item['artist'], - 'length': item['length'], - 'rating': item['rating'], - 'source': item['source'], - 'title': item['title'], - 'track': item['track'], - 'uri': item['uri'] - } - else: - contact.tune = {} - - if contact.tune: - logger.log_roster_change(message['from'].bare, 'is now listening to %s' % common.format_tune_string(contact.tune)) - - if old_tune != contact.tune and config.get_by_tabname('display_tune_notifications', contact.bare_jid): + contact.tune = {} + if contact.tune: - self.information( - 'Tune from '+ message['from'].bare + ': ' + common.format_tune_string(contact.tune), - 'Tune') - else: - self.information(contact.bare_jid + ' stopped listening to music.', 'Tune') - -def on_groupchat_message(self, message): - """ - Triggered whenever a message is received from a multi-user chat room. - """ - if message['subject']: - return - room_from = message['from'].bare - - if message['type'] == 'error': # Check if it's an error - return self.room_error(message, room_from) - - 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)) - muc.leave_groupchat(self.xmpp, room_from, self.own_nick, msg='') - return - - nick_from = message['mucnick'] - user = tab.get_user_by_name(nick_from) - if user and user in tab.ignores: - return - - self.events.trigger('muc_msg', message, tab) - use_xhtml = config.get('enable_xhtml_im') - tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') - extract_images = config.get('extract_inline_images') - body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, - tmp_dir=tmp_dir, - extract_images=extract_images) - if not body: - return - - old_state = tab.state - delayed, date = common.find_delayed_tag(message) - replaced_id = message['replace']['id'] - replaced = False - if replaced_id is not '' and config.get_by_tabname('group_corrections', - message['from'].bare): - try: - delayed_date = date or datetime.now() - if tab.modify_message(body, replaced_id, message['id'], - time=delayed_date, - nickname=nick_from, user=user): - self.events.trigger('highlight', message, tab) - replaced = True - except CorrectionError: - log.debug('Unable to correct a message', exc_info=True) - if not replaced and tab.add_message(body, date, nick_from, history=delayed, identifier=message['id'], jid=message['from'], typ=1): - self.events.trigger('highlight', message, tab) - - if message['from'].resource == tab.own_nick: - tab.last_sent_message = message - - if tab is self.current_tab(): - tab.text_win.refresh() - tab.info_header.refresh(tab, tab.text_win) - tab.input.refresh() - self.doupdate() - elif tab.state != old_state: - self.refresh_tab_win() - current = self.current_tab() - if hasattr(current, 'input') and current.input: - current.input.refresh() - self.doupdate() - - if 'message' in config.get('beep_on').split(): - if (not config.get_by_tabname('disable_beep', room_from) - and self.own_nick != message['from'].resource): - curses.beep() + logger.log_roster_change(message['from'].bare, 'is now listening to %s' % common.format_tune_string(contact.tune)) -def on_muc_own_nickchange(self, muc): - "We changed our nick in a MUC" - for tab in self.get_tabs(tabs.PrivateTab): - if tab.parent_muc == muc: - tab.own_nick = muc.own_nick - -def on_groupchat_private_message(self, message): - """ - We received a Private Message (from someone in a Muc) - """ - jid = message['from'] - nick_from = jid.resource - if not nick_from: - return self.on_groupchat_message(message) - - room_from = jid.bare - use_xhtml = config.get('enable_xhtml_im') - tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') - extract_images = config.get('extract_inline_images') - body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, - tmp_dir=tmp_dir, - extract_images=extract_images) - tab = self.get_tab_by_name(jid.full, tabs.PrivateTab) # get the tab with the private conversation - ignore = config.get_by_tabname('ignore_private', room_from) - if not tab: # It's the first message we receive: create the tab - if body and not ignore: - tab = self.open_private_window(room_from, nick_from, False) - if ignore: - self.events.trigger('ignored_private', message, tab) - msg = config.get_by_tabname('private_auto_response', room_from) - if msg and body: - self.xmpp.send_message(mto=jid.full, mbody=msg, mtype='chat') - return - self.events.trigger('private_msg', message, tab) - body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, - tmp_dir=tmp_dir, - extract_images=extract_images) - if not body or not tab: - return - replaced_id = message['replace']['id'] - replaced = False - user = tab.parent_muc.get_user_by_name(nick_from) - if replaced_id is not '' and config.get_by_tabname('group_corrections', - room_from): - try: - tab.modify_message(body, replaced_id, message['id'], user=user, jid=message['from'], - nickname=nick_from) - replaced = True - except CorrectionError: - log.debug('Unable to correct a message', exc_info=True) - if not replaced: - tab.add_message(body, time=None, nickname=nick_from, - forced_user=user, - identifier=message['id'], - jid=message['from'], - typ=1) - - if tab.remote_wants_chatstates is None: - if message['chat_state']: - tab.remote_wants_chatstates = True - else: - tab.remote_wants_chatstates = False - if 'private' in config.get('beep_on').split(): - if not config.get_by_tabname('disable_beep', jid.full): - curses.beep() - if tab is self.current_tab(): - self.refresh_window() - else: - tab.state = 'private' - self.refresh_tab_win() + if old_tune != contact.tune and config.get_by_tabname('display_tune_notifications', contact.bare_jid): + if contact.tune: + self.information( + 'Tune from '+ message['from'].bare + ': ' + common.format_tune_string(contact.tune), + 'Tune') + else: + self.information(contact.bare_jid + ' stopped listening to music.', 'Tune') -### Chatstates ### + def on_groupchat_message(self, message): + """ + Triggered whenever a message is received from a multi-user chat room. + """ + if message['subject']: + return + room_from = message['from'].bare -def on_chatstate_active(self, message): - self.on_chatstate(message, "active") + if message['type'] == 'error': # Check if it's an error + return self.room_error(message, room_from) -def on_chatstate_inactive(self, message): - self.on_chatstate(message, "inactive") + 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)) + muc.leave_groupchat(self.xmpp, room_from, self.own_nick, msg='') + return -def on_chatstate_composing(self, message): - self.on_chatstate(message, "composing") + nick_from = message['mucnick'] + user = tab.get_user_by_name(nick_from) + if user and user in tab.ignores: + return -def on_chatstate_paused(self, message): - self.on_chatstate(message, "paused") + self.events.trigger('muc_msg', message, tab) + use_xhtml = config.get('enable_xhtml_im') + tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') + extract_images = config.get('extract_inline_images') + body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, + tmp_dir=tmp_dir, + extract_images=extract_images) + if not body: + return -def on_chatstate_gone(self, message): - self.on_chatstate(message, "gone") + old_state = tab.state + delayed, date = common.find_delayed_tag(message) + replaced_id = message['replace']['id'] + replaced = False + if replaced_id is not '' and config.get_by_tabname('group_corrections', + message['from'].bare): + try: + delayed_date = date or datetime.now() + if tab.modify_message(body, replaced_id, message['id'], + time=delayed_date, + nickname=nick_from, user=user): + self.events.trigger('highlight', message, tab) + replaced = True + except CorrectionError: + log.debug('Unable to correct a message', exc_info=True) + if not replaced and tab.add_message(body, date, nick_from, history=delayed, identifier=message['id'], jid=message['from'], typ=1): + self.events.trigger('highlight', message, tab) -def on_chatstate(self, message, state): - if message['type'] == 'chat': - if not self.on_chatstate_normal_conversation(message, state): - tab = self.get_tab_by_name(message['from'].full, tabs.PrivateTab) - if not tab: - return - self.on_chatstate_private_conversation(message, state) - elif message['type'] == 'groupchat': - self.on_chatstate_groupchat_conversation(message, state) - -def on_chatstate_normal_conversation(self, message, state): - tab = self.get_conversation_by_jid(message['from'], False) - if not tab: - return False - tab.remote_wants_chatstates = True - self.events.trigger('normal_chatstate', message, tab) - tab.chatstate = state - if state == 'gone' and isinstance(tab, tabs.DynamicConversationTab): - tab.unlock() - if tab == self.current_tab(): - tab.refresh_info_header() - self.doupdate() - else: - _composing_tab_state(tab, state) - self.refresh_tab_win() - return True - -def on_chatstate_private_conversation(self, message, state): - """ - Chatstate received in a private conversation from a MUC - """ - tab = self.get_tab_by_name(message['from'].full, tabs.PrivateTab) - if not tab: - return - tab.remote_wants_chatstates = True - self.events.trigger('private_chatstate', message, tab) - tab.chatstate = state - if tab == self.current_tab(): - tab.refresh_info_header() - self.doupdate() - else: - _composing_tab_state(tab, state) - self.refresh_tab_win() - return True - -def on_chatstate_groupchat_conversation(self, message, state): - """ - Chatstate received in a MUC - """ - nick = message['mucnick'] - room_from = message.get_mucroom() - tab = self.get_tab_by_name(room_from, tabs.MucTab) - if tab and tab.get_user_by_name(nick): - self.events.trigger('muc_chatstate', message, tab) - tab.get_user_by_name(nick).chatstate = state - if tab == self.current_tab(): - if not self.size.tab_degrade_x: - tab.user_win.refresh(tab.users) - tab.input.refresh() - self.doupdate() - else: - _composing_tab_state(tab, state) - self.refresh_tab_win() - -### subscription-related handlers ### - -def on_roster_update(self, iq): - """ - The roster was received. - """ - for item in iq['roster']: - try: - jid = item['jid'] - except InvalidJID: - jid = item._get_attr('jid', '') - log.error('Invalid JID: "%s"', jid, exc_info=True) - else: - if item['subscription'] == 'remove': - del roster[jid] + if message['from'].resource == tab.own_nick: + tab.last_sent_message = message + + if tab is self.current_tab(): + tab.text_win.refresh() + tab.info_header.refresh(tab, tab.text_win) + tab.input.refresh() + self.doupdate() + elif tab.state != old_state: + self.refresh_tab_win() + current = self.current_tab() + if hasattr(current, 'input') and current.input: + current.input.refresh() + self.doupdate() + + if 'message' in config.get('beep_on').split(): + if (not config.get_by_tabname('disable_beep', room_from) + and self.own_nick != message['from'].resource): + curses.beep() + + def on_muc_own_nickchange(self, muc): + "We changed our nick in a MUC" + for tab in self.get_tabs(tabs.PrivateTab): + if tab.parent_muc == muc: + tab.own_nick = muc.own_nick + + def on_groupchat_private_message(self, message): + """ + We received a Private Message (from someone in a Muc) + """ + jid = message['from'] + nick_from = jid.resource + if not nick_from: + return self.on_groupchat_message(message) + + room_from = jid.bare + use_xhtml = config.get('enable_xhtml_im') + tmp_dir = config.get('tmp_image_dir') or path.join(CACHE_DIR, 'images') + extract_images = config.get('extract_inline_images') + body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, + tmp_dir=tmp_dir, + extract_images=extract_images) + tab = self.get_tab_by_name(jid.full, tabs.PrivateTab) # get the tab with the private conversation + ignore = config.get_by_tabname('ignore_private', room_from) + if not tab: # It's the first message we receive: create the tab + if body and not ignore: + tab = self.open_private_window(room_from, nick_from, False) + if ignore: + self.events.trigger('ignored_private', message, tab) + msg = config.get_by_tabname('private_auto_response', room_from) + if msg and body: + self.xmpp.send_message(mto=jid.full, mbody=msg, mtype='chat') + return + self.events.trigger('private_msg', message, tab) + body = xhtml.get_body_from_message_stanza(message, use_xhtml=use_xhtml, + tmp_dir=tmp_dir, + extract_images=extract_images) + if not body or not tab: + return + replaced_id = message['replace']['id'] + replaced = False + user = tab.parent_muc.get_user_by_name(nick_from) + if replaced_id is not '' and config.get_by_tabname('group_corrections', + room_from): + try: + tab.modify_message(body, replaced_id, message['id'], user=user, jid=message['from'], + nickname=nick_from) + replaced = True + except CorrectionError: + log.debug('Unable to correct a message', exc_info=True) + if not replaced: + tab.add_message(body, time=None, nickname=nick_from, + forced_user=user, + identifier=message['id'], + jid=message['from'], + typ=1) + + if tab.remote_wants_chatstates is None: + if message['chat_state']: + tab.remote_wants_chatstates = True else: - roster.update_contact_groups(jid) - roster.update_size() - if isinstance(self.current_tab(), tabs.RosterInfoTab): - self.refresh_window() + tab.remote_wants_chatstates = False + if 'private' in config.get('beep_on').split(): + if not config.get_by_tabname('disable_beep', jid.full): + curses.beep() + if tab is self.current_tab(): + self.refresh_window() + else: + tab.state = 'private' + self.refresh_tab_win() -def on_subscription_request(self, presence): - """subscribe received""" - jid = presence['from'].bare - contact = roster[jid] - if contact and contact.subscription in ('from', 'both'): - return - elif contact and contact.subscription == 'to': - self.xmpp.sendPresence(pto=jid, ptype='subscribed') - self.xmpp.sendPresence(pto=jid) - else: - if not contact: - contact = roster.get_and_set(jid) - roster.update_contact_groups(contact) - contact.pending_in = True - self.information('%s wants to subscribe to your presence, use ' - '/accept or /deny in the roster ' - 'tab 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): - self.refresh_window() + ### Chatstates ### -def on_subscription_authorized(self, presence): - """subscribed received""" - jid = presence['from'].bare - contact = roster[jid] - if contact.subscription not in ('both', 'from'): - self.information('%s accepted your contact proposal' % jid, 'Roster') - if contact.pending_out: - contact.pending_out = False + def on_chatstate_active(self, message): + self.on_chatstate(message, "active") - roster.modified() + def on_chatstate_inactive(self, message): + self.on_chatstate(message, "inactive") - if isinstance(self.current_tab(), tabs.RosterInfoTab): - self.refresh_window() + def on_chatstate_composing(self, message): + self.on_chatstate(message, "composing") -def on_subscription_remove(self, presence): - """unsubscribe received""" - jid = presence['from'].bare - contact = roster[jid] - if not contact: - return - roster.modified() - self.information('%s does not want to receive your status anymore.' % jid, 'Roster') - self.get_tab_by_number(0).state = 'highlight' - if isinstance(self.current_tab(), tabs.RosterInfoTab): - self.refresh_window() + def on_chatstate_paused(self, message): + self.on_chatstate(message, "paused") -def on_subscription_removed(self, presence): - """unsubscribed received""" - jid = presence['from'].bare - contact = roster[jid] - if not contact: - return - roster.modified() - if contact.pending_out: - self.information('%s rejected your contact proposal' % jid, 'Roster') - contact.pending_out = False - else: - self.information('%s does not want you to receive his/her/its status anymore.'%jid, 'Roster') - self.get_tab_by_number(0).state = 'highlight' - if isinstance(self.current_tab(), tabs.RosterInfoTab): - self.refresh_window() + def on_chatstate_gone(self, message): + self.on_chatstate(message, "gone") -### Presence-related handlers ### - -def on_presence(self, presence): - if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x'): - return - jid = presence['from'] - contact = roster[jid.bare] - tab = self.get_conversation_by_jid(jid, create=False) - if isinstance(tab, tabs.DynamicConversationTab): - if tab.get_dest_jid() != jid.full: - tab.unlock(from_=jid.full) - elif presence['type'] == 'unavailable': + def on_chatstate(self, message, state): + if message['type'] == 'chat': + if not self.on_chatstate_normal_conversation(message, state): + tab = self.get_tab_by_name(message['from'].full, tabs.PrivateTab) + if not tab: + return + self.on_chatstate_private_conversation(message, state) + elif message['type'] == 'groupchat': + self.on_chatstate_groupchat_conversation(message, state) + + def on_chatstate_normal_conversation(self, message, state): + tab = self.get_conversation_by_jid(message['from'], False) + if not tab: + return False + tab.remote_wants_chatstates = True + self.events.trigger('normal_chatstate', message, tab) + tab.chatstate = state + if state == 'gone' and isinstance(tab, tabs.DynamicConversationTab): tab.unlock() - if contact is None: - return - roster.modified() - contact.error = None - self.events.trigger('normal_presence', presence, contact[jid.full]) - tab = self.get_conversation_by_jid(jid, create=False) - if isinstance(self.current_tab(), tabs.RosterInfoTab): - self.refresh_window() - elif self.current_tab() == tab: - tab.refresh() - self.doupdate() - -def on_presence_error(self, presence): - jid = presence['from'] - contact = roster[jid.bare] - if not contact: - return - roster.modified() - contact.error = presence['error']['type'] + ': ' + presence['error']['condition'] - # reset chat states status on presence error - tab = self.get_tab_by_name(jid.full, tabs.ConversationTab) - if tab: - tab.remote_wants_chatstates = None - -def on_got_offline(self, presence): - """ - A JID got offline - """ - if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x'): - return - jid = presence['from'] - if not logger.log_roster_change(jid.bare, 'got offline'): - 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. - contact = roster[jid.bare] - name = jid.bare - if contact: - roster.connected -= 1 - if contact.name: - name = contact.name - if jid.resource: - self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x191}offline' % name) - self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x191}offline' % name) - self.information('\x193}%s \x195}is \x191}offline' % name, 'Roster') - roster.modified() - if isinstance(self.current_tab(), tabs.RosterInfoTab): - self.refresh_window() + if tab == self.current_tab(): + tab.refresh_info_header() + self.doupdate() + else: + _composing_tab_state(tab, state) + self.refresh_tab_win() + return True -def on_got_online(self, presence): - """ - A JID got online - """ - if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x'): - return - jid = presence['from'] - contact = roster[jid.bare] - if contact is None: - # Todo, handle presence coming from contacts not in roster - return - roster.connected += 1 - roster.modified() - if not logger.log_roster_change(jid.bare, 'got online'): - self.information('Unable to write in the log file', 'Error') - resource = Resource(jid.full, { - 'priority': presence.get_priority() or 0, - 'status': presence['status'], - 'show': presence['show'], - }) - self.events.trigger('normal_presence', presence, resource) - name = contact.name if contact.name else jid.bare - self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x194}online' % name) - if time.time() - self.connection_time > 10: - # We do not display messages if we recently logged in - if presence['status']: - self.information("\x193}%s \x195}is \x194}online\x195} (\x19o%s\x195})" % (name, presence['status']), "Roster") + def on_chatstate_private_conversation(self, message, state): + """ + Chatstate received in a private conversation from a MUC + """ + tab = self.get_tab_by_name(message['from'].full, tabs.PrivateTab) + if not tab: + return + tab.remote_wants_chatstates = True + self.events.trigger('private_chatstate', message, tab) + tab.chatstate = state + if tab == self.current_tab(): + tab.refresh_info_header() + self.doupdate() else: - self.information("\x193}%s \x195}is \x194}online\x195}" % name, "Roster") - self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x194}online' % name) - if isinstance(self.current_tab(), tabs.RosterInfoTab): - self.refresh_window() + _composing_tab_state(tab, state) + self.refresh_tab_win() + return True + + def on_chatstate_groupchat_conversation(self, message, state): + """ + Chatstate received in a MUC + """ + nick = message['mucnick'] + room_from = message.get_mucroom() + tab = self.get_tab_by_name(room_from, tabs.MucTab) + if tab and tab.get_user_by_name(nick): + self.events.trigger('muc_chatstate', message, tab) + tab.get_user_by_name(nick).chatstate = state + if tab == self.current_tab(): + if not self.size.tab_degrade_x: + tab.user_win.refresh(tab.users) + tab.input.refresh() + self.doupdate() + else: + _composing_tab_state(tab, state) + self.refresh_tab_win() -def on_groupchat_presence(self, presence): - """ - Triggered whenever a presence stanza is received from a user in a multi-user chat room. - Display the presence on the room window and update the - presence information of the concerned user - """ - from_room = presence['from'].bare - tab = self.get_tab_by_name(from_room, tabs.MucTab) - if tab: - self.events.trigger('muc_presence', presence, tab) - tab.handle_presence(presence) - - -### Connection-related handlers ### - -def on_failed_connection(self, error): - """ - We cannot contact the remote server - """ - self.information("Connection to remote server failed: %s" % (error,), 'Error') - -def on_disconnected(self, event): - """ - When we are disconnected from remote server - """ - roster.connected = 0 - # Stop the ping plugin. It would try to send stanza on regular basis - self.xmpp.plugin['xep_0199'].disable_keepalive() - roster.modified() - for tab in self.get_tabs(tabs.MucTab): - tab.disconnect() - 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.start() - -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') - -def on_failed_all_auth(self, event): - """ - Authentication failed - """ - 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.legitimate_disconnect = True - -def on_connected(self, event): - """ - Remote host responded, but we are not yet authenticated - """ - self.information("Connected to server.", 'Info') - -def on_connecting(self, event): - """ - Just before we try to connect to the server - """ - self.legitimate_disconnect = False - -def on_session_start(self, event): - """ - Called when we are connected and authenticated - """ - 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') - 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() - pres['show'] = self.status.show - pres['status'] = self.status.message - self.events.trigger('send_normal_presence', pres) - pres.send() - 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) - asyncio.async(self.xmpp.plugin['xep_0115'].update_caps()) - # Start the ping's plugin regular event - self.xmpp.set_keepalive_values() - -### Other handlers ### - -def on_status_codes(self, message): - """ - Handle groupchat messages with status codes. - Those are received when a room configuration change occurs. - """ - room_from = message['from'] - tab = self.get_tab_by_name(room_from, tabs.MucTab) - status_codes = set([s.attrib['code'] for s in message.findall('{%s}x/{%s}status' % (tabs.NS_MUC_USER, tabs.NS_MUC_USER))]) - if '101' in status_codes: - self.information('Your affiliation in the room %s changed' % room_from, 'Info') - elif tab and status_codes: - show_unavailable = '102' in status_codes - hide_unavailable = '103' in status_codes - non_priv = '104' in status_codes - logging_on = '170' in status_codes - logging_off = '171' in status_codes - non_anon = '172' in status_codes - semi_anon = '173' in status_codes - full_anon = '174' in status_codes - modif = False - if show_unavailable or hide_unavailable or non_priv or logging_off\ - or non_anon or semi_anon or full_anon: - tab.add_message('\x19%(info_col)s}Info: A configuration change not privacy-related occured.' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - modif = True - if show_unavailable: - tab.add_message('\x19%(info_col)s}Info: The unavailable members are now shown.' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - elif hide_unavailable: - tab.add_message('\x19%(info_col)s}Info: The unavailable members are now hidden.' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - if non_anon: - tab.add_message('\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - elif semi_anon: - tab.add_message('\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - elif full_anon: - tab.add_message('\x19%(info_col)s}Info: The room is now fully anonymous.' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - if logging_on: - tab.add_message('\x191}Warning: \x19%(info_col)s}This room is publicly logged' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - elif logging_off: - tab.add_message('\x19%(info_col)s}Info: This room is not logged anymore.' % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, - typ=2) - if modif: + ### subscription-related handlers ### + + def on_roster_update(self, iq): + """ + The roster was received. + """ + for item in iq['roster']: + try: + jid = item['jid'] + except InvalidJID: + jid = item._get_attr('jid', '') + log.error('Invalid JID: "%s"', jid, exc_info=True) + else: + if item['subscription'] == 'remove': + del roster[jid] + else: + roster.update_contact_groups(jid) + roster.update_size() + if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() -def on_groupchat_subject(self, message): - """ - Triggered when the topic is changed. - """ - nick_from = message['mucnick'] - room_from = message.get_mucroom() - tab = self.get_tab_by_name(room_from, tabs.MucTab) - subject = message['subject'] - if subject is None or not tab: - return - if subject != tab.topic: - # 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" % - {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT), 'nick':nick_from, 'subject':subject}, - time=None, - typ=2) + def on_subscription_request(self, presence): + """subscribe received""" + jid = presence['from'].bare + contact = roster[jid] + if contact and contact.subscription in ('from', 'both'): + return + elif contact and contact.subscription == 'to': + self.xmpp.sendPresence(pto=jid, ptype='subscribed') + self.xmpp.sendPresence(pto=jid) else: - 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) - tab.topic = subject - tab.topic_from = nick_from - if self.get_tab_by_name(room_from, tabs.MucTab) is self.current_tab(): - self.refresh_window() + if not contact: + contact = roster.get_and_set(jid) + roster.update_contact_groups(contact) + contact.pending_in = True + self.information('%s wants to subscribe to your presence, use ' + '/accept or /deny in the roster ' + 'tab 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): + self.refresh_window() -def on_receipt(self, message): - """ - When a delivery receipt is received (XEP-0184) - """ - jid = message['from'] - msg_id = message['receipt'] - if not msg_id: - return - - conversation = self.get_tab_by_name(jid, tabs.ChatTab) - conversation = conversation or self.get_tab_by_name(jid.bare, tabs.ChatTab) - if not conversation: - return - - 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): - """ - When a data form is received - """ - self.information('%s' % message) - -def on_attention(self, message): - """ - Attention probe received. - """ - jid_from = message['from'] - self.information('%s requests your attention!' % jid_from, 'Info') - for tab in self.tabs: - if tab.name == jid_from: - tab.state = 'attention' - self.refresh_tab_win() + def on_subscription_authorized(self, presence): + """subscribed received""" + jid = presence['from'].bare + contact = roster[jid] + if contact.subscription not in ('both', 'from'): + self.information('%s accepted your contact proposal' % jid, 'Roster') + if contact.pending_out: + contact.pending_out = False + + roster.modified() + + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + + def on_subscription_remove(self, presence): + """unsubscribe received""" + jid = presence['from'].bare + contact = roster[jid] + if not contact: return - for tab in self.tabs: - if tab.name == jid_from.bare: - tab.state = 'attention' - self.refresh_tab_win() + roster.modified() + self.information('%s does not want to receive your status anymore.' % jid, 'Roster') + self.get_tab_by_number(0).state = 'highlight' + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + + def on_subscription_removed(self, presence): + """unsubscribed received""" + jid = presence['from'].bare + contact = roster[jid] + if not contact: return - self.information('%s tab not found.' % jid_from, 'Error') - -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)' - 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) - self.refresh_window() - -def outgoing_stanza(self, stanza): - """ - We are sending a new stanza, write it in the xml buffer if needed. - """ - if self.xml_tab: - if PYGMENTS: - xhtml_text = highlight('%s' % stanza, LEXER, FORMATTER) - poezio_colored = xhtml.xhtml_to_poezio_colors(xhtml_text, force=True).rstrip('\x19o').strip() + roster.modified() + if contact.pending_out: + self.information('%s rejected your contact proposal' % jid, 'Roster') + contact.pending_out = False 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.information('%s does not want you to receive his/her/its status anymore.'%jid, 'Roster') + self.get_tab_by_number(0).state = 'highlight' + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + + ### Presence-related handlers ### + + def on_presence(self, presence): + if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x'): + return + jid = presence['from'] + contact = roster[jid.bare] + tab = self.get_conversation_by_jid(jid, create=False) + if isinstance(tab, tabs.DynamicConversationTab): + if tab.get_dest_jid() != jid.full: + tab.unlock(from_=jid.full) + elif presence['type'] == 'unavailable': + tab.unlock() + if contact is None: + return + roster.modified() + contact.error = None + self.events.trigger('normal_presence', presence, contact[jid.full]) + tab = self.get_conversation_by_jid(jid, create=False) + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + elif self.current_tab() == tab: + tab.refresh() self.doupdate() -def incoming_stanza(self, stanza): - """ - We are receiving a new stanza, write it in the xml buffer if needed. - """ - if self.xml_tab: - 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) + def on_presence_error(self, presence): + jid = presence['from'] + contact = roster[jid.bare] + if not contact: + return + roster.modified() + contact.error = presence['error']['type'] + ': ' + presence['error']['condition'] + # reset chat states status on presence error + tab = self.get_tab_by_name(jid.full, tabs.ConversationTab) + if tab: + tab.remote_wants_chatstates = None + + def on_got_offline(self, presence): + """ + A JID got offline + """ + if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x'): + return + jid = presence['from'] + if not logger.log_roster_change(jid.bare, 'got offline'): + 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. + contact = roster[jid.bare] + name = jid.bare + if contact: + roster.connected -= 1 + if contact.name: + name = contact.name + if jid.resource: + self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x191}offline' % name) + self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x191}offline' % name) + self.information('\x193}%s \x195}is \x191}offline' % name, 'Roster') + roster.modified() + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + + def on_got_online(self, presence): + """ + A JID got online + """ + if presence.match('presence/muc') or presence.xml.find('{http://jabber.org/protocol/muc#user}x'): + return + jid = presence['from'] + contact = roster[jid.bare] + if contact is None: + # Todo, handle presence coming from contacts not in roster + return + roster.connected += 1 + roster.modified() + if not logger.log_roster_change(jid.bare, 'got online'): + self.information('Unable to write in the log file', 'Error') + resource = Resource(jid.full, { + 'priority': presence.get_priority() or 0, + 'status': presence['status'], + 'show': presence['show'], + }) + self.events.trigger('normal_presence', presence, resource) + name = contact.name if contact.name else jid.bare + self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x194}online' % name) + if time.time() - self.connection_time > 10: + # We do not display messages if we recently logged in + if presence['status']: + self.information("\x193}%s \x195}is \x194}online\x195} (\x19o%s\x195})" % (name, presence['status']), "Roster") + else: + self.information("\x193}%s \x195}is \x194}online\x195}" % name, "Roster") + self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x194}online' % name) + if isinstance(self.current_tab(), tabs.RosterInfoTab): + self.refresh_window() + + def on_groupchat_presence(self, presence): + """ + Triggered whenever a presence stanza is received from a user in a multi-user chat room. + Display the presence on the room window and update the + presence information of the concerned user + """ + from_room = presence['from'].bare + tab = self.get_tab_by_name(from_room, tabs.MucTab) + if tab: + self.events.trigger('muc_presence', presence, tab) + tab.handle_presence(presence) + + + ### Connection-related handlers ### + + def on_failed_connection(self, error): + """ + We cannot contact the remote server + """ + self.information("Connection to remote server failed: %s" % (error,), 'Error') + + def on_disconnected(self, event): + """ + When we are disconnected from remote server + """ + roster.connected = 0 + # Stop the ping plugin. It would try to send stanza on regular basis + self.xmpp.plugin['xep_0199'].disable_keepalive() + roster.modified() + for tab in self.get_tabs(tabs.MucTab): + tab.disconnect() + 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.start() + + 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') + + def on_failed_all_auth(self, event): + """ + Authentication failed + """ + 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.legitimate_disconnect = True + + def on_connected(self, event): + """ + Remote host responded, but we are not yet authenticated + """ + self.information("Connected to server.", 'Info') + + def on_connecting(self, event): + """ + Just before we try to connect to the server + """ + self.legitimate_disconnect = False + + def on_session_start(self, event): + """ + Called when we are connected and authenticated + """ + 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') + 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() + pres['show'] = self.status.show + pres['status'] = self.status.message + self.events.trigger('send_normal_presence', pres) + pres.send() + 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) + asyncio.async(self.xmpp.plugin['xep_0115'].update_caps()) + # Start the ping's plugin regular event + self.xmpp.set_keepalive_values() + + ### Other handlers ### + + def on_status_codes(self, message): + """ + Handle groupchat messages with status codes. + Those are received when a room configuration change occurs. + """ + room_from = message['from'] + tab = self.get_tab_by_name(room_from, tabs.MucTab) + status_codes = set([s.attrib['code'] for s in message.findall('{%s}x/{%s}status' % (tabs.NS_MUC_USER, tabs.NS_MUC_USER))]) + if '101' in status_codes: + self.information('Your affiliation in the room %s changed' % room_from, 'Info') + elif tab and status_codes: + show_unavailable = '102' in status_codes + hide_unavailable = '103' in status_codes + non_priv = '104' in status_codes + logging_on = '170' in status_codes + logging_off = '171' in status_codes + non_anon = '172' in status_codes + semi_anon = '173' in status_codes + full_anon = '174' in status_codes + modif = False + if show_unavailable or hide_unavailable or non_priv or logging_off\ + or non_anon or semi_anon or full_anon: + tab.add_message('\x19%(info_col)s}Info: A configuration change not privacy-related occured.' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + modif = True + if show_unavailable: + tab.add_message('\x19%(info_col)s}Info: The unavailable members are now shown.' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + elif hide_unavailable: + tab.add_message('\x19%(info_col)s}Info: The unavailable members are now hidden.' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + if non_anon: + tab.add_message('\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + elif semi_anon: + tab.add_message('\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + elif full_anon: + tab.add_message('\x19%(info_col)s}Info: The room is now fully anonymous.' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + if logging_on: + tab.add_message('\x191}Warning: \x19%(info_col)s}This room is publicly logged' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + elif logging_off: + tab.add_message('\x19%(info_col)s}Info: This room is not logged anymore.' % + {'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + typ=2) + if modif: + self.refresh_window() + + def on_groupchat_subject(self, message): + """ + Triggered when the topic is changed. + """ + nick_from = message['mucnick'] + room_from = message.get_mucroom() + tab = self.get_tab_by_name(room_from, tabs.MucTab) + subject = message['subject'] + if subject is None or not tab: + return + if subject != tab.topic: + # 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" % + {'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" % + {'subject':subject, 'info_col': dump_tuple(get_theme().COLOR_INFORMATION_TEXT)}, + time=None, + typ=2) + tab.topic = subject + tab.topic_from = nick_from + if self.get_tab_by_name(room_from, tabs.MucTab) is self.current_tab(): + self.refresh_window() + + def on_receipt(self, message): + """ + When a delivery receipt is received (XEP-0184) + """ + jid = message['from'] + msg_id = message['receipt'] + if not msg_id: + return + + conversation = self.get_tab_by_name(jid, tabs.ChatTab) + conversation = conversation or self.get_tab_by_name(jid.bare, tabs.ChatTab) + if not conversation: + return + 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() + 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): + """ + When a data form is received + """ + self.information('%s' % message) + + def on_attention(self, message): + """ + Attention probe received. + """ + jid_from = message['from'] + self.information('%s requests your attention!' % jid_from, 'Info') + for tab in self.tabs: + if tab.name == jid_from: + tab.state = 'attention' + self.refresh_tab_win() + return + for tab in self.tabs: + if tab.name == jid_from.bare: + tab.state = 'attention' + self.refresh_tab_win() + return + self.information('%s tab not found.' % jid_from, 'Error') -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 - """ - if config.get('ignore_certificate'): - return - cert = config.get('certificate') - # update the cert representation when it uses the old one - if cert and not ':' in cert: - cert = ':'.join(i + j for i, j in zip(cert[::2], cert[1::2])).upper() - config.set_and_save('certificate', cert) - - der = ssl.PEM_cert_to_DER_cert(pem) - sha1_digest = sha1(der).hexdigest().upper() - sha1_found_cert = ':'.join(i + j for i, j in zip(sha1_digest[::2], sha1_digest[1::2])) - sha2_digest = sha512(der).hexdigest().upper() - sha2_found_cert = ':'.join(i + j for i, j in zip(sha2_digest[::2], sha2_digest[1::2])) - if cert: - if sha1_found_cert == cert: - log.debug('Cert %s OK', sha1_found_cert) - log.debug('Current hash is SHA-1, moving to SHA-2 (%s)', - sha2_found_cert) - config.set_and_save('certificate', sha2_found_cert) + 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 - elif sha2_found_cert == cert: - log.debug('Cert %s OK', sha2_found_cert) + 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)' + 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) + self.refresh_window() + + def outgoing_stanza(self, stanza): + """ + We are sending a new stanza, write it in the xml buffer if needed. + """ + if self.xml_tab: + 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() + + def incoming_stanza(self, stanza): + """ + We are receiving a new stanza, write it in the xml buffer if needed. + """ + if self.xml_tab: + 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 + """ + if config.get('ignore_certificate'): return - else: - saved_input = self.current_tab().input - log.debug('\nWARNING: CERTIFICATE CHANGED old: %s, new: %s\n', cert, sha2_found_cert) - self.information('New certificate found (sha-2 hash:' - ' %s)\nPlease validate or abort' % sha2_found_cert, - 'Warning') - def check_input(): - self.current_tab().input = saved_input - 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') - else: - self.information('You refused to validate the certificate. You are now disconnected', 'Info') - self.disconnect() - new_loop.stop() - asyncio.set_event_loop(old_loop) - 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') - -def _composing_tab_state(tab, state): - """ - Set a tab state to or from the "composing" state - according to the config and the current tab state - """ - if isinstance(tab, tabs.MucTab): - values = ('true', 'muc') - elif isinstance(tab, tabs.PrivateTab): - values = ('true', 'direct', 'private') - elif isinstance(tab, tabs.ConversationTab): - values = ('true', 'direct', 'conversation') - else: - return # should not happen - - show = config.get('show_composing_tabs') - show = show in values - - if tab.state != 'composing' and state == 'composing': - if show: - if tabs.STATE_PRIORITY[tab.state] > tabs.STATE_PRIORITY[state]: + cert = config.get('certificate') + # update the cert representation when it uses the old one + if cert and not ':' in cert: + cert = ':'.join(i + j for i, j in zip(cert[::2], cert[1::2])).upper() + config.set_and_save('certificate', cert) + + der = ssl.PEM_cert_to_DER_cert(pem) + sha1_digest = sha1(der).hexdigest().upper() + sha1_found_cert = ':'.join(i + j for i, j in zip(sha1_digest[::2], sha1_digest[1::2])) + sha2_digest = sha512(der).hexdigest().upper() + sha2_found_cert = ':'.join(i + j for i, j in zip(sha2_digest[::2], sha2_digest[1::2])) + if cert: + if sha1_found_cert == cert: + log.debug('Cert %s OK', sha1_found_cert) + log.debug('Current hash is SHA-1, moving to SHA-2 (%s)', + sha2_found_cert) + config.set_and_save('certificate', sha2_found_cert) + return + elif sha2_found_cert == cert: + log.debug('Cert %s OK', sha2_found_cert) return - tab.save_state() - tab.state = 'composing' - elif tab.state == 'composing' and state != 'composing': - tab.restore_state() - -### Ad-hoc commands - -def on_next_adhoc_step(self, iq, adhoc_session): - status = iq['command']['status'] - xform = iq.xml.find('{http://jabber.org/protocol/commands}command/{jabber:x:data}x') - if xform is not None: - form = self.xmpp.plugin['xep_0004'].buildForm(xform) - else: - form = None - - if status == 'error': - return self.information("An error occured while executing the command") - - if status == 'executing': - if not form: - self.information("Adhoc command step does not contain a data-form. Aborting the execution.", "Error") - return self.xmpp.plugin['xep_0050'].cancel_command(adhoc_session) - on_validate = self.validate_adhoc_step - on_cancel = self.cancel_adhoc_command - if status == 'completed': - on_validate = lambda form, session: self.close_tab() - on_cancel = lambda form, session: self.close_tab() - - # If a form is available, use it, and add the Notes from the - # response to it, if any - if form: - for note in iq['command']['notes']: - form.add_field(type='fixed', label=note[1]) - self.open_new_form(form, on_cancel, on_validate, - session=adhoc_session) - else: # otherwise, just display an information - # message - notes = '\n'.join([note[1] for note in iq['command']['notes']]) - self.information("Adhoc command %s: %s" % (status, notes), "Info") - -def on_adhoc_error(self, iq, adhoc_session): - self.xmpp.plugin['xep_0050'].terminate_command(adhoc_session) - error_message = self.get_error_message(iq) - self.information("An error occured while executing the command: %s" % (error_message), - 'Error') - -def cancel_adhoc_command(self, form, session): - self.xmpp.plugin['xep_0050'].cancel_command(session) - self.close_tab() - -def validate_adhoc_step(self, form, session): - session['payload'] = form - self.xmpp.plugin['xep_0050'].continue_command(session) - self.close_tab() - -def terminate_adhoc_command(self, form, session): - self.xmpp.plugin['xep_0050'].terminate_command(session) - self.close_tab() + else: + saved_input = self.current_tab().input + log.debug('\nWARNING: CERTIFICATE CHANGED old: %s, new: %s\n', cert, sha2_found_cert) + self.information('New certificate found (sha-2 hash:' + ' %s)\nPlease validate or abort' % sha2_found_cert, + 'Warning') + def check_input(): + self.current_tab().input = saved_input + 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') + else: + self.information('You refused to validate the certificate. You are now disconnected', 'Info') + self.disconnect() + new_loop.stop() + asyncio.set_event_loop(old_loop) + 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') + + def _composing_tab_state(tab, state): + """ + Set a tab state to or from the "composing" state + according to the config and the current tab state + """ + if isinstance(tab, tabs.MucTab): + values = ('true', 'muc') + elif isinstance(tab, tabs.PrivateTab): + values = ('true', 'direct', 'private') + elif isinstance(tab, tabs.ConversationTab): + values = ('true', 'direct', 'conversation') + else: + return # should not happen + + show = config.get('show_composing_tabs') + show = show in values + + if tab.state != 'composing' and state == 'composing': + if show: + if tabs.STATE_PRIORITY[tab.state] > tabs.STATE_PRIORITY[state]: + return + tab.save_state() + tab.state = 'composing' + elif tab.state == 'composing' and state != 'composing': + tab.restore_state() + + ### Ad-hoc commands + + def on_next_adhoc_step(self, iq, adhoc_session): + status = iq['command']['status'] + xform = iq.xml.find('{http://jabber.org/protocol/commands}command/{jabber:x:data}x') + if xform is not None: + form = self.xmpp.plugin['xep_0004'].buildForm(xform) + else: + form = None + + if status == 'error': + return self.information("An error occured while executing the command") + + if status == 'executing': + if not form: + self.information("Adhoc command step does not contain a data-form. Aborting the execution.", "Error") + return self.xmpp.plugin['xep_0050'].cancel_command(adhoc_session) + on_validate = self.validate_adhoc_step + on_cancel = self.cancel_adhoc_command + if status == 'completed': + on_validate = lambda form, session: self.close_tab() + on_cancel = lambda form, session: self.close_tab() + + # If a form is available, use it, and add the Notes from the + # response to it, if any + if form: + for note in iq['command']['notes']: + form.add_field(type='fixed', label=note[1]) + self.open_new_form(form, on_cancel, on_validate, + session=adhoc_session) + else: # otherwise, just display an information + # message + notes = '\n'.join([note[1] for note in iq['command']['notes']]) + self.information("Adhoc command %s: %s" % (status, notes), "Info") + + def on_adhoc_error(self, iq, adhoc_session): + self.xmpp.plugin['xep_0050'].terminate_command(adhoc_session) + error_message = self.get_error_message(iq) + self.information("An error occured while executing the command: %s" % (error_message), + 'Error') + + def cancel_adhoc_command(self, form, session): + self.xmpp.plugin['xep_0050'].cancel_command(session) + self.close_tab() + + def validate_adhoc_step(self, form, session): + session['payload'] = form + self.xmpp.plugin['xep_0050'].continue_command(session) + self.close_tab() + + def terminate_adhoc_command(self, form, session): + self.xmpp.plugin['xep_0050'].terminate_command(session) + self.close_tab() -- cgit v1.2.3