diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py
index afb7d9d..933f661 100644
--- a/sleekxmpp/__init__.py
+++ b/sleekxmpp/__init__.py
@@ -8,7 +8,7 @@
See the file LICENSE for copying permission.
"""
from __future__ import absolute_import, unicode_literals
-from . basexmpp import basexmpp
+from . basexmpp import BaseXMPP, basexmpp
from xml.etree import cElementTree as ET
from . xmlstream.xmlstream import XMLStream
from . xmlstream.xmlstream import RestartStream
@@ -39,17 +39,17 @@ except ImportError:
#class PresenceStanzaType(object):
#
# def fromXML(self, xml):
+
# self.ptype = xml.get('type')
-class ClientXMPP(basexmpp, XMLStream):
+class ClientXMPP(BaseXMPP):
"""SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True):
+ BaseXMPP.__init__(self, 'jabber:client')
global srvsupport
- XMLStream.__init__(self)
self.default_ns = 'jabber:client'
- basexmpp.__init__(self)
self.plugin_config = plugin_config
self.escape_quotes = escape_quotes
self.set_jid(jid)
@@ -80,16 +80,6 @@ class ClientXMPP(basexmpp, XMLStream):
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
#self.register_plugins()
- def __getitem__(self, key):
- if key in self.plugin:
- return self.plugin[key]
- else:
- logging.warning("""Plugin "%s" is not loaded.""" % key)
- return False
-
- def get(self, key, default):
- return self.plugin.get(key, default)
-
def connect(self, address=tuple()):
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
the JID server."""
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index 927e1fc..edb20d0 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -5,238 +5,558 @@
See the file LICENSE for copying permission.
"""
+
from __future__ import with_statement, unicode_literals
-
-from xml.etree import cElementTree as ET
-from . xmlstream.xmlstream import XMLStream
-from . xmlstream.matcher.xmlmask import MatchXMLMask
-from . xmlstream.matcher.many import MatchMany
-from . xmlstream.handler.xmlcallback import XMLCallback
-from . xmlstream.handler.xmlwaiter import XMLWaiter
-from . xmlstream.handler.waiter import Waiter
-from . xmlstream.handler.callback import Callback
-from . xmlstream.stanzabase import registerStanzaPlugin
-from . import plugins
-from . stanza.message import Message
-from . stanza.iq import Iq
-from . stanza.presence import Presence
-from . stanza.roster import Roster
-from . stanza.nick import Nick
-from . stanza.htmlim import HTMLIM
-from . stanza.error import Error
-from sleekxmpp.xmlstream.tostring import tostring
-
-import logging
-import threading
-import copy
-
import sys
+import copy
+import logging
-if sys.version_info < (3,0):
- reload(sys)
- sys.setdefaultencoding('utf8')
+import sleekxmpp
+from sleekxmpp import plugins
-class basexmpp(object):
- def __init__(self):
- self.sentpresence = False
- self.fulljid = ''
- self.resource = ''
- self.jid = ''
- self.username = ''
- self.server = ''
- self.plugin = {}
- self.auto_authorize = True
- self.auto_subscribe = True
- self.roster = {}
- self.registerHandler(Callback('IM', MatchXMLMask("" % self.default_ns), self._handleMessage))
- self.registerHandler(Callback('Presence', MatchXMLMask("" % self.default_ns), self._handlePresence))
- self.add_event_handler('presence_subscribe', self._handlePresenceSubscribe)
- self.registerStanza(Message)
- self.registerStanza(Iq)
- self.registerStanza(Presence)
- registerStanzaPlugin(Iq, Roster)
- registerStanzaPlugin(Message, Nick)
- registerStanzaPlugin(Message, HTMLIM)
+from sleekxmpp.stanza import Message, Presence, Iq, Error
+from sleekxmpp.stanza.roster import Roster
+from sleekxmpp.stanza.nick import Nick
+from sleekxmpp.stanza.htmlim import HTMLIM
- def Message(self, *args, **kwargs):
- return Message(self, *args, **kwargs)
+from sleekxmpp.xmlstream import XMLStream, JID, tostring
+from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin
+from sleekxmpp.xmlstream.matcher import *
+from sleekxmpp.xmlstream.handler import *
- def Iq(self, *args, **kwargs):
- return Iq(self, *args, **kwargs)
- def Presence(self, *args, **kwargs):
- return Presence(self, *args, **kwargs)
+# 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')
- def set_jid(self, jid):
- """Rip a JID apart and claim it as our own."""
- self.fulljid = jid
- self.resource = self.getjidresource(jid)
- self.jid = self.getjidbare(jid)
- self.username = jid.split('@', 1)[0]
- self.server = jid.split('@',1)[-1].split('/', 1)[0]
- def process(self, *args, **kwargs):
- for idx in self.plugin:
- if not self.plugin[idx].post_inited: self.plugin[idx].post_init()
- return super(basexmpp, self).process(*args, **kwargs)
+class BaseXMPP(XMLStream):
- def registerPlugin(self, plugin, pconfig = {}):
- """Register a plugin not in plugins.__init__.__all__ but in the plugins
- directory."""
- # discover relative "path" to the plugins module from the main app, and import it.
- # TODO:
- # gross, this probably isn't necessary anymore, especially for an installed module
- __import__("%s.%s" % (globals()['plugins'].__name__, plugin))
- # init the plugin class
- self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek
- # all of this for a nice debug? sure.
- xep = ''
- if hasattr(self.plugin[plugin], 'xep'):
- xep = "(XEP-%s) " % self.plugin[plugin].xep
- logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description))
+ """
+ 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.
- def register_plugins(self):
- """Initiates all plugins in the plugins/__init__.__all__"""
- 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.registerPlugin(plugin, self.plugin_config.get(plugin, {}), False)
- else:
- raise NameError("No plugin by the name of %s listed in plugins.__all__." % plugin)
- # run post_init() for cross-plugin interaction
- for plugin in self.plugin:
- self.plugin[plugin].post_init()
+ Attributes:
+ auto_authorize -- Manage automatically accepting roster
+ subscriptions.
+ auto_subscribe -- Manage automatically requesting mutual
+ subscriptions.
+ is_component -- Indicates if this stream is for an XMPP component.
+ jid -- The XMPP JID for this stream.
+ plugin -- A dictionary of loaded plugins.
+ plugin_config -- A dictionary of plugin configurations.
+ plugin_whitelist -- A list of approved plugins.
+ sentpresence -- Indicates if an initial presence has been sent.
+ roster -- A dictionary containing subscribed JIDs and
+ their presence statuses.
- def makeIq(self, id=0, ifrom=None):
- return self.Iq().setStanzaValues({'id': str(id), 'from': ifrom})
+ Methods:
+ Iq -- Factory for creating an Iq stanzas.
+ Message -- Factory for creating Message stanzas.
+ Presence -- Factory for creating Presence stanzas.
+ get -- Return a plugin given its name.
+ make_iq -- Create and initialize an Iq stanza.
+ make_iq_error -- Create an Iq stanza of type 'error'.
+ make_iq_get -- Create an Iq stanza of type 'get'.
+ make_iq_query -- Create an Iq stanza with a given query.
+ make_iq_result -- Create an Iq stanza of type 'result'.
+ make_iq_set -- Create an Iq stanza of type 'set'.
+ make_message -- Create and initialize a Message stanza.
+ make_presence -- Create and initialize a Presence stanza.
+ make_query_roster -- Create a roster query.
+ process -- Overrides XMLStream.process.
+ register_plugin -- Load and configure a plugin.
+ register_plugins -- Load and configure multiple plugins.
+ send_message -- Create and send a Message stanza.
+ send_presence -- Create and send a Presence stanza.
+ send_presence_subscribe -- Send a subscription request.
+ """
- def makeIqGet(self, queryxmlns = None):
- iq = self.Iq().setStanzaValues({'type': 'get'})
- if queryxmlns:
- iq.append(ET.Element("{%s}query" % queryxmlns))
- return iq
+ def __init__(self, default_ns='jabber:client'):
+ """
+ Adapt an XML stream for use with XMPP.
- def makeIqResult(self, id):
- return self.Iq().setStanzaValues({'id': id, 'type': 'result'})
+ Arguments:
+ default_ns -- Ensure that the correct default XML namespace
+ is used during initialization.
+ """
+ XMLStream.__init__(self)
- def makeIqSet(self, sub=None):
- iq = self.Iq().setStanzaValues({'type': 'set'})
- if sub != None:
- iq.append(sub)
- return iq
+ # To comply with PEP8, method names now use underscores.
+ # Deprecated method names are re-mapped for backwards compatibility.
+ self.registerPlugin = self.register_plugin
+ self.makeIq = self.make_iq
+ self.makeIqGet = self.make_iq_get
+ self.makeIqResult = self.make_iq_result
+ self.makeIqSet = self.make_iq_set
+ self.makeIqError = self.make_iq_error
+ self.makeIqQuery = self.make_iq_query
+ self.makeQueryRoster = self.make_query_roster
+ self.makeMessage = self.make_message
+ self.makePresence = self.make_presence
+ self.sendMessage = self.send_message
+ self.sendPresence = self.send_presence
+ self.sendPresenceSubscription = self.send_presence_subscription
- def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None):
- iq = self.Iq().setStanzaValues({'id': id})
- iq['error'].setStanzaValues({'type': type, 'condition': condition, 'text': text})
- return iq
+ self.default_ns = default_ns
- def makeIqQuery(self, iq, xmlns):
- query = ET.Element("{%s}query" % xmlns)
- iq.append(query)
- return iq
+ self.jid = ''
+ self.fulljid = ''
+ self.resource = ''
+ self.jid = ''
+ self.username = ''
+ self.server = ''
- def makeQueryRoster(self, iq=None):
- query = ET.Element("{jabber:iq:roster}query")
- if iq:
- iq.append(query)
- return query
+ self.plugin = {}
+ self.roster = {}
+ self.is_component = False
+ self.auto_authorize = True
+ self.auto_subscribe = True
- def makeMessage(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None):
- 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']['html'] = mhtml
- return message
+ self.sentpresence = False
- def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None):
- presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
- if pshow is not None: presence['type'] = pshow
- if pfrom is None: #maybe this should be done in stanzabase
- presence['from'] = self.fulljid
- presence['priority'] = ppriority
- presence['status'] = pstatus
- return presence
+ logging.warning(self.default_ns)
+ 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))
- def sendMessage(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None):
- self.send(self.makeMessage(mto,mbody,msubject,mtype,mhtml,mfrom,mnick))
+ self.add_event_handler('presence_subscribe',
+ self._handle_subscribe)
- def sendPresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None, ptype=None):
- self.send(self.makePresence(pshow,pstatus,ppriority,pto, ptype=ptype, pfrom=pfrom))
- if not self.sentpresence:
- self.event('sent_presence')
- self.sentpresence = True
+ # Set up the XML stream with XMPP's root stanzas.
+ self.registerStanza(Message)
+ self.registerStanza(Iq)
+ self.registerStanza(Presence)
- def sendPresenceSubscription(self, pto, pfrom=None, ptype='subscribe', pnick=None) :
- 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)
- self.send(presence)
+ # Initialize a few default stanza plugins.
+ registerStanzaPlugin(Iq, Roster)
+ registerStanzaPlugin(Message, Nick)
+ registerStanzaPlugin(Message, HTMLIM)
- def getjidresource(self, fulljid):
- if '/' in fulljid:
- return fulljid.split('/', 1)[-1]
- else:
- return ''
+ def process(self, *args, **kwargs):
+ """
+ Ensure that plugin inter-dependencies are handled before starting
+ event processing.
- def getjidbare(self, fulljid):
- return fulljid.split('/', 1)[0]
+ Overrides XMLStream.process.
+ """
+ for name in self.plugin:
+ if not self.plugin[name].post_inited:
+ self.plugin[name].post_init()
+ return XMLStream.process(self, *args, **kwargs)
- def _handleMessage(self, msg):
- self.event('message', msg)
+ def register_plugin(self, plugin, pconfig={}, module=None):
+ """
+ Register and configure a plugin for use in this stream.
- def _handlePresence(self, presence):
- """Update roster items based on presence"""
- self.event("presence_%s" % presence['type'], presence)
- 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
- jid = presence['from'].bare
- resource = presence['from'].resource
- show = presence['type']
- status = presence['status']
- priority = presence['priority']
- wasoffline = False
- oldroster = self.roster.get(jid, {}).get(resource, {})
- if not presence['from'].bare in self.roster:
- self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False}
- if not resource in self.roster[jid]['presence']:
- if (show == 'available' or show in presence.showtypes):
- self.event("got_online", presence)
- wasoffline = True
- self.roster[jid]['presence'][resource] = {}
- if self.roster[jid]['presence'][resource].get('show', 'unavailable') == 'unavailable':
- wasoffline = True
- self.roster[jid]['presence'][resource] = {'show': show, 'status': status, 'priority': priority}
- name = self.roster[jid].get('name', '')
- if show == 'unavailable':
- logging.debug("%s %s got offline" % (jid, resource))
- del self.roster[jid]['presence'][resource]
- if len(self.roster[jid]['presence']) == 0 and not self.roster[jid]['in_roster']:
- del self.roster[jid]
- if not wasoffline:
- self.event("got_offline", presence)
- else:
- return False
- self.event("changed_status", presence)
- name = ''
- if name:
- name = "(%s) " % name
- logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show,status))
+ Arguments:
+ plugin -- The name of the plugin class. Plugin names must
+ be unique.
+ pconfig -- A dictionary of configuration data for the plugin.
+ Defaults to an empty dictionary.
+ 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:
+ module = sleekxmpp.plugins
+ module = __import__("%s.%s" % (module.__name__, plugin),
+ globals(), locals(), [plugin])
- def _handlePresenceSubscribe(self, presence):
- """Handling subscriptions automatically."""
- if self.auto_authorize == True:
- self.send(self.makePresence(ptype='subscribed', pto=presence['from'].bare))
- if self.auto_subscribe:
- self.send(self.makePresence(ptype='subscribe', pto=presence['from'].bare))
- elif self.auto_authorize == False:
- self.send(self.makePresence(ptype='unsubscribed', pto=presence['from'].bare))
+ # Load the plugin class from the module.
+ self.plugin[plugin] = getattr(module, plugin)(self, pconfig)
+
+ # Let XEP implementing plugins have some extra logging info.
+ xep = ''
+ if hasattr(self.plugin[plugin], 'xep'):
+ xep = "(XEP-%s) " % self.plugin[plugin].xep
+
+ desc = (xep, self.plugin[plugin].description)
+ logging.debug("Loaded Plugin %s%s" % desc)
+ except:
+ logging.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 self.plugin_whitelist.
+
+ Plugin configurations stored in self.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:
+ logging.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):
+ """
+ Create a new Iq stanza with a given Id and from JID.
+
+ Arguments:
+ id -- An ideally unique ID value for this stanza thread.
+ Defaults to 0.
+ ifrom -- The from JID to use for this stanza.
+ """
+ return self.Iq().setStanzaValues({'id': str(id),
+ 'from': ifrom})
+
+ def make_iq_get(self, queryxmlns=None):
+ """
+ Create an Iq stanza of type 'get'.
+
+ Optionally, a query element may be added.
+
+ Arguments:
+ queryxmlns -- The namespace of the query to use.
+ """
+ return self.Iq().setStanzaValues({'type': 'get',
+ 'query': queryxmlns})
+
+ def make_iq_result(self, id):
+ """
+ Create an Iq stanza of type 'result' with the given ID value.
+
+ Arguments:
+ id -- An ideally unique ID value. May use self.new_id().
+ """
+ return self.Iq().setStanzaValues({'id': id,
+ 'type': 'result'})
+
+ def make_iq_set(self, sub=None):
+ """
+ Create an Iq stanza of type 'set'.
+
+ Optionally, a substanza may be given to use as the
+ stanza's payload.
+
+ Arguments:
+ sub -- A stanza or XML object to use as the Iq's payload.
+ """
+ iq = self.Iq().setStanzaValues({'type': 'set'})
+ if sub != None:
+ iq.append(sub)
+ return iq
+
+ def make_iq_error(self, id, type='cancel',
+ condition='feature-not-implemented', text=None):
+ """
+ Create an Iq stanza of type 'error'.
+
+ Arguments:
+ id -- An ideally unique ID value. May use self.new_id().
+ type -- The type of the error, such as 'cancel' or 'modify'.
+ Defaults to 'cancel'.
+ condition -- The error condition.
+ Defaults to 'feature-not-implemented'.
+ text -- A message describing the cause of the error.
+ """
+ iq = self.Iq().setStanzaValues({'id': id})
+ iq['error'].setStanzaValues({'type': type,
+ 'condition': condition,
+ 'text': text})
+ return iq
+
+ def make_iq_query(self, iq=None, xmlns=''):
+ """
+ Create or modify an Iq stanza to use the given
+ query namespace.
+
+ Arguments:
+ iq -- Optional Iq stanza to modify. A new
+ stanza is created otherwise.
+ xmlns -- The query's namespace.
+ """
+ if not iq:
+ iq = self.Iq()
+ iq['query'] = xmlns
+ return iq
+
+ def make_query_roster(self, iq=None):
+ """
+ Create a roster query element.
+
+ Arguments:
+ iq -- Optional Iq stanza to modify. A new stanza
+ is created otherwise.
+ """
+ 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 Message stanza.
+
+ Arguments:
+ mto -- The recipient of the message.
+ mbody -- The main contents of the message.
+ msubject -- Optional subject for the message.
+ mtype -- The message's type, such as 'chat' or 'groupchat'.
+ mhtml -- Optional HTML body content.
+ 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.
+ 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):
+ """
+ Create and initialize a new Presence stanza.
+
+ Arguments:
+ pshow -- The presence's show value.
+ pstatus -- The presence's status message.
+ ppriority -- This connections' priority.
+ pto -- The recipient of a directed presence.
+ ptype -- The type of presence, such as 'subscribe'.
+ pfrom -- The sender of the presence.
+ """
+ presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
+ if pshow is not None:
+ presence['type'] = pshow
+ if pfrom is None:
+ presence['from'] = self.fulljid
+ presence['priority'] = ppriority
+ presence['status'] = pstatus
+ return presence
+
+ def send_message(self, mto, mbody, msubject=None, mtype=None,
+ mhtml=None, mfrom=None, mnick=None):
+ """
+ Create, initialize, and send a Message stanza.
+
+
+ """
+ self.makeMessage(mto, mbody, msubject, mtype,
+ mhtml, mfrom, mnick).send()
+
+ def send_presence(self, pshow=None, pstatus=None, ppriority=None,
+ pto=None, pfrom=None, ptype=None):
+ """
+ Create, initialize, and send a Presence stanza.
+
+ Arguments:
+ pshow -- The presence's show value.
+ pstatus -- The presence's status message.
+ ppriority -- This connections' priority.
+ pto -- The recipient of a directed presence.
+ ptype -- The type of presence, such as 'subscribe'.
+ pfrom -- The sender of the presence.
+ """
+ self.makePresence(pshow, pstatus, ppriority, pto,
+ ptype=ptype, pfrom=pfrom).send()
+ # Unexpected errors may occur if
+ if not self.sentpresence:
+ self.event('sent_presence')
+ self.sentpresence = True
+
+ def send_presence_subscription(self, pto, pfrom=None,
+ ptype='subscribe', pnick=None):
+ """
+ Create, initialize, and send a Presence stanza of type 'subscribe'.
+
+ Arguments:
+ pto -- The recipient of a directed presence.
+ pfrom -- The sender of the presence.
+ ptype -- The type of presence. Defaults to 'subscribe'.
+ pnick -- 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()
+
+ def set_jid(self, jid):
+ """Rip a JID apart and claim it as our own."""
+ self.fulljid = jid
+ self.resource = self.getjidresource(jid)
+ self.jid = self.getjidbare(jid)
+ self.username = jid.split('@', 1)[0]
+ self.server = jid.split('@', 1)[-1].split('/', 1)[0]
+
+ 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_message(self, msg):
+ """Process incoming message stanzas."""
+ self.event('message', msg)
+
+ 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
+
+ # Strip the information from the stanza.
+ jid = presence['from'].bare
+ resource = presence['from'].resource
+ show = presence['type']
+ status = presence['status']
+ priority = presence['priority']
+
+ was_offline = False
+ old_roster = self.roster.get(jid, {}).get(resource, {})
+
+ # Create a new roster entry if needed.
+ if not jid in self.roster:
+ self.roster[jid] = {'groups': [],
+ 'name': '',
+ 'subscription': 'none',
+ 'presence': {},
+ 'in_roster': False}
+
+ # Alias to simplify some references.
+ connections = self.roster[jid]['presence']
+
+ # Determine if the user has just come online.
+ if not resource in connections:
+ if show == 'available' or show in presence.showtypes:
+ self.event("got_online", presence)
+ was_offline = True
+ connections[resource] = {}
+
+ if connections[resource].get('show', 'unavailable') == 'unavailable':
+ was_offline = True
+
+ # Update the roster's state for this JID's resource.
+ connections[resource] = {'show': show,
+ 'status': status,
+ 'priority': priority}
+
+ name = self.roster[jid].get('name', '')
+
+ # Remove unneeded state information after a resource
+ # disconnects. Determine if this was the last connection
+ # for the JID.
+ if show == 'unavailable':
+ logging.debug("%s %s got offline" % (jid, resource))
+ del connections[resource]
+
+ if not connections and not self.roster[jid]['in_roster']:
+ del self.roster[jid]
+ if not was_offline:
+ self.event("got_offline", presence)
+ else:
+ return False
+
+ name = '(%s) ' % name if name else ''
+
+ # Presence state has changed.
+ self.event("changed_status", presence)
+ logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
+ show, status))
+
+ def _handle_subscribe(self, presence):
+ """
+ Automatically managage subscription requests.
+
+ Subscription behavior is controlled by the settings
+ self.auto_authorize and self.auto_subscribe.
+
+ auto_auth auto_sub Result:
+ True True Create bi-directional subsriptions.
+ True False Create only directed subscriptions.
+ False * Decline all subscriptions.
+ None * Disable automatic handling and use
+ a custom handler.
+ """
+ presence = self.Presence()
+ presence['to'] = presence['from'].bare
+
+ # We are using trinary logic, so conditions have to be
+ # more explicit than usual.
+ if self.auto_authorize == True:
+ presence['type'] = 'subscribed'
+ presence.send()
+ if self.auto_subscribe:
+ presence['type'] = 'subscribe'
+ presence.send()
+ elif self.auto_authorize == False:
+ presence['type'] = 'unsubscribed'
+ presence.send()
+
+# Restore the old, lowercased name for backwards compatibility.
+basexmpp = BaseXMPP
diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py
index 5534a45..00e5252 100755
--- a/sleekxmpp/componentxmpp.py
+++ b/sleekxmpp/componentxmpp.py
@@ -8,7 +8,7 @@
See the file LICENSE for copying permission.
"""
from __future__ import absolute_import
-from . basexmpp import basexmpp
+from . basexmpp import BaseXMPP
from xml.etree import cElementTree as ET
from . xmlstream.xmlstream import XMLStream
@@ -39,12 +39,11 @@ class ComponentXMPP(basexmpp, XMLStream):
"""SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False):
- XMLStream.__init__(self)
if use_jc_ns:
- self.default_ns = 'jabber:client'
+ default_ns = 'jabber:client'
else:
- self.default_ns = 'jabber:component:accept'
- basexmpp.__init__(self)
+ default_ns = 'jabber:component:accept'
+ BaseXMPP.__init__(self, default_ns)
self.auto_authorize = None
self.stream_header = "" % jid
self.stream_footer = ""
@@ -54,17 +53,7 @@ class ComponentXMPP(basexmpp, XMLStream):
self.secret = secret
self.is_component = True
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
-
- def __getitem__(self, key):
- if key in self.plugin:
- return self.plugin[key]
- else:
- logging.warning("""Plugin "%s" is not loaded.""" % key)
- return False
-
- def get(self, key, default):
- return self.plugin.get(key, default)
-
+
def incoming_filter(self, xmlobj):
if xmlobj.tag.startswith('{jabber:client}'):
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
@@ -80,10 +69,10 @@ class ComponentXMPP(basexmpp, XMLStream):
else:
handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
self.sendXML(handshake)
-
+
def _handleHandshake(self, xml):
self.event("session_start")
-
+
def connect(self):
logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)
diff --git a/tests/test_presencestanzas.py b/tests/test_presencestanzas.py
index d6a5a38..f4a33aa 100644
--- a/tests/test_presencestanzas.py
+++ b/tests/test_presencestanzas.py
@@ -46,7 +46,7 @@ class TestPresenceStanzas(SleekTest):
happened.append(True)
c.add_event_handler("changed_status", handlechangedpresence)
- c._handlePresence(p)
+ c._handle_presence(p)
self.failUnless(happened == [],
"changed_status event triggered for extra unavailable presence")