summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/default_config.cfg4
-rw-r--r--doc/source/configuration.rst7
-rw-r--r--doc/source/dev/xep.rst2
-rw-r--r--src/connection.py2
-rw-r--r--src/core.py134
-rw-r--r--src/tabs.py11
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.'),