SleekXMPP/sleekxmpp/basexmpp.py

308 lines
11 KiB
Python
Raw Normal View History

2009-06-03 22:56:51 +00:00
"""
SleekXMPP: The Sleek XMPP Library
2010-03-26 21:32:16 +00:00
Copyright (C) 2010 Nathanael C. Fritz
2009-06-03 22:56:51 +00:00
This file is part of SleekXMPP.
2010-03-26 21:32:16 +00:00
See the file license.txt for copying permission.
2009-06-03 22:56:51 +00:00
"""
2010-01-08 06:03:02 +00:00
from __future__ import with_statement, unicode_literals
2009-06-03 22:56:51 +00:00
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
2009-06-03 22:56:51 +00:00
from . xmlstream.handler.callback import Callback
from . xmlstream.stanzabase import registerStanzaPlugin
2009-06-03 22:56:51 +00:00
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
2009-06-03 22:56:51 +00:00
import logging
import threading
import copy
2009-06-03 22:56:51 +00:00
2010-02-25 01:12:15 +00:00
import sys
if sys.version_info < (3,0):
reload(sys)
sys.setdefaultencoding('utf8')
2009-06-03 22:56:51 +00:00
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)
2009-06-03 22:56:51 +00:00
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)
2009-06-03 22:56:51 +00:00
def registerPlugin(self, plugin, pconfig = {}):
2009-06-03 22:56:51 +00:00
"""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
2009-06-03 22:56:51 +00:00
__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)
2009-06-03 22:56:51 +00:00
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, threaded, disposable, instream))
2009-06-03 22:56:51 +00:00
def getId(self):
return "%x".upper() % self.id
def sendXML(self, data, mask=None, timeout=10):
return self.send(self.tostring(data), mask, timeout)
2009-06-03 22:56:51 +00:00
def send(self, data, mask=None, timeout=10):
2009-06-03 22:56:51 +00:00
#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)
2009-06-03 22:56:51 +00:00
if mask is not None:
logging.warning("Use of send mask waiters is deprecated")
waitfor = Waiter('SendWait_%s' % self.getNewId(), MatchXMLMask(mask))
2009-06-03 22:56:51 +00:00
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})
2009-06-03 22:56:51 +00:00
def makeIqGet(self, queryxmlns = None):
iq = self.Iq().setStanzaValues({'type': 'get'})
2009-06-03 22:56:51 +00:00
if queryxmlns:
iq.append(ET.Element("{%s}query" % queryxmlns))
2009-06-03 22:56:51 +00:00
return iq
def makeIqResult(self, id):
return self.Iq().setStanzaValues({'id': id, 'type': 'result'})
2009-06-03 22:56:51 +00:00
def makeIqSet(self, sub=None):
iq = self.Iq().setStanzaValues({'type': 'set'})
2009-06-03 22:56:51 +00:00
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})
2009-06-03 22:56:51 +00:00
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])
2009-06-03 22:56:51 +00:00
def event(self, name, eventdata = {}): # called on an event
for handler in self.event_handlers.get(name, []):
handlerdata = copy.copy(eventdata)
2009-06-03 22:56:51 +00:00
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,))
2009-06-03 22:56:51 +00:00
x.start()
else:
handler[0](handlerdata)
2009-06-03 22:56:51 +00:00
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
2010-04-08 06:56:44 +00:00
if mhtml is not None: message['html']['html'] = mhtml
2009-06-03 22:56:51 +00:00
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
2009-06-03 22:56:51 +00:00
return presence
2009-09-05 07:38:29 +00:00
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))
2009-06-03 22:56:51 +00:00
2009-09-25 17:35:10 +00:00
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))
2009-06-03 22:56:51 +00:00
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):
2009-12-10 07:33:59 +00:00
self.event('message', msg)
2009-06-03 22:56:51 +00:00
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
2010-01-28 03:37:26 +00:00
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']
2009-06-03 22:56:51 +00:00
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}
2009-06-03 22:56:51 +00:00
if not resource in self.roster[jid]['presence']:
if (show == 'available' or show in presence.showtypes):
self.event("got_online", presence)
2009-06-03 22:56:51 +00:00
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}
2009-06-03 22:56:51 +00:00
name = self.roster[jid].get('name', '')
if show == 'unavailable':
2010-02-15 23:45:57 +00:00
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']:
2009-11-11 03:14:16 +00:00
del self.roster[jid]
if not wasoffline:
self.event("got_offline", presence)
else:
return False
self.event("changed_status", presence)
2009-06-03 22:56:51 +00:00
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))
2009-06-03 22:56:51 +00:00
if self.auto_subscribe:
self.send(self.makePresence(ptype='subscribe', pto=presence['from'].bare))
2009-06-03 22:56:51 +00:00
elif self.auto_authorize == False:
self.send(self.makePresence(ptype='unsubscribed', pto=presence['from'].bare))