summaryrefslogtreecommitdiff
path: root/sleekxmpp/basexmpp.py
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/basexmpp.py')
-rw-r--r--sleekxmpp/basexmpp.py792
1 files changed, 792 insertions, 0 deletions
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
new file mode 100644
index 00000000..e4fd03a1
--- /dev/null
+++ b/sleekxmpp/basexmpp.py
@@ -0,0 +1,792 @@
+# -*- coding: utf-8 -*-
+"""
+ sleekxmpp.basexmpp
+ ~~~~~~~~~~~~~~~~~~
+
+ This module provides the common XMPP functionality
+ for both clients and components.
+
+ Part of SleekXMPP: The Sleek XMPP Library
+
+ :copyright: (c) 2011 Nathanael C. Fritz
+ :license: MIT, see LICENSE for more details
+"""
+
+from __future__ import with_statement, unicode_literals
+
+import sys
+import copy
+import logging
+
+import sleekxmpp
+from sleekxmpp import plugins, roster
+from sleekxmpp.exceptions import IqError, IqTimeout
+
+from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError
+from sleekxmpp.stanza.roster import Roster
+from sleekxmpp.stanza.nick import Nick
+from sleekxmpp.stanza.htmlim import HTMLIM
+
+from sleekxmpp.xmlstream import XMLStream, JID, tostring
+from sleekxmpp.xmlstream import ET, register_stanza_plugin
+from sleekxmpp.xmlstream.matcher import *
+from sleekxmpp.xmlstream.handler import *
+
+
+log = logging.getLogger(__name__)
+
+# In order to make sure that Unicode is handled properly
+# in Python 2.x, reset the default encoding.
+if sys.version_info < (3, 0):
+ reload(sys)
+ sys.setdefaultencoding('utf8')
+
+
+class BaseXMPP(XMLStream):
+
+ """
+ The BaseXMPP class adapts the generic XMLStream class for use
+ with XMPP. It also provides a plugin mechanism to easily extend
+ and add support for new XMPP features.
+
+ :param default_ns: Ensure that the correct default XML namespace
+ is used during initialization.
+ """
+
+ def __init__(self, jid='', default_ns='jabber:client'):
+ XMLStream.__init__(self)
+
+ self.default_ns = default_ns
+ self.stream_ns = 'http://etherx.jabber.org/streams'
+ self.namespace_map[self.stream_ns] = 'stream'
+
+ #: An identifier for the stream as given by the server.
+ self.stream_id = None
+
+ #: The JabberID (JID) used by this connection.
+ self.boundjid = JID(jid)
+
+ #: A dictionary mapping plugin names to plugins.
+ self.plugin = {}
+
+ #: Configuration options for whitelisted plugins.
+ #: If a plugin is registered without any configuration,
+ #: and there is an entry here, it will be used.
+ self.plugin_config = {}
+
+ #: A list of plugins that will be loaded if
+ #: :meth:`register_plugins` is called.
+ self.plugin_whitelist = []
+
+ #: The main roster object. This roster supports multiple
+ #: 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)
+
+ #: 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]
+
+ #: The distinction between clients and components can be
+ #: important, primarily for choosing how to handle the
+ #: ``'to'`` and ``'from'`` JIDs of stanzas.
+ self.is_component = False
+
+ #: Flag indicating that the initial presence broadcast has
+ #: been sent. Until this happens, some servers may not
+ #: behave as expected when sending stanzas.
+ self.sentpresence = False
+
+ #: A reference to :mod:`sleekxmpp.stanza` to make accessing
+ #: stanza classes easier.
+ self.stanza = sleekxmpp.stanza
+
+ self.register_handler(
+ Callback('IM',
+ MatchXPath('{%s}message/{%s}body' % (self.default_ns,
+ self.default_ns)),
+ self._handle_message))
+ self.register_handler(
+ Callback('Presence',
+ MatchXPath("{%s}presence" % self.default_ns),
+ self._handle_presence))
+ self.register_handler(
+ Callback('Stream Error',
+ MatchXPath("{%s}error" % self.stream_ns),
+ self._handle_stream_error))
+
+ self.add_event_handler('disconnected',
+ self._handle_disconnected)
+ self.add_event_handler('presence_available',
+ self._handle_available)
+ self.add_event_handler('presence_dnd',
+ self._handle_available)
+ self.add_event_handler('presence_xa',
+ self._handle_available)
+ self.add_event_handler('presence_chat',
+ self._handle_available)
+ self.add_event_handler('presence_away',
+ self._handle_available)
+ self.add_event_handler('presence_unavailable',
+ self._handle_unavailable)
+ self.add_event_handler('presence_subscribe',
+ self._handle_subscribe)
+ self.add_event_handler('presence_subscribed',
+ self._handle_subscribed)
+ self.add_event_handler('presence_unsubscribe',
+ self._handle_unsubscribe)
+ self.add_event_handler('presence_unsubscribed',
+ self._handle_unsubscribed)
+ self.add_event_handler('roster_subscription_request',
+ self._handle_new_subscription)
+
+ # Set up the XML stream with XMPP's root stanzas.
+ self.register_stanza(Message)
+ self.register_stanza(Iq)
+ self.register_stanza(Presence)
+ self.register_stanza(StreamError)
+
+ # Initialize a few default stanza plugins.
+ register_stanza_plugin(Iq, Roster)
+ register_stanza_plugin(Message, Nick)
+ register_stanza_plugin(Message, HTMLIM)
+
+ def start_stream_handler(self, xml):
+ """Save the stream ID once the streams have been established.
+
+ :param xml: The incoming stream's root element.
+ """
+ self.stream_id = xml.get('id', '')
+
+ def process(self, *args, **kwargs):
+ """Initialize plugins and begin processing the XML stream.
+
+ The number of threads used for processing stream events is determined
+ by :data:`HANDLER_THREADS`.
+
+ :param bool block: If ``False``, then event dispatcher will run
+ in a separate thread, allowing for the stream to be
+ used in the background for another application.
+ Otherwise, ``process(block=True)`` blocks the current
+ thread. Defaults to ``False``.
+ :param bool threaded: **DEPRECATED**
+ If ``True``, then event dispatcher will run
+ in a separate thread, allowing for the stream to be
+ used in the background for another application.
+ Defaults to ``True``. This does **not** mean that no
+ threads are used at all if ``threaded=False``.
+
+ Regardless of these threading options, these threads will
+ always exist:
+
+ - The event queue processor
+ - The send queue processor
+ - The scheduler
+ """
+ for name in self.plugin:
+ if not self.plugin[name].post_inited:
+ self.plugin[name].post_init()
+ return XMLStream.process(self, *args, **kwargs)
+
+ def register_plugin(self, plugin, pconfig={}, module=None):
+ """Register and configure a plugin for use in this stream.
+
+ :param plugin: The name of the plugin class. Plugin names must
+ be unique.
+ :param pconfig: A dictionary of configuration data for the plugin.
+ Defaults to an empty dictionary.
+ :param module: Optional refence to the module containing the plugin
+ class if using custom plugins.
+ """
+ try:
+ # Import the given module that contains the plugin.
+ if not module:
+ try:
+ module = sleekxmpp.plugins
+ module = __import__(
+ str("%s.%s" % (module.__name__, plugin)),
+ globals(), locals(), [str(plugin)])
+ except ImportError:
+ module = sleekxmpp.features
+ module = __import__(
+ str("%s.%s" % (module.__name__, plugin)),
+ globals(), locals(), [str(plugin)])
+ if isinstance(module, str):
+ # We probably want to load a module from outside
+ # the sleekxmpp package, so leave out the globals().
+ module = __import__(module, fromlist=[plugin])
+
+ # Use the global plugin config cache, if applicable
+ if not pconfig:
+ pconfig = self.plugin_config.get(plugin, {})
+
+ # Load the plugin class from the module.
+ self.plugin[plugin] = getattr(module, plugin)(self, pconfig)
+
+ # Let XEP/RFC implementing plugins have some extra logging info.
+ spec = '(CUSTOM) %s'
+ if self.plugin[plugin].xep:
+ spec = "(XEP-%s) " % self.plugin[plugin].xep
+ elif self.plugin[plugin].rfc:
+ spec = "(RFC-%s) " % self.plugin[plugin].rfc
+
+ desc = (spec, self.plugin[plugin].description)
+ log.debug("Loaded Plugin %s %s" % desc)
+ except:
+ log.exception("Unable to load plugin: %s", plugin)
+
+ def register_plugins(self):
+ """Register and initialize all built-in plugins.
+
+ Optionally, the list of plugins loaded may be limited to those
+ contained in :attr:`plugin_whitelist`.
+
+ Plugin configurations stored in :attr:`plugin_config` will be used.
+ """
+ if self.plugin_whitelist:
+ plugin_list = self.plugin_whitelist
+ else:
+ plugin_list = plugins.__all__
+
+ for plugin in plugin_list:
+ if plugin in plugins.__all__:
+ self.register_plugin(plugin,
+ self.plugin_config.get(plugin, {}))
+ else:
+ raise NameError("Plugin %s not in plugins.__all__." % plugin)
+
+ # Resolve plugin inter-dependencies.
+ for plugin in self.plugin:
+ self.plugin[plugin].post_init()
+
+ def __getitem__(self, key):
+ """Return a plugin given its name, if it has been registered."""
+ if key in self.plugin:
+ return self.plugin[key]
+ else:
+ log.warning("Plugin '%s' is not loaded.", key)
+ return False
+
+ def get(self, key, default):
+ """Return a plugin given its name, if it has been registered."""
+ return self.plugin.get(key, default)
+
+ def Message(self, *args, **kwargs):
+ """Create a Message stanza associated with this stream."""
+ return Message(self, *args, **kwargs)
+
+ def Iq(self, *args, **kwargs):
+ """Create an Iq stanza associated with this stream."""
+ return Iq(self, *args, **kwargs)
+
+ def Presence(self, *args, **kwargs):
+ """Create a Presence stanza associated with this stream."""
+ return Presence(self, *args, **kwargs)
+
+ def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
+ """Create a new Iq stanza with a given Id and from JID.
+
+ :param id: An ideally unique ID value for this stanza thread.
+ Defaults to 0.
+ :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
+ to use for this stanza.
+ :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
+ for this stanza.
+ :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
+ one of: ``'get'``, ``'set'``, ``'result'``,
+ or ``'error'``.
+ :param iquery: Optional namespace for adding a query element.
+ """
+ iq = self.Iq()
+ iq['id'] = str(id)
+ iq['to'] = ito
+ iq['from'] = ifrom
+ iq['type'] = itype
+ iq['query'] = iquery
+ return iq
+
+ def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
+ """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``.
+
+ Optionally, a query element may be added.
+
+ :param queryxmlns: The namespace of the query to use.
+ :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
+ for this stanza.
+ :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
+ to use for this stanza.
+ :param iq: Optionally use an existing stanza instead
+ of generating a new one.
+ """
+ if not iq:
+ iq = self.Iq()
+ iq['type'] = 'get'
+ iq['query'] = queryxmlns
+ if ito:
+ iq['to'] = ito
+ if ifrom:
+ iq['from'] = ifrom
+ return iq
+
+ def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
+ """
+ Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
+ ``'result'`` with the given ID value.
+
+ :param id: An ideally unique ID value. May use :meth:`new_id()`.
+ :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
+ for this stanza.
+ :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
+ to use for this stanza.
+ :param iq: Optionally use an existing stanza instead
+ of generating a new one.
+ """
+ if not iq:
+ iq = self.Iq()
+ if id is None:
+ id = self.new_id()
+ iq['id'] = id
+ iq['type'] = 'result'
+ if ito:
+ iq['to'] = ito
+ if ifrom:
+ iq['from'] = ifrom
+ return iq
+
+ def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
+ """
+ Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``.
+
+ Optionally, a substanza may be given to use as the
+ stanza's payload.
+
+ :param sub: Either an
+ :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
+ stanza object or an
+ :class:`~xml.etree.ElementTree.Element` XML object
+ to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
+ :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
+ for this stanza.
+ :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
+ to use for this stanza.
+ :param iq: Optionally use an existing stanza instead
+ of generating a new one.
+ """
+ if not iq:
+ iq = self.Iq()
+ iq['type'] = 'set'
+ if sub != None:
+ iq.append(sub)
+ if ito:
+ iq['to'] = ito
+ if ifrom:
+ iq['from'] = ifrom
+ return iq
+
+ def make_iq_error(self, id, type='cancel',
+ condition='feature-not-implemented',
+ text=None, ito=None, ifrom=None, iq=None):
+ """
+ Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
+
+ :param id: An ideally unique ID value. May use :meth:`new_id()`.
+ :param type: The type of the error, such as ``'cancel'`` or
+ ``'modify'``. Defaults to ``'cancel'``.
+ :param condition: The error condition. Defaults to
+ ``'feature-not-implemented'``.
+ :param text: A message describing the cause of the error.
+ :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
+ for this stanza.
+ :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
+ to use for this stanza.
+ :param iq: Optionally use an existing stanza instead
+ of generating a new one.
+ """
+ if not iq:
+ iq = self.Iq()
+ iq['id'] = id
+ iq['error']['type'] = type
+ iq['error']['condition'] = condition
+ iq['error']['text'] = text
+ if ito:
+ iq['to'] = ito
+ if ifrom:
+ iq['from'] = ifrom
+ return iq
+
+ def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
+ """
+ Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
+ to use the given query namespace.
+
+ :param iq: Optionally use an existing stanza instead
+ of generating a new one.
+ :param xmlns: The query's namespace.
+ :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
+ for this stanza.
+ :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
+ to use for this stanza.
+ """
+ if not iq:
+ iq = self.Iq()
+ iq['query'] = xmlns
+ if ito:
+ iq['to'] = ito
+ if ifrom:
+ iq['from'] = ifrom
+ return iq
+
+ def make_query_roster(self, iq=None):
+ """Create a roster query element.
+
+ :param iq: Optionally use an existing stanza instead
+ of generating a new one.
+ """
+ if iq:
+ iq['query'] = 'jabber:iq:roster'
+ return ET.Element("{jabber:iq:roster}query")
+
+ def make_message(self, mto, mbody=None, msubject=None, mtype=None,
+ mhtml=None, mfrom=None, mnick=None):
+ """
+ Create and initialize a new
+ :class:`~sleekxmpp.stanza.message.Message` stanza.
+
+ :param mto: The recipient of the message.
+ :param mbody: The main contents of the message.
+ :param msubject: Optional subject for the message.
+ :param mtype: The message's type, such as ``'chat'`` or
+ ``'groupchat'``.
+ :param mhtml: Optional HTML body content in the form of a string.
+ :param mfrom: The sender of the message. if sending from a client,
+ be aware that some servers require that the full JID
+ of the sender be used.
+ :param mnick: Optional nickname of the sender.
+ """
+ message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
+ message['body'] = mbody
+ message['subject'] = msubject
+ if mnick is not None:
+ message['nick'] = mnick
+ if mhtml is not None:
+ message['html']['body'] = mhtml
+ return message
+
+ def make_presence(self, pshow=None, pstatus=None, ppriority=None,
+ pto=None, ptype=None, pfrom=None, pnick=None):
+ """
+ Create and initialize a new
+ :class:`~sleekxmpp.stanza.presence.Presence` stanza.
+
+ :param pshow: The presence's show value.
+ :param pstatus: The presence's status message.
+ :param ppriority: This connection's priority.
+ :param pto: The recipient of a directed presence.
+ :param ptype: The type of presence, such as ``'subscribe'``.
+ :param pfrom: The sender of the presence.
+ :param pnick: Optional nickname of the presence's sender.
+ """
+ presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
+ if pshow is not None:
+ presence['type'] = pshow
+ if pfrom is None and self.is_component:
+ presence['from'] = self.boundjid.full
+ presence['priority'] = ppriority
+ presence['status'] = pstatus
+ presence['nick'] = pnick
+ return presence
+
+ def send_message(self, mto, mbody, msubject=None, mtype=None,
+ mhtml=None, mfrom=None, mnick=None):
+ """
+ Create, initialize, and send a new
+ :class:`~sleekxmpp.stanza.message.Message` stanza.
+
+ :param mto: The recipient of the message.
+ :param mbody: The main contents of the message.
+ :param msubject: Optional subject for the message.
+ :param mtype: The message's type, such as ``'chat'`` or
+ ``'groupchat'``.
+ :param mhtml: Optional HTML body content in the form of a string.
+ :param mfrom: The sender of the message. if sending from a client,
+ be aware that some servers require that the full JID
+ of the sender be used.
+ :param mnick: Optional nickname of the sender.
+ """
+ self.make_message(mto, mbody, msubject, mtype,
+ mhtml, mfrom, mnick).send()
+
+ def send_presence(self, pshow=None, pstatus=None, ppriority=None,
+ pto=None, pfrom=None, ptype=None, pnick=None):
+ """
+ Create, initialize, and send a new
+ :class:`~sleekxmpp.stanza.presence.Presence` stanza.
+
+ :param pshow: The presence's show value.
+ :param pstatus: The presence's status message.
+ :param ppriority: This connection's priority.
+ :param pto: The recipient of a directed presence.
+ :param ptype: The type of presence, such as ``'subscribe'``.
+ :param pfrom: The sender of the presence.
+ :param pnick: Optional nickname of the presence's sender.
+ """
+ # Python2.6 chokes on Unicode strings for dict keys.
+ args = {str('pto'): pto,
+ str('ptype'): ptype,
+ str('pshow'): pshow,
+ str('pstatus'): pstatus,
+ str('ppriority'): ppriority,
+ str('pnick'): pnick}
+
+ if self.is_component:
+ self.roster[pfrom].send_presence(**args)
+ else:
+ self.client_roster.send_presence(**args)
+
+ def send_presence_subscription(self, pto, pfrom=None,
+ ptype='subscribe', pnick=None):
+ """
+ Create, initialize, and send a new
+ :class:`~sleekxmpp.stanza.presence.Presence` stanza of
+ type ``'subscribe'``.
+
+ :param pto: The recipient of a directed presence.
+ :param pfrom: The sender of the presence.
+ :param ptype: The type of presence, such as ``'subscribe'``.
+ :param pnick: Optional nickname of the presence's sender.
+ """
+ presence = self.makePresence(ptype=ptype,
+ pfrom=pfrom,
+ pto=self.getjidbare(pto))
+ if pnick:
+ nick = ET.Element('{http://jabber.org/protocol/nick}nick')
+ nick.text = pnick
+ presence.append(nick)
+ presence.send()
+
+ @property
+ def jid(self):
+ """Attribute accessor for bare jid"""
+ log.warning("jid property deprecated. Use boundjid.bare")
+ return self.boundjid.bare
+
+ @jid.setter
+ def jid(self, value):
+ log.warning("jid property deprecated. Use boundjid.bare")
+ self.boundjid.bare = value
+
+ @property
+ def fulljid(self):
+ """Attribute accessor for full jid"""
+ log.warning("fulljid property deprecated. Use boundjid.full")
+ return self.boundjid.full
+
+ @fulljid.setter
+ def fulljid(self, value):
+ log.warning("fulljid property deprecated. Use boundjid.full")
+ self.boundjid.full = value
+
+ @property
+ def resource(self):
+ """Attribute accessor for jid resource"""
+ log.warning("resource property deprecated. Use boundjid.resource")
+ return self.boundjid.resource
+
+ @resource.setter
+ def resource(self, value):
+ log.warning("fulljid property deprecated. Use boundjid.full")
+ self.boundjid.resource = value
+
+ @property
+ def username(self):
+ """Attribute accessor for jid usernode"""
+ log.warning("username property deprecated. Use boundjid.user")
+ return self.boundjid.user
+
+ @username.setter
+ def username(self, value):
+ log.warning("username property deprecated. Use boundjid.user")
+ self.boundjid.user = value
+
+ @property
+ def server(self):
+ """Attribute accessor for jid host"""
+ log.warning("server property deprecated. Use boundjid.host")
+ return self.boundjid.server
+
+ @server.setter
+ def server(self, value):
+ log.warning("server property deprecated. Use boundjid.host")
+ self.boundjid.server = value
+
+ @property
+ def auto_authorize(self):
+ """Auto accept or deny subscription requests.
+
+ If ``True``, auto accept subscription requests.
+ If ``False``, auto deny subscription requests.
+ If ``None``, don't automatically respond.
+ """
+ return self.roster.auto_authorize
+
+ @auto_authorize.setter
+ def auto_authorize(self, value):
+ self.roster.auto_authorize = value
+
+ @property
+ def auto_subscribe(self):
+ """Auto send requests for mutual subscriptions.
+
+ If ``True``, auto send mutual subscription requests.
+ """
+ return self.roster.auto_subscribe
+
+ @auto_subscribe.setter
+ def auto_subscribe(self, value):
+ self.roster.auto_subscribe = value
+
+ def set_jid(self, jid):
+ """Rip a JID apart and claim it as our own."""
+ log.debug("setting jid to %s", jid)
+ self.boundjid.full = jid
+
+ def getjidresource(self, fulljid):
+ if '/' in fulljid:
+ return fulljid.split('/', 1)[-1]
+ else:
+ return ''
+
+ def getjidbare(self, fulljid):
+ return fulljid.split('/', 1)[0]
+
+ def _handle_disconnected(self, event):
+ """When disconnected, reset the roster"""
+ self.roster.reset()
+
+ def _handle_stream_error(self, error):
+ self.event('stream_error', error)
+
+ def _handle_message(self, msg):
+ """Process incoming message stanzas."""
+ 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_unavailable(self, presence):
+ pto = presence['to'].bare
+ pfrom = presence['from'].bare
+ self.roster[pto][pfrom].handle_unavailable(presence)
+
+ def _handle_new_subscription(self, stanza):
+ """Attempt to automatically handle subscription requests.
+
+ Subscriptions will be approved if the request is from
+ a whitelisted JID, of :attr:`auto_authorize` is True. They
+ will be rejected if :attr:`auto_authorize` is False. Setting
+ :attr:`auto_authorize` to ``None`` will disable automatic
+ subscription handling (except for whitelisted JIDs).
+
+ 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]
+ if item['whitelisted']:
+ item.authorize()
+ elif roster.auto_authorize:
+ item.authorize()
+ if roster.auto_subscribe:
+ item.subscribe()
+ 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_subscribe(self, presence):
+ pto = presence['to'].bare
+ pfrom = presence['from'].bare
+ self.roster[pto][pfrom].handle_subscribe(presence)
+
+ def _handle_subscribed(self, presence):
+ pto = presence['to'].bare
+ pfrom = presence['from'].bare
+ self.roster[pto][pfrom].handle_subscribed(presence)
+
+ def _handle_unsubscribe(self, presence):
+ pto = presence['to'].bare
+ pfrom = presence['from'].bare
+ self.roster[pto][pfrom].handle_unsubscribe(presence)
+
+ def _handle_unsubscribed(self, presence):
+ pto = presence['to'].bare
+ pfrom = presence['from'].bare
+ self.roster[pto][pfrom].handle_unsubscribed(presence)
+
+ def _handle_presence(self, presence):
+ """Process incoming presence stanzas.
+
+ Update the roster with presence information.
+ """
+ self.event("presence_%s" % presence['type'], presence)
+
+ # Check for changes in subscription state.
+ if presence['type'] in ('subscribe', 'subscribed',
+ 'unsubscribe', 'unsubscribed'):
+ self.event('changed_subscription', presence)
+ return
+ elif not presence['type'] in ('available', 'unavailable') and \
+ not presence['type'] in presence.showtypes:
+ return
+
+ def exception(self, exception):
+ """Process any uncaught exceptions, notably
+ :class:`~sleekxmpp.exceptions.IqError` and
+ :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
+
+ :param exception: An unhandled :class:`Exception` object.
+ """
+ if isinstance(exception, IqError):
+ iq = exception.iq
+ log.error('%s: %s', iq['error']['condition'],
+ iq['error']['text'])
+ log.warning('You should catch IqError exceptions')
+ elif isinstance(exception, IqTimeout):
+ iq = exception.iq
+ log.error('Request timed out: %s', iq)
+ log.warning('You should catch IqTimeout exceptions')
+ elif isinstance(exception, SyntaxError):
+ # Hide stream parsing errors that occur when the
+ # stream is disconnected (they've been handled, we
+ # don't need to make a mess in the logs).
+ pass
+ else:
+ log.exception(exception)
+
+
+# Restore the old, lowercased name for backwards compatibility.
+basexmpp = BaseXMPP
+
+# To comply with PEP8, method names now use underscores.
+# Deprecated method names are re-mapped for backwards compatibility.
+BaseXMPP.registerPlugin = BaseXMPP.register_plugin
+BaseXMPP.makeIq = BaseXMPP.make_iq
+BaseXMPP.makeIqGet = BaseXMPP.make_iq_get
+BaseXMPP.makeIqResult = BaseXMPP.make_iq_result
+BaseXMPP.makeIqSet = BaseXMPP.make_iq_set
+BaseXMPP.makeIqError = BaseXMPP.make_iq_error
+BaseXMPP.makeIqQuery = BaseXMPP.make_iq_query
+BaseXMPP.makeQueryRoster = BaseXMPP.make_query_roster
+BaseXMPP.makeMessage = BaseXMPP.make_message
+BaseXMPP.makePresence = BaseXMPP.make_presence
+BaseXMPP.sendMessage = BaseXMPP.send_message
+BaseXMPP.sendPresence = BaseXMPP.send_presence
+BaseXMPP.sendPresenceSubscription = BaseXMPP.send_presence_subscription