From 8854509ccdbc1f9ea74e1eb00f3af098f2a3b6b6 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Fri, 11 Dec 2009 01:29:46 +0000 Subject: [PATCH] * started converstion to stanza objects --- sleekxmpp/__init__.py | 71 +++----- sleekxmpp/basexmpp.py | 237 ++++++-------------------- sleekxmpp/stanza/iq.py | 20 ++- sleekxmpp/stanza/message.py | 21 +-- sleekxmpp/stanza/presence.py | 62 +++++-- sleekxmpp/xmlstream/handler/waiter.py | 3 +- sleekxmpp/xmlstream/stanzabase.py | 209 ++++++++++++++--------- 7 files changed, 276 insertions(+), 347 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index f0be089..91dffa0 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -75,6 +75,7 @@ class ClientXMPP(basexmpp, XMLStream): #self.map_namespace('http://etherx.jabber.org/streams', 'stream') #self.map_namespace('jabber:client', '') self.features = [] + #TODO: Use stream state here self.authenticated = False self.sessionstarted = False self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) @@ -88,14 +89,6 @@ class ClientXMPP(basexmpp, XMLStream): #self.registerStanzaExtension('PresenceStanza', PresenceStanzaType) #self.register_plugins() - def importStanzas(self): - pass - return - for modname in stanza.__all__: - __import__("%s.%s" % (globals()['stanza'].__name__, modname)) - for register in getattr(stanza, modname).stanzas: - self.registerStanza(**register) - def __getitem__(self, key): if key in self.plugin: return self.plugin[key] @@ -109,7 +102,6 @@ class ClientXMPP(basexmpp, XMLStream): def connect(self, address=tuple()): """Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses the JID server.""" - self.importStanzas() if not address or len(address) < 2: if not self.srvsupport: logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using server hostname from JID.") @@ -163,26 +155,15 @@ class ClientXMPP(basexmpp, XMLStream): def updateRoster(self, jid, name=None, subscription=None, groups=[]): """Add or change a roster item.""" - iq = self.makeIqSet() - iq.attrib['from'] = self.fulljid - query = self.makeQueryRoster(iq) - item = ET.Element('item') - item.attrib['jid'] = jid - if name: - item.attrib['name'] = name - if subscription in ['to', 'from', 'both']: - item.attrib['subscription'] = subscription - else: - item.attrib['subscription'] = 'none' - for group in groups: - groupxml = ET.Element('group') - groupxml.text = group - item.append.groupxml - return self.send(iq, self.makeIq(self.getId())) + iq = self.Iq().setValues({'type': 'set'}) + iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} + #self.send(iq, self.Iq().setValues({'id': iq['id']})) + r = iq.send() + return r['type'] == 'result' def getRoster(self): """Request the roster be sent.""" - self.send(self.makeIqGet('jabber:iq:roster')) + self.Iq().setValues({'type': 'get'}).enable('roster').send() def _handleStreamFeatures(self, features): self.features = [] @@ -198,7 +179,7 @@ class ClientXMPP(basexmpp, XMLStream): def handler_starttls(self, xml): if not self.authenticated and self.ssl_support: self.add_handler("", self.handler_tls_start, instream=True) - self.send(xml) + self.sendXML(xml) return True else: logging.warning("The module tlslite is required in to some servers, and has not been found.") @@ -240,14 +221,14 @@ class ClientXMPP(basexmpp, XMLStream): def handler_bind_resource(self, xml): logging.debug("Requesting resource: %s" % self.resource) - out = self.makeIqSet() + iq = self.Iq(stype='set') res = ET.Element('resource') res.text = self.resource xml.append(res) - out.append(xml) - id = out.get('id') - response = self.send(out, self.makeIqResult(id)) - self.set_jid(response.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text) + iq.append(xml) + response = iq.send() + #response = self.send(iq, self.Iq(sid=iq['id'])) + self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text) logging.info("Node set to: %s" % self.fulljid) if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features: logging.debug("Established Session") @@ -261,21 +242,11 @@ class ClientXMPP(basexmpp, XMLStream): self.sessionstarted = True self.event("session_start") - def _handleRoster(self, roster): - xml = roster.xml - xml = roster.xml - roster_update = {} - for item in xml.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'): - if not item.attrib['jid'] in self.roster: - self.roster[item.attrib['jid']] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False} - self.roster[item.attrib['jid']]['name'] = item.get('name', '') - self.roster[item.attrib['jid']]['subscription'] = item.get('subscription', 'none') - self.roster[item.attrib['jid']]['in_roster'] = 'True' - for group in item.findall('{jabber:iq:roster}group'): - self.roster[item.attrib['jid']]['groups'].append(group.text) - if self.roster[item.attrib['jid']]['groups'] == []: - self.roster[item.attrib['jid']]['groups'].append('Default') - roster_update[item.attrib['jid']] = self.roster[item.attrib['jid']] - if xml.get('type', 'result') == 'set': - self.send(self.makeIqResult(xml.get('id', '0'))) - self.event("roster_update", roster_update) + def _handleRoster(self, iq): + for jid in iq['roster']['items']: + if not jid.bare in self.roster: + self.roster[jid.bare] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} + self.roster[jid.bare].update(iq['roster']['jid']) + if iq['type'] == 'set': + self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) + self.event("roster_update", iq) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 0eeb52e..2288e8e 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -28,61 +28,22 @@ from . xmlstream.handler.callback import Callback from . import plugins from . stanza.message import Message from . stanza.iq import Iq +from . stanza.presence import Presence +from . stanza.roster import Roster import logging import threading +def stanzaPlugin(stanza, plugin): + stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin + stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin + +stanzaPlugin(Iq, Roster) + class basexmpp(object): def __init__(self): self.id = 0 self.id_lock = threading.Lock() - self.stanza_errors = { - 'bad-request':False, - 'conflict':False, - 'feature-not-implemented':False, - 'forbidden':False, - 'gone':True, - 'internal-server-error':False, - 'item-not-found':False, - 'jid-malformed':False, - 'not-acceptable':False, - 'not-allowed':False, - 'payment-required':False, - 'recipient-unavailable':False, - 'redirect':True, - 'registration-required':False, - 'remote-server-not-found':False, - 'remote-server-timeout':False, - 'resource-constraint':False, - 'service-unavailable':False, - 'subscription-required':False, - 'undefined-condition':False, - 'unexpected-request':False} - self.stream_errors = { - 'bad-format':False, - 'bad-namespace-prefix':False, - 'conflict':False, - 'connection-timeout':False, - 'host-gone':False, - 'host-unknown':False, - 'improper-addressing':False, - 'internal-server-error':False, - 'invalid-from':False, - 'invalid-id':False, - 'invalid-namespace':False, - 'invalid-xml':False, - 'not-authorized':False, - 'policy-violation':False, - 'remote-connection-failed':False, - 'resource-constraint':False, - 'restricted-xml':False, - 'see-other-host':True, - 'system-shutdown':False, - 'undefined-condition':False, - 'unsupported-encoding':False, - 'unsupported-stanza-type':False, - 'unsupported-version':False, - 'xml-not-well-formed':False} self.sentpresence = False self.fulljid = '' self.resource = '' @@ -94,11 +55,21 @@ class basexmpp(object): self.auto_subscribe = True self.event_handlers = {} self.roster = {} - self.registerHandler(Callback('IM', MatchMany((MatchXMLMask("" % self.default_ns),MatchXMLMask("" % self.default_ns),MatchXMLMask("" % self.default_ns))), self._handleMessage, thread=False)) - self.registerHandler(Callback('Presence', MatchMany((MatchXMLMask("" % self.default_ns),MatchXMLMask("" % self.default_ns),MatchXMLMask("" % self.default_ns))), self._handlePresence, thread=False)) - self.registerHandler(Callback('PresenceSubscribe', MatchMany((MatchXMLMask("" % self.default_ns),MatchXMLMask("" % self.default_ns))), self._handlePresenceSubscribe)) + 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) + + def Message(self, *args, **kwargs): + return Presence(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.""" @@ -147,11 +118,17 @@ class basexmpp(object): def getId(self): return "%x".upper() % self.id + + def sendXML(self, data, mask=None, timeout=10): + return self.send(self.tostring(data), mask, timeout) - def send(self, data, mask=None, timeout=60): + 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 not type(data) == type(''): + # data = self.tostring(data) + if hasattr(mask, 'xml'): + mask = mask.xml + data = str(data) if mask is not None: waitfor = XMLWaiter('SendWait_%s' % self.getNewId(), MatchXMLMask(mask)) self.registerHandler(waitfor) @@ -160,86 +137,28 @@ class basexmpp(object): return waitfor.wait(timeout) def makeIq(self, id=0, ifrom=None): - iq = ET.Element('{%s}iq' % self.default_ns) - if id == 0: - id = self.getNewId() - iq.set('id', str(id)) - if ifrom is not None: - iq.attrib['from'] = ifrom - return iq + return self.Iq().setValues({'id': id, 'from': ifrom}) def makeIqGet(self, queryxmlns = None): - iq = self.makeIq() - iq.set('type', 'get') + iq = self.Iq().setValues({'type': 'get'}) if queryxmlns: - query = ET.Element("{%s}query" % queryxmlns) - iq.append(query) + iq.append(ET.Element("{%s}query" % queryxmlns)) return iq def makeIqResult(self, id): - iq = self.makeIq(id) - iq.set('type', 'result') - return iq + return self.Iq().setValues({'id': id, 'type': 'result'}) def makeIqSet(self, sub=None): - iq = self.makeIq() - iq.set('type', 'set') + iq = self.Iq().setValues({'type': 'set'}) if sub != None: iq.append(sub) return iq - def makeIqError(self, id): - iq = self.makeIq(id) - iq.set('type', 'error') + def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None): + iq = self.Iq().setValues({'id': id}) + iq['error'].setValues({'type': type, 'condition': condition, 'text': text}) return iq - def makeStanzaErrorCondition(self, condition, cdata=None): - if condition not in self.stanza_errors: - raise ValueError() - stanzaError = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}'+condition) - if cdata is not None: - if not self.stanza_errors[condition]: - raise ValueError() - stanzaError.text = cdata - return stanzaError - - - def makeStanzaError(self, condition, errorType, code=None, text=None, customElem=None): - if errorType not in ['auth', 'cancel', 'continue', 'modify', 'wait']: - raise ValueError() - error = ET.Element('error') - error.append(self.makeStanzaErrorCondition(condition)) - error.set('type',errorType) - if code is not None: - error.set('code', code) - if text is not None: - textElem = ET.Element('text') - textElem.text = text - error.append(textElem) - if customElem is not None: - error.append(customElem) - return error - - def makeStreamErrorCondition(self, condition, cdata=None): - if condition not in self.stream_errors: - raise ValueError() - streamError = ET.Element('{urn:ietf:params:xml:ns:xmpp-streams}'+condition) - if cdata is not None: - if not self.stream_errors[condition]: - raise ValueError() - textElem = ET.Element('text') - textElem.text = text - streamError.append(textElem) - - def makeStreamError(self, errorElem, text=None): - error = ET.Element('error') - error.append(errorElem) - if text is not None: - textElem = ET.Element('text') - textElem.text = text - error.append(text) - return error - def makeIqQuery(self, iq, xmlns): query = ET.Element("{%s}query" % xmlns) iq.append(query) @@ -355,61 +274,21 @@ class basexmpp(object): def _handleMessage(self, msg): self.event('message', msg) - #xml = msg.xml - #ns = xml.tag.split('}')[0] - #if ns == 'message': - # ns = '' - #else: - # ns = "%s}" % ns - #mfrom = xml.attrib['from'] - #message = xml.find('%sbody' % ns).text - #subject = xml.find('%ssubject' % ns) - #if subject is not None: - # subject = subject.text - #else: - # subject = '' - #resource = self.getjidresource(mfrom) - #mfrom = self.getjidbare(mfrom) - #mtype = xml.attrib.get('type', 'normal') - #name = self.roster.get('name', '') - #self.event("message", {'jid': mfrom, 'resource': resource, 'name': name, 'type': mtype, 'subject': subject, 'message': message, 'to': xml.attrib.get('to', '')}) - def _handlePresence(self, presence): - xml = presence.xml - ns = xml.tag.split('}')[0] - if ns == 'presence': - ns = '' - else: - ns = "%s}" % ns """Update roster items based on presence""" - show = xml.find('%sshow' % ns) - status = xml.find('%sstatus' % ns) - priority = xml.find('%spriority' % ns) - fulljid = xml.attrib['from'] - to = xml.attrib['to'] - resource = self.getjidresource(fulljid) - if not resource: - resouce = None - jid = self.getjidbare(fulljid) - if type(status) == type(None) or status.text is None: - status = '' - else: - status = status.text - if type(show) == type(None): - show = 'available' - else: - show = show.text - if xml.get('type', None) == 'unavailable': - show = 'unavailable' - if type(priority) == type(None): - priority = 0 - else: - priority = int(priority.text) + self.event("presence_%s" % presence['type'], presence) + if not presence['type'] in ('available', 'unavailable'): + 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 jid in self.roster: - self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False} + 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']: wasoffline = True self.roster[jid]['presence'][resource] = {'show': show, 'status': status, 'priority': priority} @@ -417,12 +296,10 @@ class basexmpp(object): if self.roster[jid]['presence'][resource].get('show', None) == 'unavailable': wasoffline = True self.roster[jid]['presence'][resource] = {'show': show, 'status': status} - if priority: - self.roster[jid]['presence'][resource]['priority'] = priority + self.roster[jid]['presence'][resource]['priority'] = priority name = self.roster[jid].get('name', '') - eventdata = {'jid': jid, 'to': to, 'resource': resource, 'name': name, 'type': show, 'priority': priority, 'message': status} - if wasoffline and show in ('available', 'away', 'xa', 'na'): - self.event("got_online", eventdata) + if wasoffline and show in ('available', 'away', 'xa', 'na', 'ffc'): + self.event("got_online", presence) elif not wasoffline and show == 'unavailable': self.event("got_offline", eventdata) if len(self.roster[jid]['presence']) > 1: @@ -430,7 +307,7 @@ class basexmpp(object): else: del self.roster[jid] elif oldroster != self.roster.get(jid, {'presence': {}})['presence'].get(resource, {}) and show != 'unavailable': - self.event("changed_status", eventdata) + self.event("changed_status", presence) name = '' if name: name = "(%s) " % name @@ -438,13 +315,9 @@ class basexmpp(object): def _handlePresenceSubscribe(self, presence): """Handling subscriptions automatically.""" - xml = presence.xml if self.auto_authorize == True: - #self.updateRoster(self.getjidbare(xml.attrib['from'])) - self.send(self.makePresence(ptype='subscribed', pto=self.getjidbare(xml.attrib['from']))) + self.send(self.makePresence(ptype='subscribed', pto=presence['from'].bare)) if self.auto_subscribe: - self.send(self.makePresence(ptype='subscribe', pto=self.getjidbare(xml.attrib['from']))) + self.send(self.makePresence(ptype='subscribe', pto=presence['from'].bare)) elif self.auto_authorize == False: - self.send(self.makePresence(ptype='unsubscribed', pto=self.getjidbare(xml.attrib['from']))) - elif self.auto_authorize == None: - pass + self.send(self.makePresence(ptype='unsubscribed', pto=presence['from'].bare)) diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index eece37b..68a429e 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -1,5 +1,8 @@ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET +from . error import Error +from .. xmlstream.handler.waiter import Waiter +from .. xmlstream.matcher.id import MatcherId class Iq(StanzaBase): interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) @@ -11,7 +14,6 @@ class Iq(StanzaBase): StanzaBase.__init__(self, *args, **kwargs) if self['id'] == '': self['id'] = self.stream.getId() - print("________LOADED IQ CLASS") def result(self): self['type'] = 'result' @@ -37,3 +39,19 @@ class Iq(StanzaBase): def unhandled(self): pass # returned unhandled error + + def exception(self, traceback=None): + pass + + def send(self, block=True, timeout=10): + if block: + waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) + self.stream.registerHandler(waitfor) + StanzaBase.send(self) + return waitfor.wait(timeout) + else: + return StanzaBase.send(self) + + +Iq.plugin_attrib_map['error'] = Error +Iq.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py index 3f9eb2b..999f792 100644 --- a/sleekxmpp/stanza/message.py +++ b/sleekxmpp/stanza/message.py @@ -1,5 +1,6 @@ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET +from . error import Error class Message(StanzaBase): interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) @@ -25,21 +26,5 @@ class Message(StanzaBase): self['body'] = body return self -if __name__ == '__main__': - m = Message() - m['to'] = 'me' - m['from'] = 'you' - m['type'] = 'chat' - m.reply() - m['body'] = 'Hello there!' - m['subject'] = 'whatever' - m['id'] = 'abc' - print(str(m)) - print(m['body']) - print(m['subject']) - print(m['id']) - m['type'] = None - m['body'] = None - m['id'] = None - print(str(m)) - print(m['type']) +Message.plugin_attrib_map['error'] = Error +Message.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py index 0733e9a..f591211 100644 --- a/sleekxmpp/stanza/presence.py +++ b/sleekxmpp/stanza/presence.py @@ -1,21 +1,51 @@ from .. xmlstream.stanzabase import StanzaBase -from .. xmlstream import xmlstream as xmlstreammod -from .. xmlstream.matcher.xpath import MatchXPath +from xml.etree import cElementTree as ET +from . error import Error -#_bases = [StanzaBase] + xmlstreammod.stanza_extensions.get('PresenceStanza', []) +class Presence(StanzaBase): + interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority')) + types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed')) + showtypes = set(('dnd', 'ffc', 'xa', 'away')) + sub_interfaces = set(('status', 'priority')) + name = 'presence' + namespace = 'jabber:client' -#class PresenceStanza(*_bases): -class PresenceStanza(StanzaBase): + def getShowElement(self): + return self.xml.find("{%s}show" % self.namespace) + + def setType(self, value): + if value in self.types: + show = self.getShowElement() + if value in self.types: + if show is not None: + self.xml.remove(show) + self._setAttr('type', value) + elif value in self.showtypes: + if show is None: + show = ET.Element("{%s}show" % self.namespace) + show.text = value + return self + + def setPriority(self, value): + self._setSubText('priority', str(value)) - def __init__(self, stream, xml=None): - self.pfrom = '' - self.pto = '' - StanzaBase.__init__(self, stream, xml, xmlstreammod.stanza_extensions.get('PresenceStanza', [])) + def getPriority(self): + p = self._getSubText('priority') + if not p: p = 0 + return int(p) + + def getType(self): + out = self._getAttr('type') + if not out: + show = self.getShowElement() + if show is not None: + out = show.text + if not out or out is None: + out = 'available' + return out + + def delType(self): + self.setType('available') - def fromXML(self, xml): - StanzaBase.fromXML(self, xml) - self.pfrom = xml.get('from') - self.pto = xml.get('to') - self.ptype = xml.get('type') - -stanzas = ({'stanza_class': PresenceStanza, 'matcher': MatchXPath('{jabber:client}presence'), 'root': True},) +Presence.plugin_attrib_map['error'] = Error +Presence.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py index 57d96f9..140ce44 100644 --- a/sleekxmpp/xmlstream/handler/waiter.py +++ b/sleekxmpp/xmlstream/handler/waiter.py @@ -1,6 +1,7 @@ from . import base import queue import logging +from .. stanzabase import StanzaBase class Waiter(base.BaseHandler): @@ -19,7 +20,7 @@ class Waiter(base.BaseHandler): return self._payload.get(True, timeout) except queue.Empty: logging.warning("Timed out waiting for %s" % self.name) - return False + return StanzaBase(stype='error') def checkDelete(self): return True diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index b21354e..5403c69 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -13,33 +13,53 @@ class JID(object): return self.jid.split('@', 1)[-1].split('/', 1)[0] elif name == 'full': return self.jid + elif name == 'bare': + return self.jid.split('/', 1)[0] def __str__(self): return self.jid -class StanzaBase(object): +class ElementBase(object): name = 'stanza' + plugin_attrib = 'plugin' namespace = 'jabber:client' interfaces = set(('type', 'to', 'from', 'id', 'payload')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) sub_interfaces = tuple() + plugin_attrib_map = {} + plugin_tag_map = {} - def __init__(self, stream, xml=None, stype=None, sto=None, sfrom=None, sid=None): - self.stream = stream + def __init__(self, xml=None, parent=None): + self.parent = parent self.xml = xml - if xml is None: - self.xml = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) - if stype is not None: - self['type'] = stype - if sto is not None: - self['to'] = sto - if sfrom is not None: - self['from'] = sfrom - self.tag = "{%s}%s" % (self.stream.default_ns, self.name) - + self.plugins = {} + if not self.setup(xml) and len(self.plugin_attrib_map): + for child in self.xml.getchildren(): + if child.tag in self.plugin_tag_map: + self.plugins[self.plugin_tag_map[child.tag].plugin_attrib] = self.plugin_tag_map[child.tag](xml=child, parent=self) + def match(self, xml): return xml.tag == self.tag + def setup(self, xml=None): + if self.xml is None: + self.xml = xml + if self.xml is None: + self.xml = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) + if self.parent is not None: + self.parent.xml.append(self.xml) + return True #had to generate XML + else: + return False + + def enable(self, attrib): + self.initPlugin(attrib) + return self + + def initPlugin(self, attrib): + if attrib not in self.plugins: + self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self) + def __getitem__(self, attrib): if attrib in self.interfaces: if hasattr(self, "get%s" % attrib.title()): @@ -49,11 +69,14 @@ class StanzaBase(object): return self._getSubText(attrib) else: return self._getAttr(attrib) + elif attrib in self.plugin_attrib_map: + self.initPlugin(attrib) + return self.plugins[attrib] else: return '' def __setitem__(self, attrib, value): - if attrib.lower() in self.interfaces: + if attrib in self.interfaces: if value is not None: if hasattr(self, "set%s" % attrib.title()): getattr(self, "set%s" % attrib.title())(value,) @@ -64,6 +87,9 @@ class StanzaBase(object): self._setAttr(attrib, value) else: self.__delitem__(attrib) + elif attrib in self.plugin_map: + self.initPlugin(attrib) + self.plugins[attrib].setValues(value) return self def __delitem__(self, attrib): @@ -75,62 +101,18 @@ class StanzaBase(object): return self._delSub(attrib) else: self._delAttr(attrib) - return self - - def setType(self, value): - if value in self.types: - if value is None and 'type' in self.xml.attrib: - del self.xml.attrib['type'] - elif value is not None: - self.xml.attrib['type'] = value - else: - raise ValueError - return self - - def getPayload(self): - return self.xml.getchildren() - - def setPayload(self, value): - self.xml.append(value) - - def delPayload(self): - self.clear() - - def clear(self): - for child in self.xml.getchildren(): - self.xml.remove(child) - - def reply(self): - self['from'], self['to'] = self['to'], self['from'] - self.clear() + elif attrib in self.plugin_map: + if attrib in self.plugins: + del self.plugins[attrib] return self - def error(self): - self['type'] = 'error' + def __eq__(self, other): + values = self.getValues() + for key in other: + if key not in values or values[key] != other[key]: + return False + return True - def getTo(self): - return JID(self._getAttr('to')) - - def setTo(self, value): - return self._setAttr('to', str(value)) - - def getFrom(self): - return JID(self._getAttr('from')) - - def setFrom(self, value): - return self._setAttr('from', str(value)) - - def getValues(self): - out = {} - for interface in self.interfaces: - out[interface] = self[interface] - return out - - def setValues(self, attrib): - for interface in attrib: - if interface in self.interfaces: - self[interface] = attrib[interface] - def _setAttr(self, name, value): self.xml.attrib[name] = value @@ -162,6 +144,87 @@ class StanzaBase(object): if child.tag == "{%s}%s" % (self.namespace, name): self.xml.remove(child) + def getValues(self): + out = {} + for interface in self.interfaces: + out[interface] = self[interface] + return out + + def setValues(self, attrib): + for interface in attrib: + if interface in self.interfaces: + self[interface] = attrib[interface] + return self + + def append(self, xml): + self.xml.append(xml) + return self + + def __del__(self): + if self.parent is not None: + self.parent.xml.remove(self.xml) + +class StanzaBase(ElementBase): + name = 'stanza' + namespace = 'jabber:client' + interfaces = set(('type', 'to', 'from', 'id', 'payload')) + types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) + sub_interfaces = tuple() + + def __init__(self, stream, xml=None, stype=None, sto=None, sfrom=None, sid=None): + self.stream = stream + self.namespace = stream.default_ns + ElementBase.__init__(self, xml) + if stype is not None: + self['type'] = stype + if sto is not None: + self['to'] = sto + if sfrom is not None: + self['from'] = sfrom + self.tag = "{%s}%s" % (self.stream.default_ns, self.name) + + def setType(self, value): + if value in self.types: + self.xml.attrib['type'] = value + else: + raise ValueError + return self + + def getPayload(self): + return self.xml.getchildren() + + def setPayload(self, value): + self.xml.append(value) + + def delPayload(self): + self.clear() + + def clear(self): + for child in self.xml.getchildren(): + self.xml.remove(child) + for plugin in self.plugins: + del self.plugins[plugin] + + def reply(self): + self['from'], self['to'] = self['to'], self['from'] + self.clear() + return self + + def error(self): + self['type'] = 'error' + + def getTo(self): + return JID(self._getAttr('to')) + + def setTo(self, value): + return self._setAttr('to', str(value)) + + def getFrom(self): + return JID(self._getAttr('from')) + + def setFrom(self, value): + return self._setAttr('from', str(value)) + def unhandled(self): pass @@ -226,15 +289,3 @@ class StanzaBase(object): text[cc] = '"' cc += 1 return ''.join(text) - - -if __name__ == '__main__': - x = Stanza() - x['from'] = 'you' - x['to'] = 'me' - print(x['from'], x['to']) - x.reply() - print(x['from'], x['to']) - x['from'] = None - print(x['from'], x['to']) - print(str(x))