From 007b04dd3086a0b02865c924022db70bbd47445e Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 10 Dec 2009 01:23:03 +0000 Subject: * added proper message and iq stanzas. presence left to do --- sleekxmpp/__init__.py | 4 +- sleekxmpp/basexmpp.py | 4 + sleekxmpp/stanza/iq.py | 39 +++++++ sleekxmpp/stanza/message.py | 39 +++++++ sleekxmpp/xmlstream/stanzabase.py | 215 +++++++++++++++++++++++++++++++++----- sleekxmpp/xmlstream/xmlstream.py | 13 ++- 6 files changed, 279 insertions(+), 35 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index f95c6665..49c9a66b 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -37,7 +37,7 @@ import sys import random import copy from . import plugins -from . import stanza +#from . import stanza srvsupport = True try: import dns.resolver @@ -87,6 +87,8 @@ class ClientXMPP(basexmpp, XMLStream): #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: diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 07715e0c..0f4f799c 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -26,6 +26,8 @@ from . xmlstream.handler.xmlcallback import XMLCallback from . xmlstream.handler.xmlwaiter import XMLWaiter from . xmlstream.handler.callback import Callback from . import plugins +from . stanza.message import Message +from . stanza.iq import Iq import logging import threading @@ -95,6 +97,8 @@ class basexmpp(object): 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.registerStanza(Message) + self.registerStanza(Iq) def set_jid(self, jid): """Rip a JID apart and claim it as our own.""" diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index e69de29b..eece37bd 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -0,0 +1,39 @@ +from .. xmlstream.stanzabase import StanzaBase +from xml.etree import cElementTree as ET + +class Iq(StanzaBase): + interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) + types = set(('get', 'result', 'set', 'error')) + name = 'iq' + namespace = 'jabber:client' + + def __init__(self, *args, **kwargs): + StanzaBase.__init__(self, *args, **kwargs) + if self['id'] == '': + self['id'] = self.stream.getId() + print("________LOADED IQ CLASS") + + def result(self): + self['type'] = 'result' + return self + + def set(self): + self['type'] = 'set' + return self + + def error(self): + #TODO add error payloads + self['type'] = 'error' + return self + + def get(self): + self['type'] = 'get' + return self + + def setPayload(self, value): + self.clear() + StanzaBase.setPayload(self, value) + + def unhandled(self): + pass + # returned unhandled error diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py index e69de29b..37e2e5f3 100644 --- a/sleekxmpp/stanza/message.py +++ b/sleekxmpp/stanza/message.py @@ -0,0 +1,39 @@ +from .. xmlstream.stanzabase import StanzaBase +from xml.etree import cElementTree as ET + +class Message(StanzaBase): + interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject')) + types = set((None, 'normal', 'chat', 'headline', 'error')) + sub_interfaces = set(('body', 'subject')) + name = 'message' + namespace = 'jabber:client' + + def getType(self): + return self.xml.attrib.get('type', 'normal') + + def chat(self): + self['type'] = 'chat' + return self + + def normal(self): + self['type'] = 'normal' + 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']) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 5232ff5e..787ae5e3 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -1,37 +1,198 @@ -from __future__ import absolute_import -from sleekxmpp.xmlstream.matcher.xpath import MatchXPath +from xml.etree import cElementTree as ET class StanzaBase(object): + name = 'stanza' + namespace = 'jabber:client' + interfaces = set(('type', 'to', 'from', 'id', 'payload')) + types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) + sub_interfaces = tuple() - MATCHER = MatchXPath("") - - def __init__(self, stream, xml=None, extensions=[]): - self.extensions = extensions - self.p = {} #plugins - - self.xml = xml + def __init__(self, stream, xml=None, stype=None, sto=None, sfrom=None, sid=None): self.stream = stream - if xml is not None: - self.fromXML(xml) - - def fromXML(self, xml): - "Initialize based on incoming XML" - self._processXML(xml) - for ext in self.extensions: - ext.fromXML(self, xml) + 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) + + def match(self, xml): + return xml.tag == self.tag + def __getitem__(self, attrib): + if attrib in self.interfaces: + if hasattr(self, "get%s" % attrib.title()): + return getattr(self, "get%s" % attrib.title())() + else: + if attrib in self.sub_interfaces: + return self._getSubText(attrib) + else: + return self._getAttr(attrib) + else: + return '' - def _processXML(self, xml, cur_ns=''): - if '}' in xml.tag: - ns,tag = xml.tag[1:].split('}') + def __setitem__(self, attrib, value): + if attrib.lower() in self.interfaces: + if value is not None: + if hasattr(self, "set%s" % attrib.title()): + getattr(self, "set%s" % attrib.title())(value,) + else: + if attrib in self.sub_interfaces: + return self._setSubText(attrib, text=value) + else: + self._setAttr(attrib, value) + else: + self.__delitem__(attrib) + return self + + def __delitem__(self, attrib): + if attrib.lower() in self.interfaces: + if hasattr(self, "del%s" % attrib.title()): + getattr(self, "del%s" % attrib.title())() + else: + if attrib in self.sub_interfaces: + 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: - tag = xml.tag + raise ValueError + return self + + def getPayload(self): + return self.xml.getchildren() - def toXML(self, xml): - "Set outgoing XML" + def setPayload(self, value): + self.xml.append(value) - def extend(self, extension_class, xml=None): - "Initialize extension" + def delPayload(self): + self.clear() - def match(self, xml): - return self.MATCHER.match(xml) + def clear(self): + for child in self.xml.getchildren(): + self.xml.remove(child) + + def reply(self): + self['from'], self['to'] = self['to'], self['from'] + return self + + def error(self): + self['type'] = 'error' + + def _setAttr(self, name, value): + self.xml.attrib[name] = value + + def _delAttr(self, name): + if name in self.xml.attrib: + del self.xml.attrib[name] + + def _getAttr(self, name): + return self.xml.attrib.get(name, '') + + def _getSubText(self, name): + stanza = self.xml.find("{%s}%s" % (self.namespace, name)) + if stanza is None or stanza.text is None: + return '' + else: + return stanza.text + + def _setSubText(self, name, attrib={}, text=None): + stanza = self.xml.find("{%s}%s" % (self.namespace, name)) + if stanza is None: + self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib)) + stanza = self.xml.find("{%s}%s" % (self.namespace, name)) + if text is not None: + stanza.text = text + return stanza + + def _delSub(self, name): + for child in self.xml.getchildren(): + if child.tag == "{%s}%s" % (self.namespace, name): + self.xml.remove(child) + + def unhandled(self): + pass + + def send(self): + self.stream.sendRaw(str(self)) + + def __str__(self, xml=None, xmlns='', stringbuffer=''): + if xml is None: + xml = self.xml + newoutput = [stringbuffer] + #TODO respect ET mapped namespaces + itag = xml.tag.split('}', 1)[-1] + if '}' in xml.tag: + ixmlns = xml.tag.split('}', 1)[0][1:] + else: + ixmlns = '' + nsbuffer = '' + #if xmlns != ixmlns and ixmlns != '': + # if ixmlns in self.namespace_map: + # if self.namespace_map[ixmlns] != '': + # itag = "%s:%s" % (self.namespace_map[ixmlns], itag) + # else: + # nsbuffer = """ xmlns="%s\"""" % ixmlns + if ixmlns not in (xmlns, self.namespace): + nsbuffer = """ xmlns="%s\"""" % ixmlns + newoutput.append("<%s" % itag) + newoutput.append(nsbuffer) + for attrib in xml.attrib: + newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) + if len(xml) or xml.text or xml.tail: + newoutput.append(">") + if xml.text: + newoutput.append(self.xmlesc(xml.text)) + if len(xml): + for child in xml.getchildren(): + newoutput.append(self.__str__(child, ixmlns)) + newoutput.append("" % (itag, )) + if xml.tail: + newoutput.append(self.xmlesc(xml.tail)) + elif xml.text: + newoutput.append(">%s" % (self.xmlesc(xml.text), itag)) + else: + newoutput.append(" />") + return ''.join(newoutput) + + def xmlesc(self, text): + text = list(text) + cc = 0 + matches = ('&', '<', '"', '>', "'") + for c in text: + if c in matches: + if c == '&': + text[cc] = '&' + elif c == '<': + text[cc] = '<' + elif c == '>': + text[cc] = '>' + elif c == "'": + text[cc] = ''' + elif self.escape_quotes: + 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)) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 8ffb7841..8971a02c 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -44,7 +44,7 @@ class XMLStream(object): self.__thread = {} - self.__root_stanza = {} + self.__root_stanza = [] self.__stanza = {} self.__stanza_extension = {} self.__handlers = [] @@ -251,11 +251,13 @@ class XMLStream(object): xmlobj = self.incoming_filter(xmlobj) stanza = None for stanza_class in self.__root_stanza: - if self.__root_stanza[stanza_class].match(xmlobj): + if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name): + #if self.__root_stanza[stanza_class].match(xmlobj): stanza = stanza_class(self, xmlobj) break if stanza is None: stanza = StanzaBase(self, xmlobj) + logging.debug(self.__handlers) for handler in self.__handlers: if handler.match(xmlobj): handler.prerun(stanza) @@ -293,12 +295,9 @@ class XMLStream(object): return idx += 1 - def registerStanza(self, matcher, stanza_class, root=True): + def registerStanza(self, stanza_class): "Adds stanza. If root stanzas build stanzas sent in events while non-root stanzas build substanza objects." - if root: - self.__root_stanza[stanza_class] = matcher - else: - self.__stanza[stanza_class] = matcher + self.__root_stanza.append(stanza_class) def registerStanzaExtension(self, stanza_class, stanza_extension): if stanza_class not in stanza_extensions: -- cgit v1.2.3