mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-27 19:19:54 +00:00
Made first pass at cleaning BaseXMPP.
Have not intregrated the new JID class yet.
This commit is contained in:
parent
433c147627
commit
9f0baec7b2
4 changed files with 538 additions and 239 deletions
|
@ -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."""
|
||||
|
|
|
@ -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("<message xmlns='%s'><body /></message>" % self.default_ns), self._handleMessage))
|
||||
self.registerHandler(Callback('Presence', MatchXMLMask("<presence xmlns='%s' />" % 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
|
||||
|
|
|
@ -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 = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid
|
||||
self.stream_footer = "</stream:stream>"
|
||||
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue