Commit 37f7a803 authored by Philipp Hörist's avatar Philipp Hörist

Move message handler into own module

parent d4fd621d
......@@ -241,6 +241,9 @@ class ChatControl(ChatControlBase):
app.ged.register_event_handler(
'decrypted-message-received',
ged.GUI1, self._nec_decrypted_message_received)
app.ged.register_event_handler(
'receipt-received',
ged.GUI1, self._receipt_received)
# PluginSystem: adding GUI extension point for this ChatControl
# instance object
......@@ -984,6 +987,14 @@ class ChatControl(ChatControlBase):
else:
self.old_msg_kind = kind
def _receipt_received(self, event):
if event.conn.name != self.account:
return
if event.jid != self.contact.jid:
return
self.conv_textview.show_xep0184_ack(event.receipt_id)
def get_tab_label(self):
unread = ''
if self.resource:
......@@ -1153,6 +1164,9 @@ class ChatControl(ChatControlBase):
app.ged.remove_event_handler(
'decrypted-message-received',
ged.GUI1, self._nec_decrypted_message_received)
app.ged.remove_event_handler(
'receipt-received',
ged.GUI1, self._receipt_received)
self.unsubscribe_events()
......
......@@ -292,22 +292,10 @@ class ConnectionHandlersBase:
# We decrypt GPG messages one after the other. Keep queue in mem
self.gpg_messages_to_decrypt = []
# XEPs that are based on Message
self._message_namespaces = set([nbxmpp.NS_HTTP_AUTH,
nbxmpp.NS_PUBSUB_EVENT,
nbxmpp.NS_ROSTERX,
nbxmpp.NS_MAM_1,
nbxmpp.NS_MAM_2,
nbxmpp.NS_CONFERENCE])
app.ged.register_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received)
app.ged.register_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
app.ged.register_event_handler('message-received', ged.CORE,
self._nec_message_received)
app.ged.register_event_handler('decrypted-message-received', ged.CORE,
self._nec_decrypted_message_received)
app.ged.register_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received)
......@@ -316,10 +304,6 @@ class ConnectionHandlersBase:
self._nec_iq_error_received)
app.ged.remove_event_handler('presence-received', ged.CORE,
self._nec_presence_received)
app.ged.remove_event_handler('message-received', ged.CORE,
self._nec_message_received)
app.ged.remove_event_handler('decrypted-message-received', ged.CORE,
self._nec_decrypted_message_received)
app.ged.remove_event_handler('gc-message-received', ged.CORE,
self._nec_gc_message_received)
......@@ -448,96 +432,10 @@ class ConnectionHandlersBase:
message=obj.status,
show=show)
def _nec_message_received(self, obj):
if obj.conn.name != self.name:
return
app.plugin_manager.extension_point(
'decrypt', self, obj, self._on_message_received)
if not obj.encrypted:
eme = parse_eme(obj.stanza)
if eme is not None:
obj.msgtxt = eme
self._on_message_received(obj)
def _on_message_received(self, obj):
app.nec.push_incoming_event(
DecryptedMessageReceivedEvent(
None, conn=self, msg_obj=obj, stanza_id=obj.unique_id))
def _nec_decrypted_message_received(self, obj):
if obj.conn.name != self.name:
return
# Receipt requested
# TODO: We shouldn't answer if we're invisible!
contact = app.contacts.get_contact(self.name, obj.jid)
nick = obj.resource
gc_contact = app.contacts.get_gc_contact(self.name, obj.jid, nick)
if obj.sent:
jid_to = obj.stanza.getFrom()
else:
jid_to = obj.stanza.getTo()
reply = False
if not jid_to:
reply = True
else:
fjid_to = str(jid_to)
if self.name != 'Local':
# Dont check precis for zeroconf
fjid_to = helpers.parse_jid(str(jid_to))
jid_to = app.get_jid_without_resource(fjid_to)
if jid_to == app.get_jid_from_account(self.name):
reply = True
if obj.jid != app.get_jid_from_account(self.name):
if obj.receipt_request_tag and app.config.get_per('accounts',
self.name, 'answer_receipts') and ((contact and contact.sub \
not in ('to', 'none')) or gc_contact) and obj.mtype != 'error' and \
reply:
receipt = nbxmpp.Message(to=obj.fjid, typ='chat')
receipt.setTag('received', namespace='urn:xmpp:receipts',
attrs={'id': obj.id_})
if obj.thread_id:
receipt.setThread(obj.thread_id)
self.connection.send(receipt)
# We got our message's receipt
if obj.receipt_received_tag and app.config.get_per('accounts',
self.name, 'request_receipt'):
ctrl = None
if obj.session is not None:
ctrl = obj.session.control
if not ctrl:
# Received <message> doesn't have the <thread> element
# or control is not bound to session?
# --> search for it
ctrl = app.interface.msg_win_mgr.search_control(obj.jid,
obj.conn.name, obj.resource)
if ctrl:
id_ = obj.receipt_received_tag.getAttr('id')
if not id_:
# old XEP implementation
id_ = obj.id_
ctrl.conv_textview.show_xep0184_ack(id_)
if obj.mtype == 'error':
if not obj.msgtxt:
obj.msgtxt = _('message')
self.dispatch_error_message(obj.stanza, obj.msgtxt,
obj.session, obj.fjid, obj.timestamp)
return True
elif obj.mtype == 'groupchat':
app.nec.push_incoming_event(GcMessageReceivedEvent(None,
conn=self, msg_obj=obj, stanza_id=obj.unique_id))
return True
def _check_for_mam_compliance(self, room_jid, stanza_id):
namespace = muc_caps_cache.get_mam_namespace(room_jid)
if stanza_id is None and namespace == nbxmpp.NS_MAM_2:
log.warning('%s announces mam:2 without stanza-id')
log.warning('%s announces mam:2 without stanza-id', room_jid)
def _nec_gc_message_received(self, obj):
if obj.conn.name != self.name:
......@@ -743,7 +641,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
self.continue_connect_info = None
app.nec.register_incoming_event(StreamConflictReceivedEvent)
app.nec.register_incoming_event(MessageReceivedEvent)
app.nec.register_incoming_event(NotificationEvent)
app.ged.register_event_handler('roster-set-received',
......@@ -939,29 +836,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
app.config.set_per('accounts', self.name, 'roster_version',
obj.version)
def _messageCB(self, con, stanza):
"""
Called when we receive a message
"""
# Check if a child of the message contains any
# of these namespaces, so we dont execute the
# message handler for them.
# They have defined their own message handlers
# but nbxmpp executes less common handlers last
if self._message_namespaces & set(stanza.getProperties()):
return
muc_user = stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
if muc_user is not None:
if muc_user.getChildren():
# Not a PM, handled by MUC module
return
log.debug('MessageCB')
app.nec.push_incoming_event(NetworkEvent('raw-message-received',
conn=self, stanza=stanza, account=self.name))
def _dispatch_gc_msg_with_captcha(self, stanza, msg_obj):
msg_obj.stanza = stanza
app.nec.push_incoming_event(GcMessageReceivedEvent(None,
......@@ -1282,7 +1156,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
def _register_handlers(self, con, con_type):
# try to find another way to register handlers in each class
# that defines handlers
con.RegisterHandler('message', self._messageCB)
con.RegisterHandler('presence', self._presenceCB)
con.RegisterHandler('iq', self._rosterSetCB, 'set', nbxmpp.NS_ROSTER)
con.RegisterHandler('iq', self._siSetCB, 'set', nbxmpp.NS_SI)
......
......@@ -632,242 +632,6 @@ class BeforeChangeShowEvent(nec.NetworkIncomingEvent):
name = 'before-change-show'
base_network_events = []
class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'message-received'
base_network_events = ['raw-message-received']
def init(self):
self.additional_data = {}
def generate(self):
self.conn = self.base_event.conn
self.stanza = self.base_event.stanza
self.encrypted = False
self.self_message = None
self.muc_pm = None
account = self.conn.name
if self.stanza.getFrom() == self.conn.get_own_jid(warn=True):
# Drop messages sent from our own full jid
# It can happen that when we sent message to our own bare jid
# that the server routes that message back to us
log.info('Received message from self: %s, message is dropped',
self.stanza.getFrom())
return
from gajim.common.modules.carbons import parse_carbon
self.stanza, self.sent, self.forwarded = parse_carbon(self.conn,
self.stanza)
self.get_id()
try:
self.get_jid_resource()
except helpers.InvalidFormat:
log.warning('Invalid JID: %s, ignoring it',
self.stanza.getFrom())
return
# Check for duplicates
self.unique_id = self.get_unique_id()
# Check groupchat messages for duplicates,
# We do this because of MUC History messages
type_ = self.stanza.getType()
if type_ == 'groupchat' or self.self_message or self.muc_pm:
if type_ == 'groupchat':
archive_jid = self.stanza.getFrom().getStripped()
else:
archive_jid = self.conn.get_own_jid().getStripped()
if app.logger.find_stanza_id(account,
archive_jid,
self.unique_id,
groupchat=type_ == 'groupchat'):
return
self.thread_id = self.stanza.getThread()
self.mtype = self.stanza.getType()
if not self.mtype or self.mtype not in ('chat', 'groupchat', 'error'):
self.mtype = 'normal'
self.msgtxt = self.stanza.getBody()
self.get_gc_control()
if self.gc_control and self.jid == self.fjid:
if self.mtype == 'error':
self.msgtxt = _('error while sending %(message)s ( %(error)s )'\
) % {'message': self.msgtxt,
'error': self.stanza.getErrorMsg()}
if self.stanza.getTag('html'):
self.stanza.delChild('html')
# message from a gc without a resource
self.mtype = 'groupchat'
self.session = None
if self.mtype != 'groupchat':
if app.interface.is_pm_contact(self.fjid, account) and \
self.mtype == 'error':
self.session = self.conn.find_session(self.fjid, self.thread_id)
if not self.session:
self.session = self.conn.get_latest_session(self.fjid)
if not self.session:
self.session = self.conn.make_new_session(self.fjid,
self.thread_id,
type_='pm')
else:
self.session = self.conn.get_or_create_session(self.fjid,
self.thread_id)
if self.thread_id and not self.session.received_thread_id:
self.session.received_thread_id = True
self.session.last_receive = time_time()
self._generate_timestamp(self.stanza.timestamp)
return True
def get_unique_id(self):
'''
Messages to self:
Messages to self are stored multiple times in MAM so we cant use
stanza-id to deduplicate. We use origin-id instead. Its not perfect
but there is no better way for now.
We drop "received"-Carbons of Message to self, so we dont have to
parse origin-id in that case.
MUC PMs:
MUC PMs are also stored multiple times, we also depend on origin-id
for now.
'''
if self.stanza.getType() == 'groupchat':
# TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced
return self.get_stanza_id(self.stanza)
elif self.stanza.getType() != 'chat':
return
# Messages we receive live
if self.conn.get_module('MAM').archiving_namespace != nbxmpp.NS_MAM_2:
# Only mam:2 ensures valid stanza-id
return
# Sent Carbon
sent_carbon = self.stanza.getTag('sent',
namespace=nbxmpp.NS_CARBONS,
protocol=True)
if sent_carbon is not None:
message = self.get_forwarded_message(sent_carbon)
if self._is_self_message(message) or self._is_muc_pm(message):
return message.getOriginID()
return self.get_stanza_id(message)
# Received Carbon
received_carbon = self.stanza.getTag('received',
namespace=nbxmpp.NS_CARBONS,
protocol=True)
if received_carbon is not None:
message = self.get_forwarded_message(received_carbon)
if self._is_muc_pm(message):
return message.getOriginID()
return self.get_stanza_id(message)
# Normal Message
if self._is_self_message(self.stanza) or self._is_muc_pm(self.stanza):
return self.stanza.getOriginID()
return self.get_stanza_id(self.stanza)
class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
name = 'message-received'
base_network_events = []
def get_jid_resource(self):
self.fjid = str(self.stanza.getFrom())
if self.fjid is None:
for key in self.conn.connection.zeroconf.contacts:
if self.ip == self.conn.connection.zeroconf.contacts[key][
Constant.ADDRESS]:
self.fjid = key
break
self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
def generate(self):
self.base_event = nec.NetworkIncomingEvent(None, conn=self.conn,
stanza=self.stanza)
return super(ZeroconfMessageReceivedEvent, self).generate()
class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'decrypted-message-received'
base_network_events = []
def generate(self):
self.stanza = self.msg_obj.stanza
self.additional_data = self.msg_obj.additional_data
self.id_ = self.msg_obj.id_
self.unique_id = self.msg_obj.unique_id
self.jid = self.msg_obj.jid
self.fjid = self.msg_obj.fjid
self.resource = self.msg_obj.resource
self.mtype = self.msg_obj.mtype
self.thread_id = self.msg_obj.thread_id
self.msgtxt = self.msg_obj.msgtxt
self.gc_control = self.msg_obj.gc_control
self.session = self.msg_obj.session
self.timestamp = self.msg_obj.timestamp
self.encrypted = self.msg_obj.encrypted
self.forwarded = self.msg_obj.forwarded
self.sent = self.msg_obj.sent
self.conn = self.msg_obj.conn
self.muc_pm = self.msg_obj.muc_pm
self.popup = False
self.msg_log_id = None # id in log database
self.attention = False # XEP-0224
self.correct_id = None # XEP-0308
self.msghash = None
self.receipt_request_tag = self.stanza.getTag('request',
namespace=nbxmpp.NS_RECEIPTS)
self.receipt_received_tag = self.stanza.getTag('received',
namespace=nbxmpp.NS_RECEIPTS)
self.subject = self.stanza.getSubject()
self.displaymarking = None
self.seclabel = self.stanza.getTag('securitylabel',
namespace=nbxmpp.NS_SECLABEL)
if self.seclabel:
self.displaymarking = self.seclabel.getTag('displaymarking')
if self.stanza.getTag('attention', namespace=nbxmpp.NS_ATTENTION):
delayed = self.stanza.getTag('x', namespace=nbxmpp.NS_DELAY) is not\
None
if not delayed:
self.attention = True
self.form_node = self.stanza.getTag('x', namespace=nbxmpp.NS_DATA)
if app.config.get('ignore_incoming_xhtml'):
self.xhtml = None
else:
self.xhtml = self.stanza.getXHTML()
# XEP-0172 User Nickname
self.user_nick = self.stanza.getTagData('nick') or ''
self.get_chatstate()
self.get_oob_data(self.stanza)
from gajim.common.modules.misc import parse_correction
self.correct_id = parse_correction(self.stanza)
return True
class ChatstateReceivedEvent(nec.NetworkIncomingEvent):
name = 'chatstate-received'
base_network_events = []
......
......@@ -18,7 +18,7 @@ from pathlib import Path
log = logging.getLogger('gajim.c.m')
ZEROCONF_MODULES = ['adhoc_commands']
ZEROCONF_MODULES = ['adhoc_commands', 'receipts']
imported_modules = []
_modules = {}
......
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0085: Chat State Notifications
import logging
import nbxmpp
from gajim.common.modules.misc import parse_delay
log = logging.getLogger('gajim.c.m.chatstates')
def parse_chatstate(stanza):
if parse_delay(stanza) is not None:
return
children = stanza.getChildren()
for child in children:
if child.getNamespace() == nbxmpp.NS_CHATSTATES:
return child.getName()
......@@ -27,6 +27,8 @@ from gajim.common.modules.misc import parse_delay
from gajim.common.modules.misc import parse_oob
from gajim.common.modules.misc import parse_correction
from gajim.common.modules.misc import parse_eme
from gajim.common.modules.util import is_self_message
from gajim.common.modules.util import is_muc_pm
log = logging.getLogger('gajim.c.m.archiving')
......@@ -61,28 +63,6 @@ class MAM:
if archive_jid.bareMatch(expected_archive):
return archive_jid
@staticmethod
def _is_self_message(message, groupchat):
if groupchat:
return False
frm = message.getFrom()
to = message.getTo()
return frm.bareMatch(to)
@staticmethod
def _is_muc_pm(message, groupchat, with_):
if groupchat:
return False
muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER)
if muc_user is not None:
return muc_user.getChildren() == []
else:
# muc#user namespace was added in MUC 1.28 so we need a fallback
# Check if we know the jid, otherwise disco it
if app.logger.jid_is_room_jid(with_.getStripped()):
return True
return False
def _get_unique_id(self, result, message, groupchat, self_message, muc_pm):
stanza_id = result.getAttr('id')
if groupchat:
......@@ -150,8 +130,8 @@ class MAM:
else:
event_attrs.update(self._parse_chat_attrs(message))
self_message = self._is_self_message(message, groupchat)
muc_pm = self._is_muc_pm(message, groupchat, event_attrs['with_'])
self_message = is_self_message(message, groupchat)
muc_pm = is_muc_pm(message, event_attrs['with_'], groupchat)
stanza_id, origin_id = self._get_unique_id(
result, message, groupchat, self_message, muc_pm)
......
This diff is collapsed.
......@@ -18,6 +18,7 @@ import logging
import nbxmpp
from gajim.common import app
from gajim.common.modules.date_and_time import parse_datetime
log = logging.getLogger('gajim.c.m.misc')
......@@ -111,3 +112,38 @@ def parse_correction(stanza):
if id_ is not None:
return id_
log.warning('No id attr found: %s' % stanza)
# XEP-0224: Attention
def parse_attention(stanza):
attention = stanza.getTag('attention', namespace=nbxmpp.NS_ATTENTION)
if attention is None:
return False
delayed = stanza.getTag('x', namespace=nbxmpp.NS_DELAY2)
if delayed is not None:
return False
return True
# XEP-0258: Security Labels in XMPP
def parse_securitylabel(stanza):
seclabel = stanza.getTag('securitylabel', namespace=nbxmpp.NS_SECLABEL)
if seclabel is None:
return None
return seclabel.getTag('displaymarking')
# XEP-0004: Data Forms
def parse_form(stanza):
return stanza.getTag('x', namespace=nbxmpp.NS_DATA)
# XEP-0071: XHTML-IM
def parse_xhtml(stanza):
if app.config.get('ignore_incoming_xhtml'):
return None
return stanza.getXHTML()
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0184: Message Delivery Receipts
import logging
import nbxmpp
from gajim.common import app
from gajim.common.nec import NetworkIncomingEvent
log = logging.getLogger('gajim.c.m.receipts')
class Receipts:
def __init__(self, con):
self._con = con
self._account = con.name
self.handlers = []
def delegate(self, event):
request = event.stanza.getTag('request',
namespace=nbxmpp.NS_RECEIPTS)
if request is not None:
self._answer_request(event)
return
received = event.stanza.getTag('received',
namespace=nbxmpp.NS_RECEIPTS)
if received is not None:
self._receipt_received(event, received)
raise nbxmpp.NodeProcessed
def _answer_request(self, event):
if not app.config.get_per('accounts', self._account,
'answer_receipts'):
return
if event.mtype not in ('chat', 'groupchat'):
return
if event.sent:
# Never answer messages that we sent from another device
return
from_ = event.stanza.getFrom()
if self._con.get_own_jid().bareMatch(from_):
# Dont answer receipts from our other resources
return
receipt_id = event.stanza.getID()
contact = self._get_contact(event)
if contact is None:
return
receipt = self._build_answer_receipt(from_, receipt_id)
log.info('Answer %s', receipt_id)
self._con.connection.send(receipt)
def _get_contact(self, event):
if event.mtype == 'chat':
contact = app.contacts.get_contact(self._account, event.jid)
if contact and contact.sub not in ('to', 'none'):
return contact
else:
return app.contacts.get_gc_contact(self._account,
event.jid,
event.resource)
def _build_answer_receipt(self, to, receipt_id):
receipt = nbxmpp.Message(to=to, typ='chat')
receipt.setTag('received',
namespace='urn:xmpp:receipts',
attrs={'id': receipt_id})
return receipt
def _receipt_received(self, event, received):
receipt_id = received.getAttr('id')
if receipt_id is None:
log.warning('Receipt without ID: %s', event.stanza)
return
log.info('Received %s', receipt_id)
app.nec.push_incoming_event(
NetworkIncomingEvent('receipt-received',
conn=self._con,
receipt_id=receipt_id,
jid=event.jid))
def get_instance(*args, **kwargs):
return Receipts(*args, **kwargs), 'Receipts'
......@@ -78,5 +78,12 @@ class UserNickname(AbstractPEPModule):
'accounts', self._account, 'name')
def parse_nickname(stanza):