diff options
-rw-r--r-- | data/default_config.cfg | 4 | ||||
-rw-r--r-- | doc/source/configuration.rst | 7 | ||||
-rw-r--r-- | doc/source/dev/xep.rst | 2 | ||||
-rw-r--r-- | src/connection.py | 2 | ||||
-rw-r--r-- | src/core.py | 134 | ||||
-rw-r--r-- | src/tabs.py | 11 |
6 files changed, 110 insertions, 50 deletions
diff --git a/data/default_config.cfg b/data/default_config.cfg index 13b9f8bc..f7879c8c 100644 --- a/data/default_config.cfg +++ b/data/default_config.cfg @@ -343,6 +343,10 @@ display_user_color_in_join_part = false # Display user tune notifications as information messages or not display_tune_notifications = false +# Enable Message Carbons (XEP-0280) to deliver message copies from and to +# other resources with carbons enabled. +enable_carbons = false + # Receive the tune notifications or not (in order to display informations # in the roster). # If this is set to false, then the display_tune_notifications diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 0bd45377..4fa92900 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -169,6 +169,13 @@ section of this documentation. If set to true, the color of the nick will be used in MUCs information messages, instead of the default color from the theme. + enable_carbons + + **Default value:** ``false`` + + Set this to ``true`` to enable Message Carbons (XEP-280), which allows transparent message + delivery from and to other resources with carbons enabled. + enable_user_activity **Default value:** ``true`` diff --git a/doc/source/dev/xep.rst b/doc/source/dev/xep.rst index 1be26104..7cfffa20 100644 --- a/doc/source/dev/xep.rst +++ b/doc/source/dev/xep.rst @@ -50,6 +50,8 @@ Table of all XEPs implemented in poezio. +----------+-------------------------+---------------------+ |0245 |The /me Command |80% | +----------+-------------------------+---------------------+ +|0280 |Messsage Carbons |100% | ++----------+-------------------------+---------------------+ |0296 |Best Practices for |100% | | |Resource Locking | | +----------+-------------------------+---------------------+ diff --git a/src/connection.py b/src/connection.py index d931283e..91b6c1b9 100644 --- a/src/connection.py +++ b/src/connection.py @@ -101,6 +101,8 @@ class Connection(sleekxmpp.ClientXMPP): if config.get('send_time', 'true') == 'true': self.register_plugin('xep_0202') self.register_plugin('xep_0224') + self.register_plugin('xep_0280') + self.register_plugin('xep_0297') self.register_plugin('xep_0308') def start(self): diff --git a/src/core.py b/src/core.py index f9d7bda2..b4522ec4 100644 --- a/src/core.py +++ b/src/core.py @@ -237,6 +237,7 @@ class Core(object): self.xmpp.add_event_handler('disconnected', self.on_disconnected) self.xmpp.add_event_handler('no_auth', self.on_failed_auth) self.xmpp.add_event_handler("session_start", self.on_session_start) + self.xmpp.add_event_handler("session_start", self.on_session_start_features) self.xmpp.add_event_handler("groupchat_presence", self.on_groupchat_presence) self.xmpp.add_event_handler("groupchat_message", self.on_groupchat_message) self.xmpp.add_event_handler("groupchat_invite", self.on_groupchat_invite) @@ -262,6 +263,7 @@ class Core(object): self.xmpp.add_event_handler("attention", self.on_attention) self.xmpp.add_event_handler("ssl_cert", self.validate_ssl) self.all_stanzas = Callback('custom matcher', connection.MatchAll(None), self.incoming_stanza) + self.xmpp.register_handler(self.all_stanzas) if config.get('enable_user_tune', 'true') != 'false': self.xmpp.add_event_handler("user_tune_publish", self.on_tune_event) if config.get('enable_user_nick', 'true') != 'false': @@ -272,7 +274,6 @@ class Core(object): self.xmpp.add_event_handler("user_activity_publish", self.on_activity_event) if config.get('enable_user_gaming', 'true') != 'false': self.xmpp.add_event_handler("user_gaming_publish", self.on_gaming_event) - self.xmpp.register_handler(self.all_stanzas) self.initial_joins = [] @@ -2636,6 +2637,49 @@ class Core(object): ####################### XMPP Event Handlers ################################## + 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') + rostertab.check_blocking(features) + if (config.get('enable_carbons', 'true').lower() != 'false' 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) + features = self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.domain, callback=callback, block=False) + + def on_carbon_received(self, message): + recv = message['carbon_received'] + if recv['from'].bare not in roster or roster[recv['from'].bare].subscription == 'none': + try: + if self.xmpp.plugin['xep_0030'].has_identity(jid=recv['from'].server, category="conference"): + return + except: + pass + else: + return + recv['to'] = self.xmpp.boundjid.full + self.on_normal_message(recv) + + def on_carbon_sent(self, message): + sent = message['carbon_sent'] + if sent['to'].bare not in roster or roster[sent['to'].bare].subscription == 'none': + try: + if self.xmpp.plugin['xep_0030'].has_identity(jid=sent['to'].server, category="conference"): + return + except: + pass + else: + return + sent['from'] = self.xmpp.boundjid.full + self.on_normal_message(sent) + ### Invites ### def on_groupchat_invite(self, message): @@ -2686,72 +2730,80 @@ class Core(object): """ When receiving "normal" messages (from someone in our roster) """ - jid = message['from'] - - - # check for a name - if jid.bare in roster: - remote_nick = roster[jid.bare].name - else: - remote_nick = '' - # check for a received nick - if not remote_nick and config.get('enable_user_nick', 'true') != 'false': - if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None: - remote_nick = message['nick']['nick'] - # bind the nick to the conversation - conversation = self.get_conversation_by_jid(jid, create=False) - if conversation and remote_nick and jid.bare not in roster: - conversation.nick = remote_nick - - body = xhtml.get_body_from_message_stanza(message) if message['type'] == 'error': return self.information(self.get_error_message(message, deprecated=True), 'Error') elif message['type'] == 'headline' and message['body']: return self.information('%s says: %s' % (message['from'], message['body']), 'Headline') + + body = xhtml.get_body_from_message_stanza(message) if not body: return - conversation = self.get_conversation_by_jid(jid, create=True) + + 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', 'true') != 'false': + if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None: + remote_nick = message['nick']['nick'] + # 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 + # 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): + conversation.lock(conv_jid.resource) if not remote_nick and conversation.nick: remote_nick = conversation.nick - elif jid.bare not in roster and remote_nick: - conversation.nick = remote_nick elif not remote_nick: remote_nick = jid.user + conversation.nick = remote_nick self.events.trigger('conversation_msg', message, conversation) body = xhtml.get_body_from_message_stanza(message) - if not body: - return - if isinstance(conversation, tabs.DynamicConversationTab): - conversation.lock(jid.resource) - conversation.nick = remote_nick 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', 'true', jid.bare).lower() != 'false'): - try: - conversation.modify_message(body, replaced_id, message['id'], jid=message['from'], - nickname=remote_nick) - replaced = True - except CorrectionError: - pass - if not replaced : + + def try_modify(): + replaced_id = message['replace']['id'] + if replaced_id and (config.get_by_tabname('group_corrections', + 'true', conv_jid.bare).lower() != 'false'): + try: + conversation.modify_message(body, replaced_id, message['id'], jid=jid, + nickname=remote_nick) + return True + except CorrectionError: + pass + return False + + if not try_modify(): conversation.add_message(body, date, nickname=remote_nick, - nick_color=get_theme().COLOR_REMOTE_USER, + nick_color=color, history=delayed, identifier=message['id'], - jid=message['from'], + 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 'private' in config.get('beep_on', 'highlight private').split(): - if config.get_by_tabname('disable_beep', 'false', jid.bare, False).lower() != 'true': + if config.get_by_tabname('disable_beep', 'false', conv_jid.bare, False).lower() != 'true': curses.beep() if self.current_tab() is not conversation: conversation.state = 'private' diff --git a/src/tabs.py b/src/tabs.py index 3d52fcbc..c0a34f29 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -2220,19 +2220,12 @@ class RosterInfoTab(Tab): desc=_('Informs you of the last activity of a JID.'), shortdesc=_('Get the activity of someone.'), completion=self.core.completion_last_activity) - self.core.xmpp.add_event_handler('session_start', - lambda event: self.core.xmpp.plugin['xep_0030'].get_info( - jid=self.core.xmpp.boundjid.domain, - block=False, timeout=5, callback=self.check_blocking) - ) self.resize() self.update_commands() self.update_keys() - def check_blocking(self, iq): - if iq['type'] == 'error': - return - if 'urn:xmpp:blocking' in iq['disco_info'].get_features(): + def check_blocking(self, features): + if 'urn:xmpp:blocking' in features: self.register_command('block', self.command_block, usage=_('[jid]'), shortdesc=_('Prevent a JID from talking to you.'), |