From 5ab77c745270d7d5c016c1dc7ef2a82533a4b16e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 17 Jul 2014 14:19:04 +0200 Subject: Rename to slixmpp --- slixmpp/basexmpp.py | 832 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 832 insertions(+) create mode 100644 slixmpp/basexmpp.py (limited to 'slixmpp/basexmpp.py') diff --git a/slixmpp/basexmpp.py b/slixmpp/basexmpp.py new file mode 100644 index 00000000..03557218 --- /dev/null +++ b/slixmpp/basexmpp.py @@ -0,0 +1,832 @@ +# -*- coding: utf-8 -*- +""" + slixmpp.basexmpp + ~~~~~~~~~~~~~~~~~~ + + This module provides the common XMPP functionality + for both clients and components. + + Part of Slixmpp: The Slick 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 logging +import threading + +from slixmpp import plugins, roster, stanza +from slixmpp.api import APIRegistry +from slixmpp.exceptions import IqError, IqTimeout + +from slixmpp.stanza import Message, Presence, Iq, StreamError +from slixmpp.stanza.roster import Roster +from slixmpp.stanza.nick import Nick +from slixmpp.stanza.htmlim import HTMLIM + +from slixmpp.xmlstream import XMLStream, JID +from slixmpp.xmlstream import ET, register_stanza_plugin +from slixmpp.xmlstream.matcher import MatchXPath +from slixmpp.xmlstream.handler import Callback +from slixmpp.xmlstream.stanzabase import XML_NS + +from slixmpp.plugins import PluginManager, load_plugin + + +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): + from slixmpp.util.misc_ops import setdefaultencoding + 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) requested for this connection. + self.requested_jid = JID(jid, cache_lock=True) + + #: The JabberID (JID) used by this connection, + #: as set after session binding. This may even be a + #: different bare JID than what was requested. + self.boundjid = JID(jid, cache_lock=True) + + self._expected_server_name = self.boundjid.host + self._redirect_attempts = 0 + + #: The maximum number of consecutive see-other-host + #: redirections that will be followed before quitting. + self.max_redirects = 5 + + self.session_bind_event = threading.Event() + + #: A dictionary mapping plugin names to plugins. + self.plugin = PluginManager(self) + + #: 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) + + #: The single roster for the bound JID. This is the + #: equivalent of:: + #: + #: 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 + #: ``'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: + #: + #: - An API name, e.g. xep_0030 + #: - The name of an action, e.g. get_info + #: - The JID that will be affected + #: - The node that will be affected + #: + #: API handlers with no JID or node will act as global handlers, + #: while those with a JID and no node will service all nodes + #: for a JID, and handlers with both a JID and node will be + #: used only for that specific combination. The handler that + #: provides the most specificity will be used. + self.api = APIRegistry(self) + + #: 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:`slixmpp.stanza` to make accessing + #: stanza classes easier. + self.stanza = 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('session_start', + self._handle_session_start) + 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) + + 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', '') + self.stream_version = xml.get('version', '') + self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None) + + if not self.is_component and not self.stream_version: + log.warning('Legacy XMPP 0.9 protocol detected.') + self.event('legacy_protocol') + + 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 hasattr(self.plugin[name], 'post_inited'): + if hasattr(self.plugin[name], 'post_init'): + self.plugin[name].post_init() + self.plugin[name].post_inited = True + 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. + """ + + # Use the global plugin config cache, if applicable + if not pconfig: + pconfig = self.plugin_config.get(plugin, {}) + + if not self.plugin.registered(plugin): + load_plugin(plugin, module) + self.plugin.enable(plugin, pconfig) + + 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) + else: + raise NameError("Plugin %s not in plugins.__all__." % plugin) + + 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.""" + msg = Message(self, *args, **kwargs) + msg['lang'] = self.default_lang + return msg + + 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.""" + pres = Presence(self, *args, **kwargs) + pres['lang'] = self.default_lang + return pres + + 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:`~slixmpp.xmlstream.jid.JID` + to use for this stanza. + :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + for this stanza. + :param itype: The :class:`~slixmpp.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:`~slixmpp.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:`~slixmpp.xmlstream.jid.JID` + for this stanza. + :param ifrom: The ``'from'`` :class:`~slixmpp.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:`~slixmpp.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:`~slixmpp.xmlstream.jid.JID` + for this stanza. + :param ifrom: The ``'from'`` :class:`~slixmpp.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:`~slixmpp.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:`~slixmpp.xmlstream.stanzabase.ElementBase` + stanza object or an + :class:`~xml.etree.ElementTree.Element` XML object + to use as the :class:`~slixmpp.stanza.iq.Iq`'s payload. + :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID` + for this stanza. + :param ifrom: The ``'from'`` :class:`~slixmpp.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:`~slixmpp.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:`~slixmpp.xmlstream.jid.JID` + for this stanza. + :param ifrom: The ``'from'`` :class:`~slixmpp.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:`~slixmpp.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:`~slixmpp.xmlstream.jid.JID` + for this stanza. + :param ifrom: The ``'from'`` :class:`~slixmpp.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:`~slixmpp.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:`~slixmpp.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:`~slixmpp.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:`~slixmpp.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. + """ + self.make_presence(pshow, pstatus, ppriority, pto, + ptype, pfrom, pnick).send() + + def send_presence_subscription(self, pto, pfrom=None, + ptype='subscribe', pnick=None): + """ + Create, initialize, and send a new + :class:`~slixmpp.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. + """ + self.make_presence(ptype=ptype, + pfrom=pfrom, + pto=JID(pto).bare, + pnick=pnick).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.resource") + 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 = JID(jid, cache_lock=True) + + 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_session_start(self, event): + """Reset redirection attempt count.""" + self._redirect_attempts = 0 + + 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'] + if not other_host: + log.warning("No other host specified.") + return + + if self._redirect_attempts > self.max_redirects: + log.error("Exceeded maximum number of redirection attempts.") + return + + self._redirect_attempts += 1 + + 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, pres): + self.roster[pres['to']][pres['from']].handle_available(pres) + + def _handle_unavailable(self, pres): + self.roster[pres['to']][pres['from']].handle_unavailable(pres) + + def _handle_new_subscription(self, pres): + """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[pres['to']] + item = self.roster[pres['to']][pres['from']] + if item['whitelisted']: + item.authorize() + if roster.auto_subscribe: + item.subscribe() + elif roster.auto_authorize: + item.authorize() + if roster.auto_subscribe: + item.subscribe() + elif roster.auto_authorize == False: + item.unauthorize() + + def _handle_removed_subscription(self, pres): + self.roster[pres['to']][pres['from']].handle_unauthorize(pres) + + def _handle_subscribe(self, pres): + self.roster[pres['to']][pres['from']].handle_subscribe(pres) + + def _handle_subscribed(self, pres): + self.roster[pres['to']][pres['from']].handle_subscribed(pres) + + def _handle_unsubscribe(self, pres): + self.roster[pres['to']][pres['from']].handle_unsubscribe(pres) + + def _handle_unsubscribed(self, pres): + self.roster[pres['to']][pres['from']].handle_unsubscribed(pres) + + def _handle_presence(self, presence): + """Process incoming presence stanzas. + + Update the roster with presence information. + """ + if not self.is_component and not presence['to'].bare: + presence['to'] = self.boundjid + + self.event('presence', presence) + 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:`~slixmpp.exceptions.IqError` and + :class:`~slixmpp.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 -- cgit v1.2.3