Compare commits

...

30 Commits

Author SHA1 Message Date
Lance Stout
acd9c32a9f Bump version to 1.1.9 2012-07-20 00:17:53 -07:00
Lance Stout
b8581b0278 Of course Peter goes and changes the XEP title the day after I implement it. 2012-07-19 23:59:35 -07:00
Lance Stout
917faecdcb Fix issue of roster data being split across multiple rosters.
Resolved by always normalizing JIDs to bare form, regardless of if they
are JID objects or strings.

Also simplified related code to prefer use of JID objects instead of
strings so they don't need to be parsed multiple times.
2012-07-19 23:54:18 -07:00
Lance Stout
f6edaa56a6 Add plugin for XEP-0191: Simple Communications Blocking 2012-07-16 20:10:14 -07:00
Lance Stout
51fee28bf4 Add a warning log if dnspython is not found for SRV lookup.
Closes issue #183
2012-07-16 19:38:50 -07:00
Lance Stout
e8a3e92ceb Update plugins to use session_bind handler for disco, and use plugin_end 2012-07-10 01:37:44 -07:00
Lance Stout
5df3839b7a Add method to remove a filter. 2012-07-10 01:37:23 -07:00
Lance Stout
8dcb441f44 Add default plugin session_bind handler.
All plugins may now simply define a session_bind method where disco
features and other actions which require the bound JID may be done.
2012-07-10 01:36:21 -07:00
Lance Stout
a347cf625a Add session_bind_event threading event. 2012-07-10 01:35:57 -07:00
Lance Stout
46f49c7a12 Add method to unregister stream features. 2012-07-10 01:35:25 -07:00
Lance Stout
99701c947e Prevent None from being added to the schedule from a timing issue. 2012-07-09 22:59:26 -07:00
Lance Stout
1baae1b81e Fix issues of disco info leaking between entities with the same bare JIDs.
To ensure that disco info, or any settings which depend on the bound
JID, are correct, only set such information on or after the
session_bound event has fired.
2012-07-09 22:22:05 -07:00
Lance Stout
7d20f0e9a6 Fix missing import in xep_0256 plugin. 2012-07-09 22:21:40 -07:00
Lance Stout
fbad22a1cd Merge pull request #181 from whooo/upstream
Fix for the RSM iterator
2012-07-09 09:25:09 -07:00
Erik Larsson
5af2f62c04 Make sure that the last RSM stanza is returned from the iterator 2012-07-08 23:27:13 +02:00
Jay Farrimond
4a4a03858e dereference iq stanza only once for roster processing 2012-07-06 14:03:41 -07:00
Lance Stout
6819b57353 Handle converting None to byte data (b''). 2012-07-06 11:05:47 -07:00
Jay Farrimond
88b5e60807 only log cert errors if not handled by user 2012-07-05 13:38:26 -07:00
Lance Stout
a26a8bd79c Bump version to 1.1.8 2012-06-30 17:40:11 -07:00
Lance Stout
9307a6915f Add notes to echo_client.py example on working with Facebook and MSN. 2012-06-23 22:30:24 -07:00
Lance Stout
85ef2d8d0b Add support for reconnecting based on see-other-host stream errors. 2012-06-22 23:13:16 -07:00
Lance Stout
c2c7cc032b Fix plugin registration for single file plugins. 2012-06-22 21:58:50 -07:00
Lance Stout
e4911e9391 Add meta plugin for XEP-0302 for the 2012 compliance suite.
There are still a few remaining items in the RFCs to add support for,
but the current plugin support matches the advanced client profile.
2012-06-22 21:52:39 -07:00
Lance Stout
b11e1ee92d Add meta plugin for XEP-0270, 2010 compliance suite.
Registering this plugin will load the plugins required for advanced
client compliance status.
2012-06-22 21:26:25 -07:00
Lance Stout
5027d00c10 Change packaging for XEP-0256 to just a single file. 2012-06-22 21:26:01 -07:00
Lance Stout
69ddeceb49 Add support for XEP-0256: Last Activity in Presence 2012-06-22 21:13:30 -07:00
Lance Stout
82698672bb Add 'thread' and 'parent_thread' interfaces to message stanzas.
These values are perisisted across replies.
2012-06-22 20:05:34 -07:00
Lance Stout
9cec284947 Mark presence status as language aware. 2012-06-22 20:05:17 -07:00
Lance Stout
dc501d1902 Mark message body and subject as language aware interfaces. 2012-06-22 19:08:51 -07:00
Lance Stout
100e504b7f Resolve xml:lang issue with duplicated elements depending on ordering. 2012-06-22 18:19:17 -07:00
62 changed files with 1047 additions and 160 deletions

View File

@@ -45,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github
``develop`` branch.
**Latest Release**
- `1.1.7 <http://github.com/fritzy/SleekXMPP/zipball/1.1.7>`_
- `1.1.9 <http://github.com/fritzy/SleekXMPP/zipball/1.1.9>`_
**Develop Releases**
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
@@ -74,6 +74,7 @@ help with SleekXMPP.
**Chat**
`sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
Documentation and Testing
-------------------------
Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``.

View File

@@ -122,6 +122,19 @@ if __name__ == '__main__':
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
# If you are connecting to Facebook and wish to use the
# X-FACEBOOK-PLATFORM authentication mechanism, you will need
# your API key and an access token. Then you'll set:
# xmpp.credentials['api_key'] = 'THE_API_KEY'
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
# If you are connecting to MSN, then you will need an
# access token, and it does not matter what JID you
# specify other than that the domain is 'messenger.live.com',
# so '_@messenger.live.com' will work. You can specify
# the access token as so:
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3

View File

@@ -85,6 +85,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0172',
'sleekxmpp/plugins/xep_0184',
'sleekxmpp/plugins/xep_0186',
'sleekxmpp/plugins/xep_0191',
'sleekxmpp/plugins/xep_0198',
'sleekxmpp/plugins/xep_0199',
'sleekxmpp/plugins/xep_0202',

View File

@@ -99,7 +99,7 @@ class APIRegistry(object):
"""
self._setup(ctype, op)
if jid in (None, ''):
if not jid:
jid = self.xmpp.boundjid
if jid and not isinstance(jid, JID):
jid = JID(jid)
@@ -113,7 +113,7 @@ class APIRegistry(object):
else:
jid = jid.full
else:
if self.settings[ctype].get('client_bare', True):
if self.settings[ctype].get('client_bare', False):
jid = jid.bare
else:
jid = jid.full

View File

@@ -16,6 +16,7 @@ from __future__ import with_statement, unicode_literals
import sys
import logging
import threading
import sleekxmpp
from sleekxmpp import plugins, features, roster
@@ -69,8 +70,11 @@ class BaseXMPP(XMLStream):
#: The JabberID (JID) used by this connection.
self.boundjid = JID(jid)
self._expected_server_name = self.boundjid.host
self.session_bind_event = threading.Event()
#: A dictionary mapping plugin names to plugins.
self.plugin = PluginManager(self)
@@ -87,13 +91,13 @@ class BaseXMPP(XMLStream):
#: owner JIDs, as in the case for components. For clients
#: which only have a single JID, see :attr:`client_roster`.
self.roster = roster.Roster(self)
self.roster.add(self.boundjid.bare)
self.roster.add(self.boundjid)
#: The single roster for the bound JID. This is the
#: equivalent of::
#:
#: self.roster[self.boundjid.bare]
self.client_roster = self.roster[self.boundjid.bare]
self.client_roster = self.roster[self.boundjid]
#: The distinction between clients and components can be
#: important, primarily for choosing how to handle the
@@ -134,6 +138,7 @@ class BaseXMPP(XMLStream):
Callback('Presence',
MatchXPath("{%s}presence" % self.default_ns),
self._handle_presence))
self.register_handler(
Callback('Stream Error',
MatchXPath("{%s}error" % self.stream_ns),
@@ -654,27 +659,45 @@ class BaseXMPP(XMLStream):
def _handle_disconnected(self, event):
"""When disconnected, reset the roster"""
self.roster.reset()
self.session_bind_event.clear()
def _handle_stream_error(self, error):
self.event('stream_error', error)
if error['condition'] == 'see-other-host':
other_host = error['see_other_host']
host = other_host
port = 5222
if '[' in other_host and ']' in other_host:
host = other_host.split(']')[0][1:]
elif ':' in other_host:
host = other_host.split(':')[0]
port_sec = other_host.split(']')[-1]
if ':' in port_sec:
port = int(port_sec.split(':')[1])
self.address = (host, port)
self.default_domain = host
self.dns_records = None
self.reconnect_delay = None
self.reconnect()
def _handle_message(self, msg):
"""Process incoming message stanzas."""
if not self.is_component and not msg['to'].bare:
msg['to'] = self.boundjid
self.event('message', msg)
def _handle_available(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_available(presence)
def _handle_available(self, pres):
self.roster[pres['to']][pres['from']].handle_available(pres)
def _handle_unavailable(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_unavailable(presence)
def _handle_unavailable(self, pres):
self.roster[pres['to']][pres['from']].handle_unavailable(pres)
def _handle_new_subscription(self, stanza):
def _handle_new_subscription(self, pres):
"""Attempt to automatically handle subscription requests.
Subscriptions will be approved if the request is from
@@ -686,8 +709,8 @@ class BaseXMPP(XMLStream):
If a subscription is accepted, a request for a mutual
subscription will be sent if :attr:`auto_subscribe` is ``True``.
"""
roster = self.roster[stanza['to'].bare]
item = self.roster[stanza['to'].bare][stanza['from'].bare]
roster = self.roster[pres['to']]
item = self.roster[pres['to']][pres['from']]
if item['whitelisted']:
item.authorize()
elif roster.auto_authorize:
@@ -697,30 +720,20 @@ class BaseXMPP(XMLStream):
elif roster.auto_authorize == False:
item.unauthorize()
def _handle_removed_subscription(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].unauthorize()
def _handle_removed_subscription(self, pres):
self.roster[pres['to']][pres['from']].handle_unauthorize(pres)
def _handle_subscribe(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_subscribe(presence)
def _handle_subscribe(self, pres):
self.roster[pres['to']][pres['from']].handle_subscribe(pres)
def _handle_subscribed(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_subscribed(presence)
def _handle_subscribed(self, pres):
self.roster[pres['to']][pres['from']].handle_subscribed(pres)
def _handle_unsubscribe(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_unsubscribe(presence)
def _handle_unsubscribe(self, pres):
self.roster[pres['to']][pres['from']].handle_unsubscribe(pres)
def _handle_unsubscribed(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_unsubscribed(presence)
def _handle_unsubscribed(self, pres):
self.roster[pres['to']][pres['from']].handle_unsubscribed(pres)
def _handle_presence(self, presence):
"""Process incoming presence stanzas.

View File

@@ -173,6 +173,12 @@ class ClientXMPP(BaseXMPP):
self._stream_feature_order.append((order, name))
self._stream_feature_order.sort()
def unregister_feature(self, name, order):
if name in self._stream_feature_handlers:
del self._stream_feature_handlers[name]
self._stream_feature_order.remove((order, name))
self._stream_feature_order.sort()
def update_roster(self, jid, name=None, subscription=None, groups=[],
block=True, timeout=None, callback=None):
"""Add or change a roster item.
@@ -270,8 +276,9 @@ class ClientXMPP(BaseXMPP):
roster = self.client_roster
if iq['roster']['ver']:
roster.version = iq['roster']['ver']
for jid in iq['roster']['items']:
item = iq['roster']['items'][jid]
items = iq['roster']['items']
for jid in items:
item = items[jid]
roster[jid]['name'] = item['name']
roster[jid]['groups'] = item['groups']
roster[jid]['from'] = item['subscription'] in ['from', 'both']

View File

@@ -156,10 +156,10 @@ class ComponentXMPP(BaseXMPP):
:param xml: The reply handshake stanza.
"""
self.session_bind_event.set()
self.session_started_event.set()
self.event("session_bind", self.xmpp.boundjid, direct=True)
self.event("session_start")
def _handle_probe(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_probe(presence)
def _handle_probe(self, pres):
self.roster[pres['to']][pres['from']].handle_probe(pres)

View File

@@ -51,6 +51,7 @@ class FeatureBind(BasePlugin):
self.xmpp.set_jid(response['bind']['jid'])
self.xmpp.bound = True
self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
self.xmpp.session_bind_event.set()
self.xmpp.features.add('bind')

View File

@@ -46,6 +46,7 @@ __all__ = [
'xep_0172', # User Nickname
'xep_0184', # Message Receipts
'xep_0186', # Invisible Command
'xep_0191', # Blocking Command
'xep_0198', # Stream Management
'xep_0199', # Ping
'xep_0202', # Entity Time
@@ -56,5 +57,8 @@ __all__ = [
'xep_0224', # Attention
'xep_0231', # Bits of Binary
'xep_0249', # Direct MUC Invitations
'xep_0256', # Last Activity in Presence
'xep_0258', # Security Labels in XMPP
'xep_0270', # XMPP Compliance Suites 2010
'xep_0302', # XMPP Compliance Suites 2012
]

View File

@@ -167,8 +167,7 @@ class PluginManager(object):
self._plugins[name] = plugin
for dep in plugin.dependencies:
self.enable(dep, enabled=enabled)
plugin.plugin_init()
log.debug("Loaded Plugin: %s", plugin.description)
plugin._init()
if top_level:
for name in enabled:
@@ -229,7 +228,7 @@ class PluginManager(object):
raise PluginNotFound(name)
for dep in PLUGIN_DEPENDENTS[name]:
self.disable(dep, _disabled)
plugin.plugin_end()
plugin._end()
if name in self._enabled:
self._enabled.remove(name)
del self._plugins[name]
@@ -282,6 +281,28 @@ class BasePlugin(object):
#: configuration settings will be provided as a dictionary.
self.config = config if config is not None else {}
def _init(self):
"""Initialize plugin state, such as registering event handlers.
Also sets up required event handlers.
"""
if self.xmpp is not None:
self.xmpp.add_event_handler('session_bind', self.session_bind)
if self.xmpp.session_bind_event.is_set():
self.session_bind(self.xmpp.boundjid.full)
self.plugin_init()
log.debug('Loaded Plugin: %s', self.description)
def _end(self):
"""Cleanup plugin state, and prepare for plugin removal.
Also removes required event handlers.
"""
if self.xmpp is not None:
self.xmpp.del_event_handler('session_bind', self.session_bind)
self.plugin_end()
log.debug('Disabled Plugin: %s' % self.description)
def plugin_init(self):
"""Initialize plugin state, such as registering event handlers."""
pass
@@ -290,6 +311,10 @@ class BasePlugin(object):
"""Cleanup plugin state, and prepare for plugin removal."""
pass
def session_bind(self, jid):
"""Initialize plugin state based on the bound JID."""
pass
def post_init(self):
"""Initialize any cross-plugin state.

View File

@@ -27,7 +27,7 @@ class XEP_0004(BasePlugin):
stanza = stanza
def plugin_init(self):
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('Data Form',
StanzaPath('message/form'),
self.handle_form))
@@ -36,6 +36,11 @@ class XEP_0004(BasePlugin):
register_stanza_plugin(Form, FormField, iterable=True)
register_stanza_plugin(Message, Form)
def plugin_end(self):
self.xmpp.remove_handler('Data Form')
self.xmpp['xep_0030'].del_feature(feature='jabber:x:data')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('jabber:x:data')
def make_form(self, ftype='form', title='', instructions=''):

View File

@@ -37,13 +37,11 @@ class XEP_0012(BasePlugin):
self._last_activities = {}
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('Last Activity',
StanzaPath('iq@type=get/last_activity'),
self._handle_get_last_activity))
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last')
self.api.register(self._default_get_last_activity,
'get_last_activity',
default=True)
@@ -54,6 +52,13 @@ class XEP_0012(BasePlugin):
'del_last_activity',
default=True)
def plugin_end(self):
self.xmpp.remove_handler('Last Activity')
self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('jabber:iq:last')
def begin_idle(self, jid=None, status=None):
self.set_last_activity(jid, 0, status)

View File

@@ -79,6 +79,13 @@ class XEP_0027(BasePlugin):
StanzaPath('message/encrypted'),
self._handle_encrypted_message))
def plugin_end(self):
self.xmpp.remove_handler('Encrypted Message')
self.xmpp.remove_handler('Signed Presence')
self.xmpp.del_filter('out', self._sign_presence)
self.xmpp.del_event_handler('unverified_signed_presence',
self._handle_unverified_signed_presence)
def _sign_presence(self, stanza):
if isinstance(stanza, Presence):
if stanza['type'] == 'available' or \

View File

@@ -622,11 +622,7 @@ class XEP_0030(BasePlugin):
if iq['type'] == 'get':
log.debug("Received disco info query from " + \
"<%s> to <%s>.", iq['from'], iq['to'])
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
info = self.api['get_info'](jid,
info = self.api['get_info'](iq['to'],
iq['disco_info']['node'],
iq['from'],
iq)
@@ -649,7 +645,7 @@ class XEP_0030(BasePlugin):
ito = iq['to'].full
else:
ito = None
self.api['cache_info'](iq['from'].full,
self.api['cache_info'](iq['from'],
iq['disco_info']['node'],
ito,
iq)
@@ -667,13 +663,9 @@ class XEP_0030(BasePlugin):
if iq['type'] == 'get':
log.debug("Received disco items query from " + \
"<%s> to <%s>.", iq['from'], iq['to'])
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
items = self.api['get_items'](jid,
items = self.api['get_items'](iq['to'],
iq['disco_items']['node'],
iq['from'].full,
iq['from'],
iq)
if isinstance(items, Iq):
items.send()

View File

@@ -237,7 +237,7 @@ class StaticDisco(object):
with self.lock:
if not self.node_exists(jid, node):
if not node:
return DiscoInfo()
return DiscoItems()
else:
raise XMPPError(condition='item-not-found')
else:
@@ -424,9 +424,6 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if isinstance(jid, JID):
jid = jid.full
if not self.node_exists(jid, node, ifrom):
return None
else:

View File

@@ -26,7 +26,12 @@ class XEP_0033(BasePlugin):
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0030'].add_feature(Addresses.namespace)
register_stanza_plugin(Message, Addresses)
register_stanza_plugin(Presence, Addresses)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Addresses.namespace)

View File

@@ -51,6 +51,13 @@ class XEP_0047(BasePlugin):
StanzaPath('iq@type=set/ibb_data'),
self._handle_data))
def plugin_end(self):
self.xmpp.remove_handler('IBB Open')
self.xmpp.remove_handler('IBB Close')
self.xmpp.remove_handler('IBB Data')
self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
def _accept_stream(self, iq):

View File

@@ -110,6 +110,20 @@ class XEP_0050(BasePlugin):
self._handle_command_complete,
threaded=self.threaded)
def plugin_end(self):
self.xmpp.del_event_handler('command_execute',
self._handle_command_start)
self.xmpp.del_event_handler('command_next',
self._handle_command_next)
self.xmpp.del_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.del_event_handler('command_complete',
self._handle_command_complete)
self.xmpp.remove_handler('Ad-Hoc Execute')
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Command.namespace)
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())

View File

@@ -37,7 +37,6 @@ class XEP_0054(BasePlugin):
"""
register_stanza_plugin(Iq, VCardTemp)
self.xmpp['xep_0030'].add_feature('vcard-temp')
self.api.register(self._set_vcard, 'set_vcard', default=True)
self.api.register(self._get_vcard, 'get_vcard', default=True)
@@ -50,6 +49,13 @@ class XEP_0054(BasePlugin):
StanzaPath('iq/vcard_temp'),
self._handle_get_vcard))
def plugin_end(self):
self.xmpp.remove_handler('VCardTemp')
self.xmpp['xep_0030'].del_feature(feature='vcard-temp')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('vcard-temp')
def make_vcard(self):
return VCardTemp()

View File

@@ -47,6 +47,7 @@ class ResultIterator():
self.start = start
self.interface = interface
self.reverse = reverse
self._stop = False
def __iter__(self):
return self
@@ -62,6 +63,8 @@ class ResultIterator():
results will be the items before the current page
of items.
"""
if self._stop:
raise StopIteration
self.query[self.interface]['rsm']['before'] = self.reverse
self.query['id'] = self.query.stream.new_id()
self.query[self.interface]['rsm']['max'] = str(self.amount)
@@ -84,7 +87,7 @@ class ResultIterator():
first = int(r[self.interface]['rsm']['first_index'])
num_items = len(r[self.interface]['substanzas'])
if first + num_items == count:
raise StopIteration
self._stop = True
if self.reverse:
self.start = r[self.interface]['rsm']['first']
@@ -111,10 +114,15 @@ class XEP_0059(BasePlugin):
"""
Start the XEP-0059 plugin.
"""
self.xmpp['xep_0030'].add_feature(Set.namespace)
register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems,
self.stanza.Set)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Set.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Set.namespace)
def iterate(self, stanza, interface):
"""
Create a new result set iterator for a given stanza query.

View File

@@ -53,6 +53,13 @@ class XEP_0060(BasePlugin):
StanzaPath('message/pubsub_event/subscription'),
self._handle_event_subscription))
def plugin_end(self):
self.xmpp.remove_handler('Pubsub Event: Items')
self.xmpp.remove_handler('Pubsub Event: Purge')
self.xmpp.remove_handler('Pubsub Event: Delete')
self.xmpp.remove_handler('Pubsub Event: Configuration')
self.xmpp.remove_handler('Pubsub Event: Subscription')
def _handle_event_items(self, msg):
"""Raise events for publish and retraction notifications."""
node = msg['pubsub_event']['items']['node']

View File

@@ -62,6 +62,12 @@ class XEP_0066(BasePlugin):
StanzaPath('iq@type=set/oob_transfer'),
self._handle_transfer))
def plugin_end(self):
self.xmpp.remove_handler('OOB Transfer')
self.xmpp['xep_0030'].del_feature(feature=stanza.OOBTransfer.namespace)
self.xmpp['xep_0030'].del_feature(feature=stanza.OOB.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace)
self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace)

View File

@@ -34,9 +34,7 @@ class XEP_0077(BasePlugin):
register_stanza_plugin(StreamFeatures, RegisterFeature)
register_stanza_plugin(Iq, Register)
if self.xmpp.is_component:
pass
else:
if not self.xmpp.is_component:
self.xmpp.register_feature('register',
self._handle_register_feature,
restart=False,
@@ -45,6 +43,10 @@ class XEP_0077(BasePlugin):
register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
def plugin_end(self):
if not self.xmpp.is_component:
self.xmpp.unregister_feature('register', self.config.get('order', 50))
def _handle_register_feature(self, features):
if 'mechanisms' in self.xmpp.features:
# We have already logged in with an account

View File

@@ -44,6 +44,9 @@ class XEP_0078(BasePlugin):
register_stanza_plugin(Iq, stanza.IqAuth)
register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
def plugin_end(self):
self.xmpp.unregister_feature('auth', self.config.get('order', 15))
def _handle_auth(self, features):
# If we can or have already authenticated with SASL, do nothing.
if 'mechanisms' in features['features']:

View File

@@ -28,8 +28,11 @@ class XEP_0080(BasePlugin):
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_init(self):
"""Start the XEP-0080 plugin."""
def plugin_end(self):
self.xmpp['xep_0163'].remove_interest(Geoloc.namespace)
self.xmpp['xep_0030'].del_feature(feature=Geoloc.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_location', Geoloc)
def publish_location(self, **kwargs):

View File

@@ -28,14 +28,19 @@ class XEP_0084(BasePlugin):
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData)
pubsub_stanza = self.xmpp['xep_0060'].stanza
register_stanza_plugin(pubsub_stanza.Item, Data)
register_stanza_plugin(pubsub_stanza.EventItem, Data)
self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data')
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=MetaData.namespace)
self.xmpp['xep_0163'].remove_interest(MetaData.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData)
def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True,
callback=None, timeout=None):
return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,

View File

@@ -43,6 +43,10 @@ class XEP_0085(BasePlugin):
register_stanza_plugin(Message, stanza.Inactive)
register_stanza_plugin(Message, stanza.Paused)
def plugin_end(self):
self.xmpp.remove_handler('Chat State')
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace)
def _handle_chat_state(self, msg):

View File

@@ -48,6 +48,11 @@ class XEP_0092(BasePlugin):
register_stanza_plugin(Iq, Version)
def plugin_end(self):
self.xmpp.remove_handler('Software Version')
self.xmpp['xep_0030'].del_feature(feature='jabber:iq:version')
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
def _handle_version(self, iq):

View File

@@ -32,6 +32,12 @@ class XEP_0107(BasePlugin):
def plugin_init(self):
register_stanza_plugin(Message, UserMood)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserMood.namespace)
self.xmpp['xep_0163'].remove_interest(UserMood.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_mood', UserMood)
def publish_mood(self, value=None, text=None, options=None,

View File

@@ -26,7 +26,11 @@ class XEP_0108(BasePlugin):
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_init(self):
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserActivity.namespace)
self.xmpp['xep_0163'].remove_interest(UserActivity.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_activity', UserActivity)
def publish_activity(self, general, specific=None, text=None, options=None,

View File

@@ -73,16 +73,15 @@ class XEP_0115(BasePlugin):
restart=False,
order=10010)
self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace)
disco = self.xmpp['xep_0030']
self.static = StaticCaps(self.xmpp, disco.static)
self.api.settings['client_bare'] = False
self.api.settings['component_bare'] = False
for op in self._disco_ops:
self.api.register(getattr(self.static, op), op, default=True)
for op in ('supports', 'has_identity'):
self.xmpp['xep_0030'].api.register(getattr(self.static, op), op)
self._run_node_handler = disco._run_node_handler
disco.cache_caps = self.cache_caps
@@ -90,6 +89,19 @@ class XEP_0115(BasePlugin):
disco.assign_verstring = self.assign_verstring
disco.get_verstring = self.get_verstring
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
self.xmpp.del_filter('out', self._filter_add_caps)
self.xmpp.del_event_handler('entity_caps', self._process_caps)
self.xmpp.remove_handler('Entity Capabilities')
if not self.xmpp.is_component:
self.xmpp.unregister_feature('caps', 10010)
for op in ('supports', 'has_identity'):
self.xmpp['xep_0030'].restore_defaults(op)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace)
def _filter_add_caps(self, stanza):
if isinstance(stanza, Presence) and self.broadcast:
ver = self.get_verstring(stanza['from'])

View File

@@ -26,7 +26,11 @@ class XEP_0118(BasePlugin):
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_init(self):
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserTune.namespace)
self.xmpp['xep_0163'].remove_interest(UserTune.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_tune', UserTune)
def publish_tune(self, artist=None, length=None, rating=None, source=None,

View File

@@ -51,8 +51,6 @@ class XEP_0128(BasePlugin):
register_stanza_plugin(DiscoInfo, Form, iterable=True)
def post_init(self):
"""Handle cross-plugin dependencies."""
self.disco = self.xmpp['xep_0030']
self.static = StaticExtendedDisco(self.disco.static)

View File

@@ -45,6 +45,15 @@ class XEP_0153(BasePlugin):
self.api.register(self._set_hash, 'set_hash', default=True)
self.api.register(self._get_hash, 'get_hash', default=True)
def plugin_end(self):
self.xmpp.del_filter('out', self._update_presence)
self.xmpp.del_event_handler('session_start', self._start)
self.xmpp.del_event_handler('presence_available', self._recv_presence)
self.xmpp.del_event_handler('presence_dnd', self._recv_presence)
self.xmpp.del_event_handler('presence_xa', self._recv_presence)
self.xmpp.del_event_handler('presence_chat', self._recv_presence)
self.xmpp.del_event_handler('presence_away', self._recv_presence)
def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
timeout=None, callback=None):
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)

View File

@@ -74,7 +74,7 @@ class XEP_0163(BasePlugin):
be a list of such namespaces.
jid -- Optionally specify the JID.
"""
if not isinstance(namespace, set) and not isinstance(namespace, list):
if not isinstance(namespace, (set, list)):
namespace = [namespace]
for ns in namespace:

View File

@@ -34,6 +34,12 @@ class XEP_0172(BasePlugin):
def plugin_init(self):
register_stanza_plugin(Message, UserNick)
register_stanza_plugin(Presence, UserNick)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserNick.namespace)
self.xmpp['xep_0163'].remove_interest(UserNick.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_nick', UserNick)
def publish_nick(self, nick=None, options=None, ifrom=None, block=True,

View File

@@ -48,6 +48,13 @@ class XEP_0184(BasePlugin):
StanzaPath('message/request_receipt'),
self._handle_receipt_request))
def plugin_end(self):
self.xmpp['xep_0030'].del_feature('urn:xmpp:receipts')
self.xmpp.del_filter('out', self._filter_add_receipt_request)
self.xmpp.remove_handler('Message Receipt')
self.xmpp.remove_handler('Message Receipt Request')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts')
def ack(self, msg):

View 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 permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0191.stanza import Block, Unblock, BlockList
from sleekxmpp.plugins.xep_0191.blocking import XEP_0191
register_plugin(XEP_0191)

View File

@@ -0,0 +1,83 @@
"""
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 logging
from sleekxmpp import Iq
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList
log = logging.getLogger(__name__)
class XEP_0191(BasePlugin):
name = 'xep_0191'
description = 'XEP-0191: Blocking Command'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, BlockList)
register_stanza_plugin(Iq, Block)
register_stanza_plugin(Iq, Unblock)
self.xmpp.register_handler(
Callback('Blocked Contact',
StanzaPath('iq@type=set/block'),
self._handle_blocked))
self.xmpp.register_handler(
Callback('Unblocked Contact',
StanzaPath('iq@type=set/unblock'),
self._handle_unblocked))
def plugin_end(self):
self.xmpp.remove_handler('Blocked Contact')
self.xmpp.remove_handler('Unblocked Contact')
def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['from'] = 'ifrom'
iq.enable('blocklist')
return iq.send(block=block, timeout=timeout, callback=callback)
def block(self, jids, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
if not isinstance(jids, (set, list)):
jids = [jids]
iq['block']['items'] = jids
return iq.send(block=block, timeout=timeout, callback=callback)
def unblock(self, jids=None, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
if jids is None:
jids = []
if not isinstance(jids, (set, list)):
jids = [jids]
iq['unblock']['items'] = jids
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_blocked(self, iq):
self.xmpp.event('blocked', iq)
def _handle_unblocked(self, iq):
self.xmpp.event('unblocked', iq)

View File

@@ -0,0 +1,50 @@
"""
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.xmlstream import ET, ElementBase, JID
class BlockList(ElementBase):
name = 'blocklist'
namespace = 'urn:xmpp:blocking'
plugin_attrib = 'blocklist'
interfaces = set(['items'])
def get_items(self):
result = set()
items = self.xml.findall('{%s}item' % self.namespace)
if items is not None:
for item in items:
jid = JID(item.attrib.get('jid', ''))
if jid:
result.add(jid)
return result
def set_items(self, values):
self.del_items()
for jid in values:
if jid:
item = ET.Element('{%s}item' % self.namespace)
item.attrib['jid'] = JID(jid).full
self.xml.append(item)
def del_items(self):
items = self.xml.findall('{%s}item' % self.namespace)
if items is not None:
for item in items:
self.xml.remove(item)
class Block(BlockList):
name = 'block'
plugin_attrib = 'block'
class Unblock(BlockList):
name = 'unblock'
plugin_attrib = 'unblock'

View File

@@ -133,6 +133,27 @@ class XEP_0198(BasePlugin):
self.xmpp.add_event_handler('session_end', self.session_end)
def plugin_end(self):
if self.xmpp.is_component:
return
self.xmpp.unregister_feature('sm', self.config.get('order', 10100))
self.xmpp.unregister_feature('sm', self.config.get('resume_order', 9000))
self.xmpp.del_event_handler('session_end', self.session_end)
self.xmpp.del_filter('in', self._handle_incoming)
self.xmpp.del_filter('out_sync', self._handle_outgoing)
self.xmpp.remove_handler('Stream Management Enabled')
self.xmpp.remove_handler('Stream Management Resumed')
self.xmpp.remove_handler('Stream Management Failed')
self.xmpp.remove_handler('Stream Management Ack')
self.xmpp.remove_handler('Stream Management Request Ack')
self.xmpp.remove_stanza(stanza.Enable)
self.xmpp.remove_stanza(stanza.Enabled)
self.xmpp.remove_stanza(stanza.Resume)
self.xmpp.remove_stanza(stanza.Resumed)
self.xmpp.remove_stanza(stanza.Ack)
self.xmpp.remove_stanza(stanza.RequestAck)
def session_end(self, event):
"""Reset stream management state."""
self.enabled.clear()

View File

@@ -74,6 +74,16 @@ class XEP_0199(BasePlugin):
self.xmpp.add_event_handler('session_end',
self._handle_session_end)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Ping.namespace)
self.xmpp.remove_handler('Ping')
if self.keepalive:
self.xmpp.del_event_handler('session_start',
self._handle_keepalive)
self.xmpp.del_event_handler('session_end',
self._handle_session_end)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Ping.namespace)
def _handle_keepalive(self, event):

View File

@@ -53,6 +53,11 @@ class XEP_0202(BasePlugin):
self._handle_time_request))
register_stanza_plugin(Iq, stanza.EntityTime)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:time')
self.xmpp.remove_handler('Entity Time')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('urn:xmpp:time')
def _handle_time_request(self, iq):

View File

@@ -39,6 +39,11 @@ class XEP_0224(BasePlugin):
StanzaPath('message/attention'),
self._handle_attention))
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.Attention.namespace)
self.xmpp.remove_handler('Attention')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(stanza.Attention.namespace)
def request_attention(self, to, mfrom=None, mbody=''):

View File

@@ -35,8 +35,6 @@ class XEP_0231(BasePlugin):
def plugin_init(self):
self._cids = {}
self.xmpp['xep_0030'].add_feature('urn:xmpp:bob')
register_stanza_plugin(Iq, BitsOfBinary)
self.xmpp.register_handler(
@@ -58,6 +56,15 @@ class XEP_0231(BasePlugin):
self.api.register(self._set_bob, 'set_bob', default=True)
self.api.register(self._del_bob, 'del_bob', default=True)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:bob')
self.xmpp.remove_handler('Bits of Binary - Iq')
self.xmpp.remove_handler('Bits of Binary - Message')
self.xmpp.remove_handler('Bits of Binary - Presence')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('urn:xmpp:bob')
def set_bob(self, data, mtype, cid=None, max_age=None):
if cid is None:
cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest()

View File

@@ -39,6 +39,11 @@ class XEP_0249(BasePlugin):
register_stanza_plugin(Message, Invite)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Invite.namespace)
self.xmpp.remove_handler('Direct MUC Invitations')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Invite.namespace)
def _handle_invite(self, msg):

View File

@@ -0,0 +1,72 @@
"""
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 logging
from sleekxmpp import Presence
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.plugins import BasePlugin, register_plugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
log = logging.getLogger(__name__)
class XEP_0256(BasePlugin):
name = 'xep_0256'
description = 'XEP-0256: Last Activity in Presence'
dependencies = set(['xep_0012'])
stanza = stanza
def plugin_init(self):
self.auto_last_activity = self.config.get('auto_last_activity', False)
register_stanza_plugin(Presence, LastActivity)
self.xmpp.add_filter('out', self._initial_presence_activity)
self.xmpp.add_event_handler('connected', self._reset_presence_activity)
self._initial_presence = set()
def plugin_end(self):
self.xmpp.del_filter('out', self._initial_presence_activity)
self.xmpp.del_event_handler('connected', self._reset_presence_activity)
def _reset_presence_activity(self, e):
self._initial_presence = set()
def _initial_presence_activity(self, stanza):
if isinstance(stanza, Presence):
use_last_activity = False
if self.auto_last_activity and stanza['show'] in ('xa', 'away'):
use_last_activity = True
if stanza['from'] not in self._initial_presence:
self._initial_presence.add(stanza['from'])
use_last_activity = True
if use_last_activity:
plugin = self.xmpp['xep_0012']
try:
result = plugin.api['get_last_activity'](stanza['from'],
None,
stanza['to'])
seconds = result['last_activity']['seconds']
except XMPPError:
seconds = None
if seconds is not None:
stanza['last_activity']['seconds'] = seconds
return stanza
register_plugin(XEP_0256)

View File

@@ -25,11 +25,15 @@ class XEP_0258(BasePlugin):
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace)
register_stanza_plugin(Message, SecurityLabel)
register_stanza_plugin(Iq, Catalog)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=SecurityLabel.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace)
def get_catalog(self, jid, ifrom=None, block=True,
callback=None, timeout=None):
iq = self.xmpp.Iq()

View File

@@ -0,0 +1,20 @@
"""
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 import BasePlugin, register_plugin
class XEP_0270(BasePlugin):
name = 'xep_0270'
description = 'XEP-0270: XMPP Compliance Suites 2010'
dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
'xep_0163', 'xep_0045', 'xep_0085'])
register_plugin(XEP_0270)

View File

@@ -0,0 +1,21 @@
"""
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 import BasePlugin, register_plugin
class XEP_0302(BasePlugin):
name = 'xep_0302'
description = 'XEP-0302: XMPP Compliance Suites 2012'
dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
'xep_0163', 'xep_0045', 'xep_0085',
'xep_0184', 'xep_0198'])
register_plugin(XEP_0302)

View File

@@ -94,10 +94,12 @@ class Roster(object):
Arguments:
key -- Return the roster for this JID.
"""
if isinstance(key, JID):
key = key.bare
if key is None:
key = self.xmpp.boundjid.bare
key = self.xmpp.boundjid
if not isinstance(key, JID):
key = JID(key)
key = key.bare
if key not in self._rosters:
self.add(key)
self._rosters[key].auto_authorize = self.auto_authorize
@@ -119,8 +121,10 @@ class Roster(object):
Arguments:
node -- The JID for the new roster node.
"""
if isinstance(node, JID):
node = node.bare
if not isinstance(node, JID):
node = JID(node)
node = node.bare
if node not in self._rosters:
self._rosters[node] = RosterNode(self.xmpp, node, self.db)

View File

@@ -89,8 +89,11 @@ class RosterNode(object):
A new item entry will be created if one does not already exist.
"""
if isinstance(key, JID):
key = key.bare
if key is None:
key = JID('')
if not isinstance(key, JID):
key = JID(key)
key = key.bare
if key not in self._jids:
self.add(key, save=True)
return self._jids[key]
@@ -101,8 +104,11 @@ class RosterNode(object):
To remove an item from the server, use the remove() method.
"""
if isinstance(key, JID):
key = key.bare
if key is None:
key = JID('')
if not isinstance(key, JID):
key = JID(key)
key = key.bare
if key in self._jids:
del self._jids[key]

View File

@@ -7,7 +7,7 @@
"""
from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import StanzaBase
from sleekxmpp.xmlstream import StanzaBase, ET
class Message(RootStanza):
@@ -54,13 +54,14 @@ class Message(RootStanza):
del_mucnick -- Dummy method to prevent deletion.
"""
namespace = 'jabber:client'
name = 'message'
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject',
'mucroom', 'mucnick'))
sub_interfaces = set(('body', 'subject'))
namespace = 'jabber:client'
plugin_attrib = name
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject',
'thread', 'parent_thread', 'mucroom', 'mucnick'])
sub_interfaces = set(['body', 'subject', 'thread'])
lang_interfaces = sub_interfaces
types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
def get_type(self):
"""
@@ -72,6 +73,31 @@ class Message(RootStanza):
"""
return self._get_attr('type', 'normal')
def get_parent_thread(self):
"""Return the message thread's parent thread."""
thread = self.xml.find('{%s}thread' % self.namespace)
if thread is not None:
return thread.attrib.get('parent', '')
return ''
def set_parent_thread(self, value):
"""Add or change the message thread's parent thread."""
thread = self.xml.find('{%s}thread' % self.namespace)
if value:
if thread is None:
thread = ET.Element('{%s}thread' % self.namespace)
self.xml.append(thread)
thread.attrib['parent'] = value
else:
if thread is not None and 'parent' in thread.attrib:
del thread.attrib['parent']
def del_parent_thread(self):
"""Delete the message thread's parent reference."""
thread = self.xml.find('{%s}thread' % self.namespace)
if thread is not None and 'parent' in thread.attrib:
del thread.attrib['parent']
def chat(self):
"""Set the message type to 'chat'."""
self['type'] = 'chat'
@@ -96,10 +122,16 @@ class Message(RootStanza):
clear -- Indicates if existing content should be removed
before replying. Defaults to True.
"""
thread = self['thread']
parent = self['parent_thread']
StanzaBase.reply(self, clear)
if self['type'] == 'groupchat':
self['to'] = self['to'].bare
self['thread'] = thread
self['parent_thread'] = parent
del self['id']
if body is not None:

View File

@@ -60,16 +60,17 @@ class Presence(RootStanza):
set_priority -- Set the value of the <priority> element.
"""
namespace = 'jabber:client'
name = 'presence'
interfaces = set(('type', 'to', 'from', 'id', 'show',
'status', 'priority'))
sub_interfaces = set(('show', 'status', 'priority'))
namespace = 'jabber:client'
plugin_attrib = name
interfaces = set(['type', 'to', 'from', 'id', 'show',
'status', 'priority'])
sub_interfaces = set(['show', 'status', 'priority'])
lang_interfaces = set(['status'])
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'chat', 'xa', 'away'))
types = set(['available', 'unavailable', 'error', 'probe', 'subscribe',
'subscribed', 'unsubscribe', 'unsubscribed'])
showtypes = set(['dnd', 'chat', 'xa', 'away'])
def exception(self, e):
"""

View File

@@ -15,6 +15,9 @@ def bytes(text):
:param text: Unicode text to convert to bytes
:rtype: bytes (Python3), str (Python2.6+)
"""
if text is None:
return b''
if sys.version_info < (3, 0):
import __builtin__
return __builtin__.bytes(text)

View File

@@ -9,5 +9,5 @@
# We don't want to have to import the entire library
# just to get the version info for setup.py
__version__ = '1.1.7'
__version_info__ = (1, 1, 7, '', 0)
__version__ = '1.1.9'
__version_info__ = (1, 1, 9, '', 0)

View File

@@ -143,3 +143,6 @@ class JID(object):
def __hash__(self):
"""Hash a JID based on the string version of its full JID."""
return hash(self.full)
def __copy__(self):
return JID(self.jid)

View File

@@ -254,6 +254,7 @@ def get_SRV(host, port, service, proto='tcp', resolver=None):
by SRV priorities and weights.
"""
if resolver is None:
log.warning("DNS: dnspython not found. Can not use SRV lookup.")
return [(host, port)]
log.debug("DNS: Querying SRV records for %s" % host)

View File

@@ -172,7 +172,8 @@ class Scheduler(object):
else:
updated = True
self.schedule_lock.acquire()
self.schedule.append(newtask)
if newtask is not None:
self.schedule.append(newtask)
finally:
if updated:
self.schedule = sorted(self.schedule,

View File

@@ -421,12 +421,6 @@ class ElementBase(object):
#: ``'{namespace}elementname'``.
self.tag = self.tag_name()
if 'lang' not in self.interfaces:
if isinstance(self.interfaces, tuple):
self.interfaces += ('lang',)
else:
self.interfaces.add('lang')
#: A :class:`weakref.weakref` to the parent stanza, if there is one.
#: If not, then :attr:`parent` is ``None``.
self.parent = None
@@ -574,6 +568,7 @@ class ElementBase(object):
.. versionadded:: 1.0-Beta1
"""
values = {}
values['lang'] = self['lang']
for interface in self.interfaces:
values[interface] = self[interface]
if interface in self.lang_interfaces:
@@ -629,6 +624,8 @@ class ElementBase(object):
sub.values = subdict
self.iterables.append(sub)
break
elif interface == 'lang':
self[interface] = value
elif interface in self.interfaces:
self[full_interface] = value
elif interface in self.plugin_attrib_map:
@@ -678,7 +675,7 @@ class ElementBase(object):
if attrib == 'substanzas':
return self.iterables
elif attrib in self.interfaces:
elif attrib in self.interfaces or attrib == 'lang':
get_method = "get_%s" % attrib.lower()
get_method2 = "get%s" % attrib.title()
@@ -752,7 +749,7 @@ class ElementBase(object):
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib in self.interfaces:
if attrib in self.interfaces or attrib == 'lang':
if value is not None:
set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title()
@@ -838,7 +835,7 @@ class ElementBase(object):
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib in self.interfaces:
if attrib in self.interfaces or attrib == 'lang':
del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title()
@@ -973,10 +970,6 @@ class ElementBase(object):
:param keep: Indicates if the element should be kept if its text is
removed. Defaults to False.
"""
path = self._fix_ns(name, split=True)
element = self.xml.find(name)
parent = self.xml
default_lang = self.get_lang()
if lang is None:
lang = default_lang
@@ -984,32 +977,51 @@ class ElementBase(object):
if not text and not keep:
return self._del_sub(name, lang=lang)
if element is None:
# We need to add the element. If the provided name was
# an XPath expression, some of the intermediate elements
# may already exist. If so, we want to use those instead
# of generating new elements.
last_xml = self.xml
walked = []
for ename in path:
walked.append(ename)
element = self.xml.find("/".join(walked))
if element is None:
element = ET.Element(ename)
if lang:
element.attrib['{%s}lang' % XML_NS] = lang
last_xml.append(element)
parent = last_xml
last_xml = element
element = last_xml
path = self._fix_ns(name, split=True)
name = path[-1]
parent = self.xml
if lang:
if element.attrib.get('{%s}lang' % XML_NS, default_lang) != lang:
element = ET.Element(ename)
element.attrib['{%s}lang' % XML_NS] = lang
parent.append(element)
# The first goal is to find the parent of the subelement, or, if
# we can't find that, the closest grandparent element.
missing_path = []
search_order = path[:-1]
while search_order:
parent = self.xml.find('/'.join(search_order))
ename = search_order.pop()
if parent is not None:
break
else:
missing_path.append(ename)
missing_path.reverse()
# Find all existing elements that match the desired
# element path (there may be multiples due to different
# languages values).
if parent is not None:
elements = self.xml.findall('/'.join(path))
else:
parent = self.xml
elements = []
# Insert the remaining grandparent elements that don't exist yet.
for ename in missing_path:
element = ET.Element(ename)
parent.append(element)
parent = element
# Re-use an existing element with the proper language, if one exists.
for element in elements:
elang = element.attrib.get('{%s}lang' % XML_NS, default_lang)
if not lang and elang == default_lang or lang and lang == elang:
element.text = text
return element
# No useable element exists, so create a new one.
element = ET.Element(name)
element.text = text
if lang and lang != default_lang:
element.attrib['{%s}lang' % XML_NS] = lang
parent.append(element)
return element
def _set_all_sub_text(self, name, values, keep=False, lang=None):
@@ -1184,6 +1196,7 @@ class ElementBase(object):
out = []
out += [x for x in self.interfaces]
out += [x for x in self.loaded_plugins]
out.append('lang')
if self.iterables:
out.append('substanzas')
return out
@@ -1263,7 +1276,7 @@ class ElementBase(object):
"""
return "{%s}%s" % (cls.namespace, cls.name)
def get_lang(self):
def get_lang(self, lang=None):
result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
if not result and self.parent and self.parent():
return self.parent()['lang']

View File

@@ -537,8 +537,8 @@ class XMLStream(object):
try:
cert.verify(self._expected_server_name, self._der_cert)
except cert.CertificateError as err:
log.error(err.message)
if not self.event_handled('ssl_invalid_cert'):
log.error(err.message)
self.disconnect(send_close=False)
else:
self.event('ssl_invalid_cert',
@@ -828,8 +828,8 @@ class XMLStream(object):
try:
cert.verify(self._expected_server_name, self._der_cert)
except cert.CertificateError as err:
log.error(err.message)
if not self.event_handled('ssl_invalid_cert'):
log.error(err.message)
self.disconnect(self.auto_reconnect, send_close=False)
else:
self.event('ssl_invalid_cert', pem_cert, direct=True)
@@ -954,6 +954,10 @@ class XMLStream(object):
else:
self.__filters[mode].append(handler)
def del_filter(self, mode, handler):
"""Remove an incoming or outgoing filter."""
self.__filters[mode].remove(handler)
def add_handler(self, mask, pointer, name=None, disposable=False,
threaded=False, filter=False, instream=False):
"""A shortcut method for registering a handler using XML masks.

View File

@@ -1,5 +1,6 @@
from sleekxmpp.test import *
from sleekxmpp.xmlstream.stanzabase import ElementBase
from sleekxmpp.thirdparty import OrderedDict
class TestElementBase(SleekTest):
@@ -760,7 +761,7 @@ class TestElementBase(SleekTest):
<foo xmlns="foo" />
""")
self.assertFalse(stanza['bar'],
self.assertFalse(stanza['bar'],
"Returned True for missing bool interface element.")
stanza['bar'] = True
@@ -797,7 +798,7 @@ class TestElementBase(SleekTest):
namespace = 'baz'
plugin_attrib = name
plugin_multi_attrib = 'bazs'
register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True)
register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True)
@@ -829,9 +830,9 @@ class TestElementBase(SleekTest):
<baz xmlns="baz" />
""")
self.assertEqual(len(bars), 2,
self.assertEqual(len(bars), 2,
"Wrong number of <bar /> stanzas: %s" % len(bars))
self.assertEqual(len(bazs), 2,
self.assertEqual(len(bazs), 2,
"Wrong number of <baz /> stanzas: %s" % len(bazs))
def testSetMultiAttrib(self):
@@ -853,7 +854,7 @@ class TestElementBase(SleekTest):
namespace = 'baz'
plugin_attrib = name
plugin_multi_attrib = 'bazs'
register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True)
register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True)
@@ -906,7 +907,7 @@ class TestElementBase(SleekTest):
namespace = 'baz'
plugin_attrib = name
plugin_multi_attrib = 'bazs'
register_stanza_plugin(TestStanza, TestMultiStanza1, iterable=True)
register_stanza_plugin(TestStanza, TestMultiStanza2, iterable=True)
@@ -938,5 +939,313 @@ class TestElementBase(SleekTest):
self.assertEqual(len(stanza['substanzas']), 2,
"Wrong number of substanzas: %s" % len(stanza['substanzas']))
def testDefaultLang(self):
"""Test setting a normal subinterface when a default language is set"""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
stanza = TestStanza()
stanza['lang'] = 'sv'
stanza['test'] = 'hej'
self.check(stanza, """
<foo xmlns="test" xml:lang="sv">
<test>hej</test>
</foo>
""")
self.assertEqual(stanza['test'], 'hej',
"Incorrect subinterface value: %s" % stanza['test'])
self.assertEqual(stanza['test|sv'], 'hej',
"Incorrect subinterface value: %s" % stanza['test|sv'])
def testSpecifyLangWithDefault(self):
"""Test specifying various languages."""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
stanza = TestStanza()
stanza['lang'] = 'sv'
stanza['test'] = 'hej'
stanza['test|en'] = 'hi'
stanza['test|es'] = 'hola'
self.check(stanza, """
<foo xmlns="test" xml:lang="sv">
<test>hej</test>
<test xml:lang="en">hi</test>
<test xml:lang="es">hola</test>
</foo>
""")
self.assertEqual(stanza['test'], 'hej',
"Incorrect subinterface value: %s" % stanza['test'])
self.assertEqual(stanza['test|sv'], 'hej',
"Incorrect subinterface value: %s" % stanza['test|sv'])
self.assertEqual(stanza['test|en'], 'hi',
"Incorrect subinterface value: %s" % stanza['test|en'])
self.assertEqual(stanza['test|es'], 'hola',
"Incorrect subinterface value: %s" % stanza['test|es'])
def testSpecifyLangWithNoDefault(self):
"""Test specifying various languages."""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
stanza = TestStanza()
stanza['test'] = 'hej'
stanza['test|en'] = 'hi'
stanza['test|es'] = 'hola'
self.check(stanza, """
<foo xmlns="test">
<test>hej</test>
<test xml:lang="en">hi</test>
<test xml:lang="es">hola</test>
</foo>
""")
self.assertEqual(stanza['test'], 'hej',
"Incorrect subinterface value: %s" % stanza['test'])
self.assertEqual(stanza['test|en'], 'hi',
"Incorrect subinterface value: %s" % stanza['test|en'])
self.assertEqual(stanza['test|es'], 'hola',
"Incorrect subinterface value: %s" % stanza['test|es'])
def testModifyLangInterfaceWithDefault(self):
"""Test resetting an interface when a default lang is used."""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
stanza = TestStanza()
stanza['lang'] = 'es'
stanza['test'] = 'hola'
stanza['test|en'] = 'hi'
self.check(stanza, """
<foo xmlns="test" xml:lang="es">
<test>hola</test>
<test xml:lang="en">hi</test>
</foo>
""")
stanza['test'] = 'adios'
stanza['test|en'] = 'bye'
self.check(stanza, """
<foo xmlns="test" xml:lang="es">
<test>adios</test>
<test xml:lang="en">bye</test>
</foo>
""")
self.assertEqual(stanza['test'], 'adios',
"Incorrect subinterface value: %s" % stanza['test'])
self.assertEqual(stanza['test|es'], 'adios',
"Incorrect subinterface value: %s" % stanza['test|es'])
self.assertEqual(stanza['test|en'], 'bye',
"Incorrect subinterface value: %s" % stanza['test|en'])
stanza['test|es'] = 'hola'
self.check(stanza, """
<foo xmlns="test" xml:lang="es">
<test>hola</test>
<test xml:lang="en">bye</test>
</foo>
""")
self.assertEqual(stanza['test'], 'hola',
"Incorrect subinterface value: %s" % stanza['test'])
self.assertEqual(stanza['test|es'], 'hola',
"Incorrect subinterface value: %s" % stanza['test|es'])
def testModifyLangInterfaceWithNoDefault(self):
"""Test resetting an interface when no default lang is used."""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
stanza = TestStanza()
stanza['test'] = 'hola'
stanza['test|en'] = 'hi'
self.check(stanza, """
<foo xmlns="test">
<test>hola</test>
<test xml:lang="en">hi</test>
</foo>
""")
stanza['test'] = 'adios'
stanza['test|en'] = 'bye'
self.check(stanza, """
<foo xmlns="test">
<test>adios</test>
<test xml:lang="en">bye</test>
</foo>
""")
self.assertEqual(stanza['test'], 'adios',
"Incorrect subinterface value: %s" % stanza['test'])
self.assertEqual(stanza['test'], 'adios',
"Incorrect subinterface value: %s" % stanza['test|es'])
self.assertEqual(stanza['test|en'], 'bye',
"Incorrect subinterface value: %s" % stanza['test|en'])
def testDelInterfacesWithDefaultLang(self):
"""Test deleting interfaces with a default lang set."""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
stanza = TestStanza()
stanza['lang'] = 'en'
stanza['test'] = 'hi'
stanza['test|no'] = 'hej'
stanza['test|fr'] = 'bonjour'
self.check(stanza, """
<foo xmlns="test" xml:lang="en">
<test>hi</test>
<test xml:lang="no">hej</test>
<test xml:lang="fr">bonjour</test>
</foo>
""")
del stanza['test']
self.check(stanza, """
<foo xmlns="test" xml:lang="en">
<test xml:lang="no">hej</test>
<test xml:lang="fr">bonjour</test>
</foo>
""")
del stanza['test|no']
self.check(stanza, """
<foo xmlns="test" xml:lang="en">
<test xml:lang="fr">bonjour</test>
</foo>
""")
def testDelInterfacesWithNoDefaultLang(self):
"""Test deleting interfaces with no default lang set."""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
stanza = TestStanza()
stanza['test'] = 'hi'
stanza['test|no'] = 'hej'
stanza['test|fr'] = 'bonjour'
self.check(stanza, """
<foo xmlns="test">
<test>hi</test>
<test xml:lang="no">hej</test>
<test xml:lang="fr">bonjour</test>
</foo>
""")
del stanza['test']
self.check(stanza, """
<foo xmlns="test">
<test xml:lang="no">hej</test>
<test xml:lang="fr">bonjour</test>
</foo>
""")
del stanza['test|no']
self.check(stanza, """
<foo xmlns="test">
<test xml:lang="fr">bonjour</test>
</foo>
""")
def testStarLang(self):
"""Test using interface|*."""
class TestStanza(ElementBase):
name = 'foo'
namespace = 'test'
interfaces = set(['test'])
sub_interfaces = interfaces
lang_interfaces = interfaces
data = OrderedDict()
data['en'] = 'hi'
data['fr'] = 'bonjour'
data['no'] = 'hej'
stanza = TestStanza()
stanza['test|*'] = data
self.check(stanza, """
<foo xmlns="test">
<test xml:lang="en">hi</test>
<test xml:lang="fr">bonjour</test>
<test xml:lang="no">hej</test>
</foo>
""")
data2 = stanza['test|*']
self.assertEqual(data, data2,
"Did not extract expected language data: %s" % data2)
del stanza['test|*']
self.check(stanza, """
<foo xmlns="test" />
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)