slixmpp/sleekxmpp/basexmpp.py

833 lines
31 KiB
Python
Raw Normal View History

2011-12-05 16:55:05 +00:00
# -*- coding: utf-8 -*-
2009-06-03 22:56:51 +00:00
"""
2011-12-05 16:55:05 +00:00
sleekxmpp.basexmpp
~~~~~~~~~~~~~~~~~~
2009-06-03 22:56:51 +00:00
2011-12-05 16:55:05 +00:00
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
2009-06-03 22:56:51 +00:00
"""
2010-01-08 06:03:02 +00:00
from __future__ import with_statement, unicode_literals
import sys
import logging
import threading
from sleekxmpp import plugins, roster, stanza
from sleekxmpp.api import APIRegistry
from sleekxmpp.exceptions import IqError, IqTimeout
2009-06-03 22:56:51 +00:00
from sleekxmpp.stanza import Message, Presence, Iq, StreamError
from sleekxmpp.stanza.roster import Roster
from sleekxmpp.stanza.nick import Nick
from sleekxmpp.stanza.htmlim import HTMLIM
2009-06-03 22:56:51 +00:00
from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS
2013-07-26 11:04:08 +00:00
from sleekxmpp.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 sleekxmpp.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.
2011-12-05 16:55:05 +00:00
: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'
2011-12-05 16:55:05 +00:00
#: An identifier for the stream as given by the server.
self.stream_id = None
#: The JabberID (JID) requested for this connection.
2012-10-24 19:56:54 +00:00
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.
2012-10-24 19:56:54 +00:00
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()
2011-12-05 16:55:05 +00:00
#: A dictionary mapping plugin names to plugins.
self.plugin = PluginManager(self)
2011-12-05 16:55:05 +00:00
#: 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 = {}
2011-12-05 16:55:05 +00:00
#: A list of plugins that will be loaded if
#: :meth:`register_plugins` is called.
self.plugin_whitelist = []
2011-12-05 16:55:05 +00:00
#: 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`.
2010-10-27 12:09:50 +00:00
self.roster = roster.Roster(self)
self.roster.add(self.boundjid)
2011-12-05 16:55:05 +00:00
#: The single roster for the bound JID. This is the
#: equivalent of::
#:
#: self.roster[self.boundjid.bare]
self.client_roster = self.roster[self.boundjid]
2011-12-05 16:55:05 +00:00
#: 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
2011-12-05 16:55:05 +00:00
#: 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:
2012-06-19 08:29:48 +00:00
#:
#: - 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)
2011-12-05 16:55:05 +00:00
#: 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
2011-12-05 16:55:05 +00:00
#: A reference to :mod:`sleekxmpp.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)
2010-10-21 02:33:40 +00:00
self.add_event_handler('disconnected',
self._handle_disconnected)
2010-11-17 15:13:45 +00:00
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.
2010-11-17 15:13:45 +00:00
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):
2011-12-05 16:55:05 +00:00
"""Save the stream ID once the streams have been established.
2011-12-05 16:55:05 +00:00
: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):
2011-12-05 16:55:05 +00:00
"""Initialize plugins and begin processing the XML stream.
The number of threads used for processing stream events is determined
2011-12-05 16:55:05 +00:00
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``.
2012-06-19 08:29:48 +00:00
Regardless of these threading options, these threads will
2011-12-05 16:55:05 +00:00
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):
2011-12-05 16:55:05 +00:00
"""Register and configure a plugin for use in this stream.
2011-12-05 16:55:05 +00:00
:param plugin: The name of the plugin class. Plugin names must
be unique.
2011-12-05 16:55:05 +00:00
: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):
2011-12-05 16:55:05 +00:00
"""Register and initialize all built-in plugins.
Optionally, the list of plugins loaded may be limited to those
2011-12-05 16:55:05 +00:00
contained in :attr:`plugin_whitelist`.
2011-12-05 16:55:05 +00:00
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):
2011-12-05 16:55:05 +00:00
"""Return a plugin given its name, if it has been registered."""
if key in self.plugin:
return self.plugin[key]
else:
2011-11-19 20:07:57 +00:00
log.warning("Plugin '%s' is not loaded.", key)
return False
def get(self, key, default):
2011-12-05 16:55:05 +00:00
"""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
2010-12-28 21:17:08 +00:00
def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
2011-12-05 16:55:05 +00:00
"""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.
2012-06-19 08:29:48 +00:00
:param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
2011-12-05 16:55:05 +00:00
to use for this stanza.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
for this stanza.
2012-06-19 08:29:48 +00:00
:param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
2011-12-05 16:55:05 +00:00
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
2010-12-28 21:17:08 +00:00
iq['query'] = iquery
return iq
def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
2011-12-05 16:55:05 +00:00
"""Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``.
Optionally, a query element may be added.
2011-12-05 16:55:05 +00:00
: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):
"""
2012-06-19 08:29:48 +00:00
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
2011-12-05 16:55:05 +00:00
``'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):
"""
2011-12-05 16:55:05 +00:00
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``.
Optionally, a substanza may be given to use as the
stanza's payload.
2012-06-19 08:29:48 +00:00
:param sub: Either an
2011-12-05 16:55:05 +00:00
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
stanza object or an
2012-06-19 08:29:48 +00:00
:class:`~xml.etree.ElementTree.Element` XML object
2011-12-05 16:55:05 +00:00
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):
"""
2011-12-05 16:55:05 +00:00
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
:param id: An ideally unique ID value. May use :meth:`new_id()`.
2012-06-19 08:29:48 +00:00
:param type: The type of the error, such as ``'cancel'`` or
2011-12-05 16:55:05 +00:00
``'modify'``. Defaults to ``'cancel'``.
2012-06-19 08:29:48 +00:00
:param condition: The error condition. Defaults to
2011-12-05 16:55:05 +00:00
``'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):
"""
2012-06-19 08:29:48 +00:00
Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
2011-12-05 16:55:05 +00:00
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):
2011-12-05 16:55:05 +00:00
"""Create a roster query element.
2011-12-05 16:55:05 +00:00
: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):
"""
2012-06-19 08:29:48 +00:00
Create and initialize a new
2011-12-05 16:55:05 +00:00
: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):
"""
2012-06-19 08:29:48 +00:00
Create and initialize a new
2011-12-05 16:55:05 +00:00
: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):
"""
2012-06-19 08:29:48 +00:00
Create, initialize, and send a new
2011-12-05 16:55:05 +00:00
: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):
"""
2012-06-19 08:29:48 +00:00
Create, initialize, and send a new
2011-12-05 16:55:05 +00:00
: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.
"""
2012-06-19 08:29:48 +00:00
self.make_presence(pshow, pstatus, ppriority, pto,
ptype, pfrom, pnick).send()
def send_presence_subscription(self, pto, pfrom=None,
ptype='subscribe', pnick=None):
"""
2012-06-19 08:29:48 +00:00
Create, initialize, and send a new
2011-12-05 16:55:05 +00:00
:class:`~sleekxmpp.stanza.presence.Presence` stanza of
type ``'subscribe'``.
2011-12-05 16:55:05 +00:00
: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.
"""
2012-06-19 07:06:31 +00:00
self.make_presence(ptype=ptype,
pfrom=pfrom,
pto=JID(pto).bare,
pnick=pnick).send()
@property
def jid(self):
2011-12-05 16:55:05 +00:00
"""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):
2011-12-05 16:55:05 +00:00
"""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
2010-10-17 01:15:31 +00:00
@property
def resource(self):
2011-12-05 16:55:05 +00:00
"""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
2010-10-17 01:15:31 +00:00
@property
def username(self):
2011-12-05 16:55:05 +00:00
"""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):
2011-12-05 16:55:05 +00:00
"""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):
2011-12-05 16:55:05 +00:00
"""Auto accept or deny subscription requests.
2011-12-05 16:55:05 +00:00
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):
2011-12-05 16:55:05 +00:00
"""Auto send requests for mutual subscriptions.
2011-12-05 16:55:05 +00:00
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."""
2011-11-19 20:07:57 +00:00
log.debug("setting jid to %s", jid)
2012-10-24 19:56:54 +00:00
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
2010-10-21 02:33:40 +00:00
def _handle_disconnected(self, event):
"""When disconnected, reset the roster"""
self.roster.reset()
self.session_bind_event.clear()
2010-10-21 02:33:40 +00:00
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):
2011-12-05 16:55:05 +00:00
"""Attempt to automatically handle subscription requests.
2010-11-17 15:13:45 +00:00
Subscriptions will be approved if the request is from
2011-12-05 16:55:05 +00:00
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
2010-11-17 15:13:45 +00:00
subscription handling (except for whitelisted JIDs).
If a subscription is accepted, a request for a mutual
2011-12-05 16:55:05 +00:00
subscription will be sent if :attr:`auto_subscribe` is ``True``.
2010-11-17 15:13:45 +00:00
"""
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)
2010-11-17 15:13:45 +00:00
def _handle_presence(self, presence):
2011-12-05 16:55:05 +00:00
"""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):
2012-06-19 08:29:48 +00:00
"""Process any uncaught exceptions, notably
2011-12-05 16:55:05 +00:00
:class:`~sleekxmpp.exceptions.IqError` and
:class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
2011-12-05 16:55:05 +00:00
:param exception: An unhandled :class:`Exception` object.
"""
if isinstance(exception, IqError):
iq = exception.iq
2011-11-19 20:07:57 +00:00
log.error('%s: %s', iq['error']['condition'],
iq['error']['text'])
log.warning('You should catch IqError exceptions')
elif isinstance(exception, IqTimeout):
iq = exception.iq
2011-11-19 20:07:57 +00:00
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