89fb15e896
Updated XMLStream to return True or False from removeHandler to indicate if the handler existed and was removed. Waiter handlers now unregister themselves after timing out.
308 lines
11 KiB
Python
308 lines
11 KiB
Python
"""
|
|
SleekXMPP: The Sleek XMPP Library
|
|
Copyright (C) 2010 Nathanael C. Fritz
|
|
This file is part of SleekXMPP.
|
|
|
|
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
|
|
|
|
if sys.version_info < (3,0):
|
|
reload(sys)
|
|
sys.setdefaultencoding('utf8')
|
|
|
|
class basexmpp(object):
|
|
def __init__(self):
|
|
self.id = 0
|
|
self.id_lock = threading.Lock()
|
|
self.sentpresence = False
|
|
self.fulljid = ''
|
|
self.resource = ''
|
|
self.jid = ''
|
|
self.username = ''
|
|
self.server = ''
|
|
self.plugin = {}
|
|
self.auto_authorize = True
|
|
self.auto_subscribe = True
|
|
self.event_handlers = {}
|
|
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)
|
|
|
|
def Message(self, *args, **kwargs):
|
|
return Message(self, *args, **kwargs)
|
|
|
|
def Iq(self, *args, **kwargs):
|
|
return Iq(self, *args, **kwargs)
|
|
|
|
def Presence(self, *args, **kwargs):
|
|
return Presence(self, *args, **kwargs)
|
|
|
|
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)
|
|
|
|
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))
|
|
|
|
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()
|
|
|
|
def getNewId(self):
|
|
with self.id_lock:
|
|
self.id += 1
|
|
return self.getId()
|
|
|
|
def add_handler(self, mask, pointer, name=None, disposable=False, threaded=False, filter=False, instream=False):
|
|
# threaded is no longer needed, but leaving it for backwards compatibility for now
|
|
if name is None:
|
|
name = 'add_handler_%s' % self.getNewId()
|
|
self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, once=disposable, instream=instream))
|
|
|
|
def getId(self):
|
|
return "%x".upper() % self.id
|
|
|
|
def sendXML(self, data, mask=None, timeout=10):
|
|
return self.send(tostring(data), mask, timeout)
|
|
|
|
def send(self, data, mask=None, timeout=10):
|
|
#logging.warning("Deprecated send used for \"%s\"" % (data,))
|
|
#if not type(data) == type(''):
|
|
# data = self.tostring(data)
|
|
if hasattr(mask, 'xml'):
|
|
mask = mask.xml
|
|
data = str(data)
|
|
if mask is not None:
|
|
logging.warning("Use of send mask waiters is deprecated")
|
|
waitfor = Waiter('SendWait_%s' % self.getNewId(), MatchXMLMask(mask))
|
|
self.registerHandler(waitfor)
|
|
self.sendRaw(data)
|
|
if mask is not None:
|
|
return waitfor.wait(timeout)
|
|
|
|
def makeIq(self, id=0, ifrom=None):
|
|
return self.Iq().setStanzaValues({'id': str(id), 'from': ifrom})
|
|
|
|
def makeIqGet(self, queryxmlns = None):
|
|
iq = self.Iq().setStanzaValues({'type': 'get'})
|
|
if queryxmlns:
|
|
iq.append(ET.Element("{%s}query" % queryxmlns))
|
|
return iq
|
|
|
|
def makeIqResult(self, id):
|
|
return self.Iq().setStanzaValues({'id': id, 'type': 'result'})
|
|
|
|
def makeIqSet(self, sub=None):
|
|
iq = self.Iq().setStanzaValues({'type': 'set'})
|
|
if sub != None:
|
|
iq.append(sub)
|
|
return iq
|
|
|
|
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
|
|
|
|
def makeIqQuery(self, iq, xmlns):
|
|
query = ET.Element("{%s}query" % xmlns)
|
|
iq.append(query)
|
|
return iq
|
|
|
|
def makeQueryRoster(self, iq=None):
|
|
query = ET.Element("{jabber:iq:roster}query")
|
|
if iq:
|
|
iq.append(query)
|
|
return query
|
|
|
|
def add_event_handler(self, name, pointer, threaded=False, disposable=False):
|
|
if not name in self.event_handlers:
|
|
self.event_handlers[name] = []
|
|
self.event_handlers[name].append((pointer, threaded, disposable))
|
|
|
|
def del_event_handler(self, name, pointer):
|
|
"""Remove a handler for an event."""
|
|
if not name in self.event_handlers:
|
|
return
|
|
|
|
# Need to keep handlers that do not use
|
|
# the given function pointer
|
|
def filter_pointers(handler):
|
|
return handler[0] != pointer
|
|
|
|
self.event_handlers[name] = filter(filter_pointers,
|
|
self.event_handlers[name])
|
|
|
|
def event(self, name, eventdata = {}): # called on an event
|
|
for handler in self.event_handlers.get(name, []):
|
|
handlerdata = copy.copy(eventdata)
|
|
if handler[1]: #if threaded
|
|
#thread.start_new(handler[0], (eventdata,))
|
|
x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(handlerdata,))
|
|
x.start()
|
|
else:
|
|
handler[0](handlerdata)
|
|
if handler[2]: #disposable
|
|
with self.lock:
|
|
self.event_handlers[name].pop(self.event_handlers[name].index(handler))
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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))
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
def getjidresource(self, fulljid):
|
|
if '/' in fulljid:
|
|
return fulljid.split('/', 1)[-1]
|
|
else:
|
|
return ''
|
|
|
|
def getjidbare(self, fulljid):
|
|
return fulljid.split('/', 1)[0]
|
|
|
|
def _handleMessage(self, msg):
|
|
self.event('message', msg)
|
|
|
|
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))
|
|
|
|
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))
|