Expand support for XEP-0184.

New stanza interfaces:

    Adding a message receipt request:

        msg['request_receipt'] = True

    Adding a message receipt:

        msg['receipt'] = '123-24234'

    Retrieving the acked message ID:

        ack_id = msg['receipt']
        print(ack_id)
        '123-24234'

New configuration options:

    auto_ack:
        If True, auto reply to messages that request receipts.

        Defaults to True

    auto_request:
        If True, auto add receipt requests to appropriate outgoing
        messages.

        Defaults to False
This commit is contained in:
Lance Stout 2012-03-16 10:51:25 -07:00
parent 96ff2d43c0
commit 58e0f1e6c3
5 changed files with 173 additions and 65 deletions

View File

@ -9,7 +9,7 @@
from sleekxmpp.plugins.base import register_plugin from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0184.stanza import Request, Received from sleekxmpp.plugins.xep_0184.stanza import Request, Received
from sleekxmpp.plugins.xep_0184.reciept import XEP_0184 from sleekxmpp.plugins.xep_0184.receipt import XEP_0184
register_plugin(XEP_0184) register_plugin(XEP_0184)

View File

@ -0,0 +1,120 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Message
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0184 import stanza, Request, Received
class XEP_0184(BasePlugin):
"""
XEP-0184: Message Delivery Receipts
"""
name = 'xep_0184'
description = 'XEP-0184: Message Delivery Receipts'
dependencies = set(['xep_0030'])
stanza = stanza
ack_types = ('normal', 'chat', 'headline')
def plugin_init(self):
self.auto_ack = self.config.get('auto_ack', True)
self.auto_request = self.config.get('auto_request', False)
register_stanza_plugin(Message, Request)
register_stanza_plugin(Message, Received)
self.xmpp.add_filter('out', self._filter_add_receipt_request)
self.xmpp.register_handler(
Callback('Message Receipt',
StanzaPath('message/receipt'),
self._handle_receipt_received))
self.xmpp.register_handler(
Callback('Message Receipt Request',
StanzaPath('message/request_receipt'),
self._handle_receipt_request))
self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts')
def ack(self, msg):
"""
Acknowledge a message by sending a receipt.
Arguments:
msg -- The message to acknowledge.
"""
ack = self.xmpp.Message()
ack['to'] = msg['from']
ack['from'] = msg['to']
ack['receipt'] = msg['id']
ack['id'] = self.xmpp.new_id()
ack.send()
def _handle_receipt_received(self, msg):
self.xmpp.event('receipt_received', msg)
def _handle_receipt_request(self, msg):
"""
Auto-ack message receipt requests if ``self.auto_ack`` is ``True``.
Arguments:
msg -- The incoming message requesting a receipt.
"""
if self.auto_ack:
if msg['type'] in self.ack_types:
if not msg['receipt']:
self.ack(msg)
def _filter_add_receipt_request(self, stanza):
"""
Auto add receipt requests to outgoing messages, if:
- ``self.auto_request`` is set to ``True``
- The message is not for groupchat
- The message does not contain a receipt acknowledgment
- The recipient is a bare JID or, if a full JID, one
that has the ``urn:xmpp:receipts`` feature enabled
The disco cache is checked if a full JID is specified in
the outgoing message, which may mean a round-trip disco#info
delay for the first message sent to the JID if entity caps
are not used.
"""
if not self.auto_request:
return stanza
if not isinstance(stanza, Message):
return stanza
if stanza['request_receipt']:
return stanza
if not stanza['type'] in self.ack_types:
return stanza
if stanza['receipt']:
return stanza
if stanza['to'].resource:
if not self.xmpp['xep_0030'].supports(stanza['to'],
feature='urn:xmpp:receipts',
cached=True):
return stanza
stanza['request_receipt'] = True
return stanza

View File

@ -1,45 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Erik Reuterborg Larsson, Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Message
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0184 import stanza, Request, Received
class XEP_0184(BasePlugin):
"""
XEP-0184: Message Delivery Receipts
"""
name = 'xep_0184'
description = 'XEP-0184: Message Delivery Receipts'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, Request)
register_stanza_plugin(Message, Received)
self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:receipts')
def ack(self, message):
"""
Acknowledges a message
Arguments:
message -- The message to acknowledge.
"""
mto = message['to']
mfrom = message['from']
mid = message['id']
msg = self.xmpp.make_message(mto=mfrom, mfrom=mto)
msg['reciept_received']['id'] = mid
msg['id'] = self.xmpp.new_id()
msg.send()

View File

@ -12,34 +12,58 @@ from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
class Request(ElementBase): class Request(ElementBase):
namespace = 'urn:xmpp:receipts' namespace = 'urn:xmpp:receipts'
name = 'request' name = 'request'
plugin_attrib = 'request_reciept' plugin_attrib = 'request_receipt'
interfaces = set(('request_reciept',)) interfaces = set(('request_receipt',))
sub_interfaces = interfaces
is_extension = True is_extension = True
def setup(self, xml=None): def setup(self, xml=None):
self.xml = ET.Element('') self.xml = ET.Element('')
return True return True
def set_request_reciept(self, val): def set_request_receipt(self, val):
self.del_request_reciept() self.del_request_receipt()
parent = self.parent()
if val: if val:
self.xml = ET.Element("{%s}%s" % (self.namespace, self.name))
parent.append(self.xml)
def get_request_reciept(self):
parent = self.parent() parent = self.parent()
if parent.find("{%s}%s" % (self.namespace, self.name)) is not None: parent._set_sub_text("{%s}request" % self.namespace, keep=True)
def get_request_receipt(self):
parent = self.parent()
if parent.find("{%s}request" % self.namespace) is not None:
return True return True
else: else:
return False return False
def del_request_reciept(self): def del_request_receipt(self):
self.xml = ET.Element('') self.parent()._del_sub("{%s}request" % self.namespace)
class Received(ElementBase): class Received(ElementBase):
namespace = 'urn:xmpp:receipts' namespace = 'urn:xmpp:receipts'
name = 'received' name = 'received'
plugin_attrib = 'reciept_received' plugin_attrib = 'receipt'
interfaces = set(('id',)) interfaces = set(['receipt'])
sub_interfaces = interfaces
is_extension = True
def setup(self, xml=None):
self.xml = ET.Element('')
return True
def set_receipt(self, value):
self.del_receipt()
if value:
parent = self.parent()
xml = ET.Element("{%s}received" % self.namespace)
xml.attrib['id'] = value
parent.append(xml)
def get_receipt(self):
parent = self.parent()
xml = parent.find("{%s}received" % self.namespace)
if xml is not None:
return xml.attrib.get('id', '')
return ''
def del_receipt(self):
self.parent()._del_sub('{%s}received' % self.namespace)

View File

@ -9,21 +9,30 @@ class TestReciept(SleekTest):
register_stanza_plugin(Message, xep_0184.Received) register_stanza_plugin(Message, xep_0184.Received)
def testCreateRequest(self): def testCreateRequest(self):
request = """<message><request xmlns="urn:xmpp:receipts" /></message>""" request = """
<message>
<request xmlns="urn:xmpp:receipts" />
</message>
"""
msg = self.Message() msg = self.Message()
self.assertEqual(msg['request_reciept'], False) self.assertEqual(msg['request_receipt'], False)
msg['request_reciept'] = True msg['request_receipt'] = True
self.check(msg, request, use_values=False) self.check(msg, request)
def testCreateReceived(self): def testCreateReceived(self):
received = """<message><received xmlns="urn:xmpp:receipts" id="1"/></message>""" received = """
<message>
<received xmlns="urn:xmpp:receipts" id="1" />
</message>
"""
msg = self.Message() msg = self.Message()
msg['reciept_received']['id'] = '1'
msg['receipt'] = '1'
self.check(msg, received) self.check(msg, received)
suite = unittest.TestLoader().loadTestsFromTestCase(TestReciept) suite = unittest.TestLoader().loadTestsFromTestCase(TestReciept)