Move message handler into own module

This commit is contained in:
Philipp Hörist 2018-07-19 21:26:45 +02:00
parent d4fd621d11
commit 37f7a80396
13 changed files with 665 additions and 397 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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 = []

View File

@ -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 = {}

View File

@ -0,0 +1,33 @@
# 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()

View File

@ -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)

View File

@ -0,0 +1,297 @@
# 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/>.
# Message handler
import time
import logging
import nbxmpp
from gajim.common import app
from gajim.common import helpers
from gajim.common.nec import NetworkIncomingEvent, NetworkEvent
from gajim.common.modules.user_nickname import parse_nickname
from gajim.common.modules.chatstates import parse_chatstate
from gajim.common.modules.carbons import parse_carbon
from gajim.common.modules.misc import parse_delay
from gajim.common.modules.misc import parse_eme
from gajim.common.modules.misc import parse_correction
from gajim.common.modules.misc import parse_attention
from gajim.common.modules.misc import parse_securitylabel
from gajim.common.modules.misc import parse_form
from gajim.common.modules.misc import parse_oob
from gajim.common.modules.misc import parse_xhtml
from gajim.common.modules.util import is_self_message
from gajim.common.modules.util import is_muc_pm
from gajim.common.connection_handlers_events import GcMessageReceivedEvent
log = logging.getLogger('gajim.c.m.message')
class Message:
def __init__(self, con):
self._con = con
self._account = con.name
self.handlers = [('message', self._message_received)]
# XEPs for which this message module should not be executed
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,
nbxmpp.NS_IBB])
def _message_received(self, con, stanza):
# Check if a child of the message contains any
# namespaces that we handle in other modules.
# 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.info('Received from %s', stanza.getFrom())
app.nec.push_incoming_event(NetworkEvent(
'raw-message-received',
conn=self._con,
stanza=stanza,
account=self._account))
if stanza.getFrom() == self._con.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',
stanza.getFrom())
return
stanza, sent, forwarded = parse_carbon(self._con, stanza)
from_ = stanza.getFrom()
type_ = stanza.getType()
if type_ is None:
type_ = 'normal'
self_message = is_self_message(stanza, type_ == 'groupchat')
muc_pm = is_muc_pm(stanza, from_, type_ == 'groupchat')
id_ = stanza.getID()
fjid = None
if from_ is not None:
try:
fjid = helpers.parse_jid(str(from_))
except helpers.InvalidFormat:
log.warning('Invalid JID: %s, ignoring it',
stanza.getFrom())
return
jid, resource = app.get_room_and_nick_from_fjid(fjid)
# Check for duplicates
unique_id = self._get_unique_id(stanza, forwarded, sent,
self_message, muc_pm)
# Check groupchat messages for duplicates,
# We do this because of MUC History messages
if type_ == 'groupchat' or self_message or muc_pm:
if type_ == 'groupchat':
archive_jid = stanza.getFrom().getStripped()
else:
archive_jid = self._con.get_own_jid().getStripped()
if app.logger.find_stanza_id(self._account,
archive_jid,
unique_id,
groupchat=type_ == 'groupchat'):
return
thread_id = stanza.getThread()
msgtxt = stanza.getBody()
# TODO: remove all control UI stuff
gc_control = app.interface.msg_win_mgr.get_gc_control(
jid, self._account)
if not gc_control:
minimized = app.interface.minimized_controls[self._account]
gc_control = minimized.get(jid)
if gc_control and jid == fjid:
if type_ == 'error':
msgtxt = _('error while sending %(message)s ( %(error)s )') % {
'message': msgtxt, 'error': stanza.getErrorMsg()}
# TODO: why is this here?
if stanza.getTag('html'):
stanza.delChild('html')
session = None
if type_ != 'groupchat':
if muc_pm and type_ == 'error':
session = self._con.find_session(fjid, thread_id)
if not session:
session = self._con.get_latest_session(fjid)
if not session:
session = self._con.make_new_session(
fjid, thread_id, type_='pm')
else:
session = self._con.get_or_create_session(fjid, thread_id)
if thread_id and not session.received_thread_id:
session.received_thread_id = True
session.last_receive = time.time()
timestamp = parse_delay(stanza)
if timestamp is None:
timestamp = time.time()
event_attr = {
'conn': self._con,
'stanza': stanza,
'account': self._account,
'id_': id_,
'encrypted': False,
'additional_data': {},
'forwarded': forwarded,
'sent': sent,
'timestamp': timestamp,
'fjid': fjid,
'jid': jid,
'resource': resource,
'unique_id': unique_id,
'mtype': type_,
'msgtxt': msgtxt,
'thread_id': thread_id,
'session': session,
'self_message': self_message,
'muc_pm': muc_pm,
'gc_control': gc_control
}
event = MessageReceivedEvent(None, **event_attr)
app.nec.push_incoming_event(event)
app.plugin_manager.extension_point(
'decrypt', self._con, event, self._on_message_decrypted)
if not event.encrypted:
eme = parse_eme(event.stanza)
if eme is not None:
event.msgtxt = eme
self._on_message_decrypted(event)
def _on_message_decrypted(self, event):
try:
self._con.get_module('Receipts').delegate(event)
except nbxmpp.NodeProcessed:
return
event_attr = {
'popup': False,
'msg_log_id': None,
'subject': event.stanza.getSubject(),
'displaymarking': parse_securitylabel(event.stanza),
'attention': parse_attention(event.stanza),
'correct_id': parse_correction(event.stanza),
'user_nick': parse_nickname(event.stanza),
'form_node': parse_form(event.stanza),
'xhtml': parse_xhtml(event.stanza),
'chatstate': parse_chatstate(event.stanza),
'stanza_id': event.unique_id
}
parse_oob(event.stanza, event.additional_data)
for name, value in event_attr.items():
setattr(event, name, value)
if event.mtype == 'error':
if not event.msgtxt:
event.msgtxt = _('message')
self._con.dispatch_error_message(
event.stanza, event.msgtxt,
event.session, event.fjid, event.timestamp)
return
if event.mtype == 'groupchat':
app.nec.push_incoming_event(GcMessageReceivedEvent(
None,
conn=self._con,
msg_obj=event,
stanza_id=event.unique_id))
return
app.nec.push_incoming_event(
DecryptedMessageReceivedEvent(
None, **vars(event)))
def _get_unique_id(self, stanza, forwarded, sent, self_message, muc_pm):
if stanza.getType() == 'groupchat':
# TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced
return self._get_stanza_id(stanza)
if stanza.getType() != 'chat':
return
# Messages we receive live
if self._con.get_module('MAM').archiving_namespace != nbxmpp.NS_MAM_2:
# Only mam:2 ensures valid stanza-id
return
if forwarded:
if sent:
if self_message or muc_pm:
return stanza.getOriginID()
return self._get_stanza_id(stanza)
else:
if muc_pm:
return stanza.getOriginID()
return self._get_stanza_id(stanza)
# Normal Message
if self_message or muc_pm:
return stanza.getOriginID()
return self._get_stanza_id(stanza)
def _get_stanza_id(self, stanza):
stanza_id, by = stanza.getStanzaIDAttrs()
if by is None:
# We can not verify who set this stanza-id, ignore it.
return
if stanza.getType() == 'groupchat':
if stanza.getFrom().bareMatch(by):
# by attribute must match the server
return stanza_id
elif self._con.get_own_jid().bareMatch(by):
# by attribute must match the server
return stanza_id
return
class MessageReceivedEvent(NetworkIncomingEvent):
name = 'message-received'
class DecryptedMessageReceivedEvent(NetworkIncomingEvent):
name = 'decrypted-message-received'
def get_instance(*args, **kwargs):
return Message(*args, **kwargs), 'Message'

View File

@ -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()

View File

@ -0,0 +1,105 @@
# 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'

View File

@ -78,5 +78,12 @@ class UserNickname(AbstractPEPModule):
'accounts', self._account, 'name')
def parse_nickname(stanza):
nick = stanza.getTag('nick', namespace=nbxmpp.NS_NICK)
if nick is None:
return ''
return nick.getData()
def get_instance(*args, **kwargs):
return UserNickname(*args, **kwargs), 'UserNickname'

View File

@ -0,0 +1,41 @@
# 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/>.
# Util module
import nbxmpp
from gajim.common import app
def is_self_message(message, groupchat=False):
if groupchat:
return False
frm = message.getFrom()
to = message.getTo()
return frm.bareMatch(to)
def is_muc_pm(message, jid, groupchat=False):
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
if app.logger.jid_is_room_jid(jid.getStripped()):
return True
return False

View File

@ -23,21 +23,40 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
import time
import nbxmpp
from gajim.common import app
from gajim.common.protocol.bytestream import ConnectionSocks5BytestreamZeroconf
from gajim.common.connection_handlers_events import ZeroconfMessageReceivedEvent
from gajim.common.zeroconf.zeroconf import Constant
from gajim.common import connection_handlers
from gajim.common.nec import NetworkIncomingEvent, NetworkEvent
from gajim.common.modules.user_nickname import parse_nickname
from gajim.common.modules.chatstates import parse_chatstate
from gajim.common.modules.misc import parse_eme
from gajim.common.modules.misc import parse_correction
from gajim.common.modules.misc import parse_attention
from gajim.common.modules.misc import parse_oob
from gajim.common.modules.misc import parse_xhtml
import logging
log = logging.getLogger('gajim.c.z.connection_handlers_zeroconf')
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
'invisible']
# kind of events we can wait for an answer
AGENT_REMOVED = 'agent_removed'
from gajim.common import connection_handlers
class ZeroconfMessageReceivedEvent(NetworkIncomingEvent):
name = 'message-received'
class DecryptedMessageReceivedEvent(NetworkIncomingEvent):
name = 'decrypted-message-received'
class ConnectionVcard:
def add_sha(self, p, *args):
@ -57,14 +76,113 @@ connection_handlers.ConnectionJingle):
connection_handlers.ConnectionJingle.__init__(self)
connection_handlers.ConnectionHandlersBase.__init__(self)
def _messageCB(self, ip, con, msg):
def _messageCB(self, ip, con, stanza):
"""
Called when we receive a message
"""
log.debug('Zeroconf MessageCB')
app.nec.push_incoming_event(ZeroconfMessageReceivedEvent(None,
conn=self, stanza=msg, ip=ip))
return
app.nec.push_incoming_event(NetworkEvent(
'raw-message-received',
conn=self,
stanza=stanza,
account=self.name))
type_ = stanza.getType()
if type_ is None:
type_ = 'normal'
id_ = stanza.getID()
fjid = str(stanza.getFrom())
if fjid is None:
for key in self.connection.zeroconf.contacts:
if ip == self.connection.zeroconf.contacts[key][
Constant.ADDRESS]:
fjid = key
break
jid, resource = app.get_room_and_nick_from_fjid(fjid)
thread_id = stanza.getThread()
msgtxt = stanza.getBody()
session = self.get_or_create_session(fjid, thread_id)
if thread_id and not session.received_thread_id:
session.received_thread_id = True
session.last_receive = time.time()
event_attr = {
'conn': self,
'stanza': stanza,
'account': self.name,
'id_': id_,
'encrypted': False,
'additional_data': {},
'forwarded': False,
'sent': False,
'timestamp': time.time(),
'fjid': fjid,
'jid': jid,
'resource': resource,
'unique_id': id_,
'mtype': type_,
'msgtxt': msgtxt,
'thread_id': thread_id,
'session': session,
'self_message': False,
'muc_pm': False,
'gc_control': None}
event = ZeroconfMessageReceivedEvent(None, **event_attr)
app.nec.push_incoming_event(event)
app.plugin_manager.extension_point(
'decrypt', self, event, self._on_message_decrypted)
if not event.encrypted:
eme = parse_eme(event.stanza)
if eme is not None:
event.msgtxt = eme
self._on_message_decrypted(event)
def _on_message_decrypted(self, event):
try:
self.get_module('Receipts').delegate(event)
except nbxmpp.NodeProcessed:
return
event_attr = {
'popup': False,
'msg_log_id': None,
'subject': None,
'displaymarking': None,
'form_node': None,
'attention': parse_attention(event.stanza),
'correct_id': parse_correction(event.stanza),
'user_nick': parse_nickname(event.stanza),
'xhtml': parse_xhtml(event.stanza),
'chatstate': parse_chatstate(event.stanza),
'stanza_id': event.unique_id
}
parse_oob(event.stanza, event.additional_data)
for name, value in event_attr.items():
setattr(event, name, value)
if event.mtype == 'error':
if not event.msgtxt:
event.msgtxt = _('message')
self.dispatch_error_message(
event.stanza, event.msgtxt,
event.session, event.fjid, event.timestamp)
return
app.nec.push_incoming_event(
DecryptedMessageReceivedEvent(None, **vars(event)))
def store_metacontacts(self, tags):
"""

View File

@ -12,7 +12,7 @@ from gajim.common import app
from gajim.common import nec
from gajim.common import ged
from gajim.common.nec import NetworkEvent
from gajim.common.connection_handlers_events import MessageReceivedEvent
from gajim.common.modules.message import MessageReceivedEvent
from gajim.common.connection_handlers_events import DecryptedMessageReceivedEvent
import nbxmpp