Merge branch 'master' into develop
This commit is contained in:
commit
a2c60a4911
4
setup.py
4
setup.py
@ -60,6 +60,7 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins/xep_0009',
|
||||
'sleekxmpp/plugins/xep_0009/stanza',
|
||||
'sleekxmpp/plugins/xep_0012',
|
||||
'sleekxmpp/plugins/xep_0013',
|
||||
'sleekxmpp/plugins/xep_0016',
|
||||
'sleekxmpp/plugins/xep_0027',
|
||||
'sleekxmpp/plugins/xep_0030',
|
||||
@ -80,6 +81,7 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins/xep_0084',
|
||||
'sleekxmpp/plugins/xep_0085',
|
||||
'sleekxmpp/plugins/xep_0086',
|
||||
'sleekxmpp/plugins/xep_0091',
|
||||
'sleekxmpp/plugins/xep_0092',
|
||||
'sleekxmpp/plugins/xep_0107',
|
||||
'sleekxmpp/plugins/xep_0108',
|
||||
@ -105,6 +107,8 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins/xep_0279',
|
||||
'sleekxmpp/plugins/xep_0280',
|
||||
'sleekxmpp/plugins/xep_0297',
|
||||
'sleekxmpp/plugins/xep_0308',
|
||||
'sleekxmpp/plugins/xep_0313',
|
||||
'sleekxmpp/features',
|
||||
'sleekxmpp/features/feature_mechanisms',
|
||||
'sleekxmpp/features/feature_mechanisms/stanza',
|
||||
|
@ -114,6 +114,17 @@ class BaseXMPP(XMLStream):
|
||||
#: ``'to'`` and ``'from'`` JIDs of stanzas.
|
||||
self.is_component = False
|
||||
|
||||
#: Messages may optionally be tagged with ID values. Setting
|
||||
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
||||
#: messages an ID. Some plugin features require enabling
|
||||
#: this option.
|
||||
self.use_message_ids = False
|
||||
|
||||
#: Presence updates may optionally be tagged with ID values.
|
||||
#: Setting :attr:`use_message_ids` to `True` will assign all
|
||||
#: outgoing messages an ID.
|
||||
self.use_presence_ids = False
|
||||
|
||||
#: The API registry is a way to process callbacks based on
|
||||
#: JID+node combinations. Each callback in the registry is
|
||||
#: marked with:
|
||||
|
@ -18,6 +18,7 @@ __all__ = [
|
||||
'xep_0004', # Data Forms
|
||||
'xep_0009', # Jabber-RPC
|
||||
'xep_0012', # Last Activity
|
||||
'xep_0013', # Flexible Offline Message Retrieval
|
||||
'xep_0016', # Privacy Lists
|
||||
'xep_0027', # Current Jabber OpenPGP Usage
|
||||
'xep_0030', # Service Discovery
|
||||
@ -38,6 +39,7 @@ __all__ = [
|
||||
'xep_0084', # User Avatar
|
||||
'xep_0085', # Chat State Notifications
|
||||
'xep_0086', # Legacy Error Codes
|
||||
'xep_0091', # Legacy Delayed Delivery
|
||||
'xep_0092', # Software Version
|
||||
'xep_0106', # JID Escaping
|
||||
'xep_0107', # User Mood
|
||||
@ -72,4 +74,6 @@ __all__ = [
|
||||
'xep_0280', # Message Carbons
|
||||
'xep_0297', # Stanza Forwarding
|
||||
'xep_0302', # XMPP Compliance Suites 2012
|
||||
'xep_0308', # Last Message Correction
|
||||
'xep_0313', # Message Archive Management
|
||||
]
|
||||
|
15
sleekxmpp/plugins/xep_0013/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0013/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
from sleekxmpp.plugins.base import register_plugin
|
||||
|
||||
from sleekxmpp.plugins.xep_0013.stanza import Offline
|
||||
from sleekxmpp.plugins.xep_0013.offline import XEP_0013
|
||||
|
||||
|
||||
register_plugin(XEP_0013)
|
134
sleekxmpp/plugins/xep_0013/offline.py
Normal file
134
sleekxmpp/plugins/xep_0013/offline.py
Normal file
@ -0,0 +1,134 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.stanza import Message, Iq
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.xmlstream.handler import Collector
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
from sleekxmpp.plugins.xep_0013 import stanza
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XEP_0013(BasePlugin):
|
||||
|
||||
"""
|
||||
XEP-0013 Flexible Offline Message Retrieval
|
||||
"""
|
||||
|
||||
name = 'xep_0013'
|
||||
description = 'XEP-0013: Flexible Offline Message Retrieval'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, stanza.Offline)
|
||||
register_stanza_plugin(Message, stanza.Offline)
|
||||
|
||||
def get_count(self, **kwargs):
|
||||
return self.xmpp['xep_0030'].get_info(
|
||||
node='http://jabber.org/protocol/offline',
|
||||
local=False,
|
||||
**kwargs)
|
||||
|
||||
def get_headers(self, **kwargs):
|
||||
return self.xmpp['xep_0030'].get_items(
|
||||
node='http://jabber.org/protocol/offline',
|
||||
local=False,
|
||||
**kwargs)
|
||||
|
||||
def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
|
||||
if not isinstance(nodes, (list, set)):
|
||||
nodes = [nodes]
|
||||
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['from'] = ifrom
|
||||
offline = iq['offline']
|
||||
for node in nodes:
|
||||
item = stanza.Item()
|
||||
item['node'] = node
|
||||
item['action'] = 'view'
|
||||
offline.append(item)
|
||||
|
||||
collector = Collector(
|
||||
'Offline_Results_%s' % iq['id'],
|
||||
StanzaPath('message/offline'))
|
||||
self.xmpp.register_handler(collector)
|
||||
|
||||
if not block and callback is not None:
|
||||
def wrapped_cb(iq):
|
||||
results = collector.stop()
|
||||
if iq['type'] == 'result':
|
||||
iq['offline']['results'] = results
|
||||
callback(iq)
|
||||
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||
else:
|
||||
try:
|
||||
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||
resp['offline']['results'] = collector.stop()
|
||||
return resp
|
||||
except XMPPError as e:
|
||||
collector.stop()
|
||||
raise e
|
||||
|
||||
def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
|
||||
if not isinstance(nodes, (list, set)):
|
||||
nodes = [nodes]
|
||||
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
offline = iq['offline']
|
||||
for node in nodes:
|
||||
item = stanza.Item()
|
||||
item['node'] = node
|
||||
item['action'] = 'remove'
|
||||
offline.append(item)
|
||||
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def fetch(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq['offline']['fetch'] = True
|
||||
|
||||
collector = Collector(
|
||||
'Offline_Results_%s' % iq['id'],
|
||||
StanzaPath('message/offline'))
|
||||
self.xmpp.register_handler(collector)
|
||||
|
||||
if not block and callback is not None:
|
||||
def wrapped_cb(iq):
|
||||
results = collector.stop()
|
||||
if iq['type'] == 'result':
|
||||
iq['offline']['results'] = results
|
||||
callback(iq)
|
||||
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||
else:
|
||||
try:
|
||||
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||
resp['offline']['results'] = collector.stop()
|
||||
return resp
|
||||
except XMPPError as e:
|
||||
collector.stop()
|
||||
raise e
|
||||
|
||||
def purge(self, ifrom=None, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq['offline']['purge'] = True
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
53
sleekxmpp/plugins/xep_0013/stanza.py
Normal file
53
sleekxmpp/plugins/xep_0013/stanza.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
from sleekxmpp.jid import JID
|
||||
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
class Offline(ElementBase):
|
||||
name = 'offline'
|
||||
namespace = 'http://jabber.org/protocol/offline'
|
||||
plugin_attrib = 'offline'
|
||||
interfaces = set(['fetch', 'purge', 'results'])
|
||||
bool_interfaces = interfaces
|
||||
|
||||
def setup(self, xml=None):
|
||||
ElementBase.setup(self, xml)
|
||||
self._results = []
|
||||
|
||||
# The results interface is meant only as an easy
|
||||
# way to access the set of collected message responses
|
||||
# from the query.
|
||||
|
||||
def get_results(self):
|
||||
return self._results
|
||||
|
||||
def set_results(self, values):
|
||||
self._results = values
|
||||
|
||||
def del_results(self):
|
||||
self._results = []
|
||||
|
||||
|
||||
class Item(ElementBase):
|
||||
name = 'item'
|
||||
namespace = 'http://jabber.org/protocol/offline'
|
||||
plugin_attrib = 'item'
|
||||
interfaces = set(['action', 'node', 'jid'])
|
||||
|
||||
actions = set(['view', 'remove'])
|
||||
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
|
||||
register_stanza_plugin(Offline, Item, iterable=True)
|
@ -288,7 +288,7 @@ class XEP_0030(BasePlugin):
|
||||
'cached': cached}
|
||||
return self.api['has_identity'](jid, node, ifrom, data)
|
||||
|
||||
def get_info(self, jid=None, node=None, local=False,
|
||||
def get_info(self, jid=None, node=None, local=None,
|
||||
cached=None, **kwargs):
|
||||
"""
|
||||
Retrieve the disco#info results from a given JID/node combination.
|
||||
@ -325,17 +325,18 @@ class XEP_0030(BasePlugin):
|
||||
received instead of blocking and waiting for
|
||||
the reply.
|
||||
"""
|
||||
if jid is not None and not isinstance(jid, JID):
|
||||
jid = JID(jid)
|
||||
if self.xmpp.is_component:
|
||||
if jid.domain == self.xmpp.boundjid.domain:
|
||||
local = True
|
||||
else:
|
||||
if str(jid) == str(self.xmpp.boundjid):
|
||||
local = True
|
||||
jid = jid.full
|
||||
elif jid in (None, ''):
|
||||
local = True
|
||||
if local is None:
|
||||
if jid is not None and not isinstance(jid, JID):
|
||||
jid = JID(jid)
|
||||
if self.xmpp.is_component:
|
||||
if jid.domain == self.xmpp.boundjid.domain:
|
||||
local = True
|
||||
else:
|
||||
if str(jid) == str(self.xmpp.boundjid):
|
||||
local = True
|
||||
jid = jid.full
|
||||
elif jid in (None, ''):
|
||||
local = True
|
||||
|
||||
if local:
|
||||
log.debug("Looking up local disco#info data " + \
|
||||
@ -405,7 +406,7 @@ class XEP_0030(BasePlugin):
|
||||
the XEP-0059 plugin, if the plugin is loaded.
|
||||
Otherwise the parameter is ignored.
|
||||
"""
|
||||
if local or jid is None:
|
||||
if local or local is None and jid is None:
|
||||
items = self.api['get_items'](jid, node,
|
||||
kwargs.get('ifrom', None),
|
||||
kwargs)
|
||||
|
@ -25,11 +25,14 @@ class ResultIterator():
|
||||
An iterator for Result Set Managment
|
||||
"""
|
||||
|
||||
def __init__(self, query, interface, amount=10, start=None, reverse=False):
|
||||
def __init__(self, query, interface, results='substanzas', amount=10,
|
||||
start=None, reverse=False):
|
||||
"""
|
||||
Arguments:
|
||||
query -- The template query
|
||||
interface -- The substanza of the query, for example disco_items
|
||||
results -- The query stanza's interface which provides a
|
||||
countable list of query results.
|
||||
amount -- The max amounts of items to request per iteration
|
||||
start -- From which item id to start
|
||||
reverse -- If True, page backwards through the results
|
||||
@ -46,6 +49,7 @@ class ResultIterator():
|
||||
self.amount = amount
|
||||
self.start = start
|
||||
self.interface = interface
|
||||
self.results = results
|
||||
self.reverse = reverse
|
||||
self._stop = False
|
||||
|
||||
@ -85,7 +89,7 @@ class ResultIterator():
|
||||
r[self.interface]['rsm']['first_index']:
|
||||
count = int(r[self.interface]['rsm']['count'])
|
||||
first = int(r[self.interface]['rsm']['first_index'])
|
||||
num_items = len(r[self.interface]['substanzas'])
|
||||
num_items = len(r[self.interface][self.results])
|
||||
if first + num_items == count:
|
||||
self._stop = True
|
||||
|
||||
@ -123,7 +127,7 @@ class XEP_0059(BasePlugin):
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature(Set.namespace)
|
||||
|
||||
def iterate(self, stanza, interface):
|
||||
def iterate(self, stanza, interface, results='substanzas'):
|
||||
"""
|
||||
Create a new result set iterator for a given stanza query.
|
||||
|
||||
@ -135,5 +139,7 @@ class XEP_0059(BasePlugin):
|
||||
result set management stanza should be
|
||||
appended. For example, for disco#items queries
|
||||
the interface 'disco_items' should be used.
|
||||
results -- The name of the interface containing the
|
||||
query results (typically just 'substanzas').
|
||||
"""
|
||||
return ResultIterator(stanza, interface)
|
||||
return ResultIterator(stanza, interface, results)
|
||||
|
16
sleekxmpp/plugins/xep_0091/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0091/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.plugins.base import register_plugin
|
||||
|
||||
from sleekxmpp.plugins.xep_0091 import stanza
|
||||
from sleekxmpp.plugins.xep_0091.stanza import LegacyDelay
|
||||
from sleekxmpp.plugins.xep_0091.legacy_delay import XEP_0091
|
||||
|
||||
|
||||
register_plugin(XEP_0091)
|
29
sleekxmpp/plugins/xep_0091/legacy_delay.py
Normal file
29
sleekxmpp/plugins/xep_0091/legacy_delay.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
|
||||
from sleekxmpp.stanza import Message, Presence
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
from sleekxmpp.plugins.xep_0091 import stanza
|
||||
|
||||
|
||||
class XEP_0091(BasePlugin):
|
||||
|
||||
"""
|
||||
XEP-0091: Legacy Delayed Delivery
|
||||
"""
|
||||
|
||||
name = 'xep_0091'
|
||||
description = 'XEP-0091: Legacy Delayed Delivery'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Message, stanza.LegacyDelay)
|
||||
register_stanza_plugin(Presence, stanza.LegacyDelay)
|
46
sleekxmpp/plugins/xep_0091/stanza.py
Normal file
46
sleekxmpp/plugins/xep_0091/stanza.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import datetime as dt
|
||||
|
||||
from sleekxmpp.jid import JID
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
from sleekxmpp.plugins import xep_0082
|
||||
|
||||
|
||||
class LegacyDelay(ElementBase):
|
||||
|
||||
name = 'x'
|
||||
namespace = 'jabber:x:delay'
|
||||
plugin_attrib = 'legacy_delay'
|
||||
interfaces = set(('from', 'stamp', 'text'))
|
||||
|
||||
def get_from(self):
|
||||
return JID(self._get_attr('from'))
|
||||
|
||||
def set_from(self, value):
|
||||
self._set_attr('from', str(value))
|
||||
|
||||
def get_stamp(self):
|
||||
timestamp = self._get_attr('stamp')
|
||||
return xep_0082.parse('%sZ' % timestamp)
|
||||
|
||||
def set_stamp(self, value):
|
||||
if isinstance(value, dt.datetime):
|
||||
value = value.astimezone(xep_0082.tzutc)
|
||||
value = xep_0082.format_datetime(value)
|
||||
self._set_attr('stamp', value[0:19].replace('-', ''))
|
||||
|
||||
def get_text(self):
|
||||
return self.xml.text
|
||||
|
||||
def set_text(self, value):
|
||||
self.xml.text = value
|
||||
|
||||
def del_text(self):
|
||||
self.xml.text = ''
|
@ -14,14 +14,17 @@ from sleekxmpp.plugins import xep_0082
|
||||
|
||||
class Delay(ElementBase):
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
name = 'delay'
|
||||
namespace = 'urn:xmpp:delay'
|
||||
plugin_attrib = 'delay'
|
||||
interfaces = set(('from', 'stamp', 'text'))
|
||||
|
||||
def get_from(self):
|
||||
return JID(self._get_attr('from'))
|
||||
|
||||
def set_from(self, value):
|
||||
self._set_attr('from', str(value))
|
||||
|
||||
def get_stamp(self):
|
||||
timestamp = self._get_attr('stamp')
|
||||
return xep_0082.parse(timestamp)
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
|
@ -1,6 +1,6 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
|
@ -26,9 +26,14 @@ class XEP_0297(BasePlugin):
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Message, Forwarded)
|
||||
register_stanza_plugin(Forwarded, Message)
|
||||
register_stanza_plugin(Forwarded, Presence)
|
||||
register_stanza_plugin(Forwarded, Iq)
|
||||
|
||||
# While these are marked as iterable, that is just for
|
||||
# making it easier to extract the forwarded stanza. There
|
||||
# still can be only a single forwarded stanza.
|
||||
register_stanza_plugin(Forwarded, Message, iterable=True)
|
||||
register_stanza_plugin(Forwarded, Presence, iterable=True)
|
||||
register_stanza_plugin(Forwarded, Iq, iterable=True)
|
||||
|
||||
register_stanza_plugin(Forwarded, self.xmpp['xep_0203'].stanza.Delay)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
|
@ -6,6 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.stanza import Message, Presence, Iq
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
@ -16,12 +17,9 @@ class Forwarded(ElementBase):
|
||||
interfaces = set(['stanza'])
|
||||
|
||||
def get_stanza(self):
|
||||
if self.xml.find('{jabber:client}message') is not None:
|
||||
return self['message']
|
||||
elif self.xml.find('{jabber:client}presence') is not None:
|
||||
return self['presence']
|
||||
elif self.xml.find('{jabber:client}iq') is not None:
|
||||
return self['iq']
|
||||
for stanza in self:
|
||||
if isinstance(stanza, (Message, Presence, Iq)):
|
||||
return stanza
|
||||
return ''
|
||||
|
||||
def set_stanza(self, value):
|
||||
@ -29,6 +27,10 @@ class Forwarded(ElementBase):
|
||||
self.append(value)
|
||||
|
||||
def del_stanza(self):
|
||||
del self['message']
|
||||
del self['presence']
|
||||
del self['iq']
|
||||
found_stanzas = []
|
||||
for stanza in self:
|
||||
if isinstance(stanza, (Message, Presence, Iq)):
|
||||
found_stanzas.append(stanza)
|
||||
for stanza in found_stanzas:
|
||||
self.iterables.remove(stanza)
|
||||
self.xml.remove(stanza.xml)
|
||||
|
15
sleekxmpp/plugins/xep_0308/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0308/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
from sleekxmpp.plugins.base import register_plugin
|
||||
|
||||
from sleekxmpp.plugins.xep_0308.stanza import Replace
|
||||
from sleekxmpp.plugins.xep_0308.correction import XEP_0308
|
||||
|
||||
|
||||
register_plugin(XEP_0308)
|
52
sleekxmpp/plugins/xep_0308/correction.py
Normal file
52
sleekxmpp/plugins/xep_0308/correction.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.stanza import Message
|
||||
from sleekxmpp.xmlstream.handler import Callback
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
from sleekxmpp.plugins.xep_0308 import stanza, Replace
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XEP_0308(BasePlugin):
|
||||
|
||||
"""
|
||||
XEP-0308 Last Message Correction
|
||||
"""
|
||||
|
||||
name = 'xep_0308'
|
||||
description = 'XEP-0308: Last Message Correction'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
self.xmpp.register_handler(
|
||||
Callback('Message Correction',
|
||||
StanzaPath('message/replace'),
|
||||
self._handle_correction))
|
||||
|
||||
register_stanza_plugin(Message, Replace)
|
||||
|
||||
self.xmpp.use_message_ids = True
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Message Correction')
|
||||
self.xmpp.plugin['xep_0030'].del_feature(feature=Replace.namespace)
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp.plugin['xep_0030'].add_feature(Replace.namespace)
|
||||
|
||||
def _handle_correction(self, msg):
|
||||
self.xmpp.event('message_correction', msg)
|
16
sleekxmpp/plugins/xep_0308/stanza.py
Normal file
16
sleekxmpp/plugins/xep_0308/stanza.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class Replace(ElementBase):
|
||||
name = 'replace'
|
||||
namespace = 'urn:xmpp:message-correct:0'
|
||||
plugin_attrib = 'replace'
|
||||
interfaces = set(['id'])
|
15
sleekxmpp/plugins/xep_0313/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0313/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
from sleekxmpp.plugins.base import register_plugin
|
||||
|
||||
from sleekxmpp.plugins.xep_0313.stanza import Result, MAM, Preferences
|
||||
from sleekxmpp.plugins.xep_0313.mam import XEP_0313
|
||||
|
||||
|
||||
register_plugin(XEP_0313)
|
92
sleekxmpp/plugins/xep_0313/mam.py
Normal file
92
sleekxmpp/plugins/xep_0313/mam.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.stanza import Message, Iq
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.xmlstream.handler import Collector
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
from sleekxmpp.plugins.xep_0313 import stanza
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XEP_0313(BasePlugin):
|
||||
|
||||
"""
|
||||
XEP-0313 Message Archive Management
|
||||
"""
|
||||
|
||||
name = 'xep_0313'
|
||||
description = 'XEP-0313: Message Archive Management'
|
||||
dependencies = set(['xep_0030', 'xep_0050', 'xep_0059', 'xep_0297'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, stanza.MAM)
|
||||
register_stanza_plugin(Iq, stanza.Preferences)
|
||||
register_stanza_plugin(Message, stanza.Result)
|
||||
register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
|
||||
|
||||
def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
|
||||
block=True, timeout=None, callback=None, iterator=False):
|
||||
iq = self.xmpp.Iq()
|
||||
query_id = iq['id']
|
||||
|
||||
iq['to'] = jid
|
||||
iq['from'] = ifrom
|
||||
iq['type'] = 'get'
|
||||
iq['mam']['queryid'] = query_id
|
||||
iq['mam']['start'] = start
|
||||
iq['mam']['end'] = end
|
||||
iq['mam']['with'] = with_jid
|
||||
|
||||
collector = Collector(
|
||||
'MAM_Results_%s' % query_id,
|
||||
StanzaPath('message/mam_result@queryid=%s' % query_id))
|
||||
self.xmpp.register_handler(collector)
|
||||
|
||||
if iterator:
|
||||
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results')
|
||||
elif not block and callback is not None:
|
||||
def wrapped_cb(iq):
|
||||
results = collector.stop()
|
||||
if iq['type'] == 'result':
|
||||
iq['mam']['results'] = results
|
||||
callback(iq)
|
||||
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
|
||||
else:
|
||||
try:
|
||||
resp = iq.send(block=block, timeout=timeout, callback=callback)
|
||||
resp['mam']['results'] = collector.stop()
|
||||
return resp
|
||||
except XMPPError as e:
|
||||
collector.stop()
|
||||
raise e
|
||||
|
||||
def set_preferences(self, jid=None, default=None, always=None, never=None,
|
||||
ifrom=None, block=True, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['to'] = jid
|
||||
iq['from'] = ifrom
|
||||
iq['mam_prefs']['default'] = default
|
||||
iq['mam_prefs']['always'] = always
|
||||
iq['mam_prefs']['never'] = never
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
|
||||
def get_configuration_commands(self, jid, **kwargs):
|
||||
return self.xmpp['xep_0030'].get_items(
|
||||
jid=jid,
|
||||
node='urn:xmpp:mam#configure',
|
||||
**kwargs)
|
131
sleekxmpp/plugins/xep_0313/stanza.py
Normal file
131
sleekxmpp/plugins/xep_0313/stanza.py
Normal file
@ -0,0 +1,131 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
"""
|
||||
|
||||
import datetime as dt
|
||||
|
||||
from sleekxmpp.jid import JID
|
||||
from sleekxmpp.xmlstream import ElementBase, ET
|
||||
from sleekxmpp.plugins import xep_0082
|
||||
|
||||
|
||||
class MAM(ElementBase):
|
||||
name = 'query'
|
||||
namespace = 'urn:xmpp:mam:tmp'
|
||||
plugin_attrib = 'mam'
|
||||
interfaces = set(['queryid', 'start', 'end', 'with', 'results'])
|
||||
sub_interfaces = set(['start', 'end', 'with'])
|
||||
|
||||
def setup(self, xml=None):
|
||||
ElementBase.setup(self, xml)
|
||||
self._results = []
|
||||
|
||||
def get_start(self):
|
||||
timestamp = self._get_attr('start')
|
||||
return xep_0082.parse(timestamp)
|
||||
|
||||
def set_start(self, value):
|
||||
if isinstance(value, dt.datetime):
|
||||
value = xep_0082.format_datetime(value)
|
||||
self._set_attr('start', value)
|
||||
|
||||
def get_end(self):
|
||||
timestamp = self._get_sub_text('end')
|
||||
return xep_0082.parse(timestamp)
|
||||
|
||||
def set_end(self, value):
|
||||
if isinstance(value, dt.datetime):
|
||||
value = xep_0082.format_datetime(value)
|
||||
self._set_sub_text('end', value)
|
||||
|
||||
def get_with(self):
|
||||
return JID(self._get_sub_text('with'))
|
||||
|
||||
def set_with(self, value):
|
||||
self._set_sub_text('with', str(value))
|
||||
|
||||
# The results interface is meant only as an easy
|
||||
# way to access the set of collected message responses
|
||||
# from the query.
|
||||
|
||||
def get_results(self):
|
||||
return self._results
|
||||
|
||||
def set_results(self, values):
|
||||
self._results = values
|
||||
|
||||
def del_results(self):
|
||||
self._results = []
|
||||
|
||||
|
||||
class Preferences(ElementBase):
|
||||
name = 'prefs'
|
||||
namespace = 'urn:xmpp:mam:tmp'
|
||||
plugin_attrib = 'mam_prefs'
|
||||
interfaces = set(['default', 'always', 'never'])
|
||||
sub_interfaces = set(['always', 'never'])
|
||||
|
||||
def get_always(self):
|
||||
results = set()
|
||||
|
||||
jids = self.xml.findall('{%s}always/{%s}jid' % (
|
||||
self.namespace, self.namespace))
|
||||
|
||||
for jid in jids:
|
||||
results.add(JID(jid.text))
|
||||
|
||||
return results
|
||||
|
||||
def set_always(self, value):
|
||||
self._set_sub_text('always', '', keep=True)
|
||||
always = self.xml.find('{%s}always' % self.namespace)
|
||||
always.clear()
|
||||
|
||||
if not isinstance(value, (list, set)):
|
||||
value = [value]
|
||||
|
||||
for jid in value:
|
||||
jid_xml = ET.Element('{%s}jid' % self.namespace)
|
||||
jid_xml.text = str(jid)
|
||||
always.append(jid_xml)
|
||||
|
||||
def get_never(self):
|
||||
results = set()
|
||||
|
||||
jids = self.xml.findall('{%s}never/{%s}jid' % (
|
||||
self.namespace, self.namespace))
|
||||
|
||||
for jid in jids:
|
||||
results.add(JID(jid.text))
|
||||
|
||||
return results
|
||||
|
||||
def set_never(self, value):
|
||||
self._set_sub_text('never', '', keep=True)
|
||||
never = self.xml.find('{%s}never' % self.namespace)
|
||||
never.clear()
|
||||
|
||||
if not isinstance(value, (list, set)):
|
||||
value = [value]
|
||||
|
||||
for jid in value:
|
||||
jid_xml = ET.Element('{%s}jid' % self.namespace)
|
||||
jid_xml.text = str(jid)
|
||||
never.append(jid_xml)
|
||||
|
||||
|
||||
class Result(ElementBase):
|
||||
name = 'result'
|
||||
namespace = 'urn:xmpp:mam:tmp'
|
||||
plugin_attrib = 'mam_result'
|
||||
interfaces = set(['forwarded', 'queryid', 'id'])
|
||||
|
||||
def get_forwarded(self):
|
||||
return self.parent()['forwarded']
|
||||
|
||||
def del_forwarded(self):
|
||||
del self.parent()['forwarded']
|
@ -63,6 +63,17 @@ class Message(RootStanza):
|
||||
lang_interfaces = sub_interfaces
|
||||
types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize a new <message /> stanza with an optional 'id' value.
|
||||
|
||||
Overrides StanzaBase.__init__.
|
||||
"""
|
||||
StanzaBase.__init__(self, *args, **kwargs)
|
||||
if self['id'] == '':
|
||||
if self.stream is not None and self.stream.use_message_ids:
|
||||
self['id'] = self.stream.new_id()
|
||||
|
||||
def get_type(self):
|
||||
"""
|
||||
Return the message type.
|
||||
|
@ -72,6 +72,17 @@ class Presence(RootStanza):
|
||||
'subscribed', 'unsubscribe', 'unsubscribed'])
|
||||
showtypes = set(['dnd', 'chat', 'xa', 'away'])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize a new <presence /> stanza with an optional 'id' value.
|
||||
|
||||
Overrides StanzaBase.__init__.
|
||||
"""
|
||||
StanzaBase.__init__(self, *args, **kwargs)
|
||||
if self['id'] == '':
|
||||
if self.stream is not None and self.stream.use_presence_ids:
|
||||
self['id'] = self.stream.new_id()
|
||||
|
||||
def exception(self, e):
|
||||
"""
|
||||
Override exception passback for presence.
|
||||
|
@ -368,6 +368,11 @@ class SleekTest(unittest.TestCase):
|
||||
else:
|
||||
for plugin in plugins:
|
||||
self.xmpp.register_plugin(plugin)
|
||||
|
||||
# Some plugins require messages to have ID values. Set
|
||||
# this to True in tests related to those plugins.
|
||||
self.xmpp.use_message_ids = False
|
||||
|
||||
self.xmpp.process(threaded=True)
|
||||
if skip:
|
||||
if socket != 'live':
|
||||
|
@ -7,6 +7,7 @@
|
||||
"""
|
||||
|
||||
from sleekxmpp.xmlstream.handler.callback import Callback
|
||||
from sleekxmpp.xmlstream.handler.collector import Collector
|
||||
from sleekxmpp.xmlstream.handler.waiter import Waiter
|
||||
from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback
|
||||
from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter
|
||||
|
66
sleekxmpp/xmlstream/handler/collector.py
Normal file
66
sleekxmpp/xmlstream/handler/collector.py
Normal file
@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sleekxmpp.xmlstream.handler.collector
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Part of SleekXMPP: The Sleek XMPP Library
|
||||
|
||||
:copyright: (c) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
:license: MIT, see LICENSE for more details
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from sleekxmpp.util import Queue, QueueEmpty
|
||||
from sleekxmpp.xmlstream.handler.base import BaseHandler
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Collector(BaseHandler):
|
||||
|
||||
"""
|
||||
The Collector handler allows for collecting a set of stanzas
|
||||
that match a given pattern. Unlike the Waiter handler, a
|
||||
Collector does not block execution, and will continue to
|
||||
accumulate matching stanzas until told to stop.
|
||||
|
||||
:param string name: The name of the handler.
|
||||
:param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
|
||||
derived object for matching stanza objects.
|
||||
:param stream: The :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream`
|
||||
instance this handler should monitor.
|
||||
"""
|
||||
|
||||
def __init__(self, name, matcher, stream=None):
|
||||
BaseHandler.__init__(self, name, matcher, stream=stream)
|
||||
self._payload = Queue()
|
||||
|
||||
def prerun(self, payload):
|
||||
"""Store the matched stanza when received during processing.
|
||||
|
||||
:param payload: The matched
|
||||
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object.
|
||||
"""
|
||||
self._payload.put(payload)
|
||||
|
||||
def run(self, payload):
|
||||
"""Do not process this handler during the main event loop."""
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop collection of matching stanzas, and return the ones that
|
||||
have been stored so far.
|
||||
"""
|
||||
self._destroy = True
|
||||
results = []
|
||||
try:
|
||||
while True:
|
||||
results.append(self._payload.get(False))
|
||||
except QueueEmpty:
|
||||
pass
|
||||
|
||||
self.stream().remove_handler(self.name)
|
||||
return results
|
@ -17,7 +17,7 @@ class TestStreamSet(SleekTest):
|
||||
def iter(self, rev=False):
|
||||
q = self.xmpp.Iq()
|
||||
q['type'] = 'get'
|
||||
it = ResultIterator(q, 'disco_items', '1', reverse=rev)
|
||||
it = ResultIterator(q, 'disco_items', amount='1', reverse=rev)
|
||||
for i in it:
|
||||
for j in i['disco_items']['items']:
|
||||
self.items.append(j[0])
|
||||
|
Loading…
Reference in New Issue
Block a user