From 629f6e76a9c003ef8befdfdf023f2e5b9eadc718 Mon Sep 17 00:00:00 2001 From: Lance stout Date: Mon, 31 May 2010 13:24:14 -0400 Subject: Added implementation and tests for XEP-0085 - Chat State Notifications. Chat states may be set using: msg['chat_state'].active() msg['chat_state'].composing() msg['chat_state'].gone() msg['chat_state'].inactive() msg['chat_state'].paused() Checking a chat state can be done with either: msg['chat_state'].getState() msg['chat_state'].name When a message with a chat state is receieved, the following events may occur: chatstate_active chatstate_composing chatstate_gone chatstate_inactive chatstate_paused where the event data is the message stanza. Note that currently these events are also triggered for messages sent by SleekXMPP, not just those received. --- sleekxmpp/plugins/__init__.py | 2 +- sleekxmpp/plugins/xep_0085.py | 100 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 sleekxmpp/plugins/xep_0085.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 1868365e..674c3de2 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -17,4 +17,4 @@ along with SleekXMPP; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ -__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] +__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py new file mode 100644 index 00000000..e183ec27 --- /dev/null +++ b/sleekxmpp/plugins/xep_0085.py @@ -0,0 +1,100 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file license.txt for copying permissio +""" + +import logging +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. stanza.message import Message + + +class ChatState(ElementBase): + namespace = 'http://jabber.org/protocol/chatstates' + plugin_attrib = 'chat_state' + interface = set(('state',)) + states = set(('active', 'composing', 'gone', 'inactive', 'paused')) + + def active(self): + self.setState('active') + + def composing(self): + self.setState('composing') + + def gone(self): + self.setState('gone') + + def inactive(self): + self.setState('inactive') + + def paused(self): + self.setState('paused') + + def setState(self, state): + if state in self.states: + self.name = state + self.xml.tag = state + self.xml.attrib['xmlns'] = self.namespace + + def getState(self): + return self.name + +# In order to match the various chat state elements, +# we need one stanza object per state, even though +# they are all the same except for the initial name +# value. Do not depend on the type of the chat state +# stanza object for the actual state. + +class Active(ChatState): + name = 'active' +class Composing(ChatState): + name = 'composing' +class Gone(ChatState): + name = 'gone' +class Inactive(ChatState): + name = 'inactive' +class Paused(ChatState): + name = 'paused' + + +class xep_0085(base.base_plugin): + """ + XEP-0085 Chat State Notifications + """ + + def plugin_init(self): + self.xep = '0085' + self.description = 'Chat State Notifications' + + handlers = [('Active Chat State', 'active'), + ('Composing Chat State', 'composing'), + ('Gone Chat State', 'gone'), + ('Inactive Chat State', 'inactive'), + ('Paused Chat State', 'paused')] + for handler in handlers: + self.xmpp.registerHandler( + Callback(handler[0], + MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns, + ChatState.namespace, + handler[1])), + self._handleChatState)) + + self.xmpp.stanzaPlugin(Message, Active) + self.xmpp.stanzaPlugin(Message, Composing) + self.xmpp.stanzaPlugin(Message, Gone) + self.xmpp.stanzaPlugin(Message, Inactive) + self.xmpp.stanzaPlugin(Message, Paused) + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates') + + def _handleChatState(self, msg): + state = msg['chat_state'].name + logging.debug("Chat State: %s, %s" % (state, msg['from'].jid)) + self.xmpp.event('chatstate_%s' % state, msg) -- cgit v1.2.3 From 332eea3b3bae719c707e99031660197b22fe41ce Mon Sep 17 00:00:00 2001 From: Lance stout Date: Mon, 31 May 2010 13:35:15 -0400 Subject: Make sure that the node is alway set in disco responses. --- sleekxmpp/plugins/xep_0030.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 6a31d243..2c8d4354 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -138,6 +138,9 @@ class DiscoNode(object): self.info = DiscoInfo() self.items = DiscoItems() + self.info['node'] = name + self.items['node'] = name + # This is a bit like poor man's inheritance, but # to simplify adding information to the node we # map node functions to either the info or items -- cgit v1.2.3 From 01e8040a077d4fefdec2c599e75ea93918e9f538 Mon Sep 17 00:00:00 2001 From: Lance stout Date: Tue, 1 Jun 2010 10:51:03 -0400 Subject: Added additional parameter to xep_0030's getInfo and getItems methods. By using dfrom, a server component may send disco requests using any of its JIDS. --- sleekxmpp/plugins/xep_0030.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 2c8d4354..9fcc8b17 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -293,19 +293,19 @@ class xep_0030(base.base_plugin): # Older interface methods for backwards compatibility - def getInfo(self, jid, node=''): + def getInfo(self, jid, node='', dfrom=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = jid - iq['from'] = self.xmpp.fulljid + iq['from'] = dfrom iq['disco_info']['node'] = node iq.send() - def getItems(self, jid, node=''): + def getItems(self, jid, node='', dfrom=None): iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = jid - iq['from'] = self.xmpp.fulljid + iq['from'] = dfrom iq['disco_items']['node'] = node iq.send() -- cgit v1.2.3 From f5cae85af5c2f9c79ade1e6413436c1206689bdd Mon Sep 17 00:00:00 2001 From: Lance stout Date: Tue, 1 Jun 2010 10:52:37 -0400 Subject: Make sure that the id parameter used in xmpp.makeIq is converted to a string. Otherwise, SleekXMPP will barf on trying to serialize an integer when it expects text. --- sleekxmpp/basexmpp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 907067fa..d2dd3fb4 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -152,7 +152,7 @@ class basexmpp(object): return waitfor.wait(timeout) def makeIq(self, id=0, ifrom=None): - return self.Iq().setValues({'id': id, 'from': ifrom}) + return self.Iq().setValues({'id': str(id), 'from': ifrom}) def makeIqGet(self, queryxmlns = None): iq = self.Iq().setValues({'type': 'get'}) -- cgit v1.2.3 From e700a54d11a01c0ed24cddb14ceac0511fbc8372 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 2 Jun 2010 15:59:10 -0400 Subject: Return result of iq.send() for disco requests. Events are still triggered, but now the caller can determine if there was a timeout. --- sleekxmpp/plugins/xep_0030.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 9fcc8b17..73f4636f 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -299,7 +299,7 @@ class xep_0030(base.base_plugin): iq['to'] = jid iq['from'] = dfrom iq['disco_info']['node'] = node - iq.send() + return iq.send() def getItems(self, jid, node='', dfrom=None): iq = self.xmpp.Iq() @@ -307,7 +307,7 @@ class xep_0030(base.base_plugin): iq['to'] = jid iq['from'] = dfrom iq['disco_items']['node'] = node - iq.send() + return iq.send() def add_feature(self, feature, node='main'): self.add_node(node) -- cgit v1.2.3 From 253de8518cfb2461f2172fbffd9b2a252924cb00 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 3 Jun 2010 22:42:11 -0400 Subject: Modified xmlstream.py to pass a clean stanza object to each stream handler. The previous version passed the same stanza object to each registered handler, which can cause issues when the stanza object is modified by one handler. The next handler receives the stanza with the modifications, not the original stanza. --- sleekxmpp/xmlstream/xmlstream.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 6b92abca..003ead15 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -21,6 +21,7 @@ import threading import time import traceback import types +import copy import xml.sax.saxutils from . import scheduler @@ -305,19 +306,18 @@ class XMLStream(object): #convert XML into Stanza logging.debug("RECV: %s" % cElementTree.tostring(xmlobj)) xmlobj = self.incoming_filter(xmlobj) - stanza = None + stanza_type = StanzaBase for stanza_class in self.__root_stanza: if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name): - #if self.__root_stanza[stanza_class].match(xmlobj): - stanza = stanza_class(self, xmlobj) + stanza_type = stanza_class break - if stanza is None: - stanza = StanzaBase(self, xmlobj) unhandled = True + stanza = stanza_type(self, xmlobj) for handler in self.__handlers: if handler.match(stanza): - handler.prerun(stanza) - self.eventqueue.put(('stanza', handler, stanza)) + stanza_copy = stanza_type(self, copy.deepcopy(xmlobj)) + handler.prerun(stanza_copy) + self.eventqueue.put(('stanza', handler, stanza_copy)) if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler)) unhandled = False if unhandled: -- cgit v1.2.3 From 9962f1a664cfe72b88d78d8541f269f290de8643 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 6 Jun 2010 23:12:54 -0400 Subject: Added a __copy__ method to both ElementBase and StanzaBase. Stanzas may now be copied using copy.copy(), which will be useful to prevent stanza objects from being shared between event handlers. --- sleekxmpp/xmlstream/stanzabase.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 64020c8f..a0d8f5d0 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -10,6 +10,7 @@ import logging import traceback import sys import weakref +import copy if sys.version_info < (3,0): from . import tostring26 as tostring @@ -308,6 +309,9 @@ class ElementBase(tostring.ToString): def appendxml(self, xml): self.xml.append(xml) return self + + def __copy__(self): + return self.__class__(xml=copy.copy(self.xml), parent=self.parent) #def __del__(self): #prevents garbage collection of reference cycle # if self.parent is not None: @@ -386,3 +390,6 @@ class StanzaBase(ElementBase): def send(self): self.stream.sendRaw(self.__str__()) + def __copy__(self): + return self.__class__(xml=copy.copy(self.xml), stream=self.stream) + -- cgit v1.2.3 From 3c939313d2381accf4fe449dd569df2be6fe3c55 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 6 Jun 2010 23:19:07 -0400 Subject: Modified basexmpp.event() to pass a copy of the event data to each handler. --- sleekxmpp/basexmpp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index d2dd3fb4..9728c3f4 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -27,6 +27,7 @@ from . stanza.error import Error import logging import threading +import copy import sys @@ -205,12 +206,13 @@ class basexmpp(object): 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=(eventdata,)) + x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(handlerdata,)) x.start() else: - handler[0](eventdata) + handler[0](handlerdata) if handler[2]: #disposable with self.lock: self.event_handlers[name].pop(self.event_handlers[name].index(handler)) -- cgit v1.2.3 From 8bb0f5e34c443a9efc05dadb2a77a95ac94c2c98 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 7 Jun 2010 19:55:39 -0400 Subject: Needed to use copy.deepcopy() to copy XML objects to make sure that the entire tree is copied. --- sleekxmpp/xmlstream/stanzabase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index a0d8f5d0..024fe6cf 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -311,7 +311,7 @@ class ElementBase(tostring.ToString): return self def __copy__(self): - return self.__class__(xml=copy.copy(self.xml), parent=self.parent) + return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) #def __del__(self): #prevents garbage collection of reference cycle # if self.parent is not None: @@ -391,5 +391,5 @@ class StanzaBase(ElementBase): self.stream.sendRaw(self.__str__()) def __copy__(self): - return self.__class__(xml=copy.copy(self.xml), stream=self.stream) + return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) -- cgit v1.2.3 From 646a609c0b4e19203b4e2190df9b58fbe40c43fb Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 22 Jun 2010 23:09:50 -0400 Subject: Added plugin and tests for XEP-0033, Extended Stanza Addresses. XEP-0033 can be useful for interacting with XMPP<->Email gateways. --- sleekxmpp/plugins/__init__.py | 2 +- sleekxmpp/plugins/xep_0033.py | 159 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 sleekxmpp/plugins/xep_0033.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 674c3de2..fbc5e014 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -17,4 +17,4 @@ along with SleekXMPP; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ -__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] +__all__ = ['xep_0004', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py new file mode 100644 index 00000000..09d17ae0 --- /dev/null +++ b/sleekxmpp/plugins/xep_0033.py @@ -0,0 +1,159 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file license.txt for copying permission. +""" + +import logging +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. stanza.message import Message + + +class Addresses(ElementBase): + namespace = 'http://jabber.org/protocol/address' + name = 'addresses' + plugin_attrib = 'addresses' + interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) + + def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False): + address = Address(parent=self) + address['type'] = atype + address['jid'] = jid + address['node'] = node + address['uri'] = uri + address['desc'] = desc + address['delivered'] = delivered + return address + + def getAddresses(self, atype=None): + addresses = [] + for addrXML in self.xml.findall('{%s}address' % Address.namespace): + # ElementTree 1.2.6 does not support [@attr='value'] in findall + if atype is not None and addrXML.get('type') == atype: + self.xml.remove(addrXML) + addresses.append(Address(xml=addrXML, parent=None)) + return addresses + + def setAddresses(self, addresses, set_type=None): + self.delAddresses(set_type) + for addr in addresses: + # Remap 'type' to 'atype' to match the add method + if set_type is not None: + addr['type'] = set_type + curr_type = addr.get('type', None) + if curr_type is not None: + del addr['type'] + addr['atype'] = curr_type + self.addAddress(**addr) + + def delAddresses(self, atype=None): + for addrXML in self.xml.findall('{%s}address' % Address.namespace): + # ElementTree 1.2.6 does not support [@attr='value'] in findall + if atype is not None and addrXML.get('type') == atype: + self.xml.remove(addrXML) + + # -------------------------------------------------------------- + + def delBcc(self): + self.delAddresses('bcc') + + def delCc(self): + self.delAddresses('cc') + + def delNoreply(self): + self.delAddresses('noreply') + + def delReplyroom(self): + self.delAddresses('replyroom') + + def delReplyto(self): + self.delAddresses('replyto') + + def delTo(self): + self.delAddresses('to') + + # -------------------------------------------------------------- + + def getBcc(self): + return self.getAddresses('bcc') + + def getCc(self): + return self.getAddresses('cc') + + def getNoreply(self): + return self.getAddresses('noreply') + + def getReplyroom(self): + return self.getAddresses('replyroom') + + def getReplyto(self): + return self.getAddresses('replyto') + + def getTo(self): + return self.getAddresses('to') + + # -------------------------------------------------------------- + + def setBcc(self, addresses): + self.setAddresses(addresses, 'bcc') + + def setCc(self, addresses): + self.setAddresses(addresses, 'cc') + + def setNoreply(self, addresses): + self.setAddresses(addresses, 'noreply') + + def setReplyroom(self, addresses): + self.setAddresses(addresses, 'replyroom') + + def setReplyto(self, addresses): + self.setAddresses(addresses, 'replyto') + + def setTo(self, addresses): + self.setAddresses(addresses, 'to') + + +class Address(ElementBase): + namespace = 'http://jabber.org/protocol/address' + name = 'address' + plugin_attrib = 'address' + interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri')) + address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) + + def getDelivered(self): + return self.attrib.get('delivered', False) + + def setDelivered(self, delivered): + if delivered: + self.xml.attrib['delivered'] = "true" + else: + del self['delivered'] + + def setUri(self, uri): + if uri: + del self['jid'] + del self['node'] + self.xml.attrib['uri'] = uri + elif 'uri' in self.xml.attrib: + del self.xml.attrib['uri'] + + +class xep_0030(base.base_plugin): + """ + XEP-0033: Extended Stanza Addressing + """ + + def plugin_init(self): + self.xep = '0033' + self.description = 'Extended Stanza Addressing' + + self.xmpp.stanzaPlugin(Message, Addresses) + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace) -- cgit v1.2.3 From acb53ba371952e70a53e96d7c7b71961b68b36a8 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 27 Jun 2010 10:14:21 -0400 Subject: Fixed tab and spacing issue to please the Tab Nanny during unit tests. --- sleekxmpp/plugins/xep_0033.py | 44 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py index 09d17ae0..76bea6ab 100644 --- a/sleekxmpp/plugins/xep_0033.py +++ b/sleekxmpp/plugins/xep_0033.py @@ -30,7 +30,7 @@ class Addresses(ElementBase): address['delivered'] = delivered return address - def getAddresses(self, atype=None): + def getAddresses(self, atype=None): addresses = [] for addrXML in self.xml.findall('{%s}address' % Address.namespace): # ElementTree 1.2.6 does not support [@attr='value'] in findall @@ -39,7 +39,7 @@ class Addresses(ElementBase): addresses.append(Address(xml=addrXML, parent=None)) return addresses - def setAddresses(self, addresses, set_type=None): + def setAddresses(self, addresses, set_type=None): self.delAddresses(set_type) for addr in addresses: # Remap 'type' to 'atype' to match the add method @@ -51,7 +51,7 @@ class Addresses(ElementBase): addr['atype'] = curr_type self.addAddress(**addr) - def delAddresses(self, atype=None): + def delAddresses(self, atype=None): for addrXML in self.xml.findall('{%s}address' % Address.namespace): # ElementTree 1.2.6 does not support [@attr='value'] in findall if atype is not None and addrXML.get('type') == atype: @@ -62,59 +62,59 @@ class Addresses(ElementBase): def delBcc(self): self.delAddresses('bcc') - def delCc(self): + def delCc(self): self.delAddresses('cc') - def delNoreply(self): + def delNoreply(self): self.delAddresses('noreply') - def delReplyroom(self): + def delReplyroom(self): self.delAddresses('replyroom') - def delReplyto(self): + def delReplyto(self): self.delAddresses('replyto') - def delTo(self): + def delTo(self): self.delAddresses('to') # -------------------------------------------------------------- - def getBcc(self): + def getBcc(self): return self.getAddresses('bcc') def getCc(self): return self.getAddresses('cc') - def getNoreply(self): + def getNoreply(self): return self.getAddresses('noreply') - def getReplyroom(self): + def getReplyroom(self): return self.getAddresses('replyroom') - def getReplyto(self): + def getReplyto(self): return self.getAddresses('replyto') - def getTo(self): + def getTo(self): return self.getAddresses('to') # -------------------------------------------------------------- - def setBcc(self, addresses): + def setBcc(self, addresses): self.setAddresses(addresses, 'bcc') - def setCc(self, addresses): + def setCc(self, addresses): self.setAddresses(addresses, 'cc') - def setNoreply(self, addresses): + def setNoreply(self, addresses): self.setAddresses(addresses, 'noreply') - def setReplyroom(self, addresses): + def setReplyroom(self, addresses): self.setAddresses(addresses, 'replyroom') - def setReplyto(self, addresses): + def setReplyto(self, addresses): self.setAddresses(addresses, 'replyto') - def setTo(self, addresses): + def setTo(self, addresses): self.setAddresses(addresses, 'to') @@ -123,9 +123,9 @@ class Address(ElementBase): name = 'address' plugin_attrib = 'address' interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri')) - address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) + address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) - def getDelivered(self): + def getDelivered(self): return self.attrib.get('delivered', False) def setDelivered(self, delivered): @@ -134,7 +134,7 @@ class Address(ElementBase): else: del self['delivered'] - def setUri(self, uri): + def setUri(self, uri): if uri: del self['jid'] del self['node'] -- cgit v1.2.3 From 6041cd1952cbe3a54354eaa6395d49986632e871 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 27 Jun 2010 16:33:59 -0400 Subject: Fixed typo --- sleekxmpp/plugins/xep_0030.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 73f4636f..93e094f2 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permissio + See the file license.txt for copying permission. """ import logging -- cgit v1.2.3 From 309c9e74eb906af42841d2ab783aa18843c08878 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 27 Jun 2010 16:34:48 -0400 Subject: Fixed error in setState() method. --- sleekxmpp/plugins/xep_0085.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py index e183ec27..e24e9db0 100644 --- a/sleekxmpp/plugins/xep_0085.py +++ b/sleekxmpp/plugins/xep_0085.py @@ -38,8 +38,9 @@ class ChatState(ElementBase): def setState(self, state): if state in self.states: self.name = state - self.xml.tag = state - self.xml.attrib['xmlns'] = self.namespace + self.xml.tag = '{%s}%s' % (self.namespace, state) + else: + raise ValueError('Invalid chat state') def getState(self): return self.name -- cgit v1.2.3 From 059cc9ccc4c41942f6a9b2ba3baab443a91d2a60 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 27 Jun 2010 17:32:16 -0400 Subject: Fixed several errors in xep_0033 plugin. The method getAddresses was removing addresses by mistake. Several instances of using self.attrib instead of self.xml.attrib. --- sleekxmpp/plugins/xep_0033.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py index 76bea6ab..80d58a40 100644 --- a/sleekxmpp/plugins/xep_0033.py +++ b/sleekxmpp/plugins/xep_0033.py @@ -34,14 +34,14 @@ class Addresses(ElementBase): addresses = [] for addrXML in self.xml.findall('{%s}address' % Address.namespace): # ElementTree 1.2.6 does not support [@attr='value'] in findall - if atype is not None and addrXML.get('type') == atype: - self.xml.remove(addrXML) - addresses.append(Address(xml=addrXML, parent=None)) + if atype is None or addrXML.attrib.get('type') == atype: + addresses.append(Address(xml=addrXML, parent=None)) return addresses def setAddresses(self, addresses, set_type=None): self.delAddresses(set_type) for addr in addresses: + addr = dict(addr) # Remap 'type' to 'atype' to match the add method if set_type is not None: addr['type'] = set_type @@ -52,9 +52,11 @@ class Addresses(ElementBase): self.addAddress(**addr) def delAddresses(self, atype=None): + if atype is None: + return for addrXML in self.xml.findall('{%s}address' % Address.namespace): # ElementTree 1.2.6 does not support [@attr='value'] in findall - if atype is not None and addrXML.get('type') == atype: + if addrXML.attrib.get('type') == atype: self.xml.remove(addrXML) # -------------------------------------------------------------- @@ -126,7 +128,7 @@ class Address(ElementBase): address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) def getDelivered(self): - return self.attrib.get('delivered', False) + return self.xml.attrib.get('delivered', False) def setDelivered(self, delivered): if delivered: -- cgit v1.2.3 From 37ada498029c12459e5724ed27db92c828ce2a38 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 27 Jun 2010 17:39:16 -0400 Subject: Fixed indentation to please tab nanny during unit tests. --- sleekxmpp/plugins/xep_0033.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py index 80d58a40..89770dc1 100644 --- a/sleekxmpp/plugins/xep_0033.py +++ b/sleekxmpp/plugins/xep_0033.py @@ -35,7 +35,7 @@ class Addresses(ElementBase): for addrXML in self.xml.findall('{%s}address' % Address.namespace): # ElementTree 1.2.6 does not support [@attr='value'] in findall if atype is None or addrXML.attrib.get('type') == atype: - addresses.append(Address(xml=addrXML, parent=None)) + addresses.append(Address(xml=addrXML, parent=None)) return addresses def setAddresses(self, addresses, set_type=None): -- cgit v1.2.3 From d0cb400c544abb432fa9b67038381be8172c2efb Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 11 Jul 2010 21:43:51 -0400 Subject: Fixed tabs to please tab nanny. --- sleekxmpp/plugins/xep_0033.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py index 89770dc1..df8bb88d 100644 --- a/sleekxmpp/plugins/xep_0033.py +++ b/sleekxmpp/plugins/xep_0033.py @@ -41,7 +41,7 @@ class Addresses(ElementBase): def setAddresses(self, addresses, set_type=None): self.delAddresses(set_type) for addr in addresses: - addr = dict(addr) + addr = dict(addr) # Remap 'type' to 'atype' to match the add method if set_type is not None: addr['type'] = set_type @@ -52,8 +52,8 @@ class Addresses(ElementBase): self.addAddress(**addr) def delAddresses(self, atype=None): - if atype is None: - return + if atype is None: + return for addrXML in self.xml.findall('{%s}address' % Address.namespace): # ElementTree 1.2.6 does not support [@attr='value'] in findall if addrXML.attrib.get('type') == atype: -- cgit v1.2.3 From b1c997be1d7d7d456cad06ef436c01527e04df5d Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 11 Jul 2010 22:01:51 -0400 Subject: Reworked the Gmail notification plugin to use stanza objects and expose more information. --- sleekxmpp/plugins/gmail_notify.py | 193 ++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 52 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py index b709ef69..acfc38b0 100644 --- a/sleekxmpp/plugins/gmail_notify.py +++ b/sleekxmpp/plugins/gmail_notify.py @@ -1,57 +1,146 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file license.txt for copying permission. """ -from __future__ import with_statement -from . import base + import logging -from xml.etree import cElementTree as ET -import traceback -import time +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. stanza.iq import Iq + + +class GmailQuery(ElementBase): + namespace = 'google:mail:notify' + name = 'query' + plugin_attrib = 'gmail' + interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) + + def getSearch(self): + return self['q'] + + def setSearch(self, search): + self['q'] = search + + def delSearch(self): + del self['q'] + + +class MailBox(ElementBase): + namespace = 'google:mail:notify' + name = 'mailbox' + plugin_attrib = 'mailbox' + interfaces = set(('result-time', 'total-matched', 'total-estimate', + 'url', 'threads', 'matched', 'estimate')) + + def getThreads(self): + threads = [] + for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, + MailThread.name)): + threads.append(MailThread(xml=threadXML, parent=None)) + return threads + + def getMatched(self): + return self['total-matched'] + + def getEstimate(self): + return self['total-estimate'] == '1' + + +class MailThread(ElementBase): + namespace = 'google:mail:notify' + name = 'mail-thread-info' + plugin_attrib = 'thread' + interfaces = set(('tid', 'participation', 'messages', 'date', + 'senders', 'url', 'labels', 'subject', 'snippet')) + sub_interfaces = set(('labels', 'subject', 'snippet')) + + def getSenders(self): + senders = [] + sendersXML = self.xml.find('{%s}senders' % self.namespace) + if sendersXML is not None: + for senderXML in sendersXML.findall('{%s}sender' % self.namespace): + senders.append(MailSender(xml=senderXML, parent=None)) + return senders + + +class MailSender(ElementBase): + namespace = 'google:mail:notify' + name = 'sender' + plugin_attrib = 'sender' + interfaces = set(('address', 'name', 'originator', 'unread')) + + def getOriginator(self): + return self.xml.attrib.get('originator', '0') == '1' + + def getUnread(self): + return self.xml.attrib.get('unread', '0') == '1' + + +class NewMail(ElementBase): + namespace = 'google:mail:notify' + name = 'new-mail' + plugin_attrib = 'new-mail' + class gmail_notify(base.base_plugin): - - def plugin_init(self): - self.description = 'Google Talk Gmail Notification' - self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True) - self.emails = [] - - def handler_gmailcheck(self, payload): - #TODO XEP 30 should cache results and have getFeature - result = self.xmpp['xep_0030'].getInfo(self.xmpp.server) - features = [] - for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'): - features.append(feature.get('var')) - if 'google:mail:notify' in features: - logging.debug("Server supports Gmail Notify") - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_notify) - self.getEmail() - - def handler_notify(self, xml): - logging.info("New Gmail recieved!") - self.xmpp.event('gmail_notify') - - def getEmail(self): - iq = self.xmpp.makeIqGet() - iq.attrib['from'] = self.xmpp.fulljid - iq.attrib['to'] = self.xmpp.jid - self.xmpp.makeIqQuery(iq, 'google:mail:notify') - emails = iq.send() - mailbox = emails.find('{google:mail:notify}mailbox') - total = int(mailbox.get('total-matched', 0)) - logging.info("%s New Gmail Messages" % total) + """ + Google Talk: Gmail Notifications + """ + + def plugin_init(self): + self.description = 'Google Talk: Gmail Notifications' + + self.xmpp.registerHandler( + Callback('Gmail Result', + MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, + MailBox.namespace, + MailBox.name)), + self.handle_gmail)) + + self.xmpp.registerHandler( + Callback('Gmail New Mail', + MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, + NewMail.namespace, + NewMail.name)), + self.handle_new_mail)) + + self.xmpp.stanzaPlugin(Iq, GmailQuery) + self.xmpp.stanzaPlugin(Iq, MailBox) + self.xmpp.stanzaPlugin(Iq, NewMail) + + self.last_result_time = None + + def handle_gmail(self, iq): + mailbox = iq['mailbox'] + approx = ' approximately' if mailbox['estimated'] else '' + logging.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) + self.last_result_time = mailbox['result-time'] + self.xmpp.event('gmail_messages', iq) + + def handle_new_mail(self, iq): + logging.info("Gmail: New emails received!") + self.xmpp.event('gmail_notify') + self.checkEmail() + + def getEmail(self, query=None): + return self.search(query) + + def checkEmail(self): + return self.search(newer=self.last_result_time) + + def search(self, query=None, newer=None): + if query is None: + logging.info("Gmail: Checking for new emails") + else: + logging.info('Gmail: Searching for emails matching: "%s"' % query) + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = self.xmpp.jid + iq['gmail']['q'] = query + iq['gmail']['newer-than-time'] = newer + return iq.send() -- cgit v1.2.3 From 48f0843ace5a8d383abe493b999e7bd4699598d7 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 14 Jul 2010 11:59:58 -0400 Subject: Added initial stanza object version of the xep_0004 plugin. Items/reported elements still need to be unit tested --- sleekxmpp/plugins/alt_0004.py | 330 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 sleekxmpp/plugins/alt_0004.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/alt_0004.py b/sleekxmpp/plugins/alt_0004.py new file mode 100644 index 00000000..b38a4918 --- /dev/null +++ b/sleekxmpp/plugins/alt_0004.py @@ -0,0 +1,330 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file license.txt for copying permission. +""" + +import logging +import copy +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. stanza.message import Message + + +class Form(ElementBase): + namespace = 'jabber:x:data' + name = 'x' + plugin_attrib = 'form' + interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values')) + sub_interfaces = set(('title',)) + form_types = set(('cancel', 'form', 'result', 'submit')) + + def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None, options=None): + field = FormField(parent=self) + field['var'] = var + field['type'] = ftype + field['label'] = label + field['desc'] = desc + field['required'] = required + field['value'] = value + if options is not None: + field['options'] = options + return field + + def addItem(self, values): + itemXML = ET.Element('{%s}item' % self.namespace) + self.xml.append(itemXML) + reported_vars = self['reported'].keys() + for var in reported_vars: + fieldXML = ET.Element('{%s}field' % FormField.namespace) + itemXML.append(fieldXML) + field = FormField(xml=fieldXML) + field['var'] = var + field['value'] = values.get(var, None) + + def addReported(self, var, ftype='text-single', label='', desc=''): + reported = self.xml.find('{%s}reported' % self.namespace) + if reported is None: + reported = ET.Element('{%s}reported' % self.namespace) + self.xml.append(reported) + fieldXML = ET.Element('{%s}field' % FormField.namespace) + reported.append(fieldXML) + field = FormField(xml=fieldXML) + field['var'] = var + field['type'] = ftype + field['label'] = label + field['desc'] = desc + return field + + def cancel(self): + self['type'] = 'cancel' + + def delFields(self): + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + self.xml.remove(fieldXML) + + def delInstructions(self): + instsXML = self.xml.findall('{%s}instructions') + for instXML in instsXML: + self.xml.remove(instXML) + + def delItems(self): + itemsXML = self.xml.find('{%s}item' % self.namespace) + for itemXML in itemsXML: + self.xml.remove(itemXML) + + def delReported(self): + reportedXML = self.xml.find('{%s}reported' % self.namespace) + if reportedXML is not None: + self.xml.remove(reportedXML) + + def getFields(self): + fields = {} + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + fields[field['var']] = field + return fields + + def getInstructions(self): + instructions = '' + instsXML = self.xml.findall('{%s}instructions') + for instXML in instsXML: + instructions += instXML.text + + def getItems(self): + items = [] + itemsXML = self.xml.findall('{%s}item' % self.namespace) + for itemXML in itemsXML: + item = {} + fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + item[field['var']] = field['value'] + items.append(item) + return items + + def getReported(self): + fields = {} + fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, + FormField.namespace)) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + fields[field['var']] = field + return fields + + def getValues(self): + values = {} + fields = self.getFields() + for var in fields: + values[var] = fields[var]['value'] + return values + + def reply(self): + if self['type'] == 'form': + self['type'] = 'submit' + elif self['type'] == 'submit': + self['type'] = 'result' + + def setFields(self, fields): + del self['fields'] + for var in fields: + field = fields[var] + + # Remap 'type' to 'ftype' to match the addField method + ftype = field.get('type', 'text-single') + field['type'] = ftype + del field['type'] + field['ftype'] = ftype + + self.addField(var, **field) + + def setInstructions(self, instructions): + instructions = instructions.split('\n') + for instruction in instructions: + inst = ET.Element('{%s}instructions' % self.namespace) + inst.text = instruction + self.xml.append(inst) + + def setItems(self, items): + for item in items: + self.addItem(item) + + def setReported(self, reported): + for var in reported: + field = reported[var] + + # Remap 'type' to 'ftype' to match the addReported method + ftype = field.get('type', 'text-single') + field['type'] = ftype + del field['type'] + field['ftype'] = ftype + + self.addReported(var, **field) + + def setValues(self, values): + fields = self.getFields() + for field in values: + fields[field]['value'] = values[field] + + +class FormField(ElementBase): + namespace = 'jabber:x:data' + name = 'field' + plugin_attrib = 'field' + interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var')) + sub_interfaces = set(('desc',)) + field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', + 'list-single', 'text-multi', 'text-private', 'text-single')) + multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi')) + multi_line_types = set(('hidden', 'text-multi')) + option_types = set(('list-multi', 'list-single')) + true_values = set((True, '1', 'true')) + + def addOption(self, label='', value=''): + if self['type'] in self.option_types: + opt = FieldOption(parent=self) + opt['label'] = label + opt['value'] = value + else: + raise ValueError("Cannot add options to a %s field." % self['type']) + + def delOptions(self): + optsXML = self.xml.findall('{%s}option' % self.namespace) + for optXML in optsXML: + self.xml.remove(optXML) + + def delRequired(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + if reqXML is not None: + self.xml.remove(reqXML) + + def delValue(self): + valsXML = self.xml.findall('{%s}value' % self.namespace) + for valXML in valsXML: + self.xml.remove(valXML) + + def getAnswer(self): + return self.getValue() + + def getOptions(self): + options = [] + optsXML = self.xml.findall('{%s}option' % self.namespace) + for optXML in optsXML: + opt = FieldOption(xml=optXML) + options.append({'label': opt['label'], 'value':opt['value']}) + return options + + def getRequired(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + return reqXML is not None + + def getValue(self): + valsXML = self.xml.findall('{%s}value' % self.namespace) + if len(valsXML) == 0: + return None + elif self['type'] == 'boolean': + return valsXML[0].text in self.true_values + elif self['type'] in self.multi_value_types: + values = [] + for valXML in valsXML: + if valXML.text is None: + valXML.text = '' + values.append(valXML.text) + if self['type'] == 'text-multi': + values = "\n".join(values) + return values + else: + return valsXML[0].text + + def setAnswer(self, answer): + self.setValue(answer) + + def setFalse(self): + self.setValue(False) + + def setOptions(self, options): + for value in options: + if isinstance(value, dict): + self.addOption(**value) + else: + self.addOption(value=value) + + def setRequired(self, required): + exists = self.getRequired() + if not exists and required: + self.xml.append(ET.Element('{%s}required' % self.namespace)) + elif exists and not required: + self.delRequired() + + def setTrue(self): + self.setValue(True) + + def setValue(self, value): + self.delValue() + valXMLName = '{%s}value' % self.namespace + + if self['type'] == 'boolean': + if value in self.true_values: + valXML = ET.Element(valXMLName) + valXML.text = 'true' + self.xml.append(valXML) + else: + valXML = ET.Element(valXMLName) + valXML.text = 'true' + self.xml.append(valXML) + if self['type'] in self.multi_value_types: + if self['type'] in self.multi_line_types and isinstance(value, str): + value = value.split('\n') + if not isinstance(value, list): + value = [value] + for val in value: + valXML = ET.Element(valXMLName) + valXML.text = val + self.xml.append(valXML) + else: + if isinstance(value, list): + raise ValueError("Cannot add multiple values to a %s field." % self['type']) + valXML = ET.Element(valXMLName) + valXML.text = value + self.xml.append(valXML) + + +class FieldOption(ElementBase): + namespace = 'jabber:x:data' + name = 'option' + plugin_attrib = 'option' + interfaces = set(('label', 'value')) + sub_interfaces = set(('value',)) + + +class alt_0004(base.base_plugin): + """ + XEP-0004: Data Forms + """ + + def plugin_init(self): + self.xep = '0004' + self.description = 'Data Forms' + + self.xmpp.registerHandler( + Callback('Data Form', + MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, + Form.namespace)), + self.handle_form)) + + self.xmpp.stanzaPlugin(FormField, FieldOption) + self.xmpp.stanzaPlugin(Form, FormField) + self.xmpp.stanzaPlugin(Message, Form) + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') + + def handle_form(self, message): + self.xmpp.event("message_xform", message) -- cgit v1.2.3 From bae082f4370c37c4aca0afc303b2d98b22582a34 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 15 Jul 2010 11:53:35 -0700 Subject: fixed updateRoster and delRosterItem --- sleekxmpp/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index ccb43522..35df6206 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -146,10 +146,17 @@ class ClientXMPP(basexmpp, XMLStream): def updateRoster(self, jid, name=None, subscription=None, groups=[]): """Add or change a roster item.""" iq = self.Iq().setValues({'type': 'set'}) - iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} + iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} #self.send(iq, self.Iq().setValues({'id': iq['id']})) + return r = iq.send() return r['type'] == 'result' + + def delRosterItem(self, jid): + iq = self.Iq() + iq['type'] = 'set' + iq['roster']['items'] = {jid: {'subscription': 'remove'}} + return iq.send()['type'] == 'result' def getRoster(self): """Request the roster be sent.""" -- cgit v1.2.3 From 078c71ed3fd550812461795149f6ffca35397871 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 15 Jul 2010 14:25:10 -0700 Subject: accidental debugging return left in the code from last commit --- sleekxmpp/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 35df6206..df0af09c 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -148,7 +148,6 @@ class ClientXMPP(basexmpp, XMLStream): iq = self.Iq().setValues({'type': 'set'}) iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} #self.send(iq, self.Iq().setValues({'id': iq['id']})) - return r = iq.send() return r['type'] == 'result' -- cgit v1.2.3 From 797e92a6a3cf1534f6ecb0f30019b0135d0ffacb Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 19 Jul 2010 04:12:54 -0400 Subject: Fixed error in updateRoster when the name keyword parameter is left out. The Roster stanza object builds item elements manually, and did not handle the case where the name attribute is set to None, which would crash SleekXMPP. --- sleekxmpp/stanza/roster.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py index 1fefc180..69027b6c 100644 --- a/sleekxmpp/stanza/roster.py +++ b/sleekxmpp/stanza/roster.py @@ -23,7 +23,9 @@ class Roster(ElementBase): if 'subscription' in items[jid]: item.attrib['subscription'] = items[jid]['subscription'] if 'name' in items[jid]: - item.attrib['name'] = items[jid]['name'] + name = items[jid]['name'] + if name is not None: + item.attrib['name'] = name if 'groups' in items[jid]: for group in items[jid]['groups']: groupxml = ET.Element('{jabber:iq:roster}group') -- cgit v1.2.3 From e6bec8681e07ced607db1fbcbc5e356c3936f1d1 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 19 Jul 2010 04:22:31 -0400 Subject: Added implementation for XEP-0128 Service Discovery Extensions. Uses the alt_0004 plugin for jabber:x:data stanza objects. --- sleekxmpp/plugins/xep_0128.py | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 sleekxmpp/plugins/xep_0128.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0128.py b/sleekxmpp/plugins/xep_0128.py new file mode 100644 index 00000000..3e660b19 --- /dev/null +++ b/sleekxmpp/plugins/xep_0128.py @@ -0,0 +1,51 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file license.txt for copying permission. +""" + +import logging +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. stanza.iq import Iq +from . xep_0030 import DiscoInfo, DiscoItems +from . alt_0004 import Form + + +class xep_0128(base.base_plugin): + """ + XEP-0128 Service Discovery Extensions + """ + + def plugin_init(self): + self.xep = '0128' + self.description = 'Service Discovery Extensions' + + self.xmpp.stanzaPlugin(DiscoInfo, Form) + self.xmpp.stanzaPlugin(DiscoItems, Form) + + def extend_info(self, node, data=None): + if data is None: + data = {} + node = self.xmpp['xep_0030'].nodes.get(node, None) + if node is None: + self.xmpp['xep_0030'].add_node(node) + + info = node.info + info['form']['type'] = 'result' + info['form'].setFields(data, default=None) + + def extend_items(self, node, data=None): + if data is None: + data = {} + node = self.xmpp['xep_0030'].nodes.get(node, None) + if node is None: + self.xmpp['xep_0030'].add_node(node) + + items = node.items + items['form']['type'] = 'result' + items['form'].setFields(data, default=None) -- cgit v1.2.3 From d5e42ac0e7282500583bf17f21eb2f944600ce76 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 19 Jul 2010 13:58:53 -0400 Subject: Condensed all of the stanzaPlugin functions into a single registerStanzaPlugin function. Updated plugins and tests to use new function. --- sleekxmpp/basexmpp.py | 17 +++------ sleekxmpp/plugins/alt_0004.py | 8 ++--- sleekxmpp/plugins/gmail_notify.py | 8 ++--- sleekxmpp/plugins/stanza_pubsub.py | 73 ++++++++++++++++++-------------------- sleekxmpp/plugins/xep_0030.py | 6 ++-- sleekxmpp/plugins/xep_0033.py | 4 +-- sleekxmpp/plugins/xep_0045.py | 4 +-- sleekxmpp/plugins/xep_0060.py | 2 +- sleekxmpp/plugins/xep_0085.py | 12 +++---- sleekxmpp/plugins/xep_0128.py | 6 ++-- sleekxmpp/stanza/atom.py | 2 +- sleekxmpp/stanza/error.py | 2 +- sleekxmpp/stanza/htmlim.py | 2 +- sleekxmpp/stanza/nick.py | 2 +- sleekxmpp/stanza/roster.py | 2 +- sleekxmpp/xmlstream/stanzabase.py | 12 ++++++- 16 files changed, 80 insertions(+), 82 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 9728c3f4..c9439ea3 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -16,6 +16,7 @@ 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 @@ -35,12 +36,6 @@ if sys.version_info < (3,0): reload(sys) sys.setdefaultencoding('utf8') - -def stanzaPlugin(stanza, plugin): - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin - - class basexmpp(object): def __init__(self): self.id = 0 @@ -62,13 +57,9 @@ class basexmpp(object): self.registerStanza(Message) self.registerStanza(Iq) self.registerStanza(Presence) - self.stanzaPlugin(Iq, Roster) - self.stanzaPlugin(Message, Nick) - self.stanzaPlugin(Message, HTMLIM) - - def stanzaPlugin(self, stanza, plugin): - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin + registerStanzaPlugin(Iq, Roster) + registerStanzaPlugin(Message, Nick) + registerStanzaPlugin(Message, HTMLIM) def Message(self, *args, **kwargs): return Message(self, *args, **kwargs) diff --git a/sleekxmpp/plugins/alt_0004.py b/sleekxmpp/plugins/alt_0004.py index b38a4918..ff9b7efd 100644 --- a/sleekxmpp/plugins/alt_0004.py +++ b/sleekxmpp/plugins/alt_0004.py @@ -11,7 +11,7 @@ import copy from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.message import Message @@ -318,9 +318,9 @@ class alt_0004(base.base_plugin): Form.namespace)), self.handle_form)) - self.xmpp.stanzaPlugin(FormField, FieldOption) - self.xmpp.stanzaPlugin(Form, FormField) - self.xmpp.stanzaPlugin(Message, Form) + registerStanzaPlugin(FormField, FieldOption) + registerStanzaPlugin(Form, FormField) + registerStanzaPlugin(Message, Form) def post_init(self): base.base_plugin.post_init(self) diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py index acfc38b0..fb1ecb3d 100644 --- a/sleekxmpp/plugins/gmail_notify.py +++ b/sleekxmpp/plugins/gmail_notify.py @@ -10,7 +10,7 @@ import logging from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq @@ -109,9 +109,9 @@ class gmail_notify(base.base_plugin): NewMail.name)), self.handle_new_mail)) - self.xmpp.stanzaPlugin(Iq, GmailQuery) - self.xmpp.stanzaPlugin(Iq, MailBox) - self.xmpp.stanzaPlugin(Iq, NewMail) + registerStanzaPlugin(Iq, GmailQuery) + registerStanzaPlugin(Iq, MailBox) + registerStanzaPlugin(Iq, NewMail) self.last_result_time = None diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py index 1a1526f0..e04f1a7f 100644 --- a/sleekxmpp/plugins/stanza_pubsub.py +++ b/sleekxmpp/plugins/stanza_pubsub.py @@ -1,4 +1,4 @@ -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq from .. stanza.message import Message from .. basexmpp import basexmpp @@ -6,9 +6,6 @@ from .. xmlstream.xmlstream import XMLStream import logging from . import xep_0004 -def stanzaPlugin(stanza, plugin): - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin class PubsubState(ElementBase): namespace = 'http://jabber.org/protocol/psstate' @@ -30,7 +27,7 @@ class PubsubState(ElementBase): for child in self.xml.getchildren(): self.xml.remove(child) -stanzaPlugin(Iq, PubsubState) +registerStanzaPlugin(Iq, PubsubState) class PubsubStateEvent(ElementBase): namespace = 'http://jabber.org/protocol/psstate#event' @@ -40,8 +37,8 @@ class PubsubStateEvent(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Message, PubsubStateEvent) -stanzaPlugin(PubsubStateEvent, PubsubState) +registerStanzaPlugin(Message, PubsubStateEvent) +registerStanzaPlugin(PubsubStateEvent, PubsubState) class Pubsub(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -51,7 +48,7 @@ class Pubsub(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Iq, Pubsub) +registerStanzaPlugin(Iq, Pubsub) class PubsubOwner(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -61,7 +58,7 @@ class PubsubOwner(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Iq, PubsubOwner) +registerStanzaPlugin(Iq, PubsubOwner) class Affiliation(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -86,7 +83,7 @@ class Affiliations(ElementBase): self.xml.append(affiliation.xml) return self.iterables.append(affiliation) -stanzaPlugin(Pubsub, Affiliations) +registerStanzaPlugin(Pubsub, Affiliations) class Subscription(ElementBase): @@ -103,7 +100,7 @@ class Subscription(ElementBase): def getjid(self): return jid(self._getattr('jid')) -stanzaPlugin(Pubsub, Subscription) +registerStanzaPlugin(Pubsub, Subscription) class Subscriptions(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -114,7 +111,7 @@ class Subscriptions(ElementBase): plugin_tag_map = {} subitem = (Subscription,) -stanzaPlugin(Pubsub, Subscriptions) +registerStanzaPlugin(Pubsub, Subscriptions) class OptionalSetting(object): interfaces = set(('required',)) @@ -147,7 +144,7 @@ class SubscribeOptions(ElementBase, OptionalSetting): plugin_tag_map = {} interfaces = set(('required',)) -stanzaPlugin(Subscription, SubscribeOptions) +registerStanzaPlugin(Subscription, SubscribeOptions) class Item(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -178,7 +175,7 @@ class Items(ElementBase): plugin_tag_map = {} subitem = (Item,) -stanzaPlugin(Pubsub, Items) +registerStanzaPlugin(Pubsub, Items) class Create(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -188,7 +185,7 @@ class Create(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Pubsub, Create) +registerStanzaPlugin(Pubsub, Create) #class Default(ElementBase): # namespace = 'http://jabber.org/protocol/pubsub' @@ -203,7 +200,7 @@ stanzaPlugin(Pubsub, Create) # if not t: t == 'leaf' # return t # -#stanzaPlugin(Pubsub, Default) +#registerStanzaPlugin(Pubsub, Default) class Publish(Items): namespace = 'http://jabber.org/protocol/pubsub' @@ -214,7 +211,7 @@ class Publish(Items): plugin_tag_map = {} subitem = (Item,) -stanzaPlugin(Pubsub, Publish) +registerStanzaPlugin(Pubsub, Publish) class Retract(Items): namespace = 'http://jabber.org/protocol/pubsub' @@ -224,7 +221,7 @@ class Retract(Items): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Pubsub, Retract) +registerStanzaPlugin(Pubsub, Retract) class Unsubscribe(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -254,7 +251,7 @@ class Subscribe(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(Pubsub, Subscribe) +registerStanzaPlugin(Pubsub, Subscribe) class Configure(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -284,7 +281,7 @@ class Configure(ElementBase): config = self.xml.find('{jabber:x:data}x') self.xml.remove(config) -stanzaPlugin(Pubsub, Configure) +registerStanzaPlugin(Pubsub, Configure) class DefaultConfig(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -317,7 +314,7 @@ class DefaultConfig(ElementBase): if not t: t = 'leaf' return t -stanzaPlugin(PubsubOwner, DefaultConfig) +registerStanzaPlugin(PubsubOwner, DefaultConfig) class Options(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -351,8 +348,8 @@ class Options(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(Pubsub, Options) -stanzaPlugin(Subscribe, Options) +registerStanzaPlugin(Pubsub, Options) +registerStanzaPlugin(Subscribe, Options) class OwnerAffiliations(Affiliations): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -366,7 +363,7 @@ class OwnerAffiliations(Affiliations): self.xml.append(affiliation.xml) return self.affiliations.append(affiliation) -stanzaPlugin(PubsubOwner, OwnerAffiliations) +registerStanzaPlugin(PubsubOwner, OwnerAffiliations) class OwnerAffiliation(Affiliation): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -380,7 +377,7 @@ class OwnerConfigure(Configure): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(PubsubOwner, OwnerConfigure) +registerStanzaPlugin(PubsubOwner, OwnerConfigure) class OwnerDefault(OwnerConfigure): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -388,7 +385,7 @@ class OwnerDefault(OwnerConfigure): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(PubsubOwner, OwnerDefault) +registerStanzaPlugin(PubsubOwner, OwnerDefault) class OwnerDelete(ElementBase, OptionalSetting): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -398,7 +395,7 @@ class OwnerDelete(ElementBase, OptionalSetting): plugin_tag_map = {} interfaces = set(('node',)) -stanzaPlugin(PubsubOwner, OwnerDelete) +registerStanzaPlugin(PubsubOwner, OwnerDelete) class OwnerPurge(ElementBase, OptionalSetting): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -407,7 +404,7 @@ class OwnerPurge(ElementBase, OptionalSetting): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(PubsubOwner, OwnerPurge) +registerStanzaPlugin(PubsubOwner, OwnerPurge) class OwnerRedirect(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -423,7 +420,7 @@ class OwnerRedirect(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(OwnerDelete, OwnerRedirect) +registerStanzaPlugin(OwnerDelete, OwnerRedirect) class OwnerSubscriptions(Subscriptions): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -437,7 +434,7 @@ class OwnerSubscriptions(Subscriptions): self.xml.append(subscription.xml) return self.subscriptions.append(subscription) -stanzaPlugin(PubsubOwner, OwnerSubscriptions) +registerStanzaPlugin(PubsubOwner, OwnerSubscriptions) class OwnerSubscription(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -461,7 +458,7 @@ class Event(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Message, Event) +registerStanzaPlugin(Message, Event) class EventItem(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -501,7 +498,7 @@ class EventItems(ElementBase): plugin_tag_map = {} subitem = (EventItem, EventRetract) -stanzaPlugin(Event, EventItems) +registerStanzaPlugin(Event, EventItems) class EventCollection(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -511,7 +508,7 @@ class EventCollection(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Event, EventCollection) +registerStanzaPlugin(Event, EventCollection) class EventAssociate(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -521,7 +518,7 @@ class EventAssociate(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(EventCollection, EventAssociate) +registerStanzaPlugin(EventCollection, EventAssociate) class EventDisassociate(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -531,7 +528,7 @@ class EventDisassociate(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(EventCollection, EventDisassociate) +registerStanzaPlugin(EventCollection, EventDisassociate) class EventConfiguration(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -556,7 +553,7 @@ class EventConfiguration(ElementBase): config = self.xml.find('{jabber:x:data}x') self.xml.remove(config) -stanzaPlugin(Event, EventConfiguration) +registerStanzaPlugin(Event, EventConfiguration) class EventPurge(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -566,7 +563,7 @@ class EventPurge(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} -stanzaPlugin(Event, EventPurge) +registerStanzaPlugin(Event, EventPurge) class EventSubscription(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' @@ -582,4 +579,4 @@ class EventSubscription(ElementBase): def getJid(self): return JID(self._getAttr('jid')) -stanzaPlugin(Event, EventSubscription) +registerStanzaPlugin(Event, EventSubscription) diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 93e094f2..1e04fe4f 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -10,7 +10,7 @@ import logging from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq class DiscoInfo(ElementBase): @@ -204,8 +204,8 @@ class xep_0030(base.base_plugin): DiscoInfo.namespace)), self.handle_info_query)) - self.xmpp.stanzaPlugin(Iq, DiscoInfo) - self.xmpp.stanzaPlugin(Iq, DiscoItems) + registerStanzaPlugin(Iq, DiscoInfo) + registerStanzaPlugin(Iq, DiscoItems) self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py index df8bb88d..9af27e32 100644 --- a/sleekxmpp/plugins/xep_0033.py +++ b/sleekxmpp/plugins/xep_0033.py @@ -10,7 +10,7 @@ import logging from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.message import Message @@ -154,7 +154,7 @@ class xep_0030(base.base_plugin): self.xep = '0033' self.description = 'Extended Stanza Addressing' - self.xmpp.stanzaPlugin(Message, Addresses) + registerStanzaPlugin(Message, Addresses) def post_init(self): base.base_plugin.post_init(self) diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index 937c6f96..88ada19d 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -21,7 +21,7 @@ from __future__ import with_statement from . import base import logging from xml.etree import cElementTree as ET -from .. xmlstream.stanzabase import ElementBase, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID from .. stanza.presence import Presence from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath @@ -125,7 +125,7 @@ class xep_0045(base.base_plugin): self.xep = '0045' self.description = 'Multi User Chat' # load MUC support in presence stanzas - self.xmpp.stanzaPlugin(Presence, MUCPresence) + registerStanzaPlugin(Presence, MUCPresence) self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_presence)) self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_message)) diff --git a/sleekxmpp/plugins/xep_0060.py b/sleekxmpp/plugins/xep_0060.py index bff158a0..a92a3844 100644 --- a/sleekxmpp/plugins/xep_0060.py +++ b/sleekxmpp/plugins/xep_0060.py @@ -2,7 +2,7 @@ from __future__ import with_statement from . import base import logging #from xml.etree import cElementTree as ET -from .. xmlstream.stanzabase import ElementBase, ET +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET from . import stanza_pubsub class xep_0060(base.base_plugin): diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py index e24e9db0..66940af4 100644 --- a/sleekxmpp/plugins/xep_0085.py +++ b/sleekxmpp/plugins/xep_0085.py @@ -10,7 +10,7 @@ import logging from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.message import Message @@ -85,11 +85,11 @@ class xep_0085(base.base_plugin): handler[1])), self._handleChatState)) - self.xmpp.stanzaPlugin(Message, Active) - self.xmpp.stanzaPlugin(Message, Composing) - self.xmpp.stanzaPlugin(Message, Gone) - self.xmpp.stanzaPlugin(Message, Inactive) - self.xmpp.stanzaPlugin(Message, Paused) + registerStanzaPlugin(Message, Active) + registerStanzaPlugin(Message, Composing) + registerStanzaPlugin(Message, Gone) + registerStanzaPlugin(Message, Inactive) + registerStanzaPlugin(Message, Paused) def post_init(self): base.base_plugin.post_init(self) diff --git a/sleekxmpp/plugins/xep_0128.py b/sleekxmpp/plugins/xep_0128.py index 3e660b19..7ba00bf5 100644 --- a/sleekxmpp/plugins/xep_0128.py +++ b/sleekxmpp/plugins/xep_0128.py @@ -10,7 +10,7 @@ import logging from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq from . xep_0030 import DiscoInfo, DiscoItems from . alt_0004 import Form @@ -25,8 +25,8 @@ class xep_0128(base.base_plugin): self.xep = '0128' self.description = 'Service Discovery Extensions' - self.xmpp.stanzaPlugin(DiscoInfo, Form) - self.xmpp.stanzaPlugin(DiscoItems, Form) + registerStanzaPlugin(DiscoInfo, Form) + registerStanzaPlugin(DiscoItems, Form) def extend_info(self, node, data=None): if data is None: diff --git a/sleekxmpp/stanza/atom.py b/sleekxmpp/stanza/atom.py index 5e82cb98..9df85a2b 100644 --- a/sleekxmpp/stanza/atom.py +++ b/sleekxmpp/stanza/atom.py @@ -1,4 +1,4 @@ -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from xml.etree import cElementTree as ET class AtomEntry(ElementBase): diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py index ee46722a..b9ab2676 100644 --- a/sleekxmpp/stanza/error.py +++ b/sleekxmpp/stanza/error.py @@ -5,7 +5,7 @@ See the file license.txt for copying permission. """ -from .. xmlstream.stanzabase import ElementBase, ET +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET class Error(ElementBase): namespace = 'jabber:client' diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py index 60686e4a..14595e24 100644 --- a/sleekxmpp/stanza/htmlim.py +++ b/sleekxmpp/stanza/htmlim.py @@ -5,7 +5,7 @@ See the file license.txt for copying permission. """ -from .. xmlstream.stanzabase import ElementBase, ET +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET class HTMLIM(ElementBase): namespace = 'http://jabber.org/protocol/xhtml-im' diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py index ac7e3604..ec290703 100644 --- a/sleekxmpp/stanza/nick.py +++ b/sleekxmpp/stanza/nick.py @@ -5,7 +5,7 @@ See the file license.txt for copying permission. """ -from .. xmlstream.stanzabase import ElementBase, ET +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET class Nick(ElementBase): namespace = 'http://jabber.org/nick/nick' diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py index 69027b6c..708b8d40 100644 --- a/sleekxmpp/stanza/roster.py +++ b/sleekxmpp/stanza/roster.py @@ -5,7 +5,7 @@ See the file license.txt for copying permission. """ -from .. xmlstream.stanzabase import ElementBase, ET, JID +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID import logging class Roster(ElementBase): diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 024fe6cf..7592e1f6 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -19,6 +19,16 @@ else: xmltester = type(ET.Element('xml')) + +def registerStanzaPlugin(stanza, plugin): + """ + Associate a stanza object as a plugin for another stanza. + """ + tag = "{%s}%s" % (plugin.namespace, plugin.name) + stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin + stanza.plugin_tag_map[tag] = plugin + + class JID(object): def __init__(self, jid): self.jid = jid @@ -392,4 +402,4 @@ class StanzaBase(ElementBase): def __copy__(self): return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) - + -- cgit v1.2.3 From 16104b6e56892b65d91abe467b124ca1e86ccfcf Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 19 Jul 2010 13:36:28 -0700 Subject: made Lance's new XEP-4 stanzas the default, and put xep-0004 as old_0004 --- sleekxmpp/plugins/alt_0004.py | 330 ------------------- sleekxmpp/plugins/old_0004.py | 427 +++++++++++++++++++++++++ sleekxmpp/plugins/xep_0004.py | 719 ++++++++++++++++++------------------------ 3 files changed, 738 insertions(+), 738 deletions(-) delete mode 100644 sleekxmpp/plugins/alt_0004.py create mode 100644 sleekxmpp/plugins/old_0004.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/alt_0004.py b/sleekxmpp/plugins/alt_0004.py deleted file mode 100644 index ff9b7efd..00000000 --- a/sleekxmpp/plugins/alt_0004.py +++ /dev/null @@ -1,330 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file license.txt for copying permission. -""" - -import logging -import copy -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.message import Message - - -class Form(ElementBase): - namespace = 'jabber:x:data' - name = 'x' - plugin_attrib = 'form' - interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values')) - sub_interfaces = set(('title',)) - form_types = set(('cancel', 'form', 'result', 'submit')) - - def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None, options=None): - field = FormField(parent=self) - field['var'] = var - field['type'] = ftype - field['label'] = label - field['desc'] = desc - field['required'] = required - field['value'] = value - if options is not None: - field['options'] = options - return field - - def addItem(self, values): - itemXML = ET.Element('{%s}item' % self.namespace) - self.xml.append(itemXML) - reported_vars = self['reported'].keys() - for var in reported_vars: - fieldXML = ET.Element('{%s}field' % FormField.namespace) - itemXML.append(fieldXML) - field = FormField(xml=fieldXML) - field['var'] = var - field['value'] = values.get(var, None) - - def addReported(self, var, ftype='text-single', label='', desc=''): - reported = self.xml.find('{%s}reported' % self.namespace) - if reported is None: - reported = ET.Element('{%s}reported' % self.namespace) - self.xml.append(reported) - fieldXML = ET.Element('{%s}field' % FormField.namespace) - reported.append(fieldXML) - field = FormField(xml=fieldXML) - field['var'] = var - field['type'] = ftype - field['label'] = label - field['desc'] = desc - return field - - def cancel(self): - self['type'] = 'cancel' - - def delFields(self): - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - self.xml.remove(fieldXML) - - def delInstructions(self): - instsXML = self.xml.findall('{%s}instructions') - for instXML in instsXML: - self.xml.remove(instXML) - - def delItems(self): - itemsXML = self.xml.find('{%s}item' % self.namespace) - for itemXML in itemsXML: - self.xml.remove(itemXML) - - def delReported(self): - reportedXML = self.xml.find('{%s}reported' % self.namespace) - if reportedXML is not None: - self.xml.remove(reportedXML) - - def getFields(self): - fields = {} - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - field = FormField(xml=fieldXML) - fields[field['var']] = field - return fields - - def getInstructions(self): - instructions = '' - instsXML = self.xml.findall('{%s}instructions') - for instXML in instsXML: - instructions += instXML.text - - def getItems(self): - items = [] - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for itemXML in itemsXML: - item = {} - fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - field = FormField(xml=fieldXML) - item[field['var']] = field['value'] - items.append(item) - return items - - def getReported(self): - fields = {} - fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, - FormField.namespace)) - for fieldXML in fieldsXML: - field = FormField(xml=fieldXML) - fields[field['var']] = field - return fields - - def getValues(self): - values = {} - fields = self.getFields() - for var in fields: - values[var] = fields[var]['value'] - return values - - def reply(self): - if self['type'] == 'form': - self['type'] = 'submit' - elif self['type'] == 'submit': - self['type'] = 'result' - - def setFields(self, fields): - del self['fields'] - for var in fields: - field = fields[var] - - # Remap 'type' to 'ftype' to match the addField method - ftype = field.get('type', 'text-single') - field['type'] = ftype - del field['type'] - field['ftype'] = ftype - - self.addField(var, **field) - - def setInstructions(self, instructions): - instructions = instructions.split('\n') - for instruction in instructions: - inst = ET.Element('{%s}instructions' % self.namespace) - inst.text = instruction - self.xml.append(inst) - - def setItems(self, items): - for item in items: - self.addItem(item) - - def setReported(self, reported): - for var in reported: - field = reported[var] - - # Remap 'type' to 'ftype' to match the addReported method - ftype = field.get('type', 'text-single') - field['type'] = ftype - del field['type'] - field['ftype'] = ftype - - self.addReported(var, **field) - - def setValues(self, values): - fields = self.getFields() - for field in values: - fields[field]['value'] = values[field] - - -class FormField(ElementBase): - namespace = 'jabber:x:data' - name = 'field' - plugin_attrib = 'field' - interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var')) - sub_interfaces = set(('desc',)) - field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', - 'list-single', 'text-multi', 'text-private', 'text-single')) - multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi')) - multi_line_types = set(('hidden', 'text-multi')) - option_types = set(('list-multi', 'list-single')) - true_values = set((True, '1', 'true')) - - def addOption(self, label='', value=''): - if self['type'] in self.option_types: - opt = FieldOption(parent=self) - opt['label'] = label - opt['value'] = value - else: - raise ValueError("Cannot add options to a %s field." % self['type']) - - def delOptions(self): - optsXML = self.xml.findall('{%s}option' % self.namespace) - for optXML in optsXML: - self.xml.remove(optXML) - - def delRequired(self): - reqXML = self.xml.find('{%s}required' % self.namespace) - if reqXML is not None: - self.xml.remove(reqXML) - - def delValue(self): - valsXML = self.xml.findall('{%s}value' % self.namespace) - for valXML in valsXML: - self.xml.remove(valXML) - - def getAnswer(self): - return self.getValue() - - def getOptions(self): - options = [] - optsXML = self.xml.findall('{%s}option' % self.namespace) - for optXML in optsXML: - opt = FieldOption(xml=optXML) - options.append({'label': opt['label'], 'value':opt['value']}) - return options - - def getRequired(self): - reqXML = self.xml.find('{%s}required' % self.namespace) - return reqXML is not None - - def getValue(self): - valsXML = self.xml.findall('{%s}value' % self.namespace) - if len(valsXML) == 0: - return None - elif self['type'] == 'boolean': - return valsXML[0].text in self.true_values - elif self['type'] in self.multi_value_types: - values = [] - for valXML in valsXML: - if valXML.text is None: - valXML.text = '' - values.append(valXML.text) - if self['type'] == 'text-multi': - values = "\n".join(values) - return values - else: - return valsXML[0].text - - def setAnswer(self, answer): - self.setValue(answer) - - def setFalse(self): - self.setValue(False) - - def setOptions(self, options): - for value in options: - if isinstance(value, dict): - self.addOption(**value) - else: - self.addOption(value=value) - - def setRequired(self, required): - exists = self.getRequired() - if not exists and required: - self.xml.append(ET.Element('{%s}required' % self.namespace)) - elif exists and not required: - self.delRequired() - - def setTrue(self): - self.setValue(True) - - def setValue(self, value): - self.delValue() - valXMLName = '{%s}value' % self.namespace - - if self['type'] == 'boolean': - if value in self.true_values: - valXML = ET.Element(valXMLName) - valXML.text = 'true' - self.xml.append(valXML) - else: - valXML = ET.Element(valXMLName) - valXML.text = 'true' - self.xml.append(valXML) - if self['type'] in self.multi_value_types: - if self['type'] in self.multi_line_types and isinstance(value, str): - value = value.split('\n') - if not isinstance(value, list): - value = [value] - for val in value: - valXML = ET.Element(valXMLName) - valXML.text = val - self.xml.append(valXML) - else: - if isinstance(value, list): - raise ValueError("Cannot add multiple values to a %s field." % self['type']) - valXML = ET.Element(valXMLName) - valXML.text = value - self.xml.append(valXML) - - -class FieldOption(ElementBase): - namespace = 'jabber:x:data' - name = 'option' - plugin_attrib = 'option' - interfaces = set(('label', 'value')) - sub_interfaces = set(('value',)) - - -class alt_0004(base.base_plugin): - """ - XEP-0004: Data Forms - """ - - def plugin_init(self): - self.xep = '0004' - self.description = 'Data Forms' - - self.xmpp.registerHandler( - Callback('Data Form', - MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, - Form.namespace)), - self.handle_form)) - - registerStanzaPlugin(FormField, FieldOption) - registerStanzaPlugin(Form, FormField) - registerStanzaPlugin(Message, Form) - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') - - def handle_form(self, message): - self.xmpp.event("message_xform", message) diff --git a/sleekxmpp/plugins/old_0004.py b/sleekxmpp/plugins/old_0004.py new file mode 100644 index 00000000..353e7222 --- /dev/null +++ b/sleekxmpp/plugins/old_0004.py @@ -0,0 +1,427 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2007 Nathanael C. Fritz + This file is part of SleekXMPP. + + SleekXMPP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + SleekXMPP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SleekXMPP; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +from . import base +import logging +from xml.etree import cElementTree as ET +import copy +#TODO support item groups and results + +class old_0004(base.base_plugin): + + def plugin_init(self): + self.xep = '0004' + self.description = '*Deprecated Data Forms' + self.xmpp.add_handler("", self.handler_message_xform) + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') + + def handler_message_xform(self, xml): + object = self.handle_form(xml) + self.xmpp.event("message_form", object) + + def handler_presence_xform(self, xml): + object = self.handle_form(xml) + self.xmpp.event("presence_form", object) + + def handle_form(self, xml): + xmlform = xml.find('{jabber:x:data}x') + object = self.buildForm(xmlform) + self.xmpp.event("message_xform", object) + return object + + def buildForm(self, xml): + form = Form(ftype=xml.attrib['type']) + form.fromXML(xml) + return form + + def makeForm(self, ftype='form', title='', instructions=''): + return Form(self.xmpp, ftype, title, instructions) + +class FieldContainer(object): + def __init__(self, stanza = 'form'): + self.fields = [] + self.field = {} + self.stanza = stanza + + def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): + self.field[var] = FormField(var, ftype, label, desc, required, value) + self.fields.append(self.field[var]) + return self.field[var] + + def buildField(self, xml): + self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) + self.fields.append(self.field[xml.get('var', '__unnamed__')]) + self.field[xml.get('var', '__unnamed__')].buildField(xml) + + def buildContainer(self, xml): + self.stanza = xml.tag + for field in xml.findall('{jabber:x:data}field'): + self.buildField(field) + + def getXML(self, ftype): + container = ET.Element(self.stanza) + for field in self.fields: + container.append(field.getXML(ftype)) + return container + +class Form(FieldContainer): + types = ('form', 'submit', 'cancel', 'result') + def __init__(self, xmpp=None, ftype='form', title='', instructions=''): + if not ftype in self.types: + raise ValueError("Invalid Form Type") + FieldContainer.__init__(self) + self.xmpp = xmpp + self.type = ftype + self.title = title + self.instructions = instructions + self.reported = [] + self.items = [] + + def merge(self, form2): + form1 = Form(ftype=self.type) + form1.fromXML(self.getXML(self.type)) + for field in form2.fields: + if not field.var in form1.field: + form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) + else: + form1.field[field.var].value = field.value + for option, label in field.options: + if (option, label) not in form1.field[field.var].options: + form1.fields[field.var].addOption(option, label) + return form1 + + def copy(self): + newform = Form(ftype=self.type) + newform.fromXML(self.getXML(self.type)) + return newform + + def update(self, form): + values = form.getValues() + for var in values: + if var in self.fields: + self.fields[var].setValue(self.fields[var]) + + def getValues(self): + result = {} + for field in self.fields: + value = field.value + if len(value) == 1: + value = value[0] + result[field.var] = value + return result + + def setValues(self, values={}): + for field in values: + if field in self.field: + if isinstance(values[field], list) or isinstance(values[field], tuple): + for value in values[field]: + self.field[field].setValue(value) + else: + self.field[field].setValue(values[field]) + + def fromXML(self, xml): + self.buildForm(xml) + + def addItem(self): + newitem = FieldContainer('item') + self.items.append(newitem) + return newitem + + def buildItem(self, xml): + newitem = self.addItem() + newitem.buildContainer(xml) + + def addReported(self): + reported = FieldContainer('reported') + self.reported.append(reported) + return reported + + def buildReported(self, xml): + reported = self.addReported() + reported.buildContainer(xml) + + def setTitle(self, title): + self.title = title + + def setInstructions(self, instructions): + self.instructions = instructions + + def setType(self, ftype): + self.type = ftype + + def getXMLMessage(self, to): + msg = self.xmpp.makeMessage(to) + msg.append(self.getXML()) + return msg + + def buildForm(self, xml): + self.type = xml.get('type', 'form') + if xml.find('{jabber:x:data}title') is not None: + self.setTitle(xml.find('{jabber:x:data}title').text) + if xml.find('{jabber:x:data}instructions') is not None: + self.setInstructions(xml.find('{jabber:x:data}instructions').text) + for field in xml.findall('{jabber:x:data}field'): + self.buildField(field) + for reported in xml.findall('{jabber:x:data}reported'): + self.buildReported(reported) + for item in xml.findall('{jabber:x:data}item'): + self.buildItem(item) + + #def getXML(self, tostring = False): + def getXML(self, ftype=None): + if ftype: + self.type = ftype + form = ET.Element('{jabber:x:data}x') + form.attrib['type'] = self.type + if self.title and self.type in ('form', 'result'): + title = ET.Element('{jabber:x:data}title') + title.text = self.title + form.append(title) + if self.instructions and self.type == 'form': + instructions = ET.Element('{jabber:x:data}instructions') + instructions.text = self.instructions + form.append(instructions) + for field in self.fields: + form.append(field.getXML(self.type)) + for reported in self.reported: + form.append(reported.getXML('{jabber:x:data}reported')) + for item in self.items: + form.append(item.getXML(self.type)) + #if tostring: + # form = self.xmpp.tostring(form) + return form + + def getXHTML(self): + form = ET.Element('{http://www.w3.org/1999/xhtml}form') + if self.title: + title = ET.Element('h2') + title.text = self.title + form.append(title) + if self.instructions: + instructions = ET.Element('p') + instructions.text = self.instructions + form.append(instructions) + for field in self.fields: + form.append(field.getXHTML()) + for field in self.reported: + form.append(field.getXHTML()) + for field in self.items: + form.append(field.getXHTML()) + return form + + + def makeSubmit(self): + self.setType('submit') + +class FormField(object): + types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single') + listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single') + lbtypes = ('fixed', 'text-multi') + def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None): + if not ftype in self.types: + raise ValueError("Invalid Field Type") + self.type = ftype + self.var = var + self.label = label + self.desc = desc + self.options = [] + self.required = False + self.value = [] + if self.type in self.listtypes: + self.islist = True + else: + self.islist = False + if self.type in self.lbtypes: + self.islinebreak = True + else: + self.islinebreak = False + if value: + self.setValue(value) + + def addOption(self, value, label): + if self.islist: + self.options.append((value, label)) + else: + raise ValueError("Cannot add options to non-list type field.") + + def setTrue(self): + if self.type == 'boolean': + self.value = [True] + + def setFalse(self): + if self.type == 'boolean': + self.value = [False] + + def require(self): + self.required = True + + def setDescription(self, desc): + self.desc = desc + + def setValue(self, value): + if self.type == 'boolean': + if value in ('1', 1, True, 'true', 'True', 'yes'): + value = True + else: + value = False + if self.islinebreak and value is not None: + self.value += value.split('\n') + else: + if len(self.value) and (not self.islist or self.type == 'list-single'): + self.value = [value] + else: + self.value.append(value) + + def delValue(self, value): + if type(self.value) == type([]): + try: + idx = self.value.index(value) + if idx != -1: + self.value.pop(idx) + except ValueError: + pass + else: + self.value = '' + + def setAnswer(self, value): + self.setValue(value) + + def buildField(self, xml): + self.type = xml.get('type', 'text-single') + self.label = xml.get('label', '') + for option in xml.findall('{jabber:x:data}option'): + self.addOption(option.find('{jabber:x:data}value').text, option.get('label', '')) + for value in xml.findall('{jabber:x:data}value'): + self.setValue(value.text) + if xml.find('{jabber:x:data}required') is not None: + self.require() + if xml.find('{jabber:x:data}desc') is not None: + self.setDescription(xml.find('{jabber:x:data}desc').text) + + def getXML(self, ftype): + field = ET.Element('{jabber:x:data}field') + if ftype != 'result': + field.attrib['type'] = self.type + if self.type != 'fixed': + if self.var: + field.attrib['var'] = self.var + if self.label: + field.attrib['label'] = self.label + if ftype == 'form': + for option in self.options: + optionxml = ET.Element('{jabber:x:data}option') + optionxml.attrib['label'] = option[1] + optionval = ET.Element('{jabber:x:data}value') + optionval.text = option[0] + optionxml.append(optionval) + field.append(optionxml) + if self.required: + required = ET.Element('{jabber:x:data}required') + field.append(required) + if self.desc: + desc = ET.Element('{jabber:x:data}desc') + desc.text = self.desc + field.append(desc) + for value in self.value: + valuexml = ET.Element('{jabber:x:data}value') + if value is True or value is False: + if value: + valuexml.text = '1' + else: + valuexml.text = '0' + else: + valuexml.text = value + field.append(valuexml) + return field + + def getXHTML(self): + field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) + if self.label: + label = ET.Element('p') + label.text = "%s: " % self.label + else: + label = ET.Element('p') + label.text = "%s: " % self.var + field.append(label) + if self.type == 'boolean': + formf = ET.Element('input', {'type': 'checkbox', 'name': self.var}) + if len(self.value) and self.value[0] in (True, 'true', '1'): + formf.attrib['checked'] = 'checked' + elif self.type == 'fixed': + formf = ET.Element('p') + try: + formf.text = ', '.join(self.value) + except: + pass + field.append(formf) + formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) + try: + formf.text = ', '.join(self.value) + except: + pass + elif self.type == 'hidden': + formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) + try: + formf.text = ', '.join(self.value) + except: + pass + elif self.type in ('jid-multi', 'list-multi'): + formf = ET.Element('select', {'name': self.var}) + for option in self.options: + optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'}) + optf.text = option[1] + if option[1] in self.value: + optf.attrib['selected'] = 'selected' + formf.append(option) + elif self.type in ('jid-single', 'text-single'): + formf = ET.Element('input', {'type': 'text', 'name': self.var}) + try: + formf.attrib['value'] = ', '.join(self.value) + except: + pass + elif self.type == 'list-single': + formf = ET.Element('select', {'name': self.var}) + for option in self.options: + optf = ET.Element('option', {'value': option[0]}) + optf.text = option[1] + if not optf.text: + optf.text = option[0] + if option[1] in self.value: + optf.attrib['selected'] = 'selected' + formf.append(optf) + elif self.type == 'text-multi': + formf = ET.Element('textarea', {'name': self.var}) + try: + formf.text = ', '.join(self.value) + except: + pass + if not formf.text: + formf.text = ' ' + elif self.type == 'text-private': + formf = ET.Element('input', {'type': 'password', 'name': self.var}) + try: + formf.attrib['value'] = ', '.join(self.value) + except: + pass + label.append(formf) + return field + diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index 015bd8bc..e3d2b55a 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -1,427 +1,330 @@ """ SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + See the file license.txt for copying permission. """ -from . import base + import logging -from xml.etree import cElementTree as ET import copy -#TODO support item groups and results +from . import base +from .. xmlstream.handler.callback import Callback +from .. xmlstream.matcher.xpath import MatchXPath +from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID +from .. stanza.message import Message + + +class Form(ElementBase): + namespace = 'jabber:x:data' + name = 'x' + plugin_attrib = 'form' + interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values')) + sub_interfaces = set(('title',)) + form_types = set(('cancel', 'form', 'result', 'submit')) + + def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None, options=None): + field = FormField(parent=self) + field['var'] = var + field['type'] = ftype + field['label'] = label + field['desc'] = desc + field['required'] = required + field['value'] = value + if options is not None: + field['options'] = options + return field + + def addItem(self, values): + itemXML = ET.Element('{%s}item' % self.namespace) + self.xml.append(itemXML) + reported_vars = self['reported'].keys() + for var in reported_vars: + fieldXML = ET.Element('{%s}field' % FormField.namespace) + itemXML.append(fieldXML) + field = FormField(xml=fieldXML) + field['var'] = var + field['value'] = values.get(var, None) + + def addReported(self, var, ftype='text-single', label='', desc=''): + reported = self.xml.find('{%s}reported' % self.namespace) + if reported is None: + reported = ET.Element('{%s}reported' % self.namespace) + self.xml.append(reported) + fieldXML = ET.Element('{%s}field' % FormField.namespace) + reported.append(fieldXML) + field = FormField(xml=fieldXML) + field['var'] = var + field['type'] = ftype + field['label'] = label + field['desc'] = desc + return field + + def cancel(self): + self['type'] = 'cancel' + + def delFields(self): + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + self.xml.remove(fieldXML) + + def delInstructions(self): + instsXML = self.xml.findall('{%s}instructions') + for instXML in instsXML: + self.xml.remove(instXML) + + def delItems(self): + itemsXML = self.xml.find('{%s}item' % self.namespace) + for itemXML in itemsXML: + self.xml.remove(itemXML) + + def delReported(self): + reportedXML = self.xml.find('{%s}reported' % self.namespace) + if reportedXML is not None: + self.xml.remove(reportedXML) + + def getFields(self): + fields = {} + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + fields[field['var']] = field + return fields + + def getInstructions(self): + instructions = '' + instsXML = self.xml.findall('{%s}instructions') + for instXML in instsXML: + instructions += instXML.text + + def getItems(self): + items = [] + itemsXML = self.xml.findall('{%s}item' % self.namespace) + for itemXML in itemsXML: + item = {} + fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + item[field['var']] = field['value'] + items.append(item) + return items + + def getReported(self): + fields = {} + fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, + FormField.namespace)) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + fields[field['var']] = field + return fields -class xep_0004(base.base_plugin): - - def plugin_init(self): - self.xep = '0004' - self.description = 'Data Forms' - self.xmpp.add_handler("", self.handler_message_xform) - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') - - def handler_message_xform(self, xml): - object = self.handle_form(xml) - self.xmpp.event("message_form", object) - - def handler_presence_xform(self, xml): - object = self.handle_form(xml) - self.xmpp.event("presence_form", object) - - def handle_form(self, xml): - xmlform = xml.find('{jabber:x:data}x') - object = self.buildForm(xmlform) - self.xmpp.event("message_xform", object) - return object - - def buildForm(self, xml): - form = Form(ftype=xml.attrib['type']) - form.fromXML(xml) - return form - - def makeForm(self, ftype='form', title='', instructions=''): - return Form(self.xmpp, ftype, title, instructions) - -class FieldContainer(object): - def __init__(self, stanza = 'form'): - self.fields = [] - self.field = {} - self.stanza = stanza - - def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): - self.field[var] = FormField(var, ftype, label, desc, required, value) - self.fields.append(self.field[var]) - return self.field[var] - - def buildField(self, xml): - self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) - self.fields.append(self.field[xml.get('var', '__unnamed__')]) - self.field[xml.get('var', '__unnamed__')].buildField(xml) - - def buildContainer(self, xml): - self.stanza = xml.tag - for field in xml.findall('{jabber:x:data}field'): - self.buildField(field) - - def getXML(self, ftype): - container = ET.Element(self.stanza) - for field in self.fields: - container.append(field.getXML(ftype)) - return container - -class Form(FieldContainer): - types = ('form', 'submit', 'cancel', 'result') - def __init__(self, xmpp=None, ftype='form', title='', instructions=''): - if not ftype in self.types: - raise ValueError("Invalid Form Type") - FieldContainer.__init__(self) - self.xmpp = xmpp - self.type = ftype - self.title = title - self.instructions = instructions - self.reported = [] - self.items = [] - - def merge(self, form2): - form1 = Form(ftype=self.type) - form1.fromXML(self.getXML(self.type)) - for field in form2.fields: - if not field.var in form1.field: - form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) - else: - form1.field[field.var].value = field.value - for option, label in field.options: - if (option, label) not in form1.field[field.var].options: - form1.fields[field.var].addOption(option, label) - return form1 - - def copy(self): - newform = Form(ftype=self.type) - newform.fromXML(self.getXML(self.type)) - return newform - - def update(self, form): - values = form.getValues() - for var in values: - if var in self.fields: - self.fields[var].setValue(self.fields[var]) - def getValues(self): - result = {} - for field in self.fields: - value = field.value - if len(value) == 1: - value = value[0] - result[field.var] = value - return result - - def setValues(self, values={}): - for field in values: - if field in self.field: - if isinstance(values[field], list) or isinstance(values[field], tuple): - for value in values[field]: - self.field[field].setValue(value) - else: - self.field[field].setValue(values[field]) - - def fromXML(self, xml): - self.buildForm(xml) - - def addItem(self): - newitem = FieldContainer('item') - self.items.append(newitem) - return newitem - - def buildItem(self, xml): - newitem = self.addItem() - newitem.buildContainer(xml) - - def addReported(self): - reported = FieldContainer('reported') - self.reported.append(reported) - return reported - - def buildReported(self, xml): - reported = self.addReported() - reported.buildContainer(xml) - - def setTitle(self, title): - self.title = title - + values = {} + fields = self.getFields() + for var in fields: + values[var] = fields[var]['value'] + return values + + def reply(self): + if self['type'] == 'form': + self['type'] = 'submit' + elif self['type'] == 'submit': + self['type'] = 'result' + + def setFields(self, fields): + del self['fields'] + for var in fields: + field = fields[var] + + # Remap 'type' to 'ftype' to match the addField method + ftype = field.get('type', 'text-single') + field['type'] = ftype + del field['type'] + field['ftype'] = ftype + + self.addField(var, **field) + def setInstructions(self, instructions): - self.instructions = instructions - - def setType(self, ftype): - self.type = ftype - - def getXMLMessage(self, to): - msg = self.xmpp.makeMessage(to) - msg.append(self.getXML()) - return msg - - def buildForm(self, xml): - self.type = xml.get('type', 'form') - if xml.find('{jabber:x:data}title') is not None: - self.setTitle(xml.find('{jabber:x:data}title').text) - if xml.find('{jabber:x:data}instructions') is not None: - self.setInstructions(xml.find('{jabber:x:data}instructions').text) - for field in xml.findall('{jabber:x:data}field'): - self.buildField(field) - for reported in xml.findall('{jabber:x:data}reported'): - self.buildReported(reported) - for item in xml.findall('{jabber:x:data}item'): - self.buildItem(item) - - #def getXML(self, tostring = False): - def getXML(self, ftype=None): - if ftype: - self.type = ftype - form = ET.Element('{jabber:x:data}x') - form.attrib['type'] = self.type - if self.title and self.type in ('form', 'result'): - title = ET.Element('{jabber:x:data}title') - title.text = self.title - form.append(title) - if self.instructions and self.type == 'form': - instructions = ET.Element('{jabber:x:data}instructions') - instructions.text = self.instructions - form.append(instructions) - for field in self.fields: - form.append(field.getXML(self.type)) - for reported in self.reported: - form.append(reported.getXML('{jabber:x:data}reported')) - for item in self.items: - form.append(item.getXML(self.type)) - #if tostring: - # form = self.xmpp.tostring(form) - return form - - def getXHTML(self): - form = ET.Element('{http://www.w3.org/1999/xhtml}form') - if self.title: - title = ET.Element('h2') - title.text = self.title - form.append(title) - if self.instructions: - instructions = ET.Element('p') - instructions.text = self.instructions - form.append(instructions) - for field in self.fields: - form.append(field.getXHTML()) - for field in self.reported: - form.append(field.getXHTML()) - for field in self.items: - form.append(field.getXHTML()) - return form - - - def makeSubmit(self): - self.setType('submit') - -class FormField(object): - types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single') - listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single') - lbtypes = ('fixed', 'text-multi') - def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None): - if not ftype in self.types: - raise ValueError("Invalid Field Type") - self.type = ftype - self.var = var - self.label = label - self.desc = desc - self.options = [] - self.required = False - self.value = [] - if self.type in self.listtypes: - self.islist = True - else: - self.islist = False - if self.type in self.lbtypes: - self.islinebreak = True + instructions = instructions.split('\n') + for instruction in instructions: + inst = ET.Element('{%s}instructions' % self.namespace) + inst.text = instruction + self.xml.append(inst) + + def setItems(self, items): + for item in items: + self.addItem(item) + + def setReported(self, reported): + for var in reported: + field = reported[var] + + # Remap 'type' to 'ftype' to match the addReported method + ftype = field.get('type', 'text-single') + field['type'] = ftype + del field['type'] + field['ftype'] = ftype + + self.addReported(var, **field) + + def setValues(self, values): + fields = self.getFields() + for field in values: + fields[field]['value'] = values[field] + + +class FormField(ElementBase): + namespace = 'jabber:x:data' + name = 'field' + plugin_attrib = 'field' + interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var')) + sub_interfaces = set(('desc',)) + field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', + 'list-single', 'text-multi', 'text-private', 'text-single')) + multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi')) + multi_line_types = set(('hidden', 'text-multi')) + option_types = set(('list-multi', 'list-single')) + true_values = set((True, '1', 'true')) + + def addOption(self, label='', value=''): + if self['type'] in self.option_types: + opt = FieldOption(parent=self) + opt['label'] = label + opt['value'] = value else: - self.islinebreak = False - if value: - self.setValue(value) - - def addOption(self, value, label): - if self.islist: - self.options.append((value, label)) + raise ValueError("Cannot add options to a %s field." % self['type']) + + def delOptions(self): + optsXML = self.xml.findall('{%s}option' % self.namespace) + for optXML in optsXML: + self.xml.remove(optXML) + + def delRequired(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + if reqXML is not None: + self.xml.remove(reqXML) + + def delValue(self): + valsXML = self.xml.findall('{%s}value' % self.namespace) + for valXML in valsXML: + self.xml.remove(valXML) + + def getAnswer(self): + return self.getValue() + + def getOptions(self): + options = [] + optsXML = self.xml.findall('{%s}option' % self.namespace) + for optXML in optsXML: + opt = FieldOption(xml=optXML) + options.append({'label': opt['label'], 'value':opt['value']}) + return options + + def getRequired(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + return reqXML is not None + + def getValue(self): + valsXML = self.xml.findall('{%s}value' % self.namespace) + if len(valsXML) == 0: + return None + elif self['type'] == 'boolean': + return valsXML[0].text in self.true_values + elif self['type'] in self.multi_value_types: + values = [] + for valXML in valsXML: + if valXML.text is None: + valXML.text = '' + values.append(valXML.text) + if self['type'] == 'text-multi': + values = "\n".join(values) + return values else: - raise ValueError("Cannot add options to non-list type field.") - - def setTrue(self): - if self.type == 'boolean': - self.value = [True] + return valsXML[0].text + + def setAnswer(self, answer): + self.setValue(answer) def setFalse(self): - if self.type == 'boolean': - self.value = [False] + self.setValue(False) - def require(self): - self.required = True - - def setDescription(self, desc): - self.desc = desc - - def setValue(self, value): - if self.type == 'boolean': - if value in ('1', 1, True, 'true', 'True', 'yes'): - value = True + def setOptions(self, options): + for value in options: + if isinstance(value, dict): + self.addOption(**value) else: - value = False - if self.islinebreak and value is not None: - self.value += value.split('\n') - else: - if len(self.value) and (not self.islist or self.type == 'list-single'): - self.value = [value] + self.addOption(value=value) + + def setRequired(self, required): + exists = self.getRequired() + if not exists and required: + self.xml.append(ET.Element('{%s}required' % self.namespace)) + elif exists and not required: + self.delRequired() + + def setTrue(self): + self.setValue(True) + + def setValue(self, value): + self.delValue() + valXMLName = '{%s}value' % self.namespace + + if self['type'] == 'boolean': + if value in self.true_values: + valXML = ET.Element(valXMLName) + valXML.text = 'true' + self.xml.append(valXML) else: - self.value.append(value) - - def delValue(self, value): - if type(self.value) == type([]): - try: - idx = self.value.index(value) - if idx != -1: - self.value.pop(idx) - except ValueError: - pass + valXML = ET.Element(valXMLName) + valXML.text = 'true' + self.xml.append(valXML) + if self['type'] in self.multi_value_types: + if self['type'] in self.multi_line_types and isinstance(value, str): + value = value.split('\n') + if not isinstance(value, list): + value = [value] + for val in value: + valXML = ET.Element(valXMLName) + valXML.text = val + self.xml.append(valXML) else: - self.value = '' - - def setAnswer(self, value): - self.setValue(value) - - def buildField(self, xml): - self.type = xml.get('type', 'text-single') - self.label = xml.get('label', '') - for option in xml.findall('{jabber:x:data}option'): - self.addOption(option.find('{jabber:x:data}value').text, option.get('label', '')) - for value in xml.findall('{jabber:x:data}value'): - self.setValue(value.text) - if xml.find('{jabber:x:data}required') is not None: - self.require() - if xml.find('{jabber:x:data}desc') is not None: - self.setDescription(xml.find('{jabber:x:data}desc').text) - - def getXML(self, ftype): - field = ET.Element('{jabber:x:data}field') - if ftype != 'result': - field.attrib['type'] = self.type - if self.type != 'fixed': - if self.var: - field.attrib['var'] = self.var - if self.label: - field.attrib['label'] = self.label - if ftype == 'form': - for option in self.options: - optionxml = ET.Element('{jabber:x:data}option') - optionxml.attrib['label'] = option[1] - optionval = ET.Element('{jabber:x:data}value') - optionval.text = option[0] - optionxml.append(optionval) - field.append(optionxml) - if self.required: - required = ET.Element('{jabber:x:data}required') - field.append(required) - if self.desc: - desc = ET.Element('{jabber:x:data}desc') - desc.text = self.desc - field.append(desc) - for value in self.value: - valuexml = ET.Element('{jabber:x:data}value') - if value is True or value is False: - if value: - valuexml.text = '1' - else: - valuexml.text = '0' - else: - valuexml.text = value - field.append(valuexml) - return field + if isinstance(value, list): + raise ValueError("Cannot add multiple values to a %s field." % self['type']) + valXML = ET.Element(valXMLName) + valXML.text = value + self.xml.append(valXML) + + +class FieldOption(ElementBase): + namespace = 'jabber:x:data' + name = 'option' + plugin_attrib = 'option' + interfaces = set(('label', 'value')) + sub_interfaces = set(('value',)) + + +class xep_0004(base.base_plugin): + """ + XEP-0004: Data Forms + """ + + def plugin_init(self): + self.xep = '0004' + self.description = 'Data Forms' + + self.xmpp.registerHandler( + Callback('Data Form', + MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, + Form.namespace)), + self.handle_form)) + + registerStanzaPlugin(FormField, FieldOption) + registerStanzaPlugin(Form, FormField) + registerStanzaPlugin(Message, Form) - def getXHTML(self): - field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) - if self.label: - label = ET.Element('p') - label.text = "%s: " % self.label - else: - label = ET.Element('p') - label.text = "%s: " % self.var - field.append(label) - if self.type == 'boolean': - formf = ET.Element('input', {'type': 'checkbox', 'name': self.var}) - if len(self.value) and self.value[0] in (True, 'true', '1'): - formf.attrib['checked'] = 'checked' - elif self.type == 'fixed': - formf = ET.Element('p') - try: - formf.text = ', '.join(self.value) - except: - pass - field.append(formf) - formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) - try: - formf.text = ', '.join(self.value) - except: - pass - elif self.type == 'hidden': - formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) - try: - formf.text = ', '.join(self.value) - except: - pass - elif self.type in ('jid-multi', 'list-multi'): - formf = ET.Element('select', {'name': self.var}) - for option in self.options: - optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'}) - optf.text = option[1] - if option[1] in self.value: - optf.attrib['selected'] = 'selected' - formf.append(option) - elif self.type in ('jid-single', 'text-single'): - formf = ET.Element('input', {'type': 'text', 'name': self.var}) - try: - formf.attrib['value'] = ', '.join(self.value) - except: - pass - elif self.type == 'list-single': - formf = ET.Element('select', {'name': self.var}) - for option in self.options: - optf = ET.Element('option', {'value': option[0]}) - optf.text = option[1] - if not optf.text: - optf.text = option[0] - if option[1] in self.value: - optf.attrib['selected'] = 'selected' - formf.append(optf) - elif self.type == 'text-multi': - formf = ET.Element('textarea', {'name': self.var}) - try: - formf.text = ', '.join(self.value) - except: - pass - if not formf.text: - formf.text = ' ' - elif self.type == 'text-private': - formf = ET.Element('input', {'type': 'password', 'name': self.var}) - try: - formf.attrib['value'] = ', '.join(self.value) - except: - pass - label.append(formf) - return field - + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') + + def handle_form(self, message): + self.xmpp.event("message_xform", message) -- cgit v1.2.3 From 130a148d3434b001e24a544022ee4df8597dbe96 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 19 Jul 2010 13:53:41 -0700 Subject: added fromXML/getXML compatiblity to the new xep-0004 w/ deprecated warnings --- sleekxmpp/plugins/old_0004.py | 2 ++ sleekxmpp/plugins/xep_0004.py | 9 +++++++++ 2 files changed, 11 insertions(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/old_0004.py b/sleekxmpp/plugins/old_0004.py index 353e7222..263f8b21 100644 --- a/sleekxmpp/plugins/old_0004.py +++ b/sleekxmpp/plugins/old_0004.py @@ -21,6 +21,7 @@ from . import base import logging from xml.etree import cElementTree as ET import copy +import logging #TODO support item groups and results class old_0004(base.base_plugin): @@ -33,6 +34,7 @@ class old_0004(base.base_plugin): def post_init(self): base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') + logging.warning("This implementation of XEP-0004 is deprecated.") def handler_message_xform(self, xml): object = self.handle_form(xml) diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index e3d2b55a..712e84ae 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -35,6 +35,15 @@ class Form(ElementBase): field['options'] = options return field + def getXML(self): + logging.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") + return self.xml + + def fromXML(self, xml): + logging.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") + n = Form(xml=xml) + return n + def addItem(self, values): itemXML = ET.Element('{%s}item' % self.namespace) self.xml.append(itemXML) -- cgit v1.2.3 From f80b3285d49a2ca395369a98cb0f7cf1fda4e218 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 19 Jul 2010 14:57:21 -0700 Subject: indent problem on stanzabase --- sleekxmpp/xmlstream/stanzabase.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 7592e1f6..6508c0af 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -21,10 +21,10 @@ xmltester = type(ET.Element('xml')) def registerStanzaPlugin(stanza, plugin): - """ - Associate a stanza object as a plugin for another stanza. - """ - tag = "{%s}%s" % (plugin.namespace, plugin.name) + """ + Associate a stanza object as a plugin for another stanza. + """ + tag = "{%s}%s" % (plugin.namespace, plugin.name) stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin stanza.plugin_tag_map[tag] = plugin -- cgit v1.2.3 From fec8578cf61696d8ca85a6fe85a55be71d7109fd Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 19 Jul 2010 15:38:48 -0700 Subject: stanza should not have setValues/getValues because that conflicts with attribute accessors --- sleekxmpp/__init__.py | 6 +++--- sleekxmpp/basexmpp.py | 12 ++++++------ sleekxmpp/plugins/xep_0045.py | 2 +- sleekxmpp/xmlstream/stanzabase.py | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index df0af09c..3d659a85 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -145,7 +145,7 @@ class ClientXMPP(basexmpp, XMLStream): def updateRoster(self, jid, name=None, subscription=None, groups=[]): """Add or change a roster item.""" - iq = self.Iq().setValues({'type': 'set'}) + iq = self.Iq().setStanzaValues({'type': 'set'}) iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} #self.send(iq, self.Iq().setValues({'id': iq['id']})) r = iq.send() @@ -159,7 +159,7 @@ class ClientXMPP(basexmpp, XMLStream): def getRoster(self): """Request the roster be sent.""" - iq = self.Iq().setValues({'type': 'get'}).enable('roster').send() + iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send() self._handleRoster(iq, request=True) def _handleStreamFeatures(self, features): @@ -254,5 +254,5 @@ class ClientXMPP(basexmpp, XMLStream): self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} self.roster[jid].update(iq['roster']['items'][jid]) if iq['type'] == 'set': - self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) + self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster')) self.event("roster_update", iq) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index c9439ea3..12dc2a1b 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -144,26 +144,26 @@ class basexmpp(object): return waitfor.wait(timeout) def makeIq(self, id=0, ifrom=None): - return self.Iq().setValues({'id': str(id), 'from': ifrom}) + return self.Iq().setStanzaValues({'id': str(id), 'from': ifrom}) def makeIqGet(self, queryxmlns = None): - iq = self.Iq().setValues({'type': 'get'}) + iq = self.Iq().setStanzaValues({'type': 'get'}) if queryxmlns: iq.append(ET.Element("{%s}query" % queryxmlns)) return iq def makeIqResult(self, id): - return self.Iq().setValues({'id': id, 'type': 'result'}) + return self.Iq().setStanzaValues({'id': id, 'type': 'result'}) def makeIqSet(self, sub=None): - iq = self.Iq().setValues({'type': 'set'}) + 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().setValues({'id': id}) - iq['error'].setValues({'type': type, 'condition': condition, 'text': text}) + iq = self.Iq().setStanzaValues({'id': id}) + iq['error'].setStanzaValues({'type': type, 'condition': condition, 'text': text}) return iq def makeIqQuery(self, iq, xmlns): diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index 88ada19d..cc676a6f 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -134,7 +134,7 @@ class xep_0045(base.base_plugin): """ if pr['muc']['room'] not in self.rooms.keys(): return - entry = pr['muc'].getValues() + entry = pr['muc'].getStanzaValues() if pr['type'] == 'unavailable': del self.rooms[entry['room']][entry['nick']] else: diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 6508c0af..4e6afee4 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -241,7 +241,7 @@ class ElementBase(tostring.ToString): def __eq__(self, other): if not isinstance(other, ElementBase): return False - values = self.getValues() + values = self.getStanzaValues() for key in other: if key not in values or values[key] != other[key]: return False @@ -283,21 +283,21 @@ class ElementBase(tostring.ToString): if child.tag == "{%s}%s" % (self.namespace, name): self.xml.remove(child) - def getValues(self): + def getStanzaValues(self): out = {} for interface in self.interfaces: out[interface] = self[interface] for pluginkey in self.plugins: - out[pluginkey] = self.plugins[pluginkey].getValues() + out[pluginkey] = self.plugins[pluginkey].getStanzaValues() if self.iterables: iterables = [] for stanza in self.iterables: - iterables.append(stanza.getValues()) + iterables.append(stanza.getStanzaValues()) iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) out['substanzas'] = iterables return out - def setValues(self, attrib): + def setStanzaValues(self, attrib): for interface in attrib: if interface == 'substanzas': for subdict in attrib['substanzas']: @@ -305,7 +305,7 @@ class ElementBase(tostring.ToString): for subclass in self.subitem: if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name): sub = subclass(parent=self) - sub.setValues(subdict) + sub.setStanzaValues(subdict) self.iterables.append(sub) break elif interface in self.interfaces: @@ -313,7 +313,7 @@ class ElementBase(tostring.ToString): elif interface in self.plugin_attrib_map and interface not in self.plugins: self.initPlugin(interface) if interface in self.plugins: - self.plugins[interface].setValues(attrib[interface]) + self.plugins[interface].setStanzaValues(attrib[interface]) return self def appendxml(self, xml): -- cgit v1.2.3 From b5a14a0190f6ea45bfbc0e18a7ff6c61b6415865 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 19 Jul 2010 19:19:33 -0400 Subject: Can now pass a name to add_handler so that the handler can be reliably removed later. Updated uses of add_handler to include a name. --- sleekxmpp/__init__.py | 6 +++--- sleekxmpp/basexmpp.py | 8 +++++--- sleekxmpp/plugins/old_0004.py | 2 +- sleekxmpp/plugins/xep_0009.py | 9 ++++++--- sleekxmpp/plugins/xep_0050.py | 10 +++++----- sleekxmpp/plugins/xep_0092.py | 2 +- sleekxmpp/plugins/xep_0199.py | 2 +- sleekxmpp/tests/testpubsub.py | 4 ++-- 8 files changed, 24 insertions(+), 19 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 3d659a85..86b74fc2 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -175,7 +175,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.add_handler("", self.handler_tls_start, name='TLS Proceed', instream=True) self.sendXML(xml) return True else: @@ -191,8 +191,8 @@ class ClientXMPP(basexmpp, XMLStream): if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: return False logging.debug("Starting SASL Auth") - self.add_handler("", self.handler_auth_success, instream=True) - self.add_handler("", self.handler_auth_fail, instream=True) + self.add_handler("", self.handler_auth_success, name='SASL Sucess', instream=True) + self.add_handler("", self.handler_auth_fail, name='SASL Failure', instream=True) sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') if len(sasl_mechs): for sasl_mech in sasl_mechs: diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 12dc2a1b..8489b24b 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -118,9 +118,11 @@ class basexmpp(object): self.id += 1 return self.getId() - def add_handler(self, mask, pointer, disposable=False, threaded=False, filter=False, instream=False): - #logging.warning("Deprecated add_handler used for %s: %s." % (mask, pointer)) - self.registerHandler(XMLCallback('add_handler_%s' % self.getNewId(), MatchXMLMask(mask), pointer, threaded, disposable, instream)) + 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)) def getId(self): return "%x".upper() % self.id diff --git a/sleekxmpp/plugins/old_0004.py b/sleekxmpp/plugins/old_0004.py index 263f8b21..2d4203ad 100644 --- a/sleekxmpp/plugins/old_0004.py +++ b/sleekxmpp/plugins/old_0004.py @@ -29,7 +29,7 @@ class old_0004(base.base_plugin): def plugin_init(self): self.xep = '0004' self.description = '*Deprecated Data Forms' - self.xmpp.add_handler("", self.handler_message_xform) + self.xmpp.add_handler("", self.handler_message_xform, name='Old Message Form') def post_init(self): base.base_plugin.post_init(self) diff --git a/sleekxmpp/plugins/xep_0009.py b/sleekxmpp/plugins/xep_0009.py index 49ffac41..625b03fb 100644 --- a/sleekxmpp/plugins/xep_0009.py +++ b/sleekxmpp/plugins/xep_0009.py @@ -178,9 +178,12 @@ class xep_0009(base.base_plugin): def plugin_init(self): self.xep = '0009' self.description = 'Jabber-RPC' - self.xmpp.add_handler("", self._callMethod) - self.xmpp.add_handler("", self._callResult) - self.xmpp.add_handler("", self._callError) + self.xmpp.add_handler("", + self._callMethod, name='Jabber RPC Call') + self.xmpp.add_handler("", + self._callResult, name='Jabber RPC Result') + self.xmpp.add_handler("", + self._callError, name='Jabber RPC Error') self.entries = {} self.activeCalls = [] diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py index 2f356e17..11a17728 100644 --- a/sleekxmpp/plugins/xep_0050.py +++ b/sleekxmpp/plugins/xep_0050.py @@ -32,11 +32,11 @@ class xep_0050(base.base_plugin): def plugin_init(self): self.xep = '0050' self.description = 'Ad-Hoc Commands' - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command) - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command) - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_next, threaded=True) - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_cancel) - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_complete) + self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None') + self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute') + self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True) + self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel') + self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete') self.commands = {} self.sessions = {} self.sd = self.xmpp.plugin['xep_0030'] diff --git a/sleekxmpp/plugins/xep_0092.py b/sleekxmpp/plugins/xep_0092.py index aeebbe0c..77a6d9d0 100644 --- a/sleekxmpp/plugins/xep_0092.py +++ b/sleekxmpp/plugins/xep_0092.py @@ -30,7 +30,7 @@ class xep_0092(base.base_plugin): self.xep = "0092" self.name = self.config.get('name', 'SleekXMPP') self.version = self.config.get('version', '0.1-dev') - self.xmpp.add_handler("" % self.xmpp.default_ns, self.report_version) + self.xmpp.add_handler("" % self.xmpp.default_ns, self.report_version, name='Sofware Version') def post_init(self): base.base_plugin.post_init(self) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index ccaf0b3a..1da57fee 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -29,7 +29,7 @@ class xep_0199(base.base_plugin): def plugin_init(self): self.description = "XMPP Ping" self.xep = "0199" - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping) + self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') self.running = False #if self.config.get('keepalive', True): #self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) diff --git a/sleekxmpp/tests/testpubsub.py b/sleekxmpp/tests/testpubsub.py index ed9dd5c2..d8f3ff4a 100755 --- a/sleekxmpp/tests/testpubsub.py +++ b/sleekxmpp/tests/testpubsub.py @@ -34,9 +34,9 @@ class testps(sleekxmpp.ClientXMPP): self.registerPlugin('xep_0030') self.registerPlugin('xep_0060') self.registerPlugin('xep_0092') - self.add_handler("", self.pubsubEventHandler, threaded=True) + self.add_handler("", self.pubsubEventHandler, name='Pubsub Event', threaded=True) self.add_event_handler("session_start", self.start, threaded=True) - self.add_handler("", self.handleError) + self.add_handler("", self.handleError, name='Iq Error') self.events = Queue.Queue() self.default_config = None self.ps = self.plugin['xep_0060'] -- cgit v1.2.3 From bb927c7e6ad75b190ab3aeea7fd71d8cd2118eed Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 00:04:34 -0400 Subject: Updated presence stanza to include a 'show' interface. Presence stanza tests updated accordingly. --- sleekxmpp/stanza/presence.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py index c66246c9..0da7ffc8 100644 --- a/sleekxmpp/stanza/presence.py +++ b/sleekxmpp/stanza/presence.py @@ -5,36 +5,34 @@ See the file license.txt for copying permission. """ -from .. xmlstream.stanzabase import StanzaBase -from xml.etree import cElementTree as ET + from . error import Error from . rootstanza import RootStanza +from .. xmlstream.stanzabase import StanzaBase, ET + class Presence(RootStanza): - interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority')) + interfaces = set(('type', 'to', 'from', 'id', 'show', 'status', 'priority')) types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed')) showtypes = set(('dnd', 'chat', 'xa', 'away')) - sub_interfaces = set(('status', 'priority')) + sub_interfaces = set(('show', 'status', 'priority')) name = 'presence' plugin_attrib = name namespace = 'jabber:client' - def getShowElement(self): - return self.xml.find("{%s}show" % self.namespace) + def setShow(self, show): + if show in self.showtypes: + self._setSubText('show', text=show) + return self def setType(self, value): - show = self.getShowElement() if value in self.types: - if show is not None: - self.xml.remove(show) + self['show'] = None if value == 'available': value = '' self._setAttr('type', value) elif value in self.showtypes: - if show is None: - show = ET.Element("{%s}show" % self.namespace) - self.xml.append(show) - show.text = value + self['show'] = value return self def setPriority(self, value): @@ -48,9 +46,7 @@ class Presence(RootStanza): def getType(self): out = self._getAttr('type') if not out: - show = self.getShowElement() - if show is not None: - out = show.text + out = self['show'] if not out or out is None: out = 'available' return out -- cgit v1.2.3 From 9ca4bba2def8cffe1c079ce98304f1fa89b95b75 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 00:34:24 -0400 Subject: Update XEP-0128 to use new xep_0004 --- sleekxmpp/plugins/xep_0128.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0128.py b/sleekxmpp/plugins/xep_0128.py index 7ba00bf5..dfe5829f 100644 --- a/sleekxmpp/plugins/xep_0128.py +++ b/sleekxmpp/plugins/xep_0128.py @@ -13,7 +13,7 @@ from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq from . xep_0030 import DiscoInfo, DiscoItems -from . alt_0004 import Form +from . xep_0004 import Form class xep_0128(base.base_plugin): -- cgit v1.2.3 From 690eaf8d3c3856c6242612da22e6c6d323f193ed Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 11:19:49 -0400 Subject: Updated license notices to use the correct MIT format. Also corrected references to nonexistant license.txt to LICENSE. --- sleekxmpp/__init__.py | 2 +- sleekxmpp/basexmpp.py | 2 +- sleekxmpp/componentxmpp.py | 2 +- sleekxmpp/exceptions.py | 2 +- sleekxmpp/plugins/__init__.py | 18 +++--------------- sleekxmpp/plugins/base.py | 22 ++++++---------------- sleekxmpp/plugins/gmail_notify.py | 2 +- sleekxmpp/plugins/old_0004.py | 22 +++++----------------- sleekxmpp/plugins/xep_0004.py | 2 +- sleekxmpp/plugins/xep_0030.py | 2 +- sleekxmpp/plugins/xep_0033.py | 2 +- sleekxmpp/plugins/xep_0045.py | 22 +++++----------------- sleekxmpp/plugins/xep_0050.py | 22 +++++----------------- sleekxmpp/plugins/xep_0078.py | 22 +++++----------------- sleekxmpp/plugins/xep_0085.py | 2 +- sleekxmpp/plugins/xep_0092.py | 22 +++++----------------- sleekxmpp/plugins/xep_0128.py | 2 +- sleekxmpp/plugins/xep_0199.py | 23 +++++------------------ sleekxmpp/stanza/__init__.py | 2 +- sleekxmpp/stanza/error.py | 2 +- sleekxmpp/stanza/htmlim.py | 2 +- sleekxmpp/stanza/iq.py | 2 +- sleekxmpp/stanza/message.py | 2 +- sleekxmpp/stanza/nick.py | 2 +- sleekxmpp/stanza/presence.py | 2 +- sleekxmpp/stanza/rootstanza.py | 2 +- sleekxmpp/stanza/roster.py | 2 +- sleekxmpp/tests/testpubsub.py | 18 ++++-------------- sleekxmpp/xmlstream/filesocket.py | 2 +- sleekxmpp/xmlstream/handler/base.py | 2 +- sleekxmpp/xmlstream/handler/callback.py | 2 +- sleekxmpp/xmlstream/handler/waiter.py | 2 +- sleekxmpp/xmlstream/handler/xmlcallback.py | 2 +- sleekxmpp/xmlstream/handler/xmlwaiter.py | 2 +- sleekxmpp/xmlstream/matcher/base.py | 2 +- sleekxmpp/xmlstream/matcher/id.py | 2 +- sleekxmpp/xmlstream/matcher/many.py | 2 +- sleekxmpp/xmlstream/matcher/stanzapath.py | 2 +- sleekxmpp/xmlstream/matcher/xmlmask.py | 2 +- sleekxmpp/xmlstream/matcher/xpath.py | 2 +- sleekxmpp/xmlstream/stanzabase.py | 2 +- sleekxmpp/xmlstream/statemachine.py | 2 +- sleekxmpp/xmlstream/xmlstream.py | 2 +- 43 files changed, 77 insertions(+), 182 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 86b74fc2..b680ddaf 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -5,7 +5,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from __future__ import absolute_import, unicode_literals from . basexmpp import basexmpp diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 8489b24b..bfbfd594 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from __future__ import with_statement, unicode_literals diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index de125814..4d39ce80 100755 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -5,7 +5,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from __future__ import absolute_import from . basexmpp import basexmpp diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py index 5b761cf3..bbbd69d5 100644 --- a/sleekxmpp/exceptions.py +++ b/sleekxmpp/exceptions.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. -See the file license.txt for copying permission. +See the file LICENSE for copying permission. """ class XMPPError(Exception): diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index fbc5e014..b51977b8 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -1,20 +1,8 @@ """ SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz + Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + See the file LICENSE for copying permission. """ __all__ = ['xep_0004', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] diff --git a/sleekxmpp/plugins/base.py b/sleekxmpp/plugins/base.py index 4223646a..a5260b0c 100644 --- a/sleekxmpp/plugins/base.py +++ b/sleekxmpp/plugins/base.py @@ -1,22 +1,12 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -""" class base_plugin(object): def __init__(self, xmpp, config): diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py index fb1ecb3d..7e442346 100644 --- a/sleekxmpp/plugins/gmail_notify.py +++ b/sleekxmpp/plugins/gmail_notify.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ import logging diff --git a/sleekxmpp/plugins/old_0004.py b/sleekxmpp/plugins/old_0004.py index 2d4203ad..651408ae 100644 --- a/sleekxmpp/plugins/old_0004.py +++ b/sleekxmpp/plugins/old_0004.py @@ -1,21 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + 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 . import base import logging diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index 712e84ae..50f2b5ee 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ import logging diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 1e04fe4f..a9d8d6a7 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ import logging diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py index 9af27e32..ea0b10b7 100644 --- a/sleekxmpp/plugins/xep_0033.py +++ b/sleekxmpp/plugins/xep_0033.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ import logging diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index cc676a6f..cd1a9a09 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -1,21 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + 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 from . import base diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py index 11a17728..319401b4 100644 --- a/sleekxmpp/plugins/xep_0050.py +++ b/sleekxmpp/plugins/xep_0050.py @@ -1,21 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + 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 from . import base diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py index f8732905..4b3ab829 100644 --- a/sleekxmpp/plugins/xep_0078.py +++ b/sleekxmpp/plugins/xep_0078.py @@ -1,21 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + 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 from xml.etree import cElementTree as ET diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py index 66940af4..b7b5d6dd 100644 --- a/sleekxmpp/plugins/xep_0085.py +++ b/sleekxmpp/plugins/xep_0085.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permissio + See the file LICENSE for copying permissio """ import logging diff --git a/sleekxmpp/plugins/xep_0092.py b/sleekxmpp/plugins/xep_0092.py index 77a6d9d0..ca02c4a8 100644 --- a/sleekxmpp/plugins/xep_0092.py +++ b/sleekxmpp/plugins/xep_0092.py @@ -1,21 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2007 Nathanael C. Fritz - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + 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 xml.etree import cElementTree as ET from . import base diff --git a/sleekxmpp/plugins/xep_0128.py b/sleekxmpp/plugins/xep_0128.py index dfe5829f..824977b6 100644 --- a/sleekxmpp/plugins/xep_0128.py +++ b/sleekxmpp/plugins/xep_0128.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ import logging diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index 1da57fee..3fc62f55 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -1,22 +1,9 @@ """ - SleekXMPP: The Sleek XMPP Library - XEP-0199 (Ping) support - Copyright (C) 2007 Kevin Smith - This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + 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 xml.etree import cElementTree as ET from . import base diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py index c3d8a318..b8fca228 100644 --- a/sleekxmpp/stanza/__init__.py +++ b/sleekxmpp/stanza/__init__.py @@ -3,6 +3,6 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ __all__ = ['presence'] diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py index b9ab2676..7771c87b 100644 --- a/sleekxmpp/stanza/error.py +++ b/sleekxmpp/stanza/error.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py index 14595e24..50195b11 100644 --- a/sleekxmpp/stanza/htmlim.py +++ b/sleekxmpp/stanza/htmlim.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index ded7515f..daf05b43 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py index 38341809..75ecc232 100644 --- a/sleekxmpp/stanza/message.py +++ b/sleekxmpp/stanza/message.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py index ec290703..47d4620a 100644 --- a/sleekxmpp/stanza/nick.py +++ b/sleekxmpp/stanza/nick.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py index 0da7ffc8..ec681763 100644 --- a/sleekxmpp/stanza/presence.py +++ b/sleekxmpp/stanza/presence.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . error import Error diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index 3b4822d8..e568b62b 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py index 708b8d40..eda65ec7 100644 --- a/sleekxmpp/stanza/roster.py +++ b/sleekxmpp/stanza/roster.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID import logging diff --git a/sleekxmpp/tests/testpubsub.py b/sleekxmpp/tests/testpubsub.py index d8f3ff4a..24855c90 100755 --- a/sleekxmpp/tests/testpubsub.py +++ b/sleekxmpp/tests/testpubsub.py @@ -1,19 +1,9 @@ """ + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - - SleekXMPP is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - SleekXMPP is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SleekXMPP; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + See the file LICENSE for copying permission. """ import logging diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py index f60c5b8e..07b395dc 100644 --- a/sleekxmpp/xmlstream/filesocket.py +++ b/sleekxmpp/xmlstream/filesocket.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from socket import _fileobject import socket diff --git a/sleekxmpp/xmlstream/handler/base.py b/sleekxmpp/xmlstream/handler/base.py index 5d55f4ee..720846d6 100644 --- a/sleekxmpp/xmlstream/handler/base.py +++ b/sleekxmpp/xmlstream/handler/base.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ class BaseHandler(object): diff --git a/sleekxmpp/xmlstream/handler/callback.py b/sleekxmpp/xmlstream/handler/callback.py index 49cfa14d..889b0aa7 100644 --- a/sleekxmpp/xmlstream/handler/callback.py +++ b/sleekxmpp/xmlstream/handler/callback.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . import base import logging diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py index c85a0c46..7c4330a4 100644 --- a/sleekxmpp/xmlstream/handler/waiter.py +++ b/sleekxmpp/xmlstream/handler/waiter.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . import base try: diff --git a/sleekxmpp/xmlstream/handler/xmlcallback.py b/sleekxmpp/xmlstream/handler/xmlcallback.py index 632c142b..67879dfe 100644 --- a/sleekxmpp/xmlstream/handler/xmlcallback.py +++ b/sleekxmpp/xmlstream/handler/xmlcallback.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ import threading from . callback import Callback diff --git a/sleekxmpp/xmlstream/handler/xmlwaiter.py b/sleekxmpp/xmlstream/handler/xmlwaiter.py index 2344403b..cf90751d 100644 --- a/sleekxmpp/xmlstream/handler/xmlwaiter.py +++ b/sleekxmpp/xmlstream/handler/xmlwaiter.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . waiter import Waiter diff --git a/sleekxmpp/xmlstream/matcher/base.py b/sleekxmpp/xmlstream/matcher/base.py index 8185bdc5..51da0942 100644 --- a/sleekxmpp/xmlstream/matcher/base.py +++ b/sleekxmpp/xmlstream/matcher/base.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ class MatcherBase(object): diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py index bb858fc4..43972c23 100644 --- a/sleekxmpp/xmlstream/matcher/id.py +++ b/sleekxmpp/xmlstream/matcher/id.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . import base diff --git a/sleekxmpp/xmlstream/matcher/many.py b/sleekxmpp/xmlstream/matcher/many.py index cf860e62..ff0c4e4d 100644 --- a/sleekxmpp/xmlstream/matcher/many.py +++ b/sleekxmpp/xmlstream/matcher/many.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . import base from xml.etree import cElementTree diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py index bd091c0e..e315445d 100644 --- a/sleekxmpp/xmlstream/matcher/stanzapath.py +++ b/sleekxmpp/xmlstream/matcher/stanzapath.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . import base from xml.etree import cElementTree diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py index eba3e954..89fd6422 100644 --- a/sleekxmpp/xmlstream/matcher/xmlmask.py +++ b/sleekxmpp/xmlstream/matcher/xmlmask.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . import base from xml.etree import cElementTree diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py index f6d04243..7f3d20be 100644 --- a/sleekxmpp/xmlstream/matcher/xpath.py +++ b/sleekxmpp/xmlstream/matcher/xpath.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from . import base from xml.etree import cElementTree diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 4e6afee4..6436fc55 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from xml.etree import cElementTree as ET import logging diff --git a/sleekxmpp/xmlstream/statemachine.py b/sleekxmpp/xmlstream/statemachine.py index fb7d1508..8a1aa22d 100644 --- a/sleekxmpp/xmlstream/statemachine.py +++ b/sleekxmpp/xmlstream/statemachine.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from __future__ import with_statement import threading diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 003ead15..cea204b9 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -3,7 +3,7 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - See the file license.txt for copying permission. + See the file LICENSE for copying permission. """ from __future__ import with_statement, unicode_literals -- cgit v1.2.3 From 9724efa123c2727e7617236a0c55238e286f6b00 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 12:16:06 -0400 Subject: Please tab nanny. --- sleekxmpp/basexmpp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index bfbfd594..2c2bb91e 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -122,7 +122,7 @@ class basexmpp(object): # 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)) + self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, threaded, disposable, instream)) def getId(self): return "%x".upper() % self.id -- cgit v1.2.3 From de24e9ed458cea4bccb9962b69e5fb4271841b3d Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 12:16:57 -0400 Subject: Lots of XEP-0004 bug fixes. Forms have default type of 'form' setFields now uses a list of tuples instead of a dictionary because ordering is important. getFields defaults to returning a list of tuples, but the use_dict parameter can change that --- sleekxmpp/plugins/xep_0004.py | 80 ++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 36 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index 50f2b5ee..037fc090 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -23,10 +23,18 @@ class Form(ElementBase): sub_interfaces = set(('title',)) form_types = set(('cancel', 'form', 'result', 'submit')) - def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None, options=None): + def setup(self, xml=None): + if ElementBase.setup(self, xml): #if we had to generate xml + self['type'] = 'form' + + def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs): + kwtype = kwargs.get('type', None) + if kwtype is None: + kwtype = ftype + field = FormField(parent=self) field['var'] = var - field['type'] = ftype + field['type'] = kwtype field['label'] = label field['desc'] = desc field['required'] = required @@ -55,7 +63,10 @@ class Form(ElementBase): field['var'] = var field['value'] = values.get(var, None) - def addReported(self, var, ftype='text-single', label='', desc=''): + def addReported(self, var, ftype=None, label='', desc='', **kwargs): + kwtype = kwargs.get('type', None) + if kwtype is None: + kwtype = ftype reported = self.xml.find('{%s}reported' % self.namespace) if reported is None: reported = ET.Element('{%s}reported' % self.namespace) @@ -64,7 +75,7 @@ class Form(ElementBase): reported.append(fieldXML) field = FormField(xml=fieldXML) field['var'] = var - field['type'] = ftype + field['type'] = kwtype field['label'] = label field['desc'] = desc return field @@ -92,19 +103,21 @@ class Form(ElementBase): if reportedXML is not None: self.xml.remove(reportedXML) - def getFields(self): - fields = {} - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + def getFields(self, use_dict=False): + fields = {} if use_dict else [] + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) for fieldXML in fieldsXML: field = FormField(xml=fieldXML) - fields[field['var']] = field + if use_dict: + fields[field['var']] = field + else: + fields.append((field['var'], field)) return fields def getInstructions(self): instructions = '' - instsXML = self.xml.findall('{%s}instructions') - for instXML in instsXML: - instructions += instXML.text + instsXML = self.xml.findall('{%s}instructions' % self.namespace) + return "\n".join([instXML.text for instXML in instsXML]) def getItems(self): items = [] @@ -129,7 +142,7 @@ class Form(ElementBase): def getValues(self): values = {} - fields = self.getFields() + fields = self.getFields(use_dict=True) for var in fields: values[var] = fields[var]['value'] return values @@ -140,20 +153,19 @@ class Form(ElementBase): elif self['type'] == 'submit': self['type'] = 'result' - def setFields(self, fields): + def setFields(self, fields, default=None): del self['fields'] - for var in fields: - field = fields[var] - - # Remap 'type' to 'ftype' to match the addField method - ftype = field.get('type', 'text-single') - field['type'] = ftype - del field['type'] - field['ftype'] = ftype + for field_data in fields: + var = field_data[0] + field = field_data[1] + field['var'] = var - self.addField(var, **field) + self.addField(**field) def setInstructions(self, instructions): + del self['instructions'] + if instructions in [None, '']: + return instructions = instructions.split('\n') for instruction in instructions: inst = ET.Element('{%s}instructions' % self.namespace) @@ -164,20 +176,14 @@ class Form(ElementBase): for item in items: self.addItem(item) - def setReported(self, reported): + def setReported(self, reported, default=None): for var in reported: field = reported[var] - - # Remap 'type' to 'ftype' to match the addReported method - ftype = field.get('type', 'text-single') - field['type'] = ftype - del field['type'] - field['ftype'] = ftype - + field['var'] = var self.addReported(var, **field) def setValues(self, values): - fields = self.getFields() + fields = self.getFields(use_dict=True) for field in values: fields[field]['value'] = values[field] @@ -226,7 +232,7 @@ class FormField(ElementBase): optsXML = self.xml.findall('{%s}option' % self.namespace) for optXML in optsXML: opt = FieldOption(xml=optXML) - options.append({'label': opt['label'], 'value':opt['value']}) + options.append({'label': opt['label'], 'value':opt['value']}) return options def getRequired(self): @@ -277,22 +283,24 @@ class FormField(ElementBase): def setValue(self, value): self.delValue() valXMLName = '{%s}value' % self.namespace - + if self['type'] == 'boolean': if value in self.true_values: valXML = ET.Element(valXMLName) - valXML.text = 'true' + valXML.text = '1' self.xml.append(valXML) else: valXML = ET.Element(valXMLName) - valXML.text = 'true' + valXML.text = '0' self.xml.append(valXML) - if self['type'] in self.multi_value_types: + elif self['type'] in self.multi_value_types or self['type'] in ['', None]: if self['type'] in self.multi_line_types and isinstance(value, str): value = value.split('\n') if not isinstance(value, list): value = [value] for val in value: + if self['type'] in ['', None] and val in self.true_values: + val = '1' valXML = ET.Element(valXMLName) valXML.text = val self.xml.append(valXML) -- cgit v1.2.3 From 7ad014368709873ed8ab760acc62264278f7723f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 12:18:38 -0400 Subject: Updated pubsub stanzas to use xep_0004 stanza objects, and updated tests to match. --- sleekxmpp/plugins/stanza_pubsub.py | 50 +++----------------------------------- 1 file changed, 4 insertions(+), 46 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py index e04f1a7f..96d02f93 100644 --- a/sleekxmpp/plugins/stanza_pubsub.py +++ b/sleekxmpp/plugins/stanza_pubsub.py @@ -257,7 +257,7 @@ class Configure(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'configure' plugin_attrib = name - interfaces = set(('node', 'type', 'config')) + interfaces = set(('node', 'type')) plugin_attrib_map = {} plugin_tag_map = {} @@ -266,22 +266,8 @@ class Configure(ElementBase): if not t: t == 'leaf' return t - def getConfig(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form - - def setConfig(self, value): - self.xml.append(value.getXML()) - return self - - def delConfig(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) - registerStanzaPlugin(Pubsub, Configure) +registerStanzaPlugin(Configure, xep_0004.Form) class DefaultConfig(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#owner' @@ -293,21 +279,6 @@ class DefaultConfig(ElementBase): def __init__(self, *args, **kwargs): ElementBase.__init__(self, *args, **kwargs) - - def getConfig(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form - - def setConfig(self, value): - self.xml.append(value.getXML()) - return self - - def delConfig(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) def getType(self): t = self._getAttr('type') @@ -315,6 +286,7 @@ class DefaultConfig(ElementBase): return t registerStanzaPlugin(PubsubOwner, DefaultConfig) +registerStanzaPlugin(DefaultConfig, xep_0004.Form) class Options(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -538,22 +510,8 @@ class EventConfiguration(ElementBase): plugin_attrib_map = {} plugin_tag_map = {} - def getConfig(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form - - def setConfig(self, value): - self.xml.append(value.getXML()) - return self - - def delConfig(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) - registerStanzaPlugin(Event, EventConfiguration) +registerStanzaPlugin(EventConfiguration, xep_0004.Form) class EventPurge(ElementBase): namespace = 'http://jabber.org/protocol/pubsub#event' -- cgit v1.2.3 From b67b930596bfb0748b67bde02d37f2277d8c9576 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 12:27:22 -0400 Subject: Updated xep_0050 to use old_0004 for now. --- sleekxmpp/plugins/xep_0050.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py index 319401b4..4ff42859 100644 --- a/sleekxmpp/plugins/xep_0050.py +++ b/sleekxmpp/plugins/xep_0050.py @@ -71,7 +71,7 @@ class xep_0050(base.base_plugin): in_command = xml.find('{http://jabber.org/protocol/commands}command') sessionid = in_command.get('sessionid', None) pointer = self.sessions[sessionid]['next'] - results = self.xmpp.plugin['xep_0004'].makeForm('result') + results = self.xmpp.plugin['old_0004'].makeForm('result') results.fromXML(in_command.find('{jabber:x:data}x')) pointer(results,sessionid) self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[])) @@ -82,7 +82,7 @@ class xep_0050(base.base_plugin): in_command = xml.find('{http://jabber.org/protocol/commands}command') sessionid = in_command.get('sessionid', None) pointer = self.sessions[sessionid]['next'] - results = self.xmpp.plugin['xep_0004'].makeForm('result') + results = self.xmpp.plugin['old_0004'].makeForm('result') results.fromXML(in_command.find('{jabber:x:data}x')) form, npointer, next = pointer(results,sessionid) self.sessions[sessionid]['next'] = npointer -- cgit v1.2.3 From 75afefb5c66c7d8a5ba7fa7bdcc3f87e69936ec5 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 20 Jul 2010 13:23:35 -0400 Subject: Upated xep_0045 to use old_0004 for now. --- sleekxmpp/plugins/xep_0045.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index cd1a9a09..1892eeab 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -154,13 +154,13 @@ class xep_0045(base.base_plugin): return False xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if xform is None: return False - form = self.xmpp.plugin['xep_0004'].buildForm(xform) + form = self.xmpp.plugin['old_0004'].buildForm(xform) return form def configureRoom(self, room, form=None, ifrom=None): if form is None: form = self.getRoomForm(room, ifrom=ifrom) - #form = self.xmpp.plugin['xep_0004'].makeForm(ftype='submit') + #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') iq = self.xmpp.makeIqSet() iq['to'] = room @@ -262,7 +262,7 @@ class xep_0045(base.base_plugin): form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') if form is None: raise ValueError - return self.xmpp.plugin['xep_0004'].buildForm(form) + return self.xmpp.plugin['old_0004'].buildForm(form) def cancelConfig(self, room): query = ET.Element('{http://jabber.org/protocol/muc#owner}query') -- cgit v1.2.3 From 9fcd2e93a360939b5a55af93239b467a7ac32028 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 20 Jul 2010 11:15:59 -0700 Subject: don't send resource in bind request if you don't have one --- sleekxmpp/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index b680ddaf..16fd1056 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -222,9 +222,10 @@ class ClientXMPP(basexmpp, XMLStream): def handler_bind_resource(self, xml): logging.debug("Requesting resource: %s" % self.resource) iq = self.Iq(stype='set') - res = ET.Element('resource') - res.text = self.resource - xml.append(res) + if self.resource: + res = ET.Element('resource') + res.text = self.resource + xml.append(res) iq.append(xml) response = iq.send() #response = self.send(iq, self.Iq(sid=iq['id'])) -- cgit v1.2.3 From ca2c421e6c5de3dc97c8555301592d64e591db47 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 20 Jul 2010 11:20:47 -0700 Subject: fixed resource binding element to conform to spec --- sleekxmpp/__init__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 16fd1056..a9bfc953 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -221,6 +221,7 @@ class ClientXMPP(basexmpp, XMLStream): def handler_bind_resource(self, xml): logging.debug("Requesting resource: %s" % self.resource) + xml.clear() iq = self.Iq(stype='set') if self.resource: res = ET.Element('resource') -- cgit v1.2.3 From d70a6e6f32d5d5f9bd4d4d97f987f1667f6eab40 Mon Sep 17 00:00:00 2001 From: Joe Hildebrand Date: Tue, 20 Jul 2010 13:55:48 -0700 Subject: Issue 26. Only set from address in reply() for components --- sleekxmpp/__init__.py | 429 +++++++++++----------- sleekxmpp/componentxmpp.py | 101 +++--- sleekxmpp/xmlstream/stanzabase.py | 733 +++++++++++++++++++------------------- 3 files changed, 635 insertions(+), 628 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index a9bfc953..3f0a1e86 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -30,231 +30,232 @@ from . import plugins #from . import stanza srvsupport = True try: - import dns.resolver + import dns.resolver except ImportError: - srvsupport = False + srvsupport = False #class PresenceStanzaType(object): -# -# def fromXML(self, xml): -# self.ptype = xml.get('type') +# +# def fromXML(self, xml): +# self.ptype = xml.get('type') class ClientXMPP(basexmpp, XMLStream): - """SleekXMPP's client class. Use only for good, not evil.""" + """SleekXMPP's client class. Use only for good, not evil.""" - def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True): - 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) - self.plugin_whitelist = plugin_whitelist - self.auto_reconnect = True - self.srvsupport = srvsupport - self.password = password - self.registered_features = [] - self.stream_header = """""" % (self.server,self.default_ns) - self.stream_footer = "" - #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.bound = False - self.bindfail = False - self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) - self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) - #self.registerHandler(Callback('Roster Update', MatchXMLMask("" % self.default_ns), self._handlePresenceSubscribe, thread=True)) - self.registerFeature("", self.handler_starttls, True) - self.registerFeature("", self.handler_sasl_auth, True) - self.registerFeature("", self.handler_bind_resource) - self.registerFeature("", self.handler_start_session) - - #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 __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True): + 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) + self.plugin_whitelist = plugin_whitelist + self.auto_reconnect = True + self.srvsupport = srvsupport + self.password = password + self.registered_features = [] + self.stream_header = """""" % (self.server,self.default_ns) + self.stream_footer = "" + #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.bound = False + self.bindfail = False + self.is_component = False + self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) + self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) + #self.registerHandler(Callback('Roster Update', MatchXMLMask("" % self.default_ns), self._handlePresenceSubscribe, thread=True)) + self.registerFeature("", self.handler_starttls, True) + self.registerFeature("", self.handler_sasl_auth, True) + self.registerFeature("", self.handler_bind_resource) + self.registerFeature("", self.handler_start_session) + + #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.""" - 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.") - else: - logging.debug("Since no address is supplied, attempting SRV lookup.") - try: - answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV) - except dns.resolver.NXDOMAIN: - logging.debug("No appropriate SRV record found. Using JID server name.") - else: - # pick a random answer, weighted by priority - # there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway - # suggestions are welcome - addresses = {} - intmax = 0 - priorities = [] - for answer in answers: - intmax += answer.priority - addresses[intmax] = (answer.target.to_text()[:-1], answer.port) - priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort() - picked = random.randint(0, intmax) - for priority in priorities: - if picked <= priority: - address = addresses[priority] - break - if not address: - # if all else fails take server from JID. - address = (self.server, 5222) - result = XMLStream.connect(self, address[0], address[1], use_tls=True) - if result: - self.event("connected") - else: - logging.warning("Failed to connect") - self.event("disconnected") - return result - - # overriding reconnect and disconnect so that we can get some events - # should events be part of or required by xmlstream? Maybe that would be cleaner - def reconnect(self): - logging.info("Reconnecting") - self.event("disconnected") - XMLStream.reconnect(self) - - def disconnect(self, init=True, close=False, reconnect=False): - self.event("disconnected") - XMLStream.disconnect(self, reconnect) - - def registerFeature(self, mask, pointer, breaker = False): - """Register a stream feature.""" - self.registered_features.append((MatchXMLMask(mask), pointer, breaker)) + def connect(self, address=tuple()): + """Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses + the JID server.""" + 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.") + else: + logging.debug("Since no address is supplied, attempting SRV lookup.") + try: + answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV) + except dns.resolver.NXDOMAIN: + logging.debug("No appropriate SRV record found. Using JID server name.") + else: + # pick a random answer, weighted by priority + # there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway + # suggestions are welcome + addresses = {} + intmax = 0 + priorities = [] + for answer in answers: + intmax += answer.priority + addresses[intmax] = (answer.target.to_text()[:-1], answer.port) + priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort() + picked = random.randint(0, intmax) + for priority in priorities: + if picked <= priority: + address = addresses[priority] + break + if not address: + # if all else fails take server from JID. + address = (self.server, 5222) + result = XMLStream.connect(self, address[0], address[1], use_tls=True) + if result: + self.event("connected") + else: + logging.warning("Failed to connect") + self.event("disconnected") + return result + + # overriding reconnect and disconnect so that we can get some events + # should events be part of or required by xmlstream? Maybe that would be cleaner + def reconnect(self): + logging.info("Reconnecting") + self.event("disconnected") + XMLStream.reconnect(self) + + def disconnect(self, init=True, close=False, reconnect=False): + self.event("disconnected") + XMLStream.disconnect(self, reconnect) + + def registerFeature(self, mask, pointer, breaker = False): + """Register a stream feature.""" + self.registered_features.append((MatchXMLMask(mask), pointer, breaker)) - def updateRoster(self, jid, name=None, subscription=None, groups=[]): - """Add or change a roster item.""" - iq = self.Iq().setStanzaValues({'type': 'set'}) - iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} - #self.send(iq, self.Iq().setValues({'id': iq['id']})) - r = iq.send() - return r['type'] == 'result' + def updateRoster(self, jid, name=None, subscription=None, groups=[]): + """Add or change a roster item.""" + iq = self.Iq().setStanzaValues({'type': 'set'}) + iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} + #self.send(iq, self.Iq().setValues({'id': iq['id']})) + r = iq.send() + return r['type'] == 'result' - def delRosterItem(self, jid): - iq = self.Iq() - iq['type'] = 'set' - iq['roster']['items'] = {jid: {'subscription': 'remove'}} - return iq.send()['type'] == 'result' - - def getRoster(self): - """Request the roster be sent.""" - iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send() - self._handleRoster(iq, request=True) - - def _handleStreamFeatures(self, features): - self.features = [] - for sub in features.xml: - self.features.append(sub.tag) - for subelement in features.xml: - for feature in self.registered_features: - if feature[0].match(subelement): - #if self.maskcmp(subelement, feature[0], True): - if feature[1](subelement) and feature[2]: #if breaker, don't continue - return True - - def handler_starttls(self, xml): - if not self.authenticated and self.ssl_support: - self.add_handler("", self.handler_tls_start, name='TLS Proceed', instream=True) - self.sendXML(xml) - return True - else: - logging.warning("The module tlslite is required in to some servers, and has not been found.") - return False + def delRosterItem(self, jid): + iq = self.Iq() + iq['type'] = 'set' + iq['roster']['items'] = {jid: {'subscription': 'remove'}} + return iq.send()['type'] == 'result' + + def getRoster(self): + """Request the roster be sent.""" + iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send() + self._handleRoster(iq, request=True) + + def _handleStreamFeatures(self, features): + self.features = [] + for sub in features.xml: + self.features.append(sub.tag) + for subelement in features.xml: + for feature in self.registered_features: + if feature[0].match(subelement): + #if self.maskcmp(subelement, feature[0], True): + if feature[1](subelement) and feature[2]: #if breaker, don't continue + return True + + def handler_starttls(self, xml): + if not self.authenticated and self.ssl_support: + self.add_handler("", self.handler_tls_start, name='TLS Proceed', instream=True) + self.sendXML(xml) + return True + else: + logging.warning("The module tlslite is required in to some servers, and has not been found.") + return False - def handler_tls_start(self, xml): - logging.debug("Starting TLS") - if self.startTLS(): - raise RestartStream() - - def handler_sasl_auth(self, xml): - if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: - return False - logging.debug("Starting SASL Auth") - self.add_handler("", self.handler_auth_success, name='SASL Sucess', instream=True) - self.add_handler("", self.handler_auth_fail, name='SASL Failure', instream=True) - sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') - if len(sasl_mechs): - for sasl_mech in sasl_mechs: - self.features.append("sasl:%s" % sasl_mech.text) - if 'sasl:PLAIN' in self.features: - if sys.version_info < (3,0): - self.send("""%s""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) - else: - self.send("""%s""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) - else: - logging.error("No appropriate login method.") - self.disconnect() - #if 'sasl:DIGEST-MD5' in self.features: - # self._auth_digestmd5() - return True - - def handler_auth_success(self, xml): - self.authenticated = True - self.features = [] - raise RestartStream() + def handler_tls_start(self, xml): + logging.debug("Starting TLS") + if self.startTLS(): + raise RestartStream() + + def handler_sasl_auth(self, xml): + if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: + return False + logging.debug("Starting SASL Auth") + self.add_handler("", self.handler_auth_success, name='SASL Sucess', instream=True) + self.add_handler("", self.handler_auth_fail, name='SASL Failure', instream=True) + sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') + if len(sasl_mechs): + for sasl_mech in sasl_mechs: + self.features.append("sasl:%s" % sasl_mech.text) + if 'sasl:PLAIN' in self.features: + if sys.version_info < (3,0): + self.send("""%s""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) + else: + self.send("""%s""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) + else: + logging.error("No appropriate login method.") + self.disconnect() + #if 'sasl:DIGEST-MD5' in self.features: + # self._auth_digestmd5() + return True + + def handler_auth_success(self, xml): + self.authenticated = True + self.features = [] + raise RestartStream() - def handler_auth_fail(self, xml): - logging.info("Authentication failed.") - self.disconnect() - self.event("failed_auth") - - def handler_bind_resource(self, xml): - logging.debug("Requesting resource: %s" % self.resource) - xml.clear() - iq = self.Iq(stype='set') - if self.resource: - res = ET.Element('resource') - res.text = self.resource - xml.append(res) - 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) - self.bound = True - logging.info("Node set to: %s" % self.fulljid) - if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail: - logging.debug("Established Session") - self.sessionstarted = True - self.event("session_start") - - def handler_start_session(self, xml): - if self.authenticated and self.bound: - iq = self.makeIqSet(xml) - response = iq.send() - logging.debug("Established Session") - self.sessionstarted = True - self.event("session_start") - else: - #bind probably hasn't happened yet - self.bindfail = True - - def _handleRoster(self, iq, request=False): - if iq['type'] == 'set' or (iq['type'] == 'result' and request): - for jid in iq['roster']['items']: - if not jid in self.roster: - self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} - self.roster[jid].update(iq['roster']['items'][jid]) - if iq['type'] == 'set': - self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster')) - self.event("roster_update", iq) + def handler_auth_fail(self, xml): + logging.info("Authentication failed.") + self.disconnect() + self.event("failed_auth") + + def handler_bind_resource(self, xml): + logging.debug("Requesting resource: %s" % self.resource) + xml.clear() + iq = self.Iq(stype='set') + if self.resource: + res = ET.Element('resource') + res.text = self.resource + xml.append(res) + 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) + self.bound = True + logging.info("Node set to: %s" % self.fulljid) + if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail: + logging.debug("Established Session") + self.sessionstarted = True + self.event("session_start") + + def handler_start_session(self, xml): + if self.authenticated and self.bound: + iq = self.makeIqSet(xml) + response = iq.send() + logging.debug("Established Session") + self.sessionstarted = True + self.event("session_start") + else: + #bind probably hasn't happened yet + self.bindfail = True + + def _handleRoster(self, iq, request=False): + if iq['type'] == 'set' or (iq['type'] == 'result' and request): + for jid in iq['roster']['items']: + if not jid in self.roster: + self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} + self.roster[jid].update(iq['roster']['items'][jid]) + if iq['type'] == 'set': + self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster')) + self.event("roster_update", iq) diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 4d39ce80..818d800b 100755 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -30,59 +30,60 @@ from . import stanza import hashlib srvsupport = True try: - import dns.resolver + import dns.resolver except ImportError: - srvsupport = False + srvsupport = False class ComponentXMPP(basexmpp, XMLStream): - """SleekXMPP's client class. Use only for good, not evil.""" + """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' - else: - self.default_ns = 'jabber:component:accept' - basexmpp.__init__(self) - self.auto_authorize = None - self.stream_header = "" % jid - self.stream_footer = "" - self.server_host = host - self.server_port = port - self.set_jid(jid) - self.secret = secret - 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) - for sub in xmlobj: - self.incoming_filter(sub) - return xmlobj + 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' + else: + self.default_ns = 'jabber:component:accept' + basexmpp.__init__(self) + self.auto_authorize = None + self.stream_header = "" % jid + self.stream_footer = "" + self.server_host = host + self.server_port = port + self.set_jid(jid) + 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) + for sub in xmlobj: + self.incoming_filter(sub) + return xmlobj - def start_stream_handler(self, xml): - sid = xml.get('id', '') - handshake = ET.Element('{jabber:component:accept}handshake') - if sys.version_info < (3,0): - handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower() - 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) + def start_stream_handler(self, xml): + sid = xml.get('id', '') + handshake = ET.Element('{jabber:component:accept}handshake') + if sys.version_info < (3,0): + handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower() + 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) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 6436fc55..66a08e49 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -13,393 +13,398 @@ import weakref import copy if sys.version_info < (3,0): - from . import tostring26 as tostring + from . import tostring26 as tostring else: - from . import tostring + from . import tostring xmltester = type(ET.Element('xml')) def registerStanzaPlugin(stanza, plugin): - """ - Associate a stanza object as a plugin for another stanza. - """ - tag = "{%s}%s" % (plugin.namespace, plugin.name) - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map[tag] = plugin + """ + Associate a stanza object as a plugin for another stanza. + """ + tag = "{%s}%s" % (plugin.namespace, plugin.name) + stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin + stanza.plugin_tag_map[tag] = plugin class JID(object): - def __init__(self, jid): - self.jid = jid - - def __getattr__(self, name): - if name == 'resource': - return self.jid.split('/', 1)[-1] - elif name == 'user': - if '@' in self.jid: - return self.jid.split('@', 1)[0] - else: - return '' - elif name == 'server': - 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 + def __init__(self, jid): + self.jid = jid + + def __getattr__(self, name): + if name == 'resource': + return self.jid.split('/', 1)[-1] + elif name == 'user': + if '@' in self.jid: + return self.jid.split('@', 1)[0] + else: + return '' + elif name == 'server': + 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 ElementBase(tostring.ToString): - 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 = {} - subitem = None + 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 = {} + subitem = None - def __init__(self, xml=None, parent=None): - if parent is None: - self.parent = None - else: - self.parent = weakref.ref(parent) - self.xml = xml - self.plugins = {} - self.iterables = [] - self.idx = 0 - if not self.setup(xml): - 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) - if self.subitem is not None: - for sub in self.subitem: - if child.tag == "{%s}%s" % (sub.namespace, sub.name): - self.iterables.append(sub(xml=child, parent=self)) - break + def __init__(self, xml=None, parent=None): + if parent is None: + self.parent = None + else: + self.parent = weakref.ref(parent) + self.xml = xml + self.plugins = {} + self.iterables = [] + self.idx = 0 + if not self.setup(xml): + 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) + if self.subitem is not None: + for sub in self.subitem: + if child.tag == "{%s}%s" % (sub.namespace, sub.name): + self.iterables.append(sub(xml=child, parent=self)) + break - @property - def attrib(self): #backwards compatibility - return self + @property + def attrib(self): #backwards compatibility + return self - def __iter__(self): - self.idx = 0 - return self + def __iter__(self): + self.idx = 0 + return self - def __bool__(self): - return True - - def __next__(self): - self.idx += 1 - if self.idx > len(self.iterables): - self.idx = 0 - raise StopIteration - return self.iterables[self.idx - 1] - - def next(self): - return self.__next__() + def __bool__(self): + return True + + def __next__(self): + self.idx += 1 + if self.idx > len(self.iterables): + self.idx = 0 + raise StopIteration + return self.iterables[self.idx - 1] + + def next(self): + return self.__next__() - def __len__(self): - return len(self.iterables) - - def append(self, item): - if not isinstance(item, ElementBase): - if type(item) == xmltester: - return self.appendxml(item) - else: - raise TypeError - self.xml.append(item.xml) - self.iterables.append(item) - return self - - def pop(self, idx=0): - aff = self.iterables.pop(idx) - self.xml.remove(aff.xml) - return aff - - def get(self, key, defaultvalue=None): - value = self[key] - if value is None or value == '': - return defaultvalue - return value - - def keys(self): - out = [] - out += [x for x in self.interfaces] - out += [x for x in self.plugins] - if self.iterables: - out.append('substanzas') - return tuple(out) - - def match(self, matchstring): - if isinstance(matchstring, str): - nodes = matchstring.split('/') - else: - nodes = matchstring - tagargs = nodes[0].split('@') - if tagargs[0] not in (self.plugins, self.plugin_attrib): return False - founditerable = False - for iterable in self.iterables: - if nodes[1:] == []: - break - founditerable = iterable.match(nodes[1:]) - if founditerable: break; - for evals in tagargs[1:]: - x,y = evals.split('=') - if self[x] != y: return False - if not founditerable and len(nodes) > 1: - next = nodes[1].split('@')[0] - if next in self.plugins: - return self.plugins[next].match(nodes[1:]) - else: - return False - return True - - def find(self, xpath): # for backwards compatiblity, expose elementtree interface - return self.xml.find(xpath) + def __len__(self): + return len(self.iterables) + + def append(self, item): + if not isinstance(item, ElementBase): + if type(item) == xmltester: + return self.appendxml(item) + else: + raise TypeError + self.xml.append(item.xml) + self.iterables.append(item) + return self + + def pop(self, idx=0): + aff = self.iterables.pop(idx) + self.xml.remove(aff.xml) + return aff + + def get(self, key, defaultvalue=None): + value = self[key] + if value is None or value == '': + return defaultvalue + return value + + def keys(self): + out = [] + out += [x for x in self.interfaces] + out += [x for x in self.plugins] + if self.iterables: + out.append('substanzas') + return tuple(out) + + def match(self, matchstring): + if isinstance(matchstring, str): + nodes = matchstring.split('/') + else: + nodes = matchstring + tagargs = nodes[0].split('@') + if tagargs[0] not in (self.plugins, self.plugin_attrib): return False + founditerable = False + for iterable in self.iterables: + if nodes[1:] == []: + break + founditerable = iterable.match(nodes[1:]) + if founditerable: break; + for evals in tagargs[1:]: + x,y = evals.split('=') + if self[x] != y: return False + if not founditerable and len(nodes) > 1: + next = nodes[1].split('@')[0] + if next in self.plugins: + return self.plugins[next].match(nodes[1:]) + else: + return False + return True + + def find(self, xpath): # for backwards compatiblity, expose elementtree interface + return self.xml.find(xpath) - def findall(self, xpath): - return self.xml.findall(xpath) - - def setup(self, xml=None): - if self.xml is None: - self.xml = xml - if self.xml is None: - for ename in self.name.split('/'): - new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) - if self.xml is None: - self.xml = new - else: - self.xml.append(new) - if self.parent is not None: - self.parent().xml.append(self.xml) - return True #had to generate XML - else: - return False + def findall(self, xpath): + return self.xml.findall(xpath) + + def setup(self, xml=None): + if self.xml is None: + self.xml = xml + if self.xml is None: + for ename in self.name.split('/'): + new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) + if self.xml is None: + self.xml = new + else: + self.xml.append(new) + 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 == 'substanzas': - return self.iterables - elif 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) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: self.initPlugin(attrib) - return self.plugins[attrib] - else: - return '' - - def __setitem__(self, attrib, value): - if attrib 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) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: self.initPlugin(attrib) - self.initPlugin(attrib) - self.plugins[attrib][attrib] = value - 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) - elif attrib in self.plugin_attrib_map: - if attrib in self.plugins: - del self.plugins[attrib] - return self - - def __eq__(self, other): - if not isinstance(other, ElementBase): - return False - values = self.getStanzaValues() - for key in other: - if key not in values or values[key] != other[key]: - return False - return True - - def _setAttr(self, name, value): - if value is None or value == '': - self.__delitem__(name) - else: - 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): - if text is None or text == '': - return self.__delitem__(name) - 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 = ET.Element("{%s}%s" % (self.namespace, name)) - self.xml.append(stanza) - 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 getStanzaValues(self): - out = {} - for interface in self.interfaces: - out[interface] = self[interface] - for pluginkey in self.plugins: - out[pluginkey] = self.plugins[pluginkey].getStanzaValues() - if self.iterables: - iterables = [] - for stanza in self.iterables: - iterables.append(stanza.getStanzaValues()) - iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) - out['substanzas'] = iterables - return out - - def setStanzaValues(self, attrib): - for interface in attrib: - if interface == 'substanzas': - for subdict in attrib['substanzas']: - if '__childtag__' in subdict: - for subclass in self.subitem: - if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name): - sub = subclass(parent=self) - sub.setStanzaValues(subdict) - self.iterables.append(sub) - break - elif interface in self.interfaces: - self[interface] = attrib[interface] - elif interface in self.plugin_attrib_map and interface not in self.plugins: - self.initPlugin(interface) - if interface in self.plugins: - self.plugins[interface].setStanzaValues(attrib[interface]) - return self - - def appendxml(self, xml): - self.xml.append(xml) - return self + 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 == 'substanzas': + return self.iterables + elif 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) + elif attrib in self.plugin_attrib_map: + if attrib not in self.plugins: self.initPlugin(attrib) + return self.plugins[attrib] + else: + return '' + + def __setitem__(self, attrib, value): + if attrib 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) + elif attrib in self.plugin_attrib_map: + if attrib not in self.plugins: self.initPlugin(attrib) + self.initPlugin(attrib) + self.plugins[attrib][attrib] = value + 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) + elif attrib in self.plugin_attrib_map: + if attrib in self.plugins: + del self.plugins[attrib] + return self + + def __eq__(self, other): + if not isinstance(other, ElementBase): + return False + values = self.getStanzaValues() + for key in other: + if key not in values or values[key] != other[key]: + return False + return True + + def _setAttr(self, name, value): + if value is None or value == '': + self.__delitem__(name) + else: + 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): + if text is None or text == '': + return self.__delitem__(name) + 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 = ET.Element("{%s}%s" % (self.namespace, name)) + self.xml.append(stanza) + 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 getStanzaValues(self): + out = {} + for interface in self.interfaces: + out[interface] = self[interface] + for pluginkey in self.plugins: + out[pluginkey] = self.plugins[pluginkey].getStanzaValues() + if self.iterables: + iterables = [] + for stanza in self.iterables: + iterables.append(stanza.getStanzaValues()) + iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) + out['substanzas'] = iterables + return out + + def setStanzaValues(self, attrib): + for interface in attrib: + if interface == 'substanzas': + for subdict in attrib['substanzas']: + if '__childtag__' in subdict: + for subclass in self.subitem: + if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name): + sub = subclass(parent=self) + sub.setStanzaValues(subdict) + self.iterables.append(sub) + break + elif interface in self.interfaces: + self[interface] = attrib[interface] + elif interface in self.plugin_attrib_map and interface not in self.plugins: + self.initPlugin(interface) + if interface in self.plugins: + self.plugins[interface].setStanzaValues(attrib[interface]) + return self + + def appendxml(self, xml): + self.xml.append(xml) + return self - def __copy__(self): - return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) - - #def __del__(self): #prevents garbage collection of reference cycle - # if self.parent is not None: - # self.parent.xml.remove(self.xml) + def __copy__(self): + return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) + + #def __del__(self): #prevents garbage collection of reference cycle + # 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() + 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=None, xml=None, stype=None, sto=None, sfrom=None, sid=None): - self.stream = stream - if stream is not None: - 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.namespace, self.name) - - def setType(self, value): - if value in self.types: - self.xml.attrib['type'] = value - return self + def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None): + self.stream = stream + if stream is not None: + 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.namespace, self.name) + + def setType(self, value): + if value in self.types: + self.xml.attrib['type'] = value + return self - def getPayload(self): - return self.xml.getchildren() - - def setPayload(self, value): - self.xml.append(value) - return self - - def delPayload(self): - self.clear() - return self - - def clear(self): - for child in self.xml.getchildren(): - self.xml.remove(child) - for plugin in list(self.plugins.keys()): - del self.plugins[plugin] - return self - - def reply(self): - self['from'], self['to'] = self['to'], self['from'] - self.clear() - return self - - def error(self): - self['type'] = 'error' - return self - - 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 - - def exception(self, e): - logging.error(traceback.format_tb(e)) - - def send(self): - self.stream.sendRaw(self.__str__()) + def getPayload(self): + return self.xml.getchildren() + + def setPayload(self, value): + self.xml.append(value) + return self + + def delPayload(self): + self.clear() + return self + + def clear(self): + for child in self.xml.getchildren(): + self.xml.remove(child) + for plugin in list(self.plugins.keys()): + del self.plugins[plugin] + return self + + def reply(self): + # if it's a component, use from + if self.stream and hasattr(self.stream, "is_component") and self.stream.is_component: + self['from'], self['to'] = self['to'], self['from'] + else: + self['to'] = self['from'] + del self['from'] + self.clear() + return self + + def error(self): + self['type'] = 'error' + return self + + 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 + + def exception(self, e): + logging.error(traceback.format_tb(e)) + + def send(self): + self.stream.sendRaw(self.__str__()) - def __copy__(self): - return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) + def __copy__(self): + return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) -- cgit v1.2.3 From d0a5c539d8f7cacbcf10730e65695a9dee89fc70 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 23 Jul 2010 19:41:11 -0400 Subject: Fix shebang lines to use #!/usr/bin/env python instead of hard coding a python version. --- sleekxmpp/__init__.py | 2 +- sleekxmpp/componentxmpp.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index 3f0a1e86..d2f5765f 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2.5 +#!/usr/bin/env python """ SleekXMPP: The Sleek XMPP Library diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 818d800b..5534a457 100755 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -1,4 +1,4 @@ -#!/usr/bin/python2.6 +#!/usr/bin/env python """ SleekXMPP: The Sleek XMPP Library -- cgit v1.2.3 From ec860bf9e29f4a88fc1797faa1dbcf0c0fc3889c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 26 Jul 2010 19:44:42 -0400 Subject: Add StateManager as replacement for StateMachine. --- sleekxmpp/xmlstream/statemanager.py | 139 ++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 sleekxmpp/xmlstream/statemanager.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/statemanager.py b/sleekxmpp/xmlstream/statemanager.py new file mode 100644 index 00000000..c7f76e75 --- /dev/null +++ b/sleekxmpp/xmlstream/statemanager.py @@ -0,0 +1,139 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from __future__ import with_statement +import threading + + +class StateError(Exception): + """Raised whenever a state transition was attempted but failed.""" + + +class StateManager(object): + """ + At the very core of SleekXMPP there is a need to track various + library configuration settings, XML stream features, and the + network connection status. The state manager is responsible for + tracking this information in a thread-safe manner. + + State 'variables' store the current state of these items as simple + string values or booleans. Changing those values must be done + according to transitions defined when creating the state variable. + + If a state variable is given a value that is not allowed according + to the transition definitions, a StateError is raised. When a + valid value is assigned an event is raised named: + + _state_changed_nameofthestatevariable + + The event carries a dictionary containing the previous and the new + state values. + """ + + def __init__(self, event_func=None): + """ + Initialize the state manager. The parameter event_func should be + the event() method of a SleekXMPP object in order to enable + _state_changed_* events. + """ + self.main_lock = threading.Lock() + self.locks = {} + self.state_variables = {} + + if event_func is not None: + self.event = event_func + else: + self.event = lambda name, data: None + + def add(self, name, default=False, values=None, transitions=None): + """ + Create a new state variable. + + When transitions is specified, only those defined state change + transitions will be allowed. + + When values is specified (and not transitions), any state changes + between those values are allowed. + + If neither values nor transitions are defined, then the state variable + will be a binary switch between True and False. + """ + if name in self.state_variables: + raise IndexError("State variable %s already exists" % name) + + self.locks[name] = threading.Lock() + with self.locks[name]: + var = {'value': default, + 'default': default, + 'transitions': {}} + + if transitions is not None: + for start in transitions: + var['transitions'][start] = set(transitions[start]) + elif values is not None: + values = set(values) + for value in values: + var['transitions'][value] = values + elif values is None: + var['transitions'] = {True: [False], + False: [True]} + + self.state_variables[name] = var + + def addStates(self, var_defs): + """ + Create multiple state variables at once. + """ + for var, data in var_defs: + self.add(var, + default=data.get('default', False), + values=data.get('values', None), + transitions=data.get('transitions', None)) + + def force_set(self, name, val): + """ + Force setting a state variable's value by overriding transition checks. + """ + with self.locks[name]: + self.state_variables[name]['value'] = val + + def reset(self, name): + """ + Reset a state variable to its default value. + """ + with self.locks[name]: + default = self.state_variables[name]['default'] + self.state_variables[name]['value'] = default + + def __getitem__(self, name): + """ + Get the value of a state variable if it exists. + """ + with self.locks[name]: + if name not in self.state_variables: + raise IndexError("State variable %s does not exist" % name) + return self.state_variables[name]['value'] + + def __setitem__(self, name, val): + """ + Attempt to set the value of a state variable, but raise StateError + if the transition is undefined. + + A _state_changed_* event is triggered after a successful transition. + """ + with self.locks[name]: + if name not in self.state_variables: + raise IndexError("State variable %s does not exist" % name) + current = self.state_variables[name]['value'] + if current == val: + return + if val in self.state_variables[name]['transitions'][current]: + self.state_variables[name]['value'] = val + self.event('_state_changed_%s' % name, {'from': current, 'to': val}) + else: + raise StateError("Can not transition from '%s' to '%s'" % (str(current), str(val))) -- cgit v1.2.3 From c8989c04f3675235e3ae730cb240e2154b5d9e76 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Mon, 26 Jul 2010 21:02:25 -0400 Subject: Replaced traceback calls to use logging.exception where applicable. --- sleekxmpp/plugins/xep_0050.py | 1 - sleekxmpp/stanza/rootstanza.py | 2 ++ sleekxmpp/xmlstream/stanzabase.py | 3 +-- sleekxmpp/xmlstream/xmlstream.py | 12 +++++------- 4 files changed, 8 insertions(+), 10 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py index 4ff42859..5efb9116 100644 --- a/sleekxmpp/plugins/xep_0050.py +++ b/sleekxmpp/plugins/xep_0050.py @@ -9,7 +9,6 @@ from __future__ import with_statement from . import base import logging from xml.etree import cElementTree as ET -import traceback import time class xep_0050(base.base_plugin): diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index e568b62b..3bae1f0d 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -9,6 +9,7 @@ from .. xmlstream.stanzabase import StanzaBase from xml.etree import cElementTree as ET from . error import Error from .. exceptions import XMPPError +import logging import traceback import sys @@ -29,6 +30,7 @@ class RootStanza(StanzaBase): self['error']['text'] = "SleekXMPP got into trouble." else: self['error']['text'] = traceback.format_tb(e.__traceback__) + logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) self.send() # all jabber:client root stanzas should have the error plugin diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 66a08e49..ab054073 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -7,7 +7,6 @@ """ from xml.etree import cElementTree as ET import logging -import traceback import sys import weakref import copy @@ -400,7 +399,7 @@ class StanzaBase(ElementBase): pass def exception(self, e): - logging.error(traceback.format_tb(e)) + logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) def send(self): self.stream.sendRaw(self.__str__()) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index cea204b9..6ce89c25 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -19,7 +19,6 @@ import logging import socket import threading import time -import traceback import types import copy import xml.sax.saxutils @@ -195,14 +194,14 @@ class XMLStream(object): return else: self.state.set('processing', False) - traceback.print_exc() + logging.exception('Socket Error') self.disconnect(reconnect=True) except: if not self.state.reconnect: return else: self.state.set('processing', False) - traceback.print_exc() + logging.exception('Connection error. Reconnecting.') self.disconnect(reconnect=True) if self.state['reconnect']: self.reconnect() @@ -258,8 +257,7 @@ class XMLStream(object): logging.warning("Failed to send %s" % data) self.state.set('connected', False) if self.state.reconnect: - logging.error("Disconnected. Socket Error.") - traceback.print_exc() + logging.exception("Disconnected. Socket Error.") self.disconnect(reconnect=True) def sendRaw(self, data): @@ -344,14 +342,14 @@ class XMLStream(object): try: handler.run(args[0]) except Exception as e: - traceback.print_exc() + logging.exception('Error processing event handler: %s' % handler.name) args[0].exception(e) elif etype == 'schedule': try: logging.debug(args) handler(*args[0]) except: - logging.error(traceback.format_exc()) + logging.exception('Error processing scheduled task') elif etype == 'quit': logging.debug("Quitting eventRunner thread") return False -- cgit v1.2.3 From 2cb82afc2cd35051a89c5d843f13bbf0132e2003 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 26 Jul 2010 18:13:09 -0700 Subject: updated and moved jid class -- jids now have setters --- sleekxmpp/xmlstream/jid.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 sleekxmpp/xmlstream/jid.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/jid.py b/sleekxmpp/xmlstream/jid.py new file mode 100644 index 00000000..2839ba58 --- /dev/null +++ b/sleekxmpp/xmlstream/jid.py @@ -0,0 +1,70 @@ +class JID(object): + def __init__(self, jid): + """Initialize a new jid""" + self.reset(jid) + + def reset(self, jid): + """Start fresh from a new jid string""" + self._full = self._jid = str(jid) + self._domain = None + self._resource = None + self._user = None + self._domain = None + self._bare = None + + def __getattr__(self, name): + """Handle getting the jid values, using cache if available""" + if name == 'resource': + if self._resource is not None: return self._resource + self._resource = self._jid.split('/', 1)[-1] + return self._resource + elif name == 'user': + if self._user is not None: return self._user + if '@' in self._jid: + self._user = self._jid.split('@', 1)[0] + else: + self._user = self._user + return self._user + elif name in ('server', 'domain'): + if self._domain is not None: return self._domain + self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0] + return self._domain + elif name == 'full': + return self._jid + elif name == 'bare': + if self._bare is not None: return self._bare + self._bare = self._jid.split('/', 1)[0] + return self._bare + + def __setattr__(self, name, value): + """Edit a jid by updating it's individual values, resetting by a generated jid in the end""" + if name in ('resource', 'user', 'domain'): + object.__setattr__(self, "_%s" % name, value) + self.regenerate() + elif name == 'server': + self.domain = value + elif name in ('full', 'jid'): + self.reset(value) + elif name == 'bare': + if '@' in value: + u, d = value.split('@', 1) + object.__setattr__(self, "_user", u) + object.__setattr__(self, "_domain", d) + else: + object.__setattr__(self, "_domain", value) + self.regenerate() + else: + object.__setattr__(self, name, value) + + + def regenerate(self): + """Generate a new jid based on current values, useful after editing""" + jid = "" + if self.user: jid = "%s@" % self.user + jid += self.domain + if self.resource: jid += "/%s" % self.resource + self.reset(jid) + + def __str__(self): + return self.full + -- cgit v1.2.3 From a349a2a317a6ca7152e0adb21d5fabbfd632ebaf Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Mon, 26 Jul 2010 18:13:34 -0700 Subject: removed jid from stanzabase to external file --- sleekxmpp/xmlstream/stanzabase.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 66a08e49..8af90a0f 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -11,6 +11,7 @@ import traceback import sys import weakref import copy +from . jid import JID if sys.version_info < (3,0): from . import tostring26 as tostring @@ -29,28 +30,6 @@ def registerStanzaPlugin(stanza, plugin): stanza.plugin_tag_map[tag] = plugin -class JID(object): - def __init__(self, jid): - self.jid = jid - - def __getattr__(self, name): - if name == 'resource': - return self.jid.split('/', 1)[-1] - elif name == 'user': - if '@' in self.jid: - return self.jid.split('@', 1)[0] - else: - return '' - elif name == 'server': - 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 ElementBase(tostring.ToString): name = 'stanza' plugin_attrib = 'plugin' -- cgit v1.2.3 From bd92ef6acfd9b1ecd390f08b28f1a3f4e7ec69a9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 28 Jul 2010 13:14:41 -0400 Subject: Updated RootStanza to use registerStanzaPlugin, and be PEP8 compliant. --- sleekxmpp/stanza/__init__.py | 7 +++- sleekxmpp/stanza/rootstanza.py | 76 ++++++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 26 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/__init__.py b/sleekxmpp/stanza/__init__.py index b8fca228..8302c43d 100644 --- a/sleekxmpp/stanza/__init__.py +++ b/sleekxmpp/stanza/__init__.py @@ -5,4 +5,9 @@ See the file LICENSE for copying permission. """ -__all__ = ['presence'] + + +from sleekxmpp.stanza.error import Error +from sleekxmpp.stanza.iq import Iq +from sleekxmpp.stanza.message import Message +from sleekxmpp.stanza.presence import Presence diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index 3bae1f0d..fb5498f5 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -5,34 +5,60 @@ See the file LICENSE for copying permission. """ -from .. xmlstream.stanzabase import StanzaBase -from xml.etree import cElementTree as ET -from . error import Error -from .. exceptions import XMPPError + import logging import traceback import sys +from sleekxmpp.exceptions import XMPPError +from sleekxmpp.stanza import Error +from sleekxmpp.xmlstream.stanzabase import ET, StanzaBase, registerStanzaPlugin + + class RootStanza(StanzaBase): - def exception(self, e): #called when a handler raises an exception - self.reply() - if isinstance(e, XMPPError): # we raised this deliberately - self['error']['condition'] = e.condition - self['error']['text'] = e.text - if e.extension is not None: # extended error tag - extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args) - self['error'].xml.append(extxml) - self['error']['type'] = e.etype - else: # we probably didn't raise this on purpose, so send back a traceback - self['error']['condition'] = 'undefined-condition' - if sys.version_info < (3,0): - self['error']['text'] = "SleekXMPP got into trouble." - else: - self['error']['text'] = traceback.format_tb(e.__traceback__) - logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) - self.send() - -# all jabber:client root stanzas should have the error plugin -RootStanza.plugin_attrib_map['error'] = Error -RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error + """ + A top-level XMPP stanza in an XMLStream. + + The RootStanza class provides a more XMPP specific exception + handler than provided by the generic StanzaBase class. + + Methods: + exception -- Overrides StanzaBase.exception + """ + + def exception(self, e): + """ + Create and send an error reply. + + Typically called when an event handler raises an exception. + The error's type and text content are based on the exception + object's type and content. + + Overrides StanzaBase.exception. + + Arguments: + e -- Exception object + """ + self.reply() + if isinstance(e, XMPPError): + # We raised this deliberately + self['error']['condition'] = e.condition + self['error']['text'] = e.text + if e.extension is not None: + # Extended error tag + extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args) + self['error'].append(extxml) + self['error']['type'] = e.etype + else: + # We probably didn't raise this on purpose, so send back a traceback + self['error']['condition'] = 'undefined-condition' + if sys.version_info < (3,0): + self['error']['text'] = "SleekXMPP got into trouble." + else: + self['error']['text'] = traceback.format_tb(e.__traceback__) + logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) + self.send() + + +registerStanzaPlugin(RootStanza, Error) -- cgit v1.2.3 From e8e934fa95ea776d067d875fa67e89cc9e273e90 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 29 Jul 2010 11:02:42 -0400 Subject: Fixed some PEP8 errors in RootStanza (trailing whitespace and line length) --- sleekxmpp/stanza/rootstanza.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index fb5498f5..eafc79a2 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -41,23 +41,25 @@ class RootStanza(StanzaBase): e -- Exception object """ self.reply() - if isinstance(e, XMPPError): + if isinstance(e, XMPPError): # We raised this deliberately self['error']['condition'] = e.condition self['error']['text'] = e.text - if e.extension is not None: + if e.extension is not None: # Extended error tag - extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args) + extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), + e.extension_args) self['error'].append(extxml) self['error']['type'] = e.etype - else: - # We probably didn't raise this on purpose, so send back a traceback + else: + # We probably didn't raise this on purpose, so send a traceback self['error']['condition'] = 'undefined-condition' - if sys.version_info < (3,0): + if sys.version_info < (3, 0): self['error']['text'] = "SleekXMPP got into trouble." else: self['error']['text'] = traceback.format_tb(e.__traceback__) - logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) + logging.exception('Error handling {%s}%s stanza' % + (self.namespace, self.name)) self.send() -- cgit v1.2.3 From d148f633f3771c43c94229bfcd2104b65396f5e1 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 29 Jul 2010 11:04:21 -0400 Subject: Modified ElementBase _getSubText, _setSubText, and _delSubText to use the namespace in a tag name if one is given and to use self.namespace otherwise. --- sleekxmpp/xmlstream/stanzabase.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index b8c80ffa..5b41a406 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -239,26 +239,31 @@ class ElementBase(tostring.ToString): return self.xml.attrib.get(name, '') def _getSubText(self, name): - stanza = self.xml.find("{%s}%s" % (self.namespace, name)) + if '}' not in name: + name = "{%s}%s" % (self.namespace, name) + stanza = self.xml.find(name) if stanza is None or stanza.text is None: return '' else: return stanza.text def _setSubText(self, name, attrib={}, text=None): + if '}' not in name: + name = "{%s}%s" % (self.namespace, name) if text is None or text == '': return self.__delitem__(name) - stanza = self.xml.find("{%s}%s" % (self.namespace, name)) + stanza = self.xml.find(name) if stanza is None: - #self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib)) - stanza = ET.Element("{%s}%s" % (self.namespace, name)) + stanza = ET.Element(name) self.xml.append(stanza) stanza.text = text return stanza def _delSub(self, name): + if '}' not in name: + name = "{%s}%s" % (self.namespace, name) for child in self.xml.getchildren(): - if child.tag == "{%s}%s" % (self.namespace, name): + if child.tag == name: self.xml.remove(child) def getStanzaValues(self): -- cgit v1.2.3 From 25f43bd21906cb13df137b926dc62ca23bd97df0 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 29 Jul 2010 11:06:10 -0400 Subject: Updated error stanza to be PEP8 compliant and include documentation. --- sleekxmpp/stanza/error.py | 175 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 53 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py index 7771c87b..3253d9c5 100644 --- a/sleekxmpp/stanza/error.py +++ b/sleekxmpp/stanza/error.py @@ -5,58 +5,127 @@ See the file LICENSE for copying permission. """ -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET + +from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin +from sleekxmpp.xmlstream.stanzabase import ElementBase, ET + class Error(ElementBase): - namespace = 'jabber:client' - name = 'error' - plugin_attrib = 'error' - conditions = set(('bad-request', 'conflict', 'feature-not-implemented', 'forbidden', 'gone', 'internal-server-error', 'item-not-found', 'jid-malformed', 'not-acceptable', 'not-allowed', 'not-authorized', 'payment-required', 'recipient-unavailable', 'redirect', 'registration-required', 'remote-server-not-found', 'remote-server-timeout', 'resource-constraint', 'service-unavailable', 'subscription-required', 'undefined-condition', 'unexpected-request')) - interfaces = set(('code', 'condition', 'text', 'type')) - types = set(('cancel', 'continue', 'modify', 'auth', 'wait')) - sub_interfaces = set(('text',)) - condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas' - - def setup(self, xml=None): - if ElementBase.setup(self, xml): #if we had to generate xml - self['type'] = 'cancel' - self['condition'] = 'feature-not-implemented' - if self.parent is not None: - self.parent()['type'] = 'error' - - def getCondition(self): - for child in self.xml.getchildren(): - if "{%s}" % self.condition_ns in child.tag: - return child.tag.split('}', 1)[-1] - return '' - - def setCondition(self, value): - if value in self.conditions: - for child in self.xml.getchildren(): - if "{%s}" % self.condition_ns in child.tag: - self.xml.remove(child) - condition = ET.Element("{%s}%s" % (self.condition_ns, value)) - self.xml.append(condition) - return self - - def delCondition(self): - return self - - def getText(self): - text = '' - textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") - if textxml is not None: - text = textxml.text - return text - - def setText(self, value): - self.delText() - textxml = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}text') - textxml.text = value - self.xml.append(textxml) - return self - - def delText(self): - textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") - if textxml is not None: - self.xml.remove(textxml) + + """ + XMPP stanzas of type 'error' should include an stanza that + describes the nature of the error and how it should be handled. + + Use the 'XEP-0086: Error Condition Mappings' plugin to include error + codes used in older XMPP versions. + + Example error stanza: + + + + The item was not found. + + + + Stanza Interface: + code -- The error code used in older XMPP versions. + condition -- The name of the condition element. + text -- Human readable description of the error. + type -- Error type indicating how the error should be handled. + + Attributes: + conditions -- The set of allowable error condition elements. + condition_ns -- The namespace for the condition element. + types -- A set of values indicating how the error + should be treated. + + Methods: + setup -- Overrides ElementBase.setup. + getCondition -- Retrieve the name of the condition element. + setCondition -- Add a condition element. + delCondition -- Remove the condition element. + getText -- Retrieve the contents of the element. + setText -- Set the contents of the element. + delText -- Remove the element. + """ + + namespace = 'jabber:client' + name = 'error' + plugin_attrib = 'error' + interfaces = set(('code', 'condition', 'text', 'type')) + sub_interfaces = set(('text',)) + conditions = set(('bad-request', 'conflict', 'feature-not-implemented', + 'forbidden', 'gone', 'internal-server-error', + 'item-not-found', 'jid-malformed', 'not-acceptable', + 'not-allowed', 'not-authorized', 'payment-required', + 'recipient-unavailable', 'redirect', + 'registration-required', 'remote-server-not-found', + 'remote-server-timeout', 'resource-constraint', + 'service-unavailable', 'subscription-required', + 'undefined-condition', 'unexpected-request')) + condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas' + types = set(('cancel', 'continue', 'modify', 'auth', 'wait')) + + def setup(self, xml=None): + """ + Populate the stanza object using an optional XML object. + + Overrides ElementBase.setup. + + Sets a default error type and condition, and changes the + parent stanza's type to 'error'. + + Arguments: + xml -- Use an existing XML object for the stanza's values. + """ + if ElementBase.setup(self, xml): + #If we had to generate XML then set default values. + self['type'] = 'cancel' + self['condition'] = 'feature-not-implemented' + if self.parent is not None: + self.parent()['type'] = 'error' + + def getCondition(self): + """Return the condition element's name.""" + for child in self.xml.getchildren(): + if "{%s}" % self.condition_ns in child.tag: + return child.tag.split('}', 1)[-1] + return '' + + def setCondition(self, value): + """ + Set the tag name of the condition element. + + Arguments: + value -- The tag name of the condition element. + """ + if value in self.conditions: + del self['condition'] + self.xml.append(ET.Element("{%s}%s" % (self.condition_ns, value))) + return self + + def delCondition(self): + """Remove the condition element.""" + for child in self.xml.getchildren(): + if "{%s}" % self.condition_ns in child.tag: + self.xml.remove(child) + return self + + def getText(self): + """Retrieve the contents of the element.""" + return self._getSubText('{%s}text' % self.condition_ns) + + def setText(self, value): + """ + Set the contents of the element. + + Arguments: + value -- The new contents for the element. + """ + self._setSubText('{%s}text' % self.condition_ns, text=value) + return self + + def delText(self): + """Remove the element.""" + self._delSub('{%s}text' % self.condition_ns) + return self -- cgit v1.2.3 From a49f511a2f844bbab261b5ce84257f026e1eb37c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 29 Jul 2010 20:16:57 -0400 Subject: Added RESPONSE_TIMEOUT constant to sleekxmpp.xmlstream to serve as a single place to specify a default timeout value when waiting for a stanza response. --- sleekxmpp/xmlstream/xmlstream.py | 1 + 1 file changed, 1 insertion(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 6ce89c25..94fed64a 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -24,6 +24,7 @@ import copy import xml.sax.saxutils from . import scheduler +RESPONSE_TIMEOUT = 10 HANDLER_THREADS = 1 ssl_support = True -- cgit v1.2.3 From 60a183b011f8c0ee5a3c1840075a43addf33fc4f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 29 Jul 2010 20:18:04 -0400 Subject: Added useful imports to the xmlstream, xmlstream.handler, and xmlstream.matcher __init__.py files to make it simpler to import common classes. --- sleekxmpp/xmlstream/__init__.py | 10 ++++++++++ sleekxmpp/xmlstream/handler/__init__.py | 12 ++++++++++++ sleekxmpp/xmlstream/matcher/__init__.py | 13 +++++++++++++ 3 files changed, 35 insertions(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py index e69de29b..2611896a 100644 --- a/sleekxmpp/xmlstream/__init__.py +++ b/sleekxmpp/xmlstream/__init__.py @@ -0,0 +1,10 @@ +""" + 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 sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase +from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT diff --git a/sleekxmpp/xmlstream/handler/__init__.py b/sleekxmpp/xmlstream/handler/__init__.py index e69de29b..50e286a3 100644 --- a/sleekxmpp/xmlstream/handler/__init__.py +++ b/sleekxmpp/xmlstream/handler/__init__.py @@ -0,0 +1,12 @@ +""" + 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 sleekxmpp.xmlstream.handler.callback import Callback +from sleekxmpp.xmlstream.handler.waiter import Waiter +from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback +from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py index e69de29b..91cb8d6e 100644 --- a/sleekxmpp/xmlstream/matcher/__init__.py +++ b/sleekxmpp/xmlstream/matcher/__init__.py @@ -0,0 +1,13 @@ +""" + 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 sleekxmpp.xmlstream.matcher.id import MatcherId +from sleekxmpp.xmlstream.matcher.many import MatchMany +from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath +from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask +from sleekxmpp.xmlstream.matcher.xpath import MatchXPath -- cgit v1.2.3 From 1da3e5b35eb59909d4d6903b1c0190a7aad98a30 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 29 Jul 2010 23:55:13 -0400 Subject: Added unit tests for error stanzas. Corrected error in deleting conditions. --- sleekxmpp/stanza/error.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/error.py b/sleekxmpp/stanza/error.py index 3253d9c5..6d18c297 100644 --- a/sleekxmpp/stanza/error.py +++ b/sleekxmpp/stanza/error.py @@ -108,7 +108,9 @@ class Error(ElementBase): """Remove the condition element.""" for child in self.xml.getchildren(): if "{%s}" % self.condition_ns in child.tag: - self.xml.remove(child) + tag = child.tag.split('}', 1)[-1] + if tag in self.conditions: + self.xml.remove(child) return self def getText(self): -- cgit v1.2.3 From cbed8029bad691f8353854dc264f83303a196a09 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 29 Jul 2010 23:58:25 -0400 Subject: Updated, cleaned, and documented Iq stanza class. Also added unit tests. --- sleekxmpp/stanza/iq.py | 233 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 165 insertions(+), 68 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index daf05b43..c735ae64 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -5,73 +5,170 @@ See the file LICENSE for copying permission. """ -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 -from . rootstanza import RootStanza + +from sleekxmpp.stanza import Error +from sleekxmpp.stanza.rootstanza import RootStanza +from sleekxmpp.xmlstream import RESPONSE_TIMEOUT +from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET +from sleekxmpp.xmlstream.handler import Waiter +from sleekxmpp.xmlstream.matcher import MatcherId + class Iq(RootStanza): - interfaces = set(('type', 'to', 'from', 'id','query')) - types = set(('get', 'result', 'set', 'error')) - name = 'iq' - plugin_attrib = name - namespace = 'jabber:client' - - def __init__(self, *args, **kwargs): - StanzaBase.__init__(self, *args, **kwargs) - if self['id'] == '': - if self.stream is not None: - self['id'] = self.stream.getNewId() - else: - self['id'] = '0' - - def unhandled(self): - if self['type'] in ('get', 'set'): - self.reply() - self['error']['condition'] = 'feature-not-implemented' - self['error']['text'] = 'No handlers registered for this request.' - self.send() - - def setPayload(self, value): - self.clear() - StanzaBase.setPayload(self, value) - return self - - def setQuery(self, value): - query = self.xml.find("{%s}query" % value) - if query is None and value: - self.clear() - query = ET.Element("{%s}query" % value) - self.xml.append(query) - return self - - def getQuery(self): - for child in self.xml.getchildren(): - if child.tag.endswith('query'): - ns =child.tag.split('}')[0] - if '{' in ns: - ns = ns[1:] - return ns - return '' - - def reply(self): - self['type'] = 'result' - StanzaBase.reply(self) - return self - - def delQuery(self): - for child in self.getchildren(): - if child.tag.endswith('query'): - self.xml.remove(child) - return self - - def send(self, block=True, timeout=10): - if block and self['type'] in ('get', 'set'): - 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) + + """ + XMPP stanzas, or info/query stanzas, are XMPP's method of + requesting and modifying information, similar to HTTP's GET and + POST methods. + + Each stanza must have an 'id' value which associates the + stanza with the response stanza. XMPP entities must always + be given a response stanza with a type of 'result' after + sending a stanza of type 'get' or 'set'. + + Most uses cases for stanzas will involve adding a + element whose namespace indicates the type of information + desired. However, some custom XMPP applications use stanzas + as a carrier stanza for an application-specific protocol instead. + + Example Stanzas: + + + + + + + + Friends + + + + + Stanza Interface: + query -- The namespace of the element if one exists. + + Methods: + __init__ -- Overrides StanzaBase.__init__. + unhandled -- Send error if there are no handlers. + setPayload -- Overrides StanzaBase.setPayload. + setQuery -- Add or modify a element. + getQuery -- Return the namespace of the element. + delQuery -- Remove the element. + reply -- Overrides StanzaBase.reply + send -- Overrides StanzaBase.send + """ + + namespace = 'jabber:client' + name = 'iq' + interfaces = set(('type', 'to', 'from', 'id', 'query')) + types = set(('get', 'result', 'set', 'error')) + plugin_attrib = name + + def __init__(self, *args, **kwargs): + """ + Initialize a new stanza with an 'id' value. + + Overrides StanzaBase.__init__. + """ + StanzaBase.__init__(self, *args, **kwargs) + if self['id'] == '': + if self.stream is not None: + self['id'] = self.stream.getNewId() + else: + self['id'] = '0' + + def unhandled(self): + """ + Send a feature-not-implemented error if the stanza is not handled. + + Overrides StanzaBase.unhandled. + """ + if self['type'] in ('get', 'set'): + self.reply() + self['error']['condition'] = 'feature-not-implemented' + self['error']['text'] = 'No handlers registered for this request.' + self.send() + + def setPayload(self, value): + """ + Set the XML contents of the stanza. + + Arguments: + value -- An XML object to use as the stanza's contents + """ + self.clear() + StanzaBase.setPayload(self, value) + return self + + def setQuery(self, value): + """ + Add or modify a element. + + Query elements are differentiated by their namespace. + + Arguments: + value -- The namespace of the element. + """ + query = self.xml.find("{%s}query" % value) + if query is None and value: + self.clear() + query = ET.Element("{%s}query" % value) + self.xml.append(query) + return self + + def getQuery(self): + """Return the namespace of the element.""" + for child in self.xml.getchildren(): + if child.tag.endswith('query'): + ns = child.tag.split('}')[0] + if '{' in ns: + ns = ns[1:] + return ns + return '' + + def delQuery(self): + """Remove the element.""" + for child in self.xml.getchildren(): + if child.tag.endswith('query'): + self.xml.remove(child) + return self + + def reply(self): + """ + Send a reply stanza. + + Overrides StanzaBase.reply + + Sets the 'type' to 'result' in addition to the default + StanzaBase.reply behavior. + """ + self['type'] = 'result' + StanzaBase.reply(self) + return self + + def send(self, block=True, timeout=RESPONSE_TIMEOUT): + """ + Send an stanza over the XML stream. + + The send call can optionally block until a response is received or + a timeout occurs. Be aware that using blocking in non-threaded event + handlers can drastically impact performance. + + Overrides StanzaBase.send + + Arguments: + block -- Specify if the send call will block until a response + is received, or a timeout occurs. Defaults to True. + timeout -- The length of time (in seconds) to wait for a response + before exiting the send call if blocking is used. + Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT + """ + if block and self['type'] in ('get', 'set'): + 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) -- cgit v1.2.3 From 1cedea280499c0b7f7debb6c95442038d8f2d89c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 30 Jul 2010 14:11:24 -0400 Subject: Added optional default value to _getAttr. --- sleekxmpp/stanza/iq.py | 3 +++ sleekxmpp/xmlstream/stanzabase.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index c735ae64..c5ef8bb4 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -49,6 +49,9 @@ class Iq(RootStanza): Stanza Interface: query -- The namespace of the element if one exists. + Attributes: + types -- May be one of: get, set, result, or error. + Methods: __init__ -- Overrides StanzaBase.__init__. unhandled -- Send error if there are no handlers. diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 5b41a406..94ff958c 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -235,8 +235,8 @@ class ElementBase(tostring.ToString): if name in self.xml.attrib: del self.xml.attrib[name] - def _getAttr(self, name): - return self.xml.attrib.get(name, '') + def _getAttr(self, name, default=''): + return self.xml.attrib.get(name, default) def _getSubText(self, name): if '}' not in name: -- cgit v1.2.3 From 939ae298c2856f095526a9e0f52216e9dc4e7db1 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 3 Aug 2010 12:19:45 -0400 Subject: Updated message stanzas and tests with documentation and PEP8 style. --- sleekxmpp/stanza/message.py | 188 +++++++++++++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 54 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py index 75ecc232..560e1d47 100644 --- a/sleekxmpp/stanza/message.py +++ b/sleekxmpp/stanza/message.py @@ -5,59 +5,139 @@ See the file LICENSE for copying permission. """ -from .. xmlstream.stanzabase import StanzaBase -from xml.etree import cElementTree as ET -from . error import Error -from . rootstanza import RootStanza + +from sleekxmpp.stanza import Error +from sleekxmpp.stanza.rootstanza import RootStanza +from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET + class Message(RootStanza): - interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject', 'mucroom', 'mucnick')) - types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) - sub_interfaces = set(('body', 'subject')) - name = 'message' - plugin_attrib = name - 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 - - def reply(self, body=None): - StanzaBase.reply(self) - if self['type'] == 'groupchat': - self['to'] = self['to'].bare - del self['id'] - if body is not None: - self['body'] = body - return self - - def getMucroom(self): - if self['type'] == 'groupchat': - return self['from'].bare - else: - return '' - - def setMucroom(self, value): - pass - - def delMucroom(self): - pass - - def getMucnick(self): - if self['type'] == 'groupchat': - return self['from'].resource - else: - return '' - - def setMucnick(self, value): - pass - - def delMucnick(self): - pass + + """ + XMPP's stanzas are a "push" mechanism to send information + to other XMPP entities without requiring a response. + + Chat clients will typically use stanzas that have a type + of either "chat" or "groupchat". + + When handling a message event, be sure to check if the message is + an error response. + + Example stanzas: + + Hi! + + + + Hi everyone! + + + Stanza Interface: + body -- The main contents of the message. + subject -- An optional description of the message's contents. + mucroom -- (Read-only) The name of the MUC room that sent the message. + mucnick -- (Read-only) The MUC nickname of message's sender. + + Attributes: + types -- May be one of: normal, chat, headline, groupchat, or error. + + Methods: + chat -- Set the message type to 'chat'. + normal -- Set the message type to 'normal'. + reply -- Overrides StanzaBase.reply + getType -- Overrides StanzaBase interface + getMucroom -- Return the name of the MUC room of the message. + setMucroom -- Dummy method to prevent assignment. + delMucroom -- Dummy method to prevent deletion. + getMucnick -- Return the MUC nickname of the message's sender. + setMucnick -- Dummy method to prevent assignment. + delMucnick -- Dummy method to prevent deletion. + """ + + namespace = 'jabber:client' + name = 'message' + interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject', + 'mucroom', 'mucnick')) + sub_interfaces = set(('body', 'subject')) + plugin_attrib = name + types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) + + def getType(self): + """ + Return the message type. + + Overrides default stanza interface behavior. + + Returns 'normal' if no type attribute is present. + """ + return self._getAttr('type', 'normal') + + def chat(self): + """Set the message type to 'chat'.""" + self['type'] = 'chat' + return self + + def normal(self): + """Set the message type to 'chat'.""" + self['type'] = 'normal' + return self + + def reply(self, body=None): + """ + Create a message reply. + + Overrides StanzaBase.reply. + + Sets proper 'to' attribute if the message is from a MUC, and + adds a message body if one is given. + + Arguments: + body -- Optional text content for the message. + """ + StanzaBase.reply(self) + if self['type'] == 'groupchat': + self['to'] = self['to'].bare + + del self['id'] + + if body is not None: + self['body'] = body + return self + + def getMucroom(self): + """ + Return the name of the MUC room where the message originated. + + Read-only stanza interface. + """ + if self['type'] == 'groupchat': + return self['from'].bare + else: + return '' + + def getMucnick(self): + """ + Return the nickname of the MUC user that sent the message. + + Read-only stanza interface. + """ + if self['type'] == 'groupchat': + return self['from'].resource + else: + return '' + + def setMucroom(self, value): + """Dummy method to prevent modification.""" + pass + + def delMucroom(self): + """Dummy method to prevent deletion.""" + pass + + def setMucnick(self, value): + """Dummy method to prevent modification.""" + pass + + def delMucnick(self): + """Dummy method to prevent deletion.""" + pass -- cgit v1.2.3 From 41ab2b84604849d0e650ecd833554b3488733785 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 3 Aug 2010 17:30:34 -0400 Subject: Updated presence stanza with documentation and PEP8 style. --- sleekxmpp/stanza/presence.py | 183 +++++++++++++++++++++++++++++++------------ 1 file changed, 135 insertions(+), 48 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py index ec681763..651bf34d 100644 --- a/sleekxmpp/stanza/presence.py +++ b/sleekxmpp/stanza/presence.py @@ -6,54 +6,141 @@ See the file LICENSE for copying permission. """ -from . error import Error -from . rootstanza import RootStanza -from .. xmlstream.stanzabase import StanzaBase, ET +from sleekxmpp.stanza import Error +from sleekxmpp.stanza.rootstanza import RootStanza +from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET class Presence(RootStanza): - interfaces = set(('type', 'to', 'from', 'id', 'show', 'status', 'priority')) - types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed')) - showtypes = set(('dnd', 'chat', 'xa', 'away')) - sub_interfaces = set(('show', 'status', 'priority')) - name = 'presence' - plugin_attrib = name - namespace = 'jabber:client' - - def setShow(self, show): - if show in self.showtypes: - self._setSubText('show', text=show) - return self - - def setType(self, value): - if value in self.types: - self['show'] = None - if value == 'available': - value = '' - self._setAttr('type', value) - elif value in self.showtypes: - self['show'] = value - return self - - def setPriority(self, value): - self._setSubText('priority', text = str(value)) - - 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: - out = self['show'] - if not out or out is None: - out = 'available' - return out - - def reply(self): - if self['type'] == 'unsubscribe': - self['type'] = 'unsubscribed' - elif self['type'] == 'subscribe': - self['type'] = 'subscribed' - return StanzaBase.reply(self) + + """ + XMPP's stanza allows entities to know the status of other + clients and components. Since it is currently the only multi-cast + stanza in XMPP, many extensions add more information to + stanzas to broadcast to every entry in the roster, such as + capabilities, music choices, or locations (XEP-0115: Entity Capabilities + and XEP-0163: Personal Eventing Protocol). + + Since stanzas are broadcast when an XMPP entity changes + its status, the bulk of the traffic in an XMPP network will be from + stanzas. Therefore, do not include more information than + necessary in a status message or within a stanza in order + to help keep the network running smoothly. + + Example stanzas: + + + + away + Getting lunch. + 5 + + + + + + + Stanza Interface: + priority -- A value used by servers to determine message routing. + show -- The type of status, such as away or available for chat. + status -- Custom, human readable status message. + + Attributes: + types -- One of: available, unavailable, error, probe, + subscribe, subscribed, unsubscribe, + and unsubscribed. + showtypes -- One of: away, chat, dnd, and xa. + + Methods: + reply -- Overrides StanzaBase.reply + setShow -- Set the value of the element. + getType -- Get the value of the type attribute or element. + setType -- Set the value of the type attribute or element. + getPriority -- Get the value of the element. + setPriority -- Set the value of the element. + """ + + namespace = 'jabber:client' + name = 'presence' + interfaces = set(('type', 'to', 'from', 'id', 'show', + 'status', 'priority')) + sub_interfaces = set(('show', 'status', 'priority')) + plugin_attrib = name + + types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', + 'subscribed', 'unsubscribe', 'unsubscribed')) + showtypes = set(('dnd', 'chat', 'xa', 'away')) + + def setShow(self, show): + """ + Set the value of the element. + + Arguments: + show -- Must be one of: away, chat, dnd, or xa. + """ + if show in self.showtypes: + self._setSubText('show', text=show) + return self + + def setType(self, value): + """ + Set the type attribute's value, and the element + if applicable. + + Arguments: + value -- Must be in either self.types or self.showtypes. + """ + if value in self.types: + self['show'] = None + if value == 'available': + value = '' + self._setAttr('type', value) + elif value in self.showtypes: + self['show'] = value + return self + + def setPriority(self, value): + """ + Set the entity's priority value. Some server use priority to + determine message routing behavior. + + Bot clients should typically use a priority of 0 if the same + JID is used elsewhere by a human-interacting client. + + Arguments: + value -- An integer value greater than or equal to 0. + """ + self._setSubText('priority', text=str(value)) + + def getPriority(self): + """ + Return the value of the element as an integer. + """ + p = self._getSubText('priority') + if not p: + p = 0 + return int(p) + + def getType(self): + """ + Return the value of the stanza's type attribute, or + the value of the element. + """ + out = self._getAttr('type') + if not out: + out = self['show'] + if not out or out is None: + out = 'available' + return out + + def reply(self): + """ + Set the appropriate presence reply type. + + Overrides StanzaBase.reply. + """ + if self['type'] == 'unsubscribe': + self['type'] = 'unsubscribed' + elif self['type'] == 'subscribe': + self['type'] = 'subscribed' + return StanzaBase.reply(self) -- cgit v1.2.3 From 183a3f1b87a8f5a6c4e174dec6de513a0bb5cb83 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 3 Aug 2010 17:58:18 -0400 Subject: Updated XHTML-IM stanza with documentation and PEP8 style. --- sleekxmpp/stanza/htmlim.py | 97 +++++++++++++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 26 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/htmlim.py b/sleekxmpp/stanza/htmlim.py index 50195b11..c2f2f0c8 100644 --- a/sleekxmpp/stanza/htmlim.py +++ b/sleekxmpp/stanza/htmlim.py @@ -5,31 +5,76 @@ See the file LICENSE for copying permission. """ -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET + +from sleekxmpp.stanza import Message +from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin +from sleekxmpp.xmlstream.stanzabase import ElementBase, ET + class HTMLIM(ElementBase): - namespace = 'http://jabber.org/protocol/xhtml-im' - name = 'html' - plugin_attrib = 'html' - interfaces = set(('html',)) - plugin_attrib_map = set() - plugin_xml_map = set() - - def setHtml(self, html): - if isinstance(html, str): - html = ET.XML(html) - if html.tag != '{http://www.w3.org/1999/xhtml}body': - body = ET.Element('{http://www.w3.org/1999/xhtml}body') - body.append(html) - self.xml.append(body) - else: - self.xml.append(html) - - def getHtml(self): - html = self.xml.find('{http://www.w3.org/1999/xhtml}body') - if html is None: return '' - return html - - def delHtml(self): - if self.parent is not None: - self.parent().xml.remove(self.xml) + + """ + XEP-0071: XHTML-IM defines a method for embedding XHTML content + within a stanza so that lightweight markup can be used + to format the message contents and to create links. + + Only a subset of XHTML is recommended for use with XHTML-IM. + See the full spec at 'http://xmpp.org/extensions/xep-0071.html' + for more information. + + Example stanza: + + Non-html message content. + + +

HTML!

+ + +
+ + Stanza Interface: + body -- The contents of the HTML body tag. + + Methods: + getBody -- Return the HTML body contents. + setBody -- Set the HTML body contents. + delBody -- Remove the HTML body contents. + """ + + namespace = 'http://jabber.org/protocol/xhtml-im' + name = 'html' + interfaces = set(('body',)) + plugin_attrib = name + + def setBody(self, html): + """ + Set the contents of the HTML body. + + Arguments: + html -- Either a string or XML object. If the top level + element is not with a namespace of + 'http://www.w3.org/1999/xhtml', it will be wrapped. + """ + if isinstance(html, str): + html = ET.XML(html) + if html.tag != '{http://www.w3.org/1999/xhtml}body': + body = ET.Element('{http://www.w3.org/1999/xhtml}body') + body.append(html) + self.xml.append(body) + else: + self.xml.append(html) + + def getBody(self): + """Return the contents of the HTML body.""" + html = self.xml.find('{http://www.w3.org/1999/xhtml}body') + if html is None: + return '' + return html + + def delBody(self): + """Remove the HTML body contents.""" + if self.parent is not None: + self.parent().xml.remove(self.xml) + + +registerStanzaPlugin(Message, HTMLIM) -- cgit v1.2.3 From fec69be7318d9ad2a8e4ac128ac57f1969a6b852 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 3 Aug 2010 18:32:53 -0400 Subject: Update nick stanza with documentation and PEP8 style. --- sleekxmpp/stanza/nick.py | 80 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 17 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/nick.py b/sleekxmpp/stanza/nick.py index 47d4620a..de54b307 100644 --- a/sleekxmpp/stanza/nick.py +++ b/sleekxmpp/stanza/nick.py @@ -5,22 +5,68 @@ See the file LICENSE for copying permission. """ -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET + +from sleekxmpp.stanza import Message, Presence +from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin +from sleekxmpp.xmlstream.stanzabase import ElementBase, ET + class Nick(ElementBase): - namespace = 'http://jabber.org/nick/nick' - name = 'nick' - plugin_attrib = 'nick' - interfaces = set(('nick')) - plugin_attrib_map = set() - plugin_xml_map = set() - - def setNick(self, nick): - self.xml.text = nick - - def getNick(self): - return self.xml.text - - def delNick(self): - if self.parent is not None: - self.parent().xml.remove(self.xml) + + """ + XEP-0172: User Nickname allows the addition of a element + in several stanza types, including and stanzas. + + The nickname contained in a should be the global, friendly or + informal name chosen by the owner of a bare JID. The element + may be included when establishing communications with new entities, + such as normal XMPP users or MUC services. + + The nickname contained in a element will not necessarily be + the same as the nickname used in a MUC. + + Example stanzas: + + The User + ... + + + + The User + + + Stanza Interface: + nick -- A global, friendly or informal name chosen by a user. + + Methods: + getNick -- Return the nickname in the element. + setNick -- Add a element with the given nickname. + delNick -- Remove the element. + """ + + namespace = 'http://jabber.org/nick/nick' + name = 'nick' + plugin_attrib = name + interfaces = set(('nick',)) + + def setNick(self, nick): + """ + Add a element with the given nickname. + + Arguments: + nick -- A human readable, informal name. + """ + self.xml.text = nick + + def getNick(self): + """Return the nickname in the element.""" + return self.xml.text + + def delNick(self): + """Remove the element.""" + if self.parent is not None: + self.parent().xml.remove(self.xml) + + +registerStanzaPlugin(Message, Nick) +registerStanzaPlugin(Presence, Nick) -- cgit v1.2.3 From aa1dbe97e0df2437a63a0b16566b75b8a874f065 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 4 Aug 2010 00:33:28 -0400 Subject: Updated and simplified new JID class to have more documentation and use PEP8 style. --- sleekxmpp/xmlstream/__init__.py | 1 + sleekxmpp/xmlstream/jid.py | 187 +++++++++++++++++++++++++--------------- 2 files changed, 120 insertions(+), 68 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py index 2611896a..c82ab346 100644 --- a/sleekxmpp/xmlstream/__init__.py +++ b/sleekxmpp/xmlstream/__init__.py @@ -6,5 +6,6 @@ See the file LICENSE for copying permission. """ +from sleekxmpp.xmlstream.jid import JID from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT diff --git a/sleekxmpp/xmlstream/jid.py b/sleekxmpp/xmlstream/jid.py index 2839ba58..292abd92 100644 --- a/sleekxmpp/xmlstream/jid.py +++ b/sleekxmpp/xmlstream/jid.py @@ -1,70 +1,121 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + + class JID(object): - def __init__(self, jid): - """Initialize a new jid""" - self.reset(jid) - - def reset(self, jid): - """Start fresh from a new jid string""" - self._full = self._jid = str(jid) - self._domain = None - self._resource = None - self._user = None - self._domain = None - self._bare = None - - def __getattr__(self, name): - """Handle getting the jid values, using cache if available""" - if name == 'resource': - if self._resource is not None: return self._resource - self._resource = self._jid.split('/', 1)[-1] - return self._resource - elif name == 'user': - if self._user is not None: return self._user - if '@' in self._jid: - self._user = self._jid.split('@', 1)[0] - else: - self._user = self._user - return self._user - elif name in ('server', 'domain'): - if self._domain is not None: return self._domain - self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0] - return self._domain - elif name == 'full': - return self._jid - elif name == 'bare': - if self._bare is not None: return self._bare - self._bare = self._jid.split('/', 1)[0] - return self._bare - - def __setattr__(self, name, value): - """Edit a jid by updating it's individual values, resetting by a generated jid in the end""" - if name in ('resource', 'user', 'domain'): - object.__setattr__(self, "_%s" % name, value) - self.regenerate() - elif name == 'server': - self.domain = value - elif name in ('full', 'jid'): - self.reset(value) - elif name == 'bare': - if '@' in value: - u, d = value.split('@', 1) - object.__setattr__(self, "_user", u) - object.__setattr__(self, "_domain", d) - else: - object.__setattr__(self, "_domain", value) - self.regenerate() - else: - object.__setattr__(self, name, value) - - - def regenerate(self): - """Generate a new jid based on current values, useful after editing""" - jid = "" - if self.user: jid = "%s@" % self.user - jid += self.domain - if self.resource: jid += "/%s" % self.resource - self.reset(jid) - - def __str__(self): - return self.full + """ + A representation of a Jabber ID, or JID. + + Each JID may have three components: a user, a domain, and an optional + resource. For example: user@domain/resource + + When a resource is not used, the JID is called a bare JID. + The JID is a full JID otherwise. + + Attributes: + jid -- Alias for 'full'. + full -- The value of the full JID. + bare -- The value of the bare JID. + user -- The username portion of the JID. + domain -- The domain name portion of the JID. + server -- Alias for 'domain'. + resource -- The resource portion of the JID. + + Methods: + reset -- Use a new JID value. + regenerate -- Recreate the JID from its components. + """ + + def __init__(self, jid): + """Initialize a new JID""" + self.reset(jid) + + def reset(self, jid): + """ + Start fresh from a new JID string. + + Arguments: + jid - The new JID value. + """ + self._full = self._jid = str(jid) + self._domain = None + self._resource = None + self._user = None + self._bare = None + + def __getattr__(self, name): + """ + Handle getting the JID values, using cache if available. + + Arguments: + name -- One of: user, server, domain, resource, + full, or bare. + """ + if name == 'resource': + if self._resource is None: + self._resource = self._jid.split('/', 1)[-1] + return self._resource + elif name == 'user': + if self._user is None: + if '@' in self._jid: + self._user = self._jid.split('@', 1)[0] + else: + self._user = self._user + return self._user + elif name in ('server', 'domain'): + if self._domain is None: + self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0] + return self._domain + elif name == 'full': + return self._jid + elif name == 'bare': + if self._bare is None: + self._bare = self._jid.split('/', 1)[0] + return self._bare + + def __setattr__(self, name, value): + """ + Edit a JID by updating it's individual values, resetting the + generated JID in the end. + + Arguments: + name -- The name of the JID part. One of: user, domain, + server, resource, full, jid, or bare. + value -- The new value for the JID part. + """ + if name in ('resource', 'user', 'domain'): + object.__setattr__(self, "_%s" % name, value) + self.regenerate() + elif name == 'server': + self.domain = value + elif name in ('full', 'jid'): + self.reset(value) + elif name == 'bare': + if '@' in value: + u, d = value.split('@', 1) + object.__setattr__(self, "_user", u) + object.__setattr__(self, "_domain", d) + else: + object.__setattr__(self, "_domain", value) + self.regenerate() + else: + object.__setattr__(self, name, value) + + def regenerate(self): + """Generate a new JID based on current values, useful after editing.""" + jid = "" + if self.user: + jid = "%s@" % self.user + jid += self.domain + if self.resource: + jid += "/%s" % self.resource + self.reset(jid) + def __str__(self): + """Use the full JID as the string value.""" + return self.full -- cgit v1.2.3 From c54466596f3dcc7a35a41c49fff0d057d4a8ed8f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 4 Aug 2010 14:41:37 -0400 Subject: Modified sleekxmpp.xmlstream.tostring to import ToString class based on Python version. The package sleekxmpp.xmlstream.tostring26 remains for now until stanzabase is updated, but is no longer needed. --- sleekxmpp/xmlstream/tostring/__init__.py | 70 +++++------------------------- sleekxmpp/xmlstream/tostring/tostring.py | 60 +++++++++++++++++++++++++ sleekxmpp/xmlstream/tostring/tostring26.py | 65 +++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 58 deletions(-) create mode 100644 sleekxmpp/xmlstream/tostring/tostring.py create mode 100644 sleekxmpp/xmlstream/tostring/tostring26.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/tostring/__init__.py b/sleekxmpp/xmlstream/tostring/__init__.py index 6603cbb8..d93fe4ea 100644 --- a/sleekxmpp/xmlstream/tostring/__init__.py +++ b/sleekxmpp/xmlstream/tostring/__init__.py @@ -1,60 +1,14 @@ +""" -class ToString(object): - 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 != '' and ixmlns != self.namespace: - if self.stream is not None and ixmlns in self.stream.namespace_map: - if self.stream.namespace_map[ixmlns] != '': - itag = "%s:%s" % (self.stream.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: - if '{' not in 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] = ''' - else: - text[cc] = '"' - cc += 1 - return ''.join(text) +import sys + + +# Import the correct ToString class based on the Python version. +if sys.version_info < (3, 0): + from sleekxmpp.xmlstream.tostring.tostring26 import ToString +else: + from sleekxmpp.xmlstream.tostring.tostring import ToString + +__all__ = ['ToString'] diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py new file mode 100644 index 00000000..6603cbb8 --- /dev/null +++ b/sleekxmpp/xmlstream/tostring/tostring.py @@ -0,0 +1,60 @@ + +class ToString(object): + 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 != '' and ixmlns != self.namespace: + if self.stream is not None and ixmlns in self.stream.namespace_map: + if self.stream.namespace_map[ixmlns] != '': + itag = "%s:%s" % (self.stream.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: + if '{' not in 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] = ''' + else: + text[cc] = '"' + cc += 1 + return ''.join(text) diff --git a/sleekxmpp/xmlstream/tostring/tostring26.py b/sleekxmpp/xmlstream/tostring/tostring26.py new file mode 100644 index 00000000..9711c300 --- /dev/null +++ b/sleekxmpp/xmlstream/tostring/tostring26.py @@ -0,0 +1,65 @@ +import types + +class ToString(object): + 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 != u'' and ixmlns != self.namespace: + if self.stream is not None and ixmlns in self.stream.namespace_map: + if self.stream.namespace_map[ixmlns] != u'': + itag = "%s:%s" % (self.stream.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: + if '{' not in attrib: + newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) + if len(xml) or xml.text or xml.tail: + newoutput.append(u">") + 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(u"" % (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 u''.join(newoutput) + + def xmlesc(self, text): + if type(text) != types.UnicodeType: + text = list(unicode(text, 'utf-8', 'ignore')) + else: + text = list(text) + + cc = 0 + matches = (u'&', u'<', u'"', u'>', u"'") + for c in text: + if c in matches: + if c == u'&': + text[cc] = u'&' + elif c == u'<': + text[cc] = u'<' + elif c == u'>': + text[cc] = u'>' + elif c == u"'": + text[cc] = u''' + else: + text[cc] = u'"' + cc += 1 + return ''.join(text) -- cgit v1.2.3 From e077204a16c76df4af90ba067e94c31af3d9e372 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 5 Aug 2010 20:26:41 -0400 Subject: Replaced the ToString class with a tostring function. The sleekxmpp.xmlstream.tostring and sleekxmpp.xmlstream.tostring26 packages have been merged to sleekxmpp.xmlstream.tostring. The __init__.py file will import the appropriate tostring function depending on the Python version. The setup.py file has been updated with the package changes. ElementBase is now a direct descendent of object and does not subclass ToString. Stanza objects now return their XML contents for __repr__. --- sleekxmpp/basexmpp.py | 53 +++++----- sleekxmpp/xmlstream/stanzabase.py | 91 +++++++++-------- sleekxmpp/xmlstream/tostring/__init__.py | 13 ++- sleekxmpp/xmlstream/tostring/tostring.py | 149 ++++++++++++++++----------- sleekxmpp/xmlstream/tostring/tostring26.py | 157 ++++++++++++++++++----------- sleekxmpp/xmlstream/tostring26/__init__.py | 65 ------------ sleekxmpp/xmlstream/xmlstream.py | 101 +++++-------------- 7 files changed, 294 insertions(+), 335 deletions(-) delete mode 100644 sleekxmpp/xmlstream/tostring26/__init__.py (limited to 'sleekxmpp') diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 2c2bb91e..b7b605b0 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -25,6 +25,7 @@ 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 @@ -60,7 +61,7 @@ class basexmpp(object): registerStanzaPlugin(Iq, Roster) registerStanzaPlugin(Message, Nick) registerStanzaPlugin(Message, HTMLIM) - + def Message(self, *args, **kwargs): return Message(self, *args, **kwargs) @@ -69,7 +70,7 @@ class basexmpp(object): 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 @@ -77,12 +78,12 @@ class basexmpp(object): 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.""" @@ -97,7 +98,7 @@ class basexmpp(object): 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: @@ -112,24 +113,24 @@ class basexmpp(object): # 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)) - + def getId(self): return "%x".upper() % self.id def sendXML(self, data, mask=None, timeout=10): - return self.send(self.tostring(data), mask, timeout) - + 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(''): @@ -144,19 +145,19 @@ class basexmpp(object): 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: @@ -172,13 +173,13 @@ class basexmpp(object): 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] = [] @@ -188,13 +189,13 @@ class basexmpp(object): """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] = filter(filter_pointers, self.event_handlers[name]) def event(self, name, eventdata = {}): # called on an event @@ -209,7 +210,7 @@ class basexmpp(object): 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 @@ -217,7 +218,7 @@ class basexmpp(object): 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 @@ -226,10 +227,10 @@ class basexmpp(object): 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: @@ -243,19 +244,19 @@ class basexmpp(object): 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) @@ -296,7 +297,7 @@ class basexmpp(object): 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: diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 94ff958c..3b5f0bf4 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -12,10 +12,7 @@ import weakref import copy from . jid import JID -if sys.version_info < (3,0): - from . import tostring26 as tostring -else: - from . import tostring +from sleekxmpp.xmlstream.tostring import tostring xmltester = type(ET.Element('xml')) @@ -29,7 +26,7 @@ def registerStanzaPlugin(stanza, plugin): stanza.plugin_tag_map[tag] = plugin -class ElementBase(tostring.ToString): +class ElementBase(object): name = 'stanza' plugin_attrib = 'plugin' namespace = 'jabber:client' @@ -70,20 +67,20 @@ class ElementBase(tostring.ToString): def __bool__(self): return True - + def __next__(self): self.idx += 1 if self.idx > len(self.iterables): self.idx = 0 raise StopIteration return self.iterables[self.idx - 1] - + def next(self): return self.__next__() def __len__(self): return len(self.iterables) - + def append(self, item): if not isinstance(item, ElementBase): if type(item) == xmltester: @@ -93,18 +90,18 @@ class ElementBase(tostring.ToString): self.xml.append(item.xml) self.iterables.append(item) return self - + def pop(self, idx=0): aff = self.iterables.pop(idx) self.xml.remove(aff.xml) return aff - + def get(self, key, defaultvalue=None): value = self[key] if value is None or value == '': return defaultvalue return value - + def keys(self): out = [] out += [x for x in self.interfaces] @@ -112,7 +109,7 @@ class ElementBase(tostring.ToString): if self.iterables: out.append('substanzas') return tuple(out) - + def match(self, matchstring): if isinstance(matchstring, str): nodes = matchstring.split('/') @@ -136,13 +133,13 @@ class ElementBase(tostring.ToString): else: return False return True - + def find(self, xpath): # for backwards compatiblity, expose elementtree interface return self.xml.find(xpath) def findall(self, xpath): return self.xml.findall(xpath) - + def setup(self, xml=None): if self.xml is None: self.xml = xml @@ -162,11 +159,11 @@ class ElementBase(tostring.ToString): 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 == 'substanzas': return self.iterables @@ -183,7 +180,7 @@ class ElementBase(tostring.ToString): return self.plugins[attrib] else: return '' - + def __setitem__(self, attrib, value): if attrib in self.interfaces: if value is not None: @@ -201,7 +198,7 @@ class ElementBase(tostring.ToString): self.initPlugin(attrib) self.plugins[attrib][attrib] = value return self - + def __delitem__(self, attrib): if attrib.lower() in self.interfaces: if hasattr(self, "del%s" % attrib.title()): @@ -215,7 +212,7 @@ class ElementBase(tostring.ToString): if attrib in self.plugins: del self.plugins[attrib] return self - + def __eq__(self, other): if not isinstance(other, ElementBase): return False @@ -224,20 +221,20 @@ class ElementBase(tostring.ToString): if key not in values or values[key] != other[key]: return False return True - + def _setAttr(self, name, value): if value is None or value == '': self.__delitem__(name) else: self.xml.attrib[name] = value - + def _delAttr(self, name): if name in self.xml.attrib: del self.xml.attrib[name] - + def _getAttr(self, name, default=''): return self.xml.attrib.get(name, default) - + def _getSubText(self, name): if '}' not in name: name = "{%s}%s" % (self.namespace, name) @@ -246,7 +243,7 @@ class ElementBase(tostring.ToString): return '' else: return stanza.text - + def _setSubText(self, name, attrib={}, text=None): if '}' not in name: name = "{%s}%s" % (self.namespace, name) @@ -258,14 +255,14 @@ class ElementBase(tostring.ToString): self.xml.append(stanza) stanza.text = text return stanza - + def _delSub(self, name): if '}' not in name: name = "{%s}%s" % (self.namespace, name) for child in self.xml.getchildren(): if child.tag == name: self.xml.remove(child) - + def getStanzaValues(self): out = {} for interface in self.interfaces: @@ -279,7 +276,7 @@ class ElementBase(tostring.ToString): iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) out['substanzas'] = iterables return out - + def setStanzaValues(self, attrib): for interface in attrib: if interface == 'substanzas': @@ -298,14 +295,20 @@ class ElementBase(tostring.ToString): if interface in self.plugins: self.plugins[interface].setStanzaValues(attrib[interface]) return self - + def appendxml(self, xml): self.xml.append(xml) return self def __copy__(self): return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) - + + def __str__(self): + return tostring(self.xml, xmlns='', stanza_ns=self.namespace) + + def __repr__(self): + return self.__str__() + #def __del__(self): #prevents garbage collection of reference cycle # if self.parent is not None: # self.parent.xml.remove(self.xml) @@ -329,7 +332,7 @@ class StanzaBase(ElementBase): if sfrom is not None: self['from'] = sfrom self.tag = "{%s}%s" % (self.namespace, self.name) - + def setType(self, value): if value in self.types: self.xml.attrib['type'] = value @@ -337,22 +340,22 @@ class StanzaBase(ElementBase): def getPayload(self): return self.xml.getchildren() - + def setPayload(self, value): self.xml.append(value) return self - + def delPayload(self): self.clear() return self - + def clear(self): for child in self.xml.getchildren(): self.xml.remove(child) for plugin in list(self.plugins.keys()): del self.plugins[plugin] return self - + def reply(self): # if it's a component, use from if self.stream and hasattr(self.stream, "is_component") and self.stream.is_component: @@ -362,32 +365,34 @@ class StanzaBase(ElementBase): del self['from'] self.clear() return self - + def error(self): self['type'] = 'error' return self - + 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 - + def exception(self, e): logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) - + def send(self): self.stream.sendRaw(self.__str__()) def __copy__(self): return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) - + + def __str__(self): + return tostring(self.xml, xmlns='', stanza_ns=self.namespace, stream=self.stream) diff --git a/sleekxmpp/xmlstream/tostring/__init__.py b/sleekxmpp/xmlstream/tostring/__init__.py index d93fe4ea..5852cba2 100644 --- a/sleekxmpp/xmlstream/tostring/__init__.py +++ b/sleekxmpp/xmlstream/tostring/__init__.py @@ -1,14 +1,19 @@ """ + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + See the file LICENSE for copying permission. """ import sys +# Import the correct tostring and xml_escape functions based on the Python +# version in order to properly handle Unicode. -# Import the correct ToString class based on the Python version. if sys.version_info < (3, 0): - from sleekxmpp.xmlstream.tostring.tostring26 import ToString + from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape else: - from sleekxmpp.xmlstream.tostring.tostring import ToString + from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape -__all__ = ['ToString'] +__all__ = ['tostring', 'xml_escape'] diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py index 6603cbb8..62ff1181 100644 --- a/sleekxmpp/xmlstream/tostring/tostring.py +++ b/sleekxmpp/xmlstream/tostring/tostring.py @@ -1,60 +1,91 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. -class ToString(object): - 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 != '' and ixmlns != self.namespace: - if self.stream is not None and ixmlns in self.stream.namespace_map: - if self.stream.namespace_map[ixmlns] != '': - itag = "%s:%s" % (self.stream.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: - if '{' not in 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] = ''' - else: - text[cc] = '"' - cc += 1 - return ''.join(text) + See the file LICENSE for copying permission. +""" + + +def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): + """ + Serialize an XML object to a Unicode string. + + Arguments: + xml -- The XML object to serialize. If the value is None, + then the XML object contained in this stanza + object will be used. + xmlns -- Optional namespace of an element wrapping the XML + object. + stanza_ns -- The namespace of the stanza object that contains + the XML object. + stream -- The XML stream that generated the XML object. + outbuffer -- Optional buffer for storing serializations during + recursive calls. + """ + # Add previous results to the start of the output. + output = [outbuffer] + + # Extract the element's tag name. + tag_name = xml.tag.split('}', 1)[-1] + + # Extract the element's namespace if it is defined. + if '}' in xml.tag: + tag_xmlns = xml.tag.split('}', 1)[0][1:] + else: + tag_xmlns = '' + + # Output the tag name and derived namespace of the element. + namespace = '' + if tag_xmlns not in ['', xmlns, stanza_ns]: + namespace = ' xmlns="%s"' % tag_xmlns + if stream and tag_xmlns in stream.namespace_map: + mapped_namespace = stream.namespace_map[tag_xmlns] + if mapped_namespace: + tag = "%s:%s" % (mapped_namespace, tag_name) + output.append("<%s" % tag_name) + output.append(namespace) + + # Output escaped attribute values. + for attrib, value in xml.attrib.items(): + if '{' not in attrib: + value = xml_escape(value) + output.append(' %s="%s"' % (attrib, value)) + + if len(xml) or xml.text: + # If there are additional child elements to serialize. + output.append(">") + if xml.text: + output.append(xml_escape(xml.text)) + if len(xml): + for child in xml.getchildren(): + output.append(tostring(child, tag_xmlns, stanza_ns, stream)) + output.append("" % tag_name) + elif xml.text: + # If we only have text content. + output.append(">%s" % (xml_escape(xml.text), tag_name)) + else: + # Empty element. + output.append(" />") + if xml.tail: + # If there is additional text after the element. + output.append(xml_escape(xml.tail)) + return ''.join(output) + + +def xml_escape(text): + """ + Convert special characters in XML to escape sequences. + + Arguments: + text -- The XML text to convert. + """ + text = list(text) + escapes = {'&': '&', + '<': '<', + '>': '>', + "'": ''', + '"': '"'} + for i, c in enumerate(text): + text[i] = escapes.get(c, c) + return ''.join(text) diff --git a/sleekxmpp/xmlstream/tostring/tostring26.py b/sleekxmpp/xmlstream/tostring/tostring26.py index 9711c300..9dba2717 100644 --- a/sleekxmpp/xmlstream/tostring/tostring26.py +++ b/sleekxmpp/xmlstream/tostring/tostring26.py @@ -1,65 +1,100 @@ +""" + 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 unicode_literals import types -class ToString(object): - 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 != u'' and ixmlns != self.namespace: - if self.stream is not None and ixmlns in self.stream.namespace_map: - if self.stream.namespace_map[ixmlns] != u'': - itag = "%s:%s" % (self.stream.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: - if '{' not in attrib: - newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) - if len(xml) or xml.text or xml.tail: - newoutput.append(u">") - 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(u"" % (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 u''.join(newoutput) - def xmlesc(self, text): - if type(text) != types.UnicodeType: - text = list(unicode(text, 'utf-8', 'ignore')) - else: - text = list(text) +def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): + """ + Serialize an XML object to a Unicode string. + + Arguments: + xml -- The XML object to serialize. If the value is None, + then the XML object contained in this stanza + object will be used. + xmlns -- Optional namespace of an element wrapping the XML + object. + stanza_ns -- The namespace of the stanza object that contains + the XML object. + stream -- The XML stream that generated the XML object. + outbuffer -- Optional buffer for storing serializations during + recursive calls. + """ + # Add previous results to the start of the output. + output = [outbuffer] + + # Extract the element's tag name. + tag_name = xml.tag.split('}', 1)[-1] + + # Extract the element's namespace if it is defined. + if '}' in xml.tag: + tag_xmlns = xml.tag.split('}', 1)[0][1:] + else: + tag_xmlns = u'' + + # Output the tag name and derived namespace of the element. + namespace = u'' + if tag_xmlns not in ['', xmlns, stanza_ns]: + namespace = u' xmlns="%s"' % tag_xmlns + if stream and tag_xmlns in stream.namespace_map: + mapped_namespace = stream.namespace_map[tag_xmlns] + if mapped_namespace: + tag = u"%s:%s" % (mapped_namespace, tag_name) + output.append(u"<%s" % tag_name) + output.append(namespace) + + # Output escaped attribute values. + for attrib, value in xml.attrib.items(): + if '{' not in attrib: + value = xml_escape(value) + output.append(u' %s="%s"' % (attrib, value)) + + if len(xml) or xml.text: + # If there are additional child elements to serialize. + output.append(u">") + if xml.text: + output.append(xml_escape(xml.text)) + if len(xml): + for child in xml.getchildren(): + output.append(tostring(child, tag_xmlns, stanza_ns, stream)) + output.append(u"" % tag_name) + if xml.tail: + # If there is additional text after the element. + output.append(xml_escape(xml.tail)) + elif xml.text: + # If we only have text content. + output.append(u">%s" % (xml_escape(xml.text), tag_name)) + else: + # Empty element. + output.append(u" />") + if xml.tail: + # If there is additional text after the element. + output.append(xml_escape(xml.tail)) + return u''.join(output) + + +def xml_escape(text): + """ + Convert special characters in XML to escape sequences. - cc = 0 - matches = (u'&', u'<', u'"', u'>', u"'") - for c in text: - if c in matches: - if c == u'&': - text[cc] = u'&' - elif c == u'<': - text[cc] = u'<' - elif c == u'>': - text[cc] = u'>' - elif c == u"'": - text[cc] = u''' - else: - text[cc] = u'"' - cc += 1 - return ''.join(text) + Arguments: + text -- The XML text to convert. + """ + if type(text) != types.UnicodeType: + text = list(unicode(text, 'utf-8', 'ignore')) + else: + text = list(text) + escapes = {u'&': u'&', + u'<': u'<', + u'>': u'>', + u"'": u''', + u'"': u'"'} + for i, c in enumerate(text): + text[i] = escapes.get(c, c) + return u''.join(text) diff --git a/sleekxmpp/xmlstream/tostring26/__init__.py b/sleekxmpp/xmlstream/tostring26/__init__.py deleted file mode 100644 index 9711c300..00000000 --- a/sleekxmpp/xmlstream/tostring26/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -import types - -class ToString(object): - 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 != u'' and ixmlns != self.namespace: - if self.stream is not None and ixmlns in self.stream.namespace_map: - if self.stream.namespace_map[ixmlns] != u'': - itag = "%s:%s" % (self.stream.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: - if '{' not in attrib: - newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) - if len(xml) or xml.text or xml.tail: - newoutput.append(u">") - 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(u"" % (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 u''.join(newoutput) - - def xmlesc(self, text): - if type(text) != types.UnicodeType: - text = list(unicode(text, 'utf-8', 'ignore')) - else: - text = list(text) - - cc = 0 - matches = (u'&', u'<', u'"', u'>', u"'") - for c in text: - if c in matches: - if c == u'&': - text[cc] = u'&' - elif c == u'<': - text[cc] = u'<' - elif c == u'>': - text[cc] = u'>' - elif c == u"'": - text[cc] = u''' - else: - text[cc] = u'"' - cc += 1 - return ''.join(text) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 94fed64a..ffaa6514 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -23,6 +23,7 @@ import types import copy import xml.sax.saxutils from . import scheduler +from sleekxmpp.xmlstream.tostring import tostring RESPONSE_TIMEOUT = 10 HANDLER_THREADS = 1 @@ -37,7 +38,7 @@ if sys.version_info < (3, 0): #monkey patch broken filesocket object from . import filesocket #socket._fileobject = filesocket.filesocket - + class RestartStream(Exception): pass @@ -82,7 +83,7 @@ class XMLStream(object): self.namespace_map = {} self.run = True - + def setSocket(self, socket): "Set the socket" self.socket = socket @@ -90,10 +91,10 @@ class XMLStream(object): self.filesocket = socket.makefile('rb', 0) # ElementTree.iterparse requires a file. 0 buffer files have to be binary self.state.set('connected', True) - + def setFileSocket(self, filesocket): self.filesocket = filesocket - + def connect(self, host='', port=0, use_ssl=False, use_tls=True): "Link to connectTCP" return self.connectTCP(host, port, use_ssl, use_tls) @@ -125,7 +126,7 @@ class XMLStream(object): except socket.error as serr: logging.error("Could not connect. Socket Error #%s: %s" % (serr.errno, serr.strerror)) time.sleep(1) - + def connectUnix(self, filepath): "Connect to Unix file and create socket" @@ -146,7 +147,7 @@ class XMLStream(object): logging.warning("Tried to enable TLS, but ssl module not found.") return False raise RestartStream() - + def process(self, threaded=True): self.scheduler.process(threaded=True) for t in range(0, HANDLER_THREADS): @@ -160,10 +161,10 @@ class XMLStream(object): self.__thread['process'].start() else: self._process() - + def schedule(self, name, seconds, callback, args=None, kwargs=None, repeat=False): self.scheduler.add(name, seconds, callback, args, kwargs, repeat, qpointer=self.eventqueue) - + def _process(self): "Start processing the socket." firstrun = True @@ -212,7 +213,7 @@ class XMLStream(object): #self.__thread['readXML'].start() #self.__thread['spawnEvents'] = threading.Thread(name='spawnEvents', target=self.__spawnEvents) #self.__thread['spawnEvents'].start() - + def __readXML(self): "Parses the incoming stream, adding to xmlin queue as it goes" #build cElementTree object from expat was we go @@ -245,7 +246,7 @@ class XMLStream(object): if event == b'start': edepth += 1 logging.debug("Ending readXML loop") - + def _sendThread(self): while self.run: data = self.sendqueue.get(True) @@ -260,11 +261,11 @@ class XMLStream(object): if self.state.reconnect: logging.exception("Disconnected. Socket Error.") self.disconnect(reconnect=True) - + def sendRaw(self, data): self.sendqueue.put(data) return True - + def disconnect(self, reconnect=False): self.state.set('reconnect', reconnect) if self.state['disconnecting']: @@ -290,20 +291,20 @@ class XMLStream(object): if self.state['processing']: #raise CloseStream pass - + def reconnect(self): self.state.set('tls',False) self.state.set('ssl',False) time.sleep(1) self.connect() - + def incoming_filter(self, xmlobj): return xmlobj - + def __spawnEvent(self, xmlobj): "watching xmlOut and processes handlers" #convert XML into Stanza - logging.debug("RECV: %s" % cElementTree.tostring(xmlobj)) + logging.debug("RECV: %s" % tostring(xmlobj)) xmlobj = self.incoming_filter(xmlobj) stanza_type = StanzaBase for stanza_class in self.__root_stanza: @@ -323,7 +324,7 @@ class XMLStream(object): stanza.unhandled() #loop through handlers and test match #spawn threads as necessary, call handlers, sending Stanza - + def _eventRunner(self): logging.debug("Loading event runner") while self.run: @@ -354,11 +355,11 @@ class XMLStream(object): elif etype == 'quit': logging.debug("Quitting eventRunner thread") return False - + def registerHandler(self, handler, before=None, after=None): "Add handler with matcher class and parameters." self.__handlers.append(handler) - + def removeHandler(self, name): "Removes the handler." idx = 0 @@ -367,81 +368,27 @@ class XMLStream(object): self.__handlers.pop(idx) return idx += 1 - + def registerStanza(self, stanza_class): "Adds stanza. If root stanzas build stanzas sent in events while non-root stanzas build substanza objects." self.__root_stanza.append(stanza_class) - + def registerStanzaExtension(self, stanza_class, stanza_extension): if stanza_class not in stanza_extensions: stanza_extensions[stanza_class] = [stanza_extension] else: stanza_extensions[stanza_class].append(stanza_extension) - + def removeStanza(self, stanza_class, root=False): "Removes the stanza's registration." if root: del self.__root_stanza[stanza_class] else: del self.__stanza[stanza_class] - + def removeStanzaExtension(self, stanza_class, stanza_extension): stanza_extension[stanza_class].pop(stanza_extension) - def tostring(self, xml, xmlns='', stringbuffer=''): - 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 - 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.tostring(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) - def start_stream_handler(self, xml): """Meant to be overridden""" pass -- cgit v1.2.3 From 3c0dfb56e6dcd864a29523950fcc23e6c3761ff7 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 5 Aug 2010 20:43:38 -0400 Subject: Update tostring docs to clarify what the xmlns and stanza_ns parameters do. --- sleekxmpp/xmlstream/tostring/tostring.py | 4 ++++ sleekxmpp/xmlstream/tostring/tostring26.py | 4 ++++ 2 files changed, 8 insertions(+) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py index 62ff1181..c2696321 100644 --- a/sleekxmpp/xmlstream/tostring/tostring.py +++ b/sleekxmpp/xmlstream/tostring/tostring.py @@ -11,6 +11,10 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): """ Serialize an XML object to a Unicode string. + If namespaces are provided using xmlns or stanza_ns, then elements + that use those namespaces will not include the xmlns attribute in + the output. + Arguments: xml -- The XML object to serialize. If the value is None, then the XML object contained in this stanza diff --git a/sleekxmpp/xmlstream/tostring/tostring26.py b/sleekxmpp/xmlstream/tostring/tostring26.py index 9dba2717..7a376374 100644 --- a/sleekxmpp/xmlstream/tostring/tostring26.py +++ b/sleekxmpp/xmlstream/tostring/tostring26.py @@ -14,6 +14,10 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): """ Serialize an XML object to a Unicode string. + If namespaces are provided using xmlns or stanza_ns, then elements + that use those namespaces will not include the xmlns attribute in + the output. + Arguments: xml -- The XML object to serialize. If the value is None, then the XML object contained in this stanza -- cgit v1.2.3 From 4d1f071f831183568147870302722e492d71d051 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 5 Aug 2010 23:11:22 -0400 Subject: Updated the use of tostring in xmlstream.py Now uses the xmlns and stream parameters to reduce the number of extra xmlns attributes used in the logging output. Added self.default_ns to XMLStream just to be safe. --- sleekxmpp/xmlstream/xmlstream.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index ffaa6514..bf39bb33 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -73,6 +73,7 @@ class XMLStream(object): self.use_ssl = False self.use_tls = False + self.default_ns = '' self.stream_header = "" self.stream_footer = "" @@ -304,7 +305,7 @@ class XMLStream(object): def __spawnEvent(self, xmlobj): "watching xmlOut and processes handlers" #convert XML into Stanza - logging.debug("RECV: %s" % tostring(xmlobj)) + logging.debug("RECV: %s" % tostring(xmlobj, xmlns=self.default_ns, stream=self)) xmlobj = self.incoming_filter(xmlobj) stanza_type = StanzaBase for stanza_class in self.__root_stanza: -- cgit v1.2.3 From c09e9c702c114f76d6cbfd9d7fb1a19cefa5a1e5 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 11 Aug 2010 18:21:12 -0400 Subject: Updated sleekxmpp.exceptions with PEP8 style and docs. --- sleekxmpp/exceptions.py | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py index bbbd69d5..40217ef6 100644 --- a/sleekxmpp/exceptions.py +++ b/sleekxmpp/exceptions.py @@ -3,14 +3,43 @@ Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. -See the file LICENSE for copying permission. + See the file LICENSE for copying permission. """ class XMPPError(Exception): - def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None): - self.condition = condition - self.text = text - self.etype = etype - self.extension = extension - self.extension_ns = extension_ns - self.extension_args = extension_args + + """ + A generic exception that may be raised while processing an XMPP stanza + to indicate that an error response stanza should be sent. + + The exception method for stanza objects extending RootStanza will create + an error stanza and initialize any additional substanzas using the + extension information included in the exception. + + Meant for use in SleekXMPP plugins and applications using SleekXMPP. + """ + + def __init__(self, condition='undefined-condition', text=None, etype=None, + extension=None, extension_ns=None, extension_args=None): + """ + Create a new XMPPError exception. + + Extension information can be included to add additional XML elements + to the generated error stanza. + + Arguments: + condition -- The XMPP defined error condition. + text -- Human readable text describing the error. + etype -- The XMPP error type, such as cancel or modify. + extension -- Tag name of the extension's XML content. + extension_ns -- XML namespace of the extensions' XML content. + extension_args -- Content and attributes for the extension + element. Same as the additional arguments to + the ET.Element constructor. + """ + self.condition = condition + self.text = text + self.etype = etype + self.extension = extension + self.extension_ns = extension_ns + self.extension_args = extension_args -- cgit v1.2.3 From b40a48979636ccb4055294427292b2decf095fea Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 11 Aug 2010 23:32:14 -0400 Subject: Updated roster stanza with docs and PEP8 style. --- sleekxmpp/stanza/roster.py | 146 +++++++++++++++++++++++++++++++-------------- 1 file changed, 100 insertions(+), 46 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py index eda65ec7..292c8956 100644 --- a/sleekxmpp/stanza/roster.py +++ b/sleekxmpp/stanza/roster.py @@ -5,51 +5,105 @@ See the file LICENSE for copying permission. """ -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -import logging + +from sleekxmpp.stanza import Iq +from sleekxmpp.xmlstream import JID +from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin +from sleekxmpp.xmlstream.stanzabase import ET, ElementBase + class Roster(ElementBase): - namespace = 'jabber:iq:roster' - name = 'query' - plugin_attrib = 'roster' - interfaces = set(('items',)) - sub_interfaces = set() - - def setItems(self, items): - self.delItems() - for jid in items: - ijid = str(jid) - item = ET.Element('{jabber:iq:roster}item', {'jid': ijid}) - if 'subscription' in items[jid]: - item.attrib['subscription'] = items[jid]['subscription'] - if 'name' in items[jid]: - name = items[jid]['name'] - if name is not None: - item.attrib['name'] = name - if 'groups' in items[jid]: - for group in items[jid]['groups']: - groupxml = ET.Element('{jabber:iq:roster}group') - groupxml.text = group - item.append(groupxml) - self.xml.append(item) - return self - - def getItems(self): - items = {} - itemsxml = self.xml.findall('{jabber:iq:roster}item') - if itemsxml is not None: - for itemxml in itemsxml: - item = {} - item['name'] = itemxml.get('name', '') - item['subscription'] = itemxml.get('subscription', '') - item['groups'] = [] - groupsxml = itemxml.findall('{jabber:iq:roster}group') - if groupsxml is not None: - for groupxml in groupsxml: - item['groups'].append(groupxml.text) - items[itemxml.get('jid')] = item - return items - - def delItems(self): - for child in self.xml.getchildren(): - self.xml.remove(child) + + """ + Example roster stanzas: + + + + Friends + + + + + Stanza Inteface: + items -- A dictionary of roster entries contained + in the stanza. + + Methods: + getItems -- Return a dictionary of roster entries. + setItems -- Add elements. + delItems -- Remove all elements. + """ + + namespace = 'jabber:iq:roster' + name = 'query' + plugin_attrib = 'roster' + interfaces = set(('items',)) + + def setItems(self, items): + """ + Set the roster entries in the stanza. + + Uses a dictionary using JIDs as keys, where each entry is itself + a dictionary that contains: + name -- An alias or nickname for the JID. + subscription -- The subscription type. Can be one of 'to', + 'from', 'both', 'none', or 'remove'. + groups -- A list of group names to which the JID + has been assigned. + + Arguments: + items -- A dictionary of roster entries. + """ + self.delItems() + for jid in items: + ijid = str(jid) + item = ET.Element('{jabber:iq:roster}item', {'jid': ijid}) + if 'subscription' in items[jid]: + item.attrib['subscription'] = items[jid]['subscription'] + if 'name' in items[jid]: + name = items[jid]['name'] + if name is not None: + item.attrib['name'] = name + if 'groups' in items[jid]: + for group in items[jid]['groups']: + groupxml = ET.Element('{jabber:iq:roster}group') + groupxml.text = group + item.append(groupxml) + self.xml.append(item) + return self + + def getItems(self): + """ + Return a dictionary of roster entries. + + Each item is keyed using its JID, and contains: + name -- An assigned alias or nickname for the JID. + subscription -- The subscription type. Can be one of 'to', + 'from', 'both', 'none', or 'remove'. + groups -- A list of group names to which the JID has + been assigned. + """ + items = {} + itemsxml = self.xml.findall('{jabber:iq:roster}item') + if itemsxml is not None: + for itemxml in itemsxml: + item = {} + item['name'] = itemxml.get('name', '') + item['subscription'] = itemxml.get('subscription', '') + item['groups'] = [] + groupsxml = itemxml.findall('{jabber:iq:roster}group') + if groupsxml is not None: + for groupxml in groupsxml: + item['groups'].append(groupxml.text) + items[itemxml.get('jid')] = item + return items + + def delItems(self): + """ + Remove all elements from the roster stanza. + """ + for child in self.xml.getchildren(): + self.xml.remove(child) + + +registerStanzaPlugin(Iq, Roster) -- cgit v1.2.3 From 4b52007e8c39566bb2083a2e4041de4b294c4948 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 12 Aug 2010 23:24:09 -0400 Subject: Cleaned stanzabase imports. --- sleekxmpp/xmlstream/stanzabase.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 3b5f0bf4..687fc4f7 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -5,13 +5,14 @@ See the file LICENSE for copying permission. """ -from xml.etree import cElementTree as ET + +import copy import logging import sys import weakref -import copy -from . jid import JID +from xml.etree import cElementTree as ET +from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream.tostring import tostring xmltester = type(ET.Element('xml')) -- cgit v1.2.3 From b0fb205c165311b3b66d36b87a2632dcd70d018a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 Aug 2010 10:12:51 -0400 Subject: Updated registerStanzaPlugin and the XML test type. --- sleekxmpp/xmlstream/stanzabase.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 687fc4f7..feadbd47 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -15,16 +15,22 @@ from xml.etree import cElementTree as ET from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream.tostring import tostring -xmltester = type(ET.Element('xml')) + +# Used to check if an argument is an XML object. +XML_TYPE = type(ET.Element('xml')) def registerStanzaPlugin(stanza, plugin): - """ - Associate a stanza object as a plugin for another stanza. - """ - tag = "{%s}%s" % (plugin.namespace, plugin.name) - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map[tag] = plugin + """ + Associate a stanza object as a plugin for another stanza. + + Arguments: + stanza -- The class of the parent stanza. + plugin -- The class of the plugin stanza. + """ + tag = "{%s}%s" % (plugin.namespace, plugin.name) + stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin + stanza.plugin_tag_map[tag] = plugin class ElementBase(object): @@ -84,7 +90,7 @@ class ElementBase(object): def append(self, item): if not isinstance(item, ElementBase): - if type(item) == xmltester: + if type(item) == XML_TYPE: return self.appendxml(item) else: raise TypeError -- cgit v1.2.3 From 747001d33c87dbd644b369f9e41da9ba16927561 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 Aug 2010 10:15:52 -0400 Subject: Adjust first level indenting in ElementBase to prepare for cleanup. --- sleekxmpp/xmlstream/stanzabase.py | 570 +++++++++++++++++++------------------- 1 file changed, 285 insertions(+), 285 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index feadbd47..a6425b80 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -34,291 +34,291 @@ def registerStanzaPlugin(stanza, plugin): 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 = {} - subitem = None - - def __init__(self, xml=None, parent=None): - if parent is None: - self.parent = None - else: - self.parent = weakref.ref(parent) - self.xml = xml - self.plugins = {} - self.iterables = [] - self.idx = 0 - if not self.setup(xml): - 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) - if self.subitem is not None: - for sub in self.subitem: - if child.tag == "{%s}%s" % (sub.namespace, sub.name): - self.iterables.append(sub(xml=child, parent=self)) - break - - - @property - def attrib(self): #backwards compatibility - return self - - def __iter__(self): - self.idx = 0 - return self - - def __bool__(self): - return True - - def __next__(self): - self.idx += 1 - if self.idx > len(self.iterables): - self.idx = 0 - raise StopIteration - return self.iterables[self.idx - 1] - - def next(self): - return self.__next__() - - def __len__(self): - return len(self.iterables) - - def append(self, item): - if not isinstance(item, ElementBase): - if type(item) == XML_TYPE: - return self.appendxml(item) - else: - raise TypeError - self.xml.append(item.xml) - self.iterables.append(item) - return self - - def pop(self, idx=0): - aff = self.iterables.pop(idx) - self.xml.remove(aff.xml) - return aff - - def get(self, key, defaultvalue=None): - value = self[key] - if value is None or value == '': - return defaultvalue - return value - - def keys(self): - out = [] - out += [x for x in self.interfaces] - out += [x for x in self.plugins] - if self.iterables: - out.append('substanzas') - return tuple(out) - - def match(self, matchstring): - if isinstance(matchstring, str): - nodes = matchstring.split('/') - else: - nodes = matchstring - tagargs = nodes[0].split('@') - if tagargs[0] not in (self.plugins, self.plugin_attrib): return False - founditerable = False - for iterable in self.iterables: - if nodes[1:] == []: - break - founditerable = iterable.match(nodes[1:]) - if founditerable: break; - for evals in tagargs[1:]: - x,y = evals.split('=') - if self[x] != y: return False - if not founditerable and len(nodes) > 1: - next = nodes[1].split('@')[0] - if next in self.plugins: - return self.plugins[next].match(nodes[1:]) - else: - return False - return True - - def find(self, xpath): # for backwards compatiblity, expose elementtree interface - return self.xml.find(xpath) - - def findall(self, xpath): - return self.xml.findall(xpath) - - def setup(self, xml=None): - if self.xml is None: - self.xml = xml - if self.xml is None: - for ename in self.name.split('/'): - new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) - if self.xml is None: - self.xml = new - else: - self.xml.append(new) - 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 == 'substanzas': - return self.iterables - elif 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) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: self.initPlugin(attrib) - return self.plugins[attrib] - else: - return '' - - def __setitem__(self, attrib, value): - if attrib 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) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: self.initPlugin(attrib) - self.initPlugin(attrib) - self.plugins[attrib][attrib] = value - 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) - elif attrib in self.plugin_attrib_map: - if attrib in self.plugins: - del self.plugins[attrib] - return self - - def __eq__(self, other): - if not isinstance(other, ElementBase): - return False - values = self.getStanzaValues() - for key in other: - if key not in values or values[key] != other[key]: - return False - return True - - def _setAttr(self, name, value): - if value is None or value == '': - self.__delitem__(name) - else: - self.xml.attrib[name] = value - - def _delAttr(self, name): - if name in self.xml.attrib: - del self.xml.attrib[name] - - def _getAttr(self, name, default=''): - return self.xml.attrib.get(name, default) - - def _getSubText(self, name): - if '}' not in name: - name = "{%s}%s" % (self.namespace, name) - stanza = self.xml.find(name) - if stanza is None or stanza.text is None: - return '' - else: - return stanza.text - - def _setSubText(self, name, attrib={}, text=None): - if '}' not in name: - name = "{%s}%s" % (self.namespace, name) - if text is None or text == '': - return self.__delitem__(name) - stanza = self.xml.find(name) - if stanza is None: - stanza = ET.Element(name) - self.xml.append(stanza) - stanza.text = text - return stanza - - def _delSub(self, name): - if '}' not in name: - name = "{%s}%s" % (self.namespace, name) - for child in self.xml.getchildren(): - if child.tag == name: - self.xml.remove(child) - - def getStanzaValues(self): - out = {} - for interface in self.interfaces: - out[interface] = self[interface] - for pluginkey in self.plugins: - out[pluginkey] = self.plugins[pluginkey].getStanzaValues() - if self.iterables: - iterables = [] - for stanza in self.iterables: - iterables.append(stanza.getStanzaValues()) - iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) - out['substanzas'] = iterables - return out - - def setStanzaValues(self, attrib): - for interface in attrib: - if interface == 'substanzas': - for subdict in attrib['substanzas']: - if '__childtag__' in subdict: - for subclass in self.subitem: - if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name): - sub = subclass(parent=self) - sub.setStanzaValues(subdict) - self.iterables.append(sub) - break - elif interface in self.interfaces: - self[interface] = attrib[interface] - elif interface in self.plugin_attrib_map and interface not in self.plugins: - self.initPlugin(interface) - if interface in self.plugins: - self.plugins[interface].setStanzaValues(attrib[interface]) - return self - - def appendxml(self, xml): - self.xml.append(xml) - return self - - def __copy__(self): - return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) - - def __str__(self): - return tostring(self.xml, xmlns='', stanza_ns=self.namespace) - - def __repr__(self): - return self.__str__() - - #def __del__(self): #prevents garbage collection of reference cycle - # if self.parent is not None: - # self.parent.xml.remove(self.xml) + 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 = {} + subitem = None + + def __init__(self, xml=None, parent=None): + if parent is None: + self.parent = None + else: + self.parent = weakref.ref(parent) + self.xml = xml + self.plugins = {} + self.iterables = [] + self.idx = 0 + if not self.setup(xml): + 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) + if self.subitem is not None: + for sub in self.subitem: + if child.tag == "{%s}%s" % (sub.namespace, sub.name): + self.iterables.append(sub(xml=child, parent=self)) + break + + + @property + def attrib(self): #backwards compatibility + return self + + def __iter__(self): + self.idx = 0 + return self + + def __bool__(self): + return True + + def __next__(self): + self.idx += 1 + if self.idx > len(self.iterables): + self.idx = 0 + raise StopIteration + return self.iterables[self.idx - 1] + + def next(self): + return self.__next__() + + def __len__(self): + return len(self.iterables) + + def append(self, item): + if not isinstance(item, ElementBase): + if type(item) == XML_TYPE: + return self.appendxml(item) + else: + raise TypeError + self.xml.append(item.xml) + self.iterables.append(item) + return self + + def pop(self, idx=0): + aff = self.iterables.pop(idx) + self.xml.remove(aff.xml) + return aff + + def get(self, key, defaultvalue=None): + value = self[key] + if value is None or value == '': + return defaultvalue + return value + + def keys(self): + out = [] + out += [x for x in self.interfaces] + out += [x for x in self.plugins] + if self.iterables: + out.append('substanzas') + return tuple(out) + + def match(self, matchstring): + if isinstance(matchstring, str): + nodes = matchstring.split('/') + else: + nodes = matchstring + tagargs = nodes[0].split('@') + if tagargs[0] not in (self.plugins, self.plugin_attrib): return False + founditerable = False + for iterable in self.iterables: + if nodes[1:] == []: + break + founditerable = iterable.match(nodes[1:]) + if founditerable: break; + for evals in tagargs[1:]: + x,y = evals.split('=') + if self[x] != y: return False + if not founditerable and len(nodes) > 1: + next = nodes[1].split('@')[0] + if next in self.plugins: + return self.plugins[next].match(nodes[1:]) + else: + return False + return True + + def find(self, xpath): # for backwards compatiblity, expose elementtree interface + return self.xml.find(xpath) + + def findall(self, xpath): + return self.xml.findall(xpath) + + def setup(self, xml=None): + if self.xml is None: + self.xml = xml + if self.xml is None: + for ename in self.name.split('/'): + new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) + if self.xml is None: + self.xml = new + else: + self.xml.append(new) + 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 == 'substanzas': + return self.iterables + elif 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) + elif attrib in self.plugin_attrib_map: + if attrib not in self.plugins: self.initPlugin(attrib) + return self.plugins[attrib] + else: + return '' + + def __setitem__(self, attrib, value): + if attrib 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) + elif attrib in self.plugin_attrib_map: + if attrib not in self.plugins: self.initPlugin(attrib) + self.initPlugin(attrib) + self.plugins[attrib][attrib] = value + 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) + elif attrib in self.plugin_attrib_map: + if attrib in self.plugins: + del self.plugins[attrib] + return self + + def __eq__(self, other): + if not isinstance(other, ElementBase): + return False + values = self.getStanzaValues() + for key in other: + if key not in values or values[key] != other[key]: + return False + return True + + def _setAttr(self, name, value): + if value is None or value == '': + self.__delitem__(name) + else: + self.xml.attrib[name] = value + + def _delAttr(self, name): + if name in self.xml.attrib: + del self.xml.attrib[name] + + def _getAttr(self, name, default=''): + return self.xml.attrib.get(name, default) + + def _getSubText(self, name): + if '}' not in name: + name = "{%s}%s" % (self.namespace, name) + stanza = self.xml.find(name) + if stanza is None or stanza.text is None: + return '' + else: + return stanza.text + + def _setSubText(self, name, attrib={}, text=None): + if '}' not in name: + name = "{%s}%s" % (self.namespace, name) + if text is None or text == '': + return self.__delitem__(name) + stanza = self.xml.find(name) + if stanza is None: + stanza = ET.Element(name) + self.xml.append(stanza) + stanza.text = text + return stanza + + def _delSub(self, name): + if '}' not in name: + name = "{%s}%s" % (self.namespace, name) + for child in self.xml.getchildren(): + if child.tag == name: + self.xml.remove(child) + + def getStanzaValues(self): + out = {} + for interface in self.interfaces: + out[interface] = self[interface] + for pluginkey in self.plugins: + out[pluginkey] = self.plugins[pluginkey].getStanzaValues() + if self.iterables: + iterables = [] + for stanza in self.iterables: + iterables.append(stanza.getStanzaValues()) + iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) + out['substanzas'] = iterables + return out + + def setStanzaValues(self, attrib): + for interface in attrib: + if interface == 'substanzas': + for subdict in attrib['substanzas']: + if '__childtag__' in subdict: + for subclass in self.subitem: + if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name): + sub = subclass(parent=self) + sub.setStanzaValues(subdict) + self.iterables.append(sub) + break + elif interface in self.interfaces: + self[interface] = attrib[interface] + elif interface in self.plugin_attrib_map and interface not in self.plugins: + self.initPlugin(interface) + if interface in self.plugins: + self.plugins[interface].setStanzaValues(attrib[interface]) + return self + + def appendxml(self, xml): + self.xml.append(xml) + return self + + def __copy__(self): + return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) + + def __str__(self): + return tostring(self.xml, xmlns='', stanza_ns=self.namespace) + + def __repr__(self): + return self.__str__() + +#def __del__(self): #prevents garbage collection of reference cycle +# if self.parent is not None: +# self.parent.xml.remove(self.xml) class StanzaBase(ElementBase): name = 'stanza' -- cgit v1.2.3 From 415520200efbc6f8a93d1e667f2b7d43b7994667 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 Aug 2010 10:26:33 -0400 Subject: Updated ElementBase.__init__ --- sleekxmpp/xmlstream/stanzabase.py | 48 ++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 18 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index a6425b80..6762792f 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -45,24 +45,36 @@ class ElementBase(object): subitem = None def __init__(self, xml=None, parent=None): - if parent is None: - self.parent = None - else: - self.parent = weakref.ref(parent) - self.xml = xml - self.plugins = {} - self.iterables = [] - self.idx = 0 - if not self.setup(xml): - 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) - if self.subitem is not None: - for sub in self.subitem: - if child.tag == "{%s}%s" % (sub.namespace, sub.name): - self.iterables.append(sub(xml=child, parent=self)) - break - + """ + Create a new stanza object. + + Arguments: + xml -- Initialize the stanza with optional existing XML. + parent -- Optional stanza object that contains this stanza. + """ + self.xml = xml + self.plugins = {} + self.iterables = [] + self.idx = 0 + if parent is None: + self.parent = None + else: + self.parent = weakref.ref(parent) + + if self.setup(xml): + # If we generated our own XML, then everything is ready. + return + + # Initialize values using provided XML + for child in self.xml.getchildren(): + if child.tag in self.plugin_tag_map: + plugin = self.plugin_tag_map[child.tag] + self.plugins[plugin.plugin_attrib] = plugin(child, self) + if self.subitem is not None: + for sub in self.subitem: + if child.tag == "{%s}%s" % (sub.namespace, sub.name): + self.iterables.append(sub(child, self)) + break @property def attrib(self): #backwards compatibility -- cgit v1.2.3 From c20fab0f6c28bcbfbe54db687be056a9b5088ad4 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 Aug 2010 12:24:47 -0400 Subject: Updated ElementBase.setup, and added unit tests. --- sleekxmpp/xmlstream/stanzabase.py | 48 ++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 16 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 6762792f..b11d59ee 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -76,6 +76,38 @@ class ElementBase(object): self.iterables.append(sub(child, self)) break + def setup(self, xml=None): + """ + Initialize the stanza's XML contents. + + Will return True if XML was generated according to the stanza's + definition. + + Arguments: + xml -- Optional XML object to use for the stanza's content + instead of generating XML. + """ + if self.xml is None: + self.xml = xml + + if self.xml is None: + # Generate XML from the stanza definition + for ename in self.name.split('/'): + new = ET.Element("{%s}%s" % (self.namespace, ename)) + if self.xml is None: + self.xml = new + else: + last_xml.append(new) + last_xml = new + if self.parent is not None: + self.parent().xml.append(self.xml) + + # We had to generate XML + return True + else: + # We did not generate XML + return False + @property def attrib(self): #backwards compatibility return self @@ -159,22 +191,6 @@ class ElementBase(object): def findall(self, xpath): return self.xml.findall(xpath) - def setup(self, xml=None): - if self.xml is None: - self.xml = xml - if self.xml is None: - for ename in self.name.split('/'): - new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace}) - if self.xml is None: - self.xml = new - else: - self.xml.append(new) - 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 -- cgit v1.2.3 From b580a3138d46933331cd9829c7dcb3f4135a194f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 Aug 2010 12:51:07 -0400 Subject: Updated ElementBase.enable and ElementBase.initPlugin --- sleekxmpp/xmlstream/stanzabase.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index b11d59ee..aba63331 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -108,6 +108,29 @@ class ElementBase(object): # We did not generate XML return False + def enable(self, attrib): + """ + Enable and initialize a stanza plugin. + + Alias for initPlugin. + + Arguments: + attrib -- The stanza interface for the plugin. + """ + return self.initPlugin(attrib) + + def initPlugin(self, attrib): + """ + Enable and initialize a stanza plugin. + + Arguments: + attrib -- The stanza interface for the plugin. + """ + if attrib not in self.plugins: + plugin_class = self.plugin_attrib_map[attrib] + self.plugins[attrib] = plugin_class(parent=self) + return self + @property def attrib(self): #backwards compatibility return self @@ -191,14 +214,6 @@ class ElementBase(object): def findall(self, xpath): return self.xml.findall(xpath) - 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 == 'substanzas': return self.iterables -- cgit v1.2.3 From fe49b8c377b8491ec5fd5bc74d44834bd384f6a8 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 Aug 2010 20:05:24 -0400 Subject: Updated getStanzaValues and setStanzaValues with docs and unit tests. --- sleekxmpp/xmlstream/stanzabase.py | 85 ++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 33 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index aba63331..3e280d61 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -131,6 +131,58 @@ class ElementBase(object): self.plugins[attrib] = plugin_class(parent=self) return self + def getStanzaValues(self): + """ + Return a dictionary of the stanza's interface values. + + Stanza plugin values are included as nested dictionaries. + """ + values = {} + for interface in self.interfaces: + values[interface] = self[interface] + for plugin, stanza in self.plugins.items(): + values[plugin] = stanza.getStanzaValues() + if self.iterables: + iterables = [] + for stanza in self.iterables: + iterables.append(stanza.getStanzaValues()) + iterables[-1].update({ + '__childtag__': "{%s}%s" % (stanza.namespace, stanza.name) + }) + values['substanzas'] = iterables + return values + + def setStanzaValues(self, values): + """ + Set multiple stanza interface values using a dictionary. + + Stanza plugin values may be set using nested dictionaries. + + Arguments: + values -- A dictionary mapping stanza interface with values. + Plugin interfaces may accept a nested dictionary that + will be used recursively. + """ + for interface, value in values.items(): + if interface == 'substanzas': + for subdict in value: + if '__childtag__' in subdict: + for subclass in self.subitem: + child_tag = "{%s}%s" % (subclass.namespace, + subclass.name) + if subdict['__childtag__'] == child_tag: + sub = subclass(parent=self) + sub.setStanzaValues(subdict) + self.iterables.append(sub) + break + elif interface in self.interfaces: + self[interface] = value + elif interface in self.plugin_attrib_map: + if interface not in self.plugins: + self.initPlugin(interface) + self.plugins[interface].setStanzaValues(value) + return self + @property def attrib(self): #backwards compatibility return self @@ -313,39 +365,6 @@ class ElementBase(object): if child.tag == name: self.xml.remove(child) - def getStanzaValues(self): - out = {} - for interface in self.interfaces: - out[interface] = self[interface] - for pluginkey in self.plugins: - out[pluginkey] = self.plugins[pluginkey].getStanzaValues() - if self.iterables: - iterables = [] - for stanza in self.iterables: - iterables.append(stanza.getStanzaValues()) - iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) - out['substanzas'] = iterables - return out - - def setStanzaValues(self, attrib): - for interface in attrib: - if interface == 'substanzas': - for subdict in attrib['substanzas']: - if '__childtag__' in subdict: - for subclass in self.subitem: - if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name): - sub = subclass(parent=self) - sub.setStanzaValues(subdict) - self.iterables.append(sub) - break - elif interface in self.interfaces: - self[interface] = attrib[interface] - elif interface in self.plugin_attrib_map and interface not in self.plugins: - self.initPlugin(interface) - if interface in self.plugins: - self.plugins[interface].setStanzaValues(attrib[interface]) - return self - def appendxml(self, xml): self.xml.append(xml) return self -- cgit v1.2.3 From 2f6f4fc16d81fd01bc5a4569e0a1bc4ede4a3af8 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 13 Aug 2010 21:33:11 -0400 Subject: Updated ElementBase.__getitem__ with docs and unit tests. --- sleekxmpp/xmlstream/stanzabase.py | 59 ++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 17 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 3e280d61..962cf8ed 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -183,6 +183,48 @@ class ElementBase(object): self.plugins[interface].setStanzaValues(value) return self + def __getitem__(self, attrib): + """ + Return the value of a stanza interface using dictionary-like syntax. + + Example: + >>> msg['body'] + 'Message contents' + + Stanza interfaces are typically mapped directly to the underlying XML + object, but can be overridden by the presence of a getAttrib method + (or getFoo where the interface is named foo, etc). + + The search order for interface value retrieval for an interface + named 'foo' is: + 1. The list of substanzas. + 2. The result of calling getFoo. + 3. The contents of the foo subelement, if foo is a sub interface. + 4. The value of the foo attribute of the XML object. + 5. The plugin named 'foo' + 6. An empty string. + + Arguments: + attrib -- The name of the requested stanza interface. + """ + if attrib == 'substanzas': + return self.iterables + elif attrib in self.interfaces: + get_method = "get%s" % attrib.title() + if hasattr(self, get_method): + return getattr(self, get_method)() + else: + if attrib in self.sub_interfaces: + return self._getSubText(attrib) + else: + return self._getAttr(attrib) + elif attrib in self.plugin_attrib_map: + if attrib not in self.plugins: + self.initPlugin(attrib) + return self.plugins[attrib] + else: + return '' + @property def attrib(self): #backwards compatibility return self @@ -266,23 +308,6 @@ class ElementBase(object): def findall(self, xpath): return self.xml.findall(xpath) - def __getitem__(self, attrib): - if attrib == 'substanzas': - return self.iterables - elif 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) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: self.initPlugin(attrib) - return self.plugins[attrib] - else: - return '' - def __setitem__(self, attrib, value): if attrib in self.interfaces: if value is not None: -- cgit v1.2.3 From e4240dd593207a5912de996c42451b3946f113b2 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 19 Aug 2010 14:21:58 -0400 Subject: Updated ElementBase.__setitem__ and added unit tests. --- sleekxmpp/xmlstream/stanzabase.py | 62 +++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 18 deletions(-) (limited to 'sleekxmpp') diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 962cf8ed..83a8ddfc 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -225,6 +225,50 @@ class ElementBase(object): else: return '' + def __setitem__(self, attrib, value): + """ + Set the value of a stanza interface using dictionary-like syntax. + + Example: + >>> msg['body'] = "Hi!" + >>> msg['body'] + 'Hi!' + + Stanza interfaces are typically mapped directly to the underlying XML + object, but can be overridden by the presence of a setAttrib method + (or setFoo where the interface is named foo, etc). + + The effect of interface value assignment for an interface + named 'foo' will be one of: + 1. Delete the interface's contents if the value is None. + 2. Call setFoo, if it exists. + 3. Set the text of a foo element, if foo is in sub_interfaces. + 4. Set the value of a top level XML attribute name foo. + 5. Attempt to pass value to a plugin named foo using the plugin's + foo interface. + 6. Do nothing. + + Arguments: + attrib -- The name of the stanza interface to modify. + value -- The new value of the stanza interface. + """ + if attrib 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) + elif attrib in self.plugin_attrib_map: + if attrib not in self.plugins: + self.initPlugin(attrib) + self.plugins[attrib][attrib] = value + return self + @property def attrib(self): #backwards compatibility return self @@ -308,24 +352,6 @@ class ElementBase(object): def findall(self, xpath): return self.xml.findall(xpath) - def __setitem__(self, attrib, value): - if attrib 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) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: self.initPlugin(attrib) - self.initPlugin(attrib) - self.plugins[attrib][attrib] = value - return self - def __delitem__(self, attrib): if attrib.lower() in self.interfaces: if hasattr(self, "del%s" % attrib.title()): -- cgit v1.2.3