From 8aa4396e4490a964e3e1b1a5e6f555e97c16fd3d Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 31 May 2011 12:48:43 -0700 Subject: [PATCH 01/25] Begin experimental use of exceptions. Provides IqTimeout and IqError which are raised when an Iq response does not arrive in time, or it arrives with type='error'. --- sleekxmpp/clientxmpp.py | 11 +++-------- sleekxmpp/exceptions.py | 13 +++++++++++++ sleekxmpp/stanza/iq.py | 8 +++++++- sleekxmpp/test/sleektest.py | 1 + tests/test_stream_handlers.py | 5 ++++- tests/test_stream_roster.py | 15 ++++----------- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index fb5b208..7771054 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -231,8 +231,8 @@ class ClientXMPP(BaseXMPP): 'subscription': subscription, 'groups': groups}} response = iq.send(block, timeout, callback) - if response in [False, None] or not isinstance(response, Iq): - return response + if response is None: + return None return response['type'] == 'result' def del_roster_item(self, jid): @@ -265,12 +265,7 @@ class ClientXMPP(BaseXMPP): iq.enable('roster') response = iq.send(block, timeout, callback) - if response == False: - self.event('roster_timeout') - - if response in [False, None] or not isinstance(response, Iq): - return response - else: + if callback is None: return self._handle_roster(response, request=True) def _handle_stream_features(self, features): diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py index 4727f0c..72c0860 100644 --- a/sleekxmpp/exceptions.py +++ b/sleekxmpp/exceptions.py @@ -52,3 +52,16 @@ class XMPPError(Exception): self.extension = extension self.extension_ns = extension_ns self.extension_args = extension_args + + +class IqTimeout(Exception): + + """ + An exception which indicates that an IQ request response has not been + received within the alloted time window. + """ + +class IqError(Exception): + + def __init__(self, iq): + self.iq = iq diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index 4a12a87..71419bc 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -11,6 +11,7 @@ from sleekxmpp.stanza.rootstanza import RootStanza from sleekxmpp.xmlstream import StanzaBase, ET from sleekxmpp.xmlstream.handler import Waiter, Callback from sleekxmpp.xmlstream.matcher import MatcherId +from sleekxmpp.exceptions import IqTimeout, IqError class Iq(RootStanza): @@ -197,7 +198,12 @@ class Iq(RootStanza): waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) self.stream.register_handler(waitfor) StanzaBase.send(self, now=now) - return waitfor.wait(timeout) + result = waitfor.wait(timeout) + if not result: + raise IqTimeout() + if result['type'] == 'error': + raise IqError(result) + return result else: return StanzaBase.send(self, now=now) diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index 7802a9b..b607a94 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -16,6 +16,7 @@ import sleekxmpp from sleekxmpp import ClientXMPP, ComponentXMPP from sleekxmpp.stanza import Message, Iq, Presence from sleekxmpp.test import TestSocket, TestLiveSocket +from sleekxmpp.exceptions import XMPPError, IqTimeout, IqError from sleekxmpp.xmlstream import ET, register_stanza_plugin from sleekxmpp.xmlstream import ElementBase, StanzaBase from sleekxmpp.xmlstream.tostring import tostring diff --git a/tests/test_stream_handlers.py b/tests/test_stream_handlers.py index dae4456..1b831e2 100644 --- a/tests/test_stream_handlers.py +++ b/tests/test_stream_handlers.py @@ -90,7 +90,10 @@ class TestHandlers(SleekTest): iq['id'] = 'test2' iq['type'] = 'set' iq['query'] = 'test2' - reply = iq.send(block=True, timeout=0) + try: + reply = iq.send(block=True, timeout=0) + except IqTimeout: + pass self.xmpp.add_event_handler('message', waiter_handler, threaded=True) diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py index e1aa176..9516374 100644 --- a/tests/test_stream_roster.py +++ b/tests/test_stream_roster.py @@ -111,19 +111,12 @@ class TestStreamRoster(SleekTest): def testRosterTimeout(self): """Test handling a timed out roster request.""" self.stream_start() - events = [] - def roster_timeout(event): - events.append('roster_timeout') + def do_test(): + self.xmpp.get_roster(timeout=0) + time.sleep(.1) - self.xmpp.add_event_handler('roster_timeout', roster_timeout) - self.xmpp.get_roster(timeout=0) - - # Give the event queue time to process. - time.sleep(.1) - - self.failUnless(events == ['roster_timeout'], - "Roster timeout event not triggered: %s." % events) + self.assertRaises(IqTimeout, do_test) def testRosterCallback(self): """Test handling a roster request callback.""" From 20d053807da1f59a2f7507fb7bb3845d31e27445 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 1 Jun 2011 15:28:33 -0700 Subject: [PATCH 02/25] IqTimeout now references the original sent stanza. A little extra bit of docs for IqError. --- sleekxmpp/exceptions.py | 8 ++++++++ sleekxmpp/stanza/iq.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py index 72c0860..8329a3c 100644 --- a/sleekxmpp/exceptions.py +++ b/sleekxmpp/exceptions.py @@ -61,7 +61,15 @@ class IqTimeout(Exception): received within the alloted time window. """ + def __init__(self, iq): + self.iq = iq + class IqError(Exception): + """ + An exception raised when an Iq stanza of type 'error' is received + after making a blocking send call. + """ + def __init__(self, iq): self.iq = iq diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py index 71419bc..f05dad1 100644 --- a/sleekxmpp/stanza/iq.py +++ b/sleekxmpp/stanza/iq.py @@ -200,7 +200,7 @@ class Iq(RootStanza): StanzaBase.send(self, now=now) result = waitfor.wait(timeout) if not result: - raise IqTimeout() + raise IqTimeout(self) if result['type'] == 'error': raise IqError(result) return result From a0767f6af61bc9c54b2526cd51aef7af4e383e90 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 00:07:30 -0700 Subject: [PATCH 03/25] Sadly, dateutil is not actually part of the standard lib. Thus, using the XEP-0082 and XEP-0202 introduces a dependency on the dateutil package (installable using pip install python-dateutil). Maybe we'll be able to rework how these plugins work to avoid needing dateutil, but for now this will have to do. --- README | 3 +++ setup.py | 6 +++++- sleekxmpp/plugins/__init__.py | 20 +++++++++++++++++--- sleekxmpp/plugins/xep_0082.py | 11 +++++++++-- sleekxmpp/plugins/xep_0202/__init__.py | 23 ++++++++++++++++++++--- sleekxmpp/plugins/xep_0202/stanza.py | 8 +++++++- 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/README b/README index 8a85365..aab5ae8 100644 --- a/README +++ b/README @@ -9,6 +9,9 @@ We try to keep requirements to a minimum, but we suggest that you install http:/ If you do not install this library, you may need to specify the server/port for services that use SRV records (like GTalk). "sudo pip install dnspython" on a *nix system with pip installed. +Two time related plugins (XEP-0082 and XEP-0202) also require the dateutil package, but that is not a hard requirement if you +don't need those plugins. + SleekXMPP has several design goals/philosophies: - Low number of dependencies. - Every XEP as a plugin. diff --git a/setup.py b/setup.py index 572dd1f..48d8869 100644 --- a/setup.py +++ b/setup.py @@ -54,11 +54,15 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0059', 'sleekxmpp/plugins/xep_0060', 'sleekxmpp/plugins/xep_0060/stanza', + 'sleekxmpp/plugins/xep_0066', 'sleekxmpp/plugins/xep_0085', 'sleekxmpp/plugins/xep_0086', 'sleekxmpp/plugins/xep_0092', 'sleekxmpp/plugins/xep_0128', - 'sleekxmpp/plugins/xep_0199', + 'sleekxmpp/plugins/xep_0202', + 'sleekxmpp/plugins/xep_0203', + 'sleekxmpp/plugins/xep_0224', + 'sleekxmpp/plugins/xep_0249', 'sleekxmpp/features', 'sleekxmpp/features/feature_mechanisms', 'sleekxmpp/features/feature_mechanisms/stanza', diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index b48a4c0..21a05fe 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -6,6 +6,20 @@ See the file LICENSE for copying permission. """ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', - 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', - 'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', - 'xep_0202', 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] + 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0085', + 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', 'xep_0203', + 'xep_0224', 'xep_0249', 'gmail_notify'] + +# Some plugins may require external dependencies beyond what the +# core SleekXMPP installation requires. Thus they should only by +# imported automatically if those dependecies are met. + +HAVE_DATEUTIL = True +try: + import dateutil +except: + HAVE_DATEUTIL = False + +if HAVE_DATEUTIL: + __all__.append('xep_0082') + __all__.append('xep_0202') diff --git a/sleekxmpp/plugins/xep_0082.py b/sleekxmpp/plugins/xep_0082.py index 785ba36..e78a50a 100644 --- a/sleekxmpp/plugins/xep_0082.py +++ b/sleekxmpp/plugins/xep_0082.py @@ -6,11 +6,18 @@ See the file LICENSE for copying permission. """ +import logging import datetime as dt -from dateutil import parser -from dateutil.tz import tzoffset, tzutc + from sleekxmpp.plugins.base import base_plugin +try: + from dateutil import parser + from dateutil.tz import tzoffset, tzutc +except e: + log = logging.getLogger(__name__) + log.warning("XEP-0082 plugin requires dateutil") + # ===================================================================== # To make it easier for stanzas without direct access to plugin objects diff --git a/sleekxmpp/plugins/xep_0202/__init__.py b/sleekxmpp/plugins/xep_0202/__init__.py index 82338d3..3fb4744 100644 --- a/sleekxmpp/plugins/xep_0202/__init__.py +++ b/sleekxmpp/plugins/xep_0202/__init__.py @@ -6,6 +6,23 @@ See the file LICENSE for copying permission. """ -from sleekxmpp.plugins.xep_0202 import stanza -from sleekxmpp.plugins.xep_0202.stanza import EntityTime -from sleekxmpp.plugins.xep_0202.time import xep_0202 +import logging +import sleekxmpp + + +log = logging.getLogger(__name__) + + +HAVE_DATEUTIL = True +try: + import dateutil +except: + HAVE_DATEUTIL = False + + +if HAVE_DATEUTIL: + from sleekxmpp.plugins.xep_0202 import stanza + from sleekxmpp.plugins.xep_0202.stanza import EntityTime + from sleekxmpp.plugins.xep_0202.time import xep_0202 +else: + log.warning("XEP-0202 requires the dateutil package") diff --git a/sleekxmpp/plugins/xep_0202/stanza.py b/sleekxmpp/plugins/xep_0202/stanza.py index bb27692..72ab403 100644 --- a/sleekxmpp/plugins/xep_0202/stanza.py +++ b/sleekxmpp/plugins/xep_0202/stanza.py @@ -6,12 +6,18 @@ See the file LICENSE for copying permission. """ +import logging import datetime as dt -from dateutil.tz import tzoffset, tzutc from sleekxmpp.xmlstream import ElementBase from sleekxmpp.plugins import xep_0082 +try: + from dateutil.tz import tzutc +except: + log = logging.getLogger(__name__) + log.warning("XEP-0202 plugin requies dateutil package") + class EntityTime(ElementBase): From a8f57d012f28de32fddf7f22de7f3adb4190bc6e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 00:41:02 -0700 Subject: [PATCH 04/25] Let's make sure licenses can be seen and checked. --- LICENSE | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/LICENSE b/LICENSE index fb9f977..b981b0a 100644 --- a/LICENSE +++ b/LICENSE @@ -17,3 +17,93 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + +Licences of Bundled Third Pary Code +----------------------------------- + +dateutil - Extensions to the standard python 2.3+ datetime module. + +Copyright (c) 2003-2011 - Gustavo Niemeyer + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + +OrderedDict - A port of the Python 2.7+ OrderedDict to Python 2.6 + +Copyright (c) 2009 Raymond Hettinger + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + + + +SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY + +This software is subject to "The MIT License" + +Copyright 2007-2010 David Alan Cridland + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. From 4d8933abdf4a190493f2762d423f469f6d8b30a9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 20:20:22 -0700 Subject: [PATCH 05/25] Actually, we can work around needing dateutil. If dateutil is present, we'll use that. If not, we'll use some regexes from the fixed_datetime module. --- LICENSE | 31 +++ README | 3 - sleekxmpp/plugins/__init__.py | 20 +- sleekxmpp/plugins/xep_0082.py | 13 +- sleekxmpp/plugins/xep_0202/__init__.py | 22 +- sleekxmpp/plugins/xep_0202/stanza.py | 7 +- sleekxmpp/thirdparty/__init__.py | 1 + sleekxmpp/thirdparty/mini_dateutil.py | 267 +++++++++++++++++++++++++ 8 files changed, 310 insertions(+), 54 deletions(-) create mode 100644 sleekxmpp/thirdparty/mini_dateutil.py diff --git a/LICENSE b/LICENSE index b981b0a..df302d0 100644 --- a/LICENSE +++ b/LICENSE @@ -25,6 +25,7 @@ Licences of Bundled Third Pary Code ----------------------------------- dateutil - Extensions to the standard python 2.3+ datetime module. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Copyright (c) 2003-2011 - Gustavo Niemeyer @@ -55,9 +56,38 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +fixed_datetime +~~~~~~~~~~~~~~ + +Copyright (c) 2008, Red Innovation Ltd., Finland +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Red Innovation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + OrderedDict - A port of the Python 2.7+ OrderedDict to Python 2.6 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Copyright (c) 2009 Raymond Hettinger @@ -85,6 +115,7 @@ subject to the following conditions: SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This software is subject to "The MIT License" diff --git a/README b/README index aab5ae8..8a85365 100644 --- a/README +++ b/README @@ -9,9 +9,6 @@ We try to keep requirements to a minimum, but we suggest that you install http:/ If you do not install this library, you may need to specify the server/port for services that use SRV records (like GTalk). "sudo pip install dnspython" on a *nix system with pip installed. -Two time related plugins (XEP-0082 and XEP-0202) also require the dateutil package, but that is not a hard requirement if you -don't need those plugins. - SleekXMPP has several design goals/philosophies: - Low number of dependencies. - Every XEP as a plugin. diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 21a05fe..3f90f05 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -6,20 +6,6 @@ See the file LICENSE for copying permission. """ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', - 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0085', - 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', 'xep_0203', - 'xep_0224', 'xep_0249', 'gmail_notify'] - -# Some plugins may require external dependencies beyond what the -# core SleekXMPP installation requires. Thus they should only by -# imported automatically if those dependecies are met. - -HAVE_DATEUTIL = True -try: - import dateutil -except: - HAVE_DATEUTIL = False - -if HAVE_DATEUTIL: - __all__.append('xep_0082') - __all__.append('xep_0202') + 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', + 'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', + 'xep_0082', 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] diff --git a/sleekxmpp/plugins/xep_0082.py b/sleekxmpp/plugins/xep_0082.py index e78a50a..d3c4cc5 100644 --- a/sleekxmpp/plugins/xep_0082.py +++ b/sleekxmpp/plugins/xep_0082.py @@ -10,13 +10,7 @@ import logging import datetime as dt from sleekxmpp.plugins.base import base_plugin - -try: - from dateutil import parser - from dateutil.tz import tzoffset, tzutc -except e: - log = logging.getLogger(__name__) - log.warning("XEP-0082 plugin requires dateutil") +from sleekxmpp.thirdparty import tzutc, tzoffset, parse_iso # ===================================================================== @@ -31,7 +25,8 @@ def parse(time_str): Arguments: time_str -- A formatted timestamp string. """ - return parser.parse(time_str) + return parse_iso(time_str) + def format_date(time_obj): """ @@ -52,7 +47,7 @@ def format_time(time_obj): Return a formatted string version of a time object. format: - hh:mm:ss[.sss][TZD + hh:mm:ss[.sss][TZD] arguments: time_obj -- A time or datetime object. diff --git a/sleekxmpp/plugins/xep_0202/__init__.py b/sleekxmpp/plugins/xep_0202/__init__.py index 3fb4744..a34b237 100644 --- a/sleekxmpp/plugins/xep_0202/__init__.py +++ b/sleekxmpp/plugins/xep_0202/__init__.py @@ -6,23 +6,7 @@ See the file LICENSE for copying permission. """ -import logging -import sleekxmpp - -log = logging.getLogger(__name__) - - -HAVE_DATEUTIL = True -try: - import dateutil -except: - HAVE_DATEUTIL = False - - -if HAVE_DATEUTIL: - from sleekxmpp.plugins.xep_0202 import stanza - from sleekxmpp.plugins.xep_0202.stanza import EntityTime - from sleekxmpp.plugins.xep_0202.time import xep_0202 -else: - log.warning("XEP-0202 requires the dateutil package") +from sleekxmpp.plugins.xep_0202 import stanza +from sleekxmpp.plugins.xep_0202.stanza import EntityTime +from sleekxmpp.plugins.xep_0202.time import xep_0202 diff --git a/sleekxmpp/plugins/xep_0202/stanza.py b/sleekxmpp/plugins/xep_0202/stanza.py index 72ab403..b6ccc96 100644 --- a/sleekxmpp/plugins/xep_0202/stanza.py +++ b/sleekxmpp/plugins/xep_0202/stanza.py @@ -11,12 +11,7 @@ import datetime as dt from sleekxmpp.xmlstream import ElementBase from sleekxmpp.plugins import xep_0082 - -try: - from dateutil.tz import tzutc -except: - log = logging.getLogger(__name__) - log.warning("XEP-0202 plugin requies dateutil package") +from sleekxmpp.thirdparty import tzutc, tzoffset class EntityTime(ElementBase): diff --git a/sleekxmpp/thirdparty/__init__.py b/sleekxmpp/thirdparty/__init__.py index 3eb6ad7..1c7bf65 100644 --- a/sleekxmpp/thirdparty/__init__.py +++ b/sleekxmpp/thirdparty/__init__.py @@ -4,3 +4,4 @@ except: from sleekxmpp.thirdparty.ordereddict import OrderedDict from sleekxmpp.thirdparty import suelta +from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py new file mode 100644 index 0000000..bfb8630 --- /dev/null +++ b/sleekxmpp/thirdparty/mini_dateutil.py @@ -0,0 +1,267 @@ +# This module is a very stripped down version of the dateutil +# package for when dateutil has not been installed. As a replacement +# for dateutil.parser.parse, the parsing methods from +# http://blog.mfabrik.com/2008/06/30/relativity-of-time-shortcomings-in-python-datetime-and-workaround/ + +#As such, the following copyrights and licenses applies: + + +# dateutil - Extensions to the standard python 2.3+ datetime module. +# +# Copyright (c) 2003-2011 - Gustavo Niemeyer +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +# fixed_dateime +# +# Copyright (c) 2008, Red Innovation Ltd., Finland +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Red Innovation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +import re +import datetime + + +ZERO = datetime.timedelta(0) + + +try: + from dateutil.parser import parse as parse_iso + from dateutil.tz import tzoffset, tzutc +except: + # As a stopgap, define the two timezones here based + # on the dateutil code. + + class tzutc(datetime.tzinfo): + + def utcoffset(self, dt): + return ZERO + + def dst(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def __eq__(self, other): + return (isinstance(other, tzutc) or + (isinstance(other, tzoffset) and other._offset == ZERO)) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + class tzoffset(datetime.tzinfo): + + def __init__(self, name, offset): + self._name = name + self._offset = datetime.timedelta(seconds=offset) + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return ZERO + + def tzname(self, dt): + return self._name + + def __eq__(self, other): + return (isinstance(other, tzoffset) and + self._offset == other._offset) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self._name), + self._offset.days*86400+self._offset.seconds) + + __reduce__ = object.__reduce__ + + + _fixed_offset_tzs = { } + UTC = tzutc() + + def _get_fixed_offset_tz(offsetmins): + """For internal use only: Returns a tzinfo with + the given fixed offset. This creates only one instance + for each offset; the zones are kept in a dictionary""" + + if offsetmins == 0: + return UTC + + if not _fixed_offset_tzs.has_key(offsetmins): + if offsetmins < 0: + sign = '-' + absoff = -offsetmins + else: + sign = '+' + absoff = offsetmins + + name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60) + inst = tzoffset(offsetmins, name) + _fixed_offset_tzs[offsetmins] = inst + + return _fixed_offset_tzs[offsetmins] + + + _iso8601_parser = re.compile(""" + ^ + (?P [0-9]{4})?(?P-?)? + (?P[0-9]{2})?(?P=ymdsep)? + (?P [0-9]{2})? + + (?: # time part... optional... at least hour must be specified + (?:T|\s+)? + (?P[0-9]{2}) + (?: + # minutes, separated with :, or none, from hours + (?P[:]?) + (?P[0-9]{2}) + (?: + # same for seconds, separated with :, or none, from hours + (?P=hmssep) + (?P[0-9]{2}) + )? + )? + + # fractions + (?: [,.] (?P[0-9]{1,10}))? + + # timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there. + ( + (?PZ) + | + (?P[+-][0-9]{2}) + (?: :? # optional separator + (?P[0-9]{2}) + )? + )? + )? + $ + """, re.X) # """ + + def parse_iso(timestamp): + """Internal function for parsing a timestamp in + ISO 8601 format""" + + timestamp = timestamp.strip() + + m = _iso8601_parser.match(timestamp) + if not m: + raise ValueError("Not a proper ISO 8601 timestamp!: %s" % timestamp) + + vals = m.groupdict() + def_vals = {'year': 1970, 'month': 1, 'day': 1} + for key in vals: + if vals[key] is None: + vals[key] = def_vals.get(key, 0) + elif key not in ['ymdsep', 'hmssep', 'tzempty']: + vals[key] = int(vals[key]) + + year = vals['year'] + month = vals['month'] + day = vals['day'] + + h, min, s, us = None, None, None, 0 + frac = 0 + if m.group('tzempty') == None and m.group('tzh') == None: + raise ValueError("Not a proper ISO 8601 timestamp: " + + "missing timezone (Z or +hh[:mm])!") + + if m.group('frac'): + frac = m.group('frac') + power = len(frac) + frac = long(frac) / 10.0 ** power + + if m.group('hour'): + h = vals['hour'] + + if m.group('minute'): + min = vals['minute'] + + if m.group('second'): + s = vals['second'] + + if frac != None: + # ok, fractions of hour? + if min == None: + frac, min = _math.modf(frac * 60.0) + min = int(min) + + # fractions of second? + if s == None: + frac, s = _math.modf(frac * 60.0) + s = int(s) + + # and extract microseconds... + us = int(frac * 1000000) + + if m.group('tzempty') == 'Z': + offsetmins = 0 + else: + # timezone: hour diff with sign + offsetmins = vals['tzh'] * 60 + tzm = m.group('tzm') + + # add optional minutes + if tzm != None: + tzm = long(tzm) + offsetmins += tzm if offsetmins > 0 else -tzm + + tz = _get_fixed_offset_tz(offsetmins) + return datetime.datetime(year, month, day, h, min, s, us, tz) From caec2976d7788dba75a82dfb9079b57c96ef7b47 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 20:34:23 -0700 Subject: [PATCH 06/25] Fix Python3 bug. Use int() instead of long() --- sleekxmpp/thirdparty/mini_dateutil.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py index bfb8630..5b3af9a 100644 --- a/sleekxmpp/thirdparty/mini_dateutil.py +++ b/sleekxmpp/thirdparty/mini_dateutil.py @@ -226,7 +226,7 @@ except: if m.group('frac'): frac = m.group('frac') power = len(frac) - frac = long(frac) / 10.0 ** power + frac = int(frac) / 10.0 ** power if m.group('hour'): h = vals['hour'] @@ -260,7 +260,7 @@ except: # add optional minutes if tzm != None: - tzm = long(tzm) + tzm = int(tzm) offsetmins += tzm if offsetmins > 0 else -tzm tz = _get_fixed_offset_tz(offsetmins) From 6c8a135612e1bc9a8da9347cdf1ae6c3858799c3 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 21:49:15 -0700 Subject: [PATCH 07/25] Fix imports using __all__. --- sleekxmpp/features/__init__.py | 4 +--- sleekxmpp/plugins/__init__.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sleekxmpp/features/__init__.py b/sleekxmpp/features/__init__.py index 5c86cfe..5bfe173 100644 --- a/sleekxmpp/features/__init__.py +++ b/sleekxmpp/features/__init__.py @@ -6,6 +6,4 @@ See the file LICENSE for copying permission. """ -__all__ = ['feature_starttls', 'feature_mechanisms', - 'feature_bind', 'feature_session', - 'sasl_plain', 'sasl_anonymous'] +__all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind'] diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 3f90f05..7fa031e 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -8,4 +8,4 @@ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', 'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', - 'xep_0082', 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] + 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] From 7f90de887a7e3ca53103babf5a9acf15a69ab959 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 4 Aug 2011 21:49:32 -0700 Subject: [PATCH 08/25] added block as process option and updated documentation. added typical use example to ClientXMPP. --- sleekxmpp/basexmpp.py | 24 +++++++++++++++++++++--- sleekxmpp/clientxmpp.py | 7 +++++-- sleekxmpp/xmlstream/xmlstream.py | 20 +++++++++++++++++--- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index b188e76..07726a4 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -140,10 +140,28 @@ class BaseXMPP(XMLStream): def process(self, *args, **kwargs): """ - Ensure that plugin inter-dependencies are handled before starting - event processing. - Overrides XMLStream.process. + + Initialize the XML streams and begin processing events. + + The number of threads used for processing stream events is determined + by HANDLER_THREADS. + + Arguments: + block -- If block=False then event dispatcher will run + in a separate thread, allowing for the stream to be + used in the background for another application. + Otherwise, process(block=True) blocks the current thread. + Defaults to False. + + **threaded is deprecated and included for API compatibility** + threaded -- If threaded=True then event dispatcher will run + in a separate thread, allowing for the stream to be + used in the background for another application. + Defaults to True. + + Event handlers and the send queue will be threaded + regardless of these parameters. """ for name in self.plugin: if not self.plugin[name].post_inited: diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 95ae108..ad12772 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -40,9 +40,12 @@ log = logging.getLogger(__name__) class ClientXMPP(BaseXMPP): """ - SleekXMPP's client class. + SleekXMPP's client class. ( Use only for good, not for evil.) - Use only for good, not for evil. + Typical Use: + xmpp = ClientXMPP('user@server.tld/resource', 'password') + xmpp.process(block=False) // when block is True, it blocks the current + // thread. False by default. Attributes: diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 15bbe65..41c53a3 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -831,7 +831,7 @@ class XMLStream(object): self.send_queue.put(data) return True - def process(self, threaded=True): + def process(self, **kwargs): """ Initialize the XML streams and begin processing events. @@ -839,14 +839,28 @@ class XMLStream(object): by HANDLER_THREADS. Arguments: + block -- If block=False then event dispatcher will run + in a separate thread, allowing for the stream to be + used in the background for another application. + Otherwise, process(block=True) blocks the current thread. + Defaults to False. + + **threaded is deprecated and included for API compatibility** threaded -- If threaded=True then event dispatcher will run in a separate thread, allowing for the stream to be used in the background for another application. Defaults to True. - Event handlers and the send queue will be threaded - regardless of this parameter's value. + Event handlers and the send queue will be threaded + regardless of these parameters. """ + if kwargs.has_key('threaded') and kwargs.has_key('block'): + raise ValueError("process() called with both block and threaded arguments") + elif kwargs.has_key('block'): + threaded = not(kwargs.get('block', False)) + else: + threaded = kwargs.get('threaded', True) + self.scheduler.process(threaded=True) def start_thread(name, target): From 940e3eba35deab4c0d965dbbac4a57a534bd3681 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 21:56:35 -0700 Subject: [PATCH 09/25] Make sure setup.py has all of the plugins. XEP-0199 got dropped during a cut/paste for 203 and 204. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index be9a0d3..3ccac3a 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0086', 'sleekxmpp/plugins/xep_0092', 'sleekxmpp/plugins/xep_0128', + 'sleekxmpp/plugins/xep_0199', 'sleekxmpp/plugins/xep_0202', 'sleekxmpp/plugins/xep_0203', 'sleekxmpp/plugins/xep_0224', From 93a4a3f8a0f64eed846895365fa3da059bbf5ea1 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 22:34:34 -0700 Subject: [PATCH 10/25] Fix Python3 issue with dict.has_key() --- sleekxmpp/plugins/xep_0009/remote.py | 2 +- sleekxmpp/thirdparty/mini_dateutil.py | 28 +++++++++++++-------------- sleekxmpp/xmlstream/xmlstream.py | 9 +++++---- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/sleekxmpp/plugins/xep_0009/remote.py b/sleekxmpp/plugins/xep_0009/remote.py index 8c53411..b5d10b8 100644 --- a/sleekxmpp/plugins/xep_0009/remote.py +++ b/sleekxmpp/plugins/xep_0009/remote.py @@ -463,7 +463,7 @@ class RemoteSession(object): key = "%s.%s" % (endpoint, name) log.debug("Registering call handler for %s (%s)." % (key, method)) with self._lock: - if self._entries.has_key(key): + if key in self._entries: raise KeyError("A handler for %s has already been regisered!" % endpoint) self._entries[key] = JabberRPCEntry(endpoint, method) return key diff --git a/sleekxmpp/thirdparty/mini_dateutil.py b/sleekxmpp/thirdparty/mini_dateutil.py index 5b3af9a..6af5ffd 100644 --- a/sleekxmpp/thirdparty/mini_dateutil.py +++ b/sleekxmpp/thirdparty/mini_dateutil.py @@ -144,17 +144,17 @@ except: if offsetmins == 0: return UTC - if not _fixed_offset_tzs.has_key(offsetmins): - if offsetmins < 0: - sign = '-' - absoff = -offsetmins - else: - sign = '+' - absoff = offsetmins + if not offsetmins in _fixed_offset_tzs: + if offsetmins < 0: + sign = '-' + absoff = -offsetmins + else: + sign = '+' + absoff = offsetmins - name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60) - inst = tzoffset(offsetmins, name) - _fixed_offset_tzs[offsetmins] = inst + name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60) + inst = tzoffset(offsetmins, name) + _fixed_offset_tzs[offsetmins] = inst return _fixed_offset_tzs[offsetmins] @@ -240,13 +240,13 @@ except: if frac != None: # ok, fractions of hour? if min == None: - frac, min = _math.modf(frac * 60.0) - min = int(min) + frac, min = _math.modf(frac * 60.0) + min = int(min) # fractions of second? if s == None: - frac, s = _math.modf(frac * 60.0) - s = int(s) + frac, s = _math.modf(frac * 60.0) + s = int(s) # and extract microseconds... us = int(frac * 1000000) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 41c53a3..5ba4269 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -854,13 +854,14 @@ class XMLStream(object): Event handlers and the send queue will be threaded regardless of these parameters. """ - if kwargs.has_key('threaded') and kwargs.has_key('block'): - raise ValueError("process() called with both block and threaded arguments") - elif kwargs.has_key('block'): + if 'threaded' in kwargs and 'block' in kwargs: + raise ValueError("process() called with both " + \ + "block and threaded arguments") + elif 'block' in kwargs: threaded = not(kwargs.get('block', False)) else: threaded = kwargs.get('threaded', True) - + self.scheduler.process(threaded=True) def start_thread(name, target): From 47bc50d9fbbe8d72b589a6360aa8b5f32d6ba74b Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 22:37:22 -0700 Subject: [PATCH 11/25] Cosmetic PEP8 fixes. --- sleekxmpp/basexmpp.py | 12 +++++++----- sleekxmpp/features/feature_mechanisms/mechanisms.py | 1 - .../features/feature_mechanisms/stanza/mechanisms.py | 6 +++--- sleekxmpp/plugins/xep_0050/adhoc.py | 2 +- sleekxmpp/test/sleektest.py | 2 ++ sleekxmpp/xmlstream/stanzabase.py | 7 ++++--- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 07726a4..4d9a896 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -141,7 +141,7 @@ class BaseXMPP(XMLStream): def process(self, *args, **kwargs): """ Overrides XMLStream.process. - + Initialize the XML streams and begin processing events. The number of threads used for processing stream events is determined @@ -185,12 +185,14 @@ class BaseXMPP(XMLStream): if not module: try: module = sleekxmpp.plugins - module = __import__(str("%s.%s" % (module.__name__, plugin)), - globals(), locals(), [str(plugin)]) + module = __import__( + str("%s.%s" % (module.__name__, plugin)), + globals(), locals(), [str(plugin)]) except ImportError: module = sleekxmpp.features - module = __import__(str("%s.%s" % (module.__name__, plugin)), - globals(), locals(), [str(plugin)]) + module = __import__( + str("%s.%s" % (module.__name__, plugin)), + globals(), locals(), [str(plugin)]) if isinstance(module, str): # We probably want to load a module from outside # the sleekxmpp package, so leave out the globals(). diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py index d60818b..2debf3b 100644 --- a/sleekxmpp/features/feature_mechanisms/mechanisms.py +++ b/sleekxmpp/features/feature_mechanisms/mechanisms.py @@ -29,7 +29,6 @@ class feature_mechanisms(base_plugin): self.description = "SASL Stream Feature" self.stanza = stanza - def tls_active(): return 'starttls' in self.xmpp.features diff --git a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py index 1189cd8..c09cafb 100644 --- a/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py +++ b/sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py @@ -42,9 +42,9 @@ class Mechanisms(ElementBase): """ self.del_mechanisms() for val in values: - mech = ET.Element('{%s}mechanism' % self.namespace) - mech.text = val - self.append(mech) + mech = ET.Element('{%s}mechanism' % self.namespace) + mech.text = val + self.append(mech) def del_mechanisms(self): """ diff --git a/sleekxmpp/plugins/xep_0050/adhoc.py b/sleekxmpp/plugins/xep_0050/adhoc.py index 72c6c51..dd1c88d 100644 --- a/sleekxmpp/plugins/xep_0050/adhoc.py +++ b/sleekxmpp/plugins/xep_0050/adhoc.py @@ -589,5 +589,5 @@ class xep_0050(base_plugin): elif iq['type'] == 'error': self.terminate_command(session) - if iq['command']['status'] == 'completed': + if iq['command']['status'] == 'completed': self.terminate_command(session) diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index 7802a9b..cb5031f 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -318,9 +318,11 @@ class SleekTest(unittest.TestCase): self.xmpp.socket.recv_data(header) elif socket == 'live': self.xmpp.socket_class = TestLiveSocket + def wait_for_session(x): self.xmpp.socket.clear() skip_queue.put('started') + self.xmpp.add_event_handler('session_start', wait_for_session) self.xmpp.connect() else: diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index f1a9e1f..a2826ea 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -482,7 +482,8 @@ class ElementBase(object): if plugin: if plugin not in self.plugins: self.init_plugin(plugin) - handler = getattr(self.plugins[plugin], set_method, None) + handler = getattr(self.plugins[plugin], + set_method, None) if handler: return handler(value) @@ -1066,7 +1067,7 @@ class ElementBase(object): stanza_ns = '' if top_level_ns else self.namespace return tostring(self.xml, xmlns='', stanza_ns=stanza_ns, - top_level = not top_level_ns) + top_level=not top_level_ns) def __repr__(self): """ @@ -1285,7 +1286,7 @@ class StanzaBase(ElementBase): return tostring(self.xml, xmlns='', stanza_ns=stanza_ns, stream=self.stream, - top_level = not top_level_ns) + top_level=not top_level_ns) # To comply with PEP8, method names now use underscores. From 168203c94def5b4f88345e2564ecb0b973e8fc3f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Aug 2011 22:42:54 -0700 Subject: [PATCH 12/25] Update 1.0 todo list. Most of these items will probably be pushed to 1.1 --- todo1.0 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/todo1.0 b/todo1.0 index c01526f..cdb0dcd 100644 --- a/todo1.0 +++ b/todo1.0 @@ -18,8 +18,6 @@ Plugins: PEP8 Documentation Stream/Unit tests - 0050 - Review replacement in github.com/legastero/adhoc 0060 PEP8 Documentation @@ -29,14 +27,6 @@ Plugins: PEP8 Documentation Stream/Unit tests - 0086 - PEP8 - Documentation - Consider any simplifications. - 0202 - PEP8 - Documentation - Stream/Unit tests gmail_notify PEP8 Documentation From 9abf37bbd1e0e68397f0f55ace5eaba05c640720 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 5 Aug 2011 09:00:42 -0700 Subject: [PATCH 13/25] Ignore Manifest and dist dir in git. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0fe2c40..ff75f76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.pyc build/ +dist/ +MANIFEST From 08cb5f42e7a37ac20928bbb6e62a35acc1de0f78 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 5 Aug 2011 09:00:55 -0700 Subject: [PATCH 14/25] Update the info in setup.py I thought I had done this a long time ago, but it must have been in a lost branch. *shrug* It's too late for Beta6, so I've manually updated the PyPI entry. --- setup.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 3ccac3a..b997106 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2008 Nathanael C. Fritz +# Copyright (C) 2007-2011 Nathanael C. Fritz # All Rights Reserved # # This software is licensed as described in the README file, @@ -29,13 +29,16 @@ import sleekxmpp VERSION = sleekxmpp.__version__ DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' -LONG_DESCRIPTION = """ -SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc). -""" +with open('README') as readme: + LONG_DESCRIPTION = '\n'.join(readme) CLASSIFIERS = [ 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT', + 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', + 'Programming Language :: Python 2.6', + 'Programming Language :: Python 2.7', + 'Programming Language :: Python 3.1', + 'Programming Language :: Python 3.2', 'Topic :: Software Development :: Libraries :: Python Modules', ] @@ -82,7 +85,7 @@ setup( long_description = LONG_DESCRIPTION, author = 'Nathanael Fritz', author_email = 'fritzy [at] netflint.net', - url = 'http://code.google.com/p/sleekxmpp', + url = 'http://github.com/fritzy/SleekXMPP', license = 'MIT', platforms = [ 'any' ], packages = packages, From ea95811c4c76ae96bc41c4fab48886dd2e85c7a9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 5 Aug 2011 09:01:50 -0700 Subject: [PATCH 15/25] The next release will be 1.0 RC1 --- sleekxmpp/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index a53cfb0..d2c014d 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -15,5 +15,5 @@ from sleekxmpp.xmlstream import XMLStream, RestartStream from sleekxmpp.xmlstream.matcher import * from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET -__version__ = '1.0beta6' -__version_info__ = (1, 0, 0, 'beta6', 0) +__version__ = '1.0rc1' +__version_info__ = (1, 0, 0, 'rc1', 0) From 148a23579c199d218325c88d7b6f567f42c7a36a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 5 Aug 2011 14:06:58 -0700 Subject: [PATCH 16/25] Hotfix for ANONYMOUS mech support. Updates version to 1.0-Beta6.1 --- setup.py | 15 +++++++++------ sleekxmpp/__init__.py | 4 ++-- .../thirdparty/suelta/mechanisms/anonymous.py | 2 +- sleekxmpp/thirdparty/suelta/sasl.py | 4 ++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 3ccac3a..b997106 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2008 Nathanael C. Fritz +# Copyright (C) 2007-2011 Nathanael C. Fritz # All Rights Reserved # # This software is licensed as described in the README file, @@ -29,13 +29,16 @@ import sleekxmpp VERSION = sleekxmpp.__version__ DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' -LONG_DESCRIPTION = """ -SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc). -""" +with open('README') as readme: + LONG_DESCRIPTION = '\n'.join(readme) CLASSIFIERS = [ 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT', + 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', + 'Programming Language :: Python 2.6', + 'Programming Language :: Python 2.7', + 'Programming Language :: Python 3.1', + 'Programming Language :: Python 3.2', 'Topic :: Software Development :: Libraries :: Python Modules', ] @@ -82,7 +85,7 @@ setup( long_description = LONG_DESCRIPTION, author = 'Nathanael Fritz', author_email = 'fritzy [at] netflint.net', - url = 'http://code.google.com/p/sleekxmpp', + url = 'http://github.com/fritzy/SleekXMPP', license = 'MIT', platforms = [ 'any' ], packages = packages, diff --git a/sleekxmpp/__init__.py b/sleekxmpp/__init__.py index a53cfb0..aee81ba 100644 --- a/sleekxmpp/__init__.py +++ b/sleekxmpp/__init__.py @@ -15,5 +15,5 @@ from sleekxmpp.xmlstream import XMLStream, RestartStream from sleekxmpp.xmlstream.matcher import * from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET -__version__ = '1.0beta6' -__version_info__ = (1, 0, 0, 'beta6', 0) +__version__ = '1.0beta6.1' +__version_info__ = (1, 0, 0, 'beta6', 1) diff --git a/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py b/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py index de89eef..e44e91a 100644 --- a/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py +++ b/sleekxmpp/thirdparty/suelta/mechanisms/anonymous.py @@ -10,7 +10,7 @@ class ANONYMOUS(Mechanism): def __init__(self, sasl, name): """ """ - super(ANONYMOUS, self).__init__(self, sasl, name, 0) + super(ANONYMOUS, self).__init__(sasl, name, 0) def get_values(self): """ diff --git a/sleekxmpp/thirdparty/suelta/sasl.py b/sleekxmpp/thirdparty/suelta/sasl.py index ec7afe9..2ae9ae6 100644 --- a/sleekxmpp/thirdparty/suelta/sasl.py +++ b/sleekxmpp/thirdparty/suelta/sasl.py @@ -225,7 +225,7 @@ class SASL(object): requested_mech = 'ANONYMOUS' else: requested_mech = self.mech - if requested_mech == '*' and self.user == 'anonymous': + if requested_mech == '*' and self.user in ['', 'anonymous', None]: requested_mech = 'ANONYMOUS' # If a specific mechanism was requested, try it @@ -243,7 +243,7 @@ class SASL(object): if MECH_SEC_SCORES[name] > best_score: best_score = MECH_SEC_SCORES[name] best_mech = name - if best_mech != None: + if best_mech is not None: best_mech = MECHANISMS[best_mech](self, best_mech) return best_mech From 5be5b8c02bea7cef40046ec29c513e669399633a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 6 Aug 2011 00:41:14 -0700 Subject: [PATCH 17/25] If no config for a plugin is given, try using self.plugin_config. Sleek loads a few plugins by default, which made it difficult to configure or even disable them. Now, if a plugin is registered without any configuration, then sleek will try finding a configuration in self.plugin_config. --- sleekxmpp/basexmpp.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 4d9a896..816f2fc 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -198,6 +198,10 @@ class BaseXMPP(XMLStream): # the sleekxmpp package, so leave out the globals(). module = __import__(module, fromlist=[plugin]) + # Use the global plugin config cache, if applicable + if not pconfig: + pconfig = self.plugin_config.get(plugin, {}) + # Load the plugin class from the module. self.plugin[plugin] = getattr(module, plugin)(self, pconfig) From e83fae3a6fed7dd143aa6fed0673ff46340ea953 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 6 Aug 2011 00:44:32 -0700 Subject: [PATCH 18/25] Save the stream ID when the stream starts. --- sleekxmpp/basexmpp.py | 11 +++++++++++ sleekxmpp/componentxmpp.py | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 816f2fc..7c13125 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -138,6 +138,17 @@ class BaseXMPP(XMLStream): register_stanza_plugin(Message, Nick) register_stanza_plugin(Message, HTMLIM) + def start_stream_handler(self, xml): + """ + Save the stream ID once the streams have been established. + + Overrides XMLStream.start_stream_handler. + + Arguments: + xml -- The incoming stream's root element. + """ + self.stream_id = xml.get('id', '') + def process(self, *args, **kwargs): """ Overrides XMLStream.process. diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index f9e7da4..ed96016 100644 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -115,11 +115,13 @@ class ComponentXMPP(BaseXMPP): Once the streams are established, attempt to handshake with the server to be accepted as a component. - Overrides XMLStream.start_stream_handler. + Overrides BaseXMPP.start_stream_handler. Arguments: xml -- The incoming stream's root element. """ + BaseXMPP.start_stream_handler(self, xml) + # Construct a hash of the stream ID and the component secret. sid = xml.get('id', '') pre_hash = '%s%s' % (sid, self.secret) From 75f23d11301f09a29db4d0cc185dd5ffdc4a18fe Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 6 Aug 2011 00:45:18 -0700 Subject: [PATCH 19/25] Fix XEP-0078 using the new stream feature workflow. Honestly, this is mainly just a demo/proof of concept that we can handle dependencies and ordering issues with stream features. DON'T use XEP-0078 if you are able to use the normal SASL method, which should be the case unless you are dealing with a very old XMPP server implementation. --- setup.py | 1 + sleekxmpp/plugins/__init__.py | 2 + sleekxmpp/plugins/xep_0078.py | 72 --------------- sleekxmpp/plugins/xep_0078/__init__.py | 12 +++ sleekxmpp/plugins/xep_0078/legacyauth.py | 108 +++++++++++++++++++++++ sleekxmpp/plugins/xep_0078/stanza.py | 43 +++++++++ sleekxmpp/stanza/stream_features.py | 2 + 7 files changed, 168 insertions(+), 72 deletions(-) delete mode 100644 sleekxmpp/plugins/xep_0078.py create mode 100644 sleekxmpp/plugins/xep_0078/__init__.py create mode 100644 sleekxmpp/plugins/xep_0078/legacyauth.py create mode 100644 sleekxmpp/plugins/xep_0078/stanza.py diff --git a/setup.py b/setup.py index b997106..e3b3aa9 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,7 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0060', 'sleekxmpp/plugins/xep_0060/stanza', 'sleekxmpp/plugins/xep_0066', + 'sleekxmpp/plugins/xep_0078', 'sleekxmpp/plugins/xep_0085', 'sleekxmpp/plugins/xep_0086', 'sleekxmpp/plugins/xep_0092', diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 7fa031e..c0b1121 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -9,3 +9,5 @@ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', 'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] + +# Don't automatically load xep_0078 diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py deleted file mode 100644 index bb6a463..0000000 --- a/sleekxmpp/plugins/xep_0078.py +++ /dev/null @@ -1,72 +0,0 @@ -""" - 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 -import logging -import hashlib -from . import base - - -log = logging.getLogger(__name__) - - -class xep_0078(base.base_plugin): - """ - XEP-0078 NON-SASL Authentication - """ - def plugin_init(self): - self.description = "Non-SASL Authentication (broken)" - self.xep = "0078" - self.xmpp.add_event_handler("session_start", self.check_stream) - #disabling until I fix conflict with PLAIN - #self.xmpp.registerFeature("", self.auth) - self.streamid = '' - - def check_stream(self, xml): - self.streamid = xml.attrib['id'] - if xml.get('version', '0') != '1.0': - self.auth() - - def auth(self, xml=None): - log.debug("Starting jabber:iq:auth Authentication") - auth_request = self.xmpp.makeIqGet() - auth_request_query = ET.Element('{jabber:iq:auth}query') - auth_request.attrib['to'] = self.xmpp.boundjid.host - username = ET.Element('username') - username.text = self.xmpp.username - auth_request_query.append(username) - auth_request.append(auth_request_query) - result = auth_request.send() - rquery = result.find('{jabber:iq:auth}query') - attempt = self.xmpp.makeIqSet() - query = ET.Element('{jabber:iq:auth}query') - resource = ET.Element('resource') - resource.text = self.xmpp.resource - query.append(username) - query.append(resource) - if rquery.find('{jabber:iq:auth}digest') is None: - log.warning("Authenticating via jabber:iq:auth Plain.") - password = ET.Element('password') - password.text = self.xmpp.password - query.append(password) - else: - log.debug("Authenticating via jabber:iq:auth Digest") - digest = ET.Element('digest') - digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest() - query.append(digest) - attempt.append(query) - result = attempt.send() - if result.attrib['type'] == 'result': - with self.xmpp.lock: - self.xmpp.authenticated = True - self.xmpp.sessionstarted = True - self.xmpp.event("session_start") - else: - log.info("Authentication failed") - self.xmpp.disconnect() - self.xmpp.event("failed_auth") diff --git a/sleekxmpp/plugins/xep_0078/__init__.py b/sleekxmpp/plugins/xep_0078/__init__.py new file mode 100644 index 0000000..5a2bda7 --- /dev/null +++ b/sleekxmpp/plugins/xep_0078/__init__.py @@ -0,0 +1,12 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.xep_0078 import stanza +from sleekxmpp.plugins.xep_0078.stanza import IqAuth, AuthFeature +from sleekxmpp.plugins.xep_0078.legacyauth import xep_0078 + diff --git a/sleekxmpp/plugins/xep_0078/legacyauth.py b/sleekxmpp/plugins/xep_0078/legacyauth.py new file mode 100644 index 0000000..bdd2df6 --- /dev/null +++ b/sleekxmpp/plugins/xep_0078/legacyauth.py @@ -0,0 +1,108 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging +import hashlib +import random + +from sleekxmpp.stanza import Iq, StreamFeatures +from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin +from sleekxmpp.plugins.base import base_plugin +from sleekxmpp.plugins.xep_0078 import stanza + + +log = logging.getLogger(__name__) + + +class xep_0078(base_plugin): + + """ + XEP-0078 NON-SASL Authentication + + This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin + unless you are forced to use an old XMPP server implementation. + """ + + def plugin_init(self): + self.xep = "0078" + self.description = "Non-SASL Authentication" + self.stanza = stanza + + self.xmpp.register_feature('auth', + self._handle_auth, + restart=False, + order=self.config.get('order', 15)) + + register_stanza_plugin(Iq, stanza.IqAuth) + register_stanza_plugin(StreamFeatures, stanza.AuthFeature) + + + def _handle_auth(self, features): + # If we can or have already authenticated with SASL, do nothing. + if 'mechanisms' in features['features']: + return False + if self.xmpp.authenticated: + return False + + log.debug("Starting jabber:iq:auth Authentication") + + # Step 1: Request the auth form + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = self.xmpp.boundjid.host + iq['auth']['username'] = self.xmpp.boundjid.user + resp = iq.send(now=True) + + if resp is None or resp['type'] != 'result': + log.info("Authentication failed: %s" % resp['error']['condition']) + self.xmpp.event('failed_auth', resp, direct=True) + self.xmpp.disconnect() + return True + + # Step 2: Fill out auth form for either password or digest auth + iq = self.xmpp.Iq() + iq['type'] = 'set' + iq['auth']['username'] = self.xmpp.boundjid.user + + # A resource is required, so create a random one if necessary + if self.xmpp.boundjid.resource: + iq['auth']['resource'] = self.xmpp.boundjid.resource + else: + iq['auth']['resource'] = '%s' % random.random() + + if 'digest' in resp['auth']['fields']: + log.debug('Authenticating via jabber:iq:auth Digest') + if sys.version_info < (3, 0): + stream_id = bytes(self.xmpp.stream_id) + password = bytes(self.xmpp.password) + else: + stream_id = bytes(self.xmpp.stream_id, encoding='utf-8') + password = bytes(self.xmpp.password, encoding='utf-8') + + digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest() + iq['auth']['digest'] = digest + else: + log.warning('Authenticating via jabber:iq:auth Plain.') + iq['auth']['password'] = self.xmpp.password + + # Step 3: Send credentials + result = iq.send(now=True) + if result is not None and result.attrib['type'] == 'result': + self.xmpp.features.add('auth') + + self.xmpp.authenticated = True + log.debug("Established Session") + self.xmpp.sessionstarted = True + self.xmpp.session_started_event.set() + self.xmpp.event('session_start') + else: + log.info("Authentication failed") + self.xmpp.disconnect() + self.xmpp.event("failed_auth") + + return True diff --git a/sleekxmpp/plugins/xep_0078/stanza.py b/sleekxmpp/plugins/xep_0078/stanza.py new file mode 100644 index 0000000..86ba09a --- /dev/null +++ b/sleekxmpp/plugins/xep_0078/stanza.py @@ -0,0 +1,43 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin + + +class IqAuth(ElementBase): + namespace = 'jabber:iq:auth' + name = 'query' + plugin_attrib = 'auth' + interfaces = set(('fields', 'username', 'password', 'resource', 'digest')) + sub_interfaces = set(('username', 'password', 'resource', 'digest')) + plugin_tag_map = {} + plugin_attrib_map = {} + + def get_fields(self): + fields = set() + for field in self.sub_interfaces: + if self.xml.find('{%s}%s' % (self.namespace, field)) is not None: + fields.add(field) + return fields + + def set_resource(self, value): + self._set_sub_text('resource', value, keep=True) + + def set_password(self, value): + self._set_sub_text('password', value, keep=True) + + +class AuthFeature(ElementBase): + namespace = 'http://jabber.org/features/iq-auth' + name = 'auth' + plugin_attrib = 'auth' + interfaces = set() + plugin_tag_map = {} + plugin_attrib_map = {} + + diff --git a/sleekxmpp/stanza/stream_features.py b/sleekxmpp/stanza/stream_features.py index 5be2e55..b800011 100644 --- a/sleekxmpp/stanza/stream_features.py +++ b/sleekxmpp/stanza/stream_features.py @@ -19,6 +19,8 @@ class StreamFeatures(StanzaBase): namespace = 'http://etherx.jabber.org/streams' interfaces = set(('features', 'required', 'optional')) sub_interfaces = interfaces + plugin_tag_map = {} + plugin_attrib_map = {} def setup(self, xml): StanzaBase.setup(self, xml) From 572becad44b8cb577a1f8357e04fa93235894536 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 9 Aug 2011 00:51:49 -0700 Subject: [PATCH 20/25] Enable forcing a specififc SASL mech: xmpp = ClientXMPP(jid, password, { 'feature_mechanisms': {'use_mech':'PLAIN'}}) --- sleekxmpp/features/feature_mechanisms/mechanisms.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/features/feature_mechanisms/mechanisms.py b/sleekxmpp/features/feature_mechanisms/mechanisms.py index 2debf3b..a6cff0a 100644 --- a/sleekxmpp/features/feature_mechanisms/mechanisms.py +++ b/sleekxmpp/features/feature_mechanisms/mechanisms.py @@ -29,6 +29,8 @@ class feature_mechanisms(base_plugin): self.description = "SASL Stream Feature" self.stanza = stanza + self.use_mech = self.config.get('use_mech', None) + def tls_active(): return 'starttls' in self.xmpp.features @@ -48,7 +50,8 @@ class feature_mechanisms(base_plugin): username=self.xmpp.boundjid.user, sec_query=suelta.sec_query_allow, request_values=sasl_callback, - tls_active=tls_active) + tls_active=tls_active, + mech=self.use_mech) register_stanza_plugin(StreamFeatures, stanza.Mechanisms) From 156b3200e3b5ad1b2e64eecc48cdc792f7b2ffd9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 10 Aug 2011 09:05:43 -0700 Subject: [PATCH 21/25] Don't include ping stanza in the ping result. --- sleekxmpp/plugins/xep_0199/ping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py index d1e08e6..0fa22f8 100644 --- a/sleekxmpp/plugins/xep_0199/ping.py +++ b/sleekxmpp/plugins/xep_0199/ping.py @@ -108,7 +108,7 @@ class xep_0199(base_plugin): iq -- The ping request. """ log.debug("Pinged by %s" % iq['from']) - iq.reply().enable('ping').send() + iq.reply().send() def send_ping(self, jid, timeout=None, errorfalse=False, ifrom=None, block=True, callback=None): From 0d4825d3ea0562f305939e653e3d414e70e4aaa8 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Wed, 10 Aug 2011 13:37:36 -0700 Subject: [PATCH 22/25] added send_client example --- examples/send_client.py | 149 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100755 examples/send_client.py diff --git a/examples/send_client.py b/examples/send_client.py new file mode 100755 index 0000000..fd99e8c --- /dev/null +++ b/examples/send_client.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" + 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 logging +import time +import getpass +from optparse import OptionParser + +import sleekxmpp + +# Python versions before 3.0 do not use UTF-8 encoding +# by default. To ensure that Unicode is handled properly +# throughout SleekXMPP, we will set the default encoding +# ourselves to UTF-8. +if sys.version_info < (3, 0): + reload(sys) + sys.setdefaultencoding('utf8') + + +class SendMsgBot(sleekxmpp.ClientXMPP): + + """ + A simple SleekXMPP bot that will echo messages it + receives, along with a short thank you message. + """ + + def __init__(self, jid, password): + sleekxmpp.ClientXMPP.__init__(self, jid, password) + + # The session_start event will be triggered when + # the bot establishes its connection with the server + # and the XML streams are ready for use. We want to + # listen for this event so that we we can intialize + # our roster. + self.add_event_handler("session_start", self.start) + + # The message event is triggered whenever a message + # stanza is received. Be aware that that includes + # MUC messages and error messages. + self.add_event_handler("message", self.message) + + def start(self, event): + """ + Process the session_start event. + + Typical actions for the session_start event are + requesting the roster and broadcasting an intial + presence stanza. + + Arguments: + event -- An empty dictionary. The session_start + event does not provide any additional + data. + """ + self.send_presence() + self.get_roster() + msg = self.Message() + msg['to'] = 'user@example.com' + msg['type'] = 'chat' + msg['body'] = "Hello there!" + msg.send() + self.disconnect() + + def message(self, msg): + """ + Process incoming message stanzas. Be aware that this also + includes MUC messages and error messages. It is usually + a good idea to check the messages's type before processing + or sending replies. + + Arguments: + msg -- The received message stanza. See the documentation + for stanza objects and the Message stanza to see + how it may be used. + """ + #msg.reply("Thanks for sending\n%(body)s" % msg).send() + print "Msg rceived from %(body)s: %(jid)s" % msg + + +if __name__ == '__main__': + # Setup the command line arguments. + optp = OptionParser() + + # Output verbosity options. + optp.add_option('-q', '--quiet', help='set logging to ERROR', + action='store_const', dest='loglevel', + const=logging.ERROR, default=logging.INFO) + optp.add_option('-d', '--debug', help='set logging to DEBUG', + action='store_const', dest='loglevel', + const=logging.DEBUG, default=logging.INFO) + optp.add_option('-v', '--verbose', help='set logging to COMM', + action='store_const', dest='loglevel', + const=5, default=logging.INFO) + + # JID and password options. + optp.add_option("-j", "--jid", dest="jid", + help="JID to use") + optp.add_option("-p", "--password", dest="password", + help="password to use") + + opts, args = optp.parse_args() + + # Setup logging. + logging.basicConfig(level=opts.loglevel, + format='%(levelname)-8s %(message)s') + + if opts.jid is None: + opts.jid = raw_input("Username: ") + if opts.password is None: + opts.password = getpass.getpass("Password: ") + + # Setup the EchoBot and register plugins. Note that while plugins may + # have interdependencies, the order in which you register them does + # not matter. + xmpp = SendMsgBot(opts.jid, opts.password) + xmpp.register_plugin('xep_0030') # Service Discovery + xmpp.register_plugin('xep_0004') # Data Forms + xmpp.register_plugin('xep_0060') # PubSub + xmpp.register_plugin('xep_0199') # XMPP Ping + + # If you are working with an OpenFire server, you may need + # to adjust the SSL version used: + # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 + + # If you want to verify the SSL certificates offered by a server: + # xmpp.ca_certs = "path/to/ca/cert" + + # Connect to the XMPP server and start processing XMPP stanzas. + if xmpp.connect(): + # If you do not have the pydns library installed, you will need + # to manually specify the name of the server if it does not match + # the one in the JID. For example, to use Google Talk you would + # need to use: + # + # if xmpp.connect(('talk.google.com', 5222)): + # ... + xmpp.process(threaded=False) + print("Done") + else: + print("Unable to connect.") From 9b7ed73f95145f88887d6fc3daa1bd2a9596b943 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 11 Aug 2011 21:59:55 -0700 Subject: [PATCH 23/25] Reorganize XEP-0004. Changes: May now use underscored method names form.field is replaced by form['fields'] form.get_fields no longer accepts use_dict parameter, it always returns an OrderedDict now form.set_fields will accept either an OrderedDict, or a list of (var, dict) tuples as before. Setting the form type to 'submit' will remove extra meta data from the form fields, leaving just the 'var' and 'value' Setting the form type to 'cancel' will remove all fields. --- sleekxmpp/plugins/xep_0004.py | 395 ------------------ sleekxmpp/plugins/xep_0004/__init__.py | 11 + sleekxmpp/plugins/xep_0004/dataforms.py | 60 +++ sleekxmpp/plugins/xep_0004/stanza/__init__.py | 10 + sleekxmpp/plugins/xep_0004/stanza/field.py | 167 ++++++++ sleekxmpp/plugins/xep_0004/stanza/form.py | 250 +++++++++++ tests/test_stanza_xep_0004.py | 41 +- tests/test_stanza_xep_0060.py | 34 +- 8 files changed, 538 insertions(+), 430 deletions(-) delete mode 100644 sleekxmpp/plugins/xep_0004.py create mode 100644 sleekxmpp/plugins/xep_0004/__init__.py create mode 100644 sleekxmpp/plugins/xep_0004/dataforms.py create mode 100644 sleekxmpp/plugins/xep_0004/stanza/__init__.py create mode 100644 sleekxmpp/plugins/xep_0004/stanza/field.py create mode 100644 sleekxmpp/plugins/xep_0004/stanza/form.py diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py deleted file mode 100644 index 5a49d70..0000000 --- a/sleekxmpp/plugins/xep_0004.py +++ /dev/null @@ -1,395 +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 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 - - -log = logging.getLogger(__name__) - - -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 __init__(self, *args, **kwargs): - title = None - if 'title' in kwargs: - title = kwargs['title'] - del kwargs['title'] - ElementBase.__init__(self, *args, **kwargs) - if title is not None: - self['title'] = title - self.field = FieldAccessor(self) - - 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'] = kwtype - field['label'] = label - field['desc'] = desc - field['required'] = required - field['value'] = value - if options is not None: - field['options'] = options - return field - - def getXML(self, type='submit'): - self['type'] = type - log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") - return self.xml - - def fromXML(self, xml): - log.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) - 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=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) - self.xml.append(reported) - fieldXML = ET.Element('{%s}field' % FormField.namespace) - reported.append(fieldXML) - field = FormField(xml=fieldXML) - field['var'] = var - field['type'] = kwtype - 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, use_dict=False): - fields = {} if use_dict else [] - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) - for fieldXML in fieldsXML: - field = FormField(xml=fieldXML) - 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' % self.namespace) - return "\n".join([instXML.text for instXML in instsXML]) - - 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(use_dict=True) - 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, default=None): - del self['fields'] - for field_data in fields: - var = field_data[0] - field = field_data[1] - field['var'] = var - - 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) - inst.text = instruction - self.xml.append(inst) - - def setItems(self, items): - for item in items: - self.addItem(item) - - def setReported(self, reported, default=None): - for var in reported: - field = reported[var] - field['var'] = var - self.addReported(var, **field) - - def setValues(self, values): - fields = self.getFields(use_dict=True) - for field in values: - fields[field]['value'] = values[field] - - def merge(self, other): - new = copy.copy(self) - if type(other) == dict: - new.setValues(other) - return new - nfields = new.getFields(use_dict=True) - ofields = other.getFields(use_dict=True) - nfields.update(ofields) - new.setFields([(x, nfields[x]) for x in nfields]) - return new - -class FieldAccessor(object): - def __init__(self, form): - self.form = form - - def __getitem__(self, key): - return self.form.getFields(use_dict=True)[key] - - def __contains__(self, key): - return key in self.form.getFields(use_dict=True) - - def has_key(self, key): - return key in self.form.getFields(use_dict=True) - - -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 = '1' - self.xml.append(valXML) - else: - valXML = ET.Element(valXMLName) - valXML.text = '0' - self.xml.append(valXML) - 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) - 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 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 makeForm(self, ftype='form', title='', instructions=''): - f = Form() - f['type'] = ftype - f['title'] = title - f['instructions'] = instructions - return f - - 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) - - def buildForm(self, xml): - return Form(xml=xml) diff --git a/sleekxmpp/plugins/xep_0004/__init__.py b/sleekxmpp/plugins/xep_0004/__init__.py new file mode 100644 index 0000000..aad4e15 --- /dev/null +++ b/sleekxmpp/plugins/xep_0004/__init__.py @@ -0,0 +1,11 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.xep_0004.stanza import Form +from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption +from sleekxmpp.plugins.xep_0004.dataforms import xep_0004 diff --git a/sleekxmpp/plugins/xep_0004/dataforms.py b/sleekxmpp/plugins/xep_0004/dataforms.py new file mode 100644 index 0000000..5414be5 --- /dev/null +++ b/sleekxmpp/plugins/xep_0004/dataforms.py @@ -0,0 +1,60 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import copy + +from sleekxmpp.thirdparty import OrderedDict + +from sleekxmpp import Message +from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET +from sleekxmpp.xmlstream.handler import Callback +from sleekxmpp.xmlstream.matcher import StanzaPath +from sleekxmpp.plugins.base import base_plugin +from sleekxmpp.plugins.xep_0004 import stanza +from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption + + +class xep_0004(base_plugin): + """ + XEP-0004: Data Forms + """ + + def plugin_init(self): + self.xep = '0004' + self.description = 'Data Forms' + self.stanza = stanza + + self.xmpp.registerHandler( + Callback('Data Form', + StanzaPath('message/form'), + self.handle_form)) + + register_stanza_plugin(FormField, FieldOption, iterable=True) + register_stanza_plugin(Form, FormField, iterable=True) + register_stanza_plugin(Message, Form) + + def make_form(self, ftype='form', title='', instructions=''): + f = Form() + f['type'] = ftype + f['title'] = title + f['instructions'] = instructions + return f + + def post_init(self): + 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) + + def build_form(self, xml): + return Form(xml=xml) + + +xep_0004.makeForm = xep_0004.make_form +xep_0004.buildForm = xep_0004.build_form diff --git a/sleekxmpp/plugins/xep_0004/stanza/__init__.py b/sleekxmpp/plugins/xep_0004/stanza/__init__.py new file mode 100644 index 0000000..6ad3529 --- /dev/null +++ b/sleekxmpp/plugins/xep_0004/stanza/__init__.py @@ -0,0 +1,10 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption +from sleekxmpp.plugins.xep_0004.stanza.form import Form diff --git a/sleekxmpp/plugins/xep_0004/stanza/field.py b/sleekxmpp/plugins/xep_0004/stanza/field.py new file mode 100644 index 0000000..9bb9231 --- /dev/null +++ b/sleekxmpp/plugins/xep_0004/stanza/field.py @@ -0,0 +1,167 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase, ET + + +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',)) + plugin_tag_map = {} + plugin_attrib_map = {} + + field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', + 'jid-single', 'list-multi', 'list-single', + 'text-multi', 'text-private', 'text-single')) + + true_values = set((True, '1', 'true')) + option_types = set(('list-multi', 'list-single')) + multi_line_types = set(('hidden', 'text-multi')) + multi_value_types = set(('hidden', 'jid-multi', + 'list-multi', 'text-multi')) + + def add_option(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 del_options(self): + optsXML = self.xml.findall('{%s}option' % self.namespace) + for optXML in optsXML: + self.xml.remove(optXML) + + def del_required(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + if reqXML is not None: + self.xml.remove(reqXML) + + def del_value(self): + valsXML = self.xml.findall('{%s}value' % self.namespace) + for valXML in valsXML: + self.xml.remove(valXML) + + def get_answer(self): + return self['value'] + + def get_options(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 get_required(self): + reqXML = self.xml.find('{%s}required' % self.namespace) + return reqXML is not None + + def get_value(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 set_answer(self, answer): + self['value'] = answer + + def set_false(self): + self['value'] = False + + def set_options(self, options): + for value in options: + if isinstance(value, dict): + self.add_option(**value) + else: + self.add_option(value=value) + + def set_required(self, required): + exists = self['required'] + if not exists and required: + self.xml.append(ET.Element('{%s}required' % self.namespace)) + elif exists and not required: + del self['required'] + + def set_true(self): + self['value'] = True + + def set_value(self, value): + del self['value'] + valXMLName = '{%s}value' % self.namespace + + if self['type'] == 'boolean': + if value in self.true_values: + valXML = ET.Element(valXMLName) + valXML.text = '1' + self.xml.append(valXML) + else: + valXML = ET.Element(valXMLName) + valXML.text = '0' + self.xml.append(valXML) + elif self['type'] in self.multi_value_types or not self['type']: + if not isinstance(value, list): + if self['type'] in self.multi_line_types: + value = value.split('\n') + else: + 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) + 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',)) + + +FormField.addOption = FormField.add_option +FormField.delOptions = FormField.del_options +FormField.delRequired = FormField.del_required +FormField.delValue = FormField.del_value +FormField.getAnswer = FormField.get_answer +FormField.getOptions = FormField.get_options +FormField.getRequired = FormField.get_required +FormField.getValue = FormField.get_value +FormField.setAnswer = FormField.set_answer +FormField.setFalse = FormField.set_false +FormField.setOptions = FormField.set_options +FormField.setRequired = FormField.set_required +FormField.setTrue = FormField.set_true +FormField.setValue = FormField.set_value diff --git a/sleekxmpp/plugins/xep_0004/stanza/form.py b/sleekxmpp/plugins/xep_0004/stanza/form.py new file mode 100644 index 0000000..d85266f --- /dev/null +++ b/sleekxmpp/plugins/xep_0004/stanza/form.py @@ -0,0 +1,250 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import copy +import logging + +from sleekxmpp.thirdparty import OrderedDict + +from sleekxmpp.xmlstream import ElementBase, ET +from sleekxmpp.plugins.xep_0004.stanza import FormField + + +log = logging.getLogger(__name__) + + +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 __init__(self, *args, **kwargs): + title = None + if 'title' in kwargs: + title = kwargs['title'] + del kwargs['title'] + ElementBase.__init__(self, *args, **kwargs) + if title is not None: + self['title'] = title + + def setup(self, xml=None): + if ElementBase.setup(self, xml): + # If we had to generate xml + self['type'] = 'form' + + def set_type(self, ftype): + self._set_attr('type', ftype) + if ftype == 'submit': + fields = self['fields'] + for var in fields: + field = fields[var] + del field['type'] + del field['label'] + del field['desc'] + del field['required'] + del field['options'] + elif ftype == 'cancel': + del self['fields'] + + def add_field(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'] = kwtype + field['value'] = value + if self['type'] in ('form', 'result'): + field['label'] = label + field['desc'] = desc + field['required'] = required + if options is not None: + field['options'] = options + else: + del field['type'] + return field + + def getXML(self, type='submit'): + self['type'] = type + log.warning("Form.getXML() is deprecated API compatibility " + \ + "with plugins/old_0004.py") + return self.xml + + def fromXML(self, xml): + log.warning("Form.fromXML() is deprecated API compatibility " + \ + "with plugins/old_0004.py") + n = Form(xml=xml) + return n + + def add_item(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 add_reported(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) + self.xml.append(reported) + fieldXML = ET.Element('{%s}field' % FormField.namespace) + reported.append(fieldXML) + field = FormField(xml=fieldXML) + field['var'] = var + field['type'] = kwtype + field['label'] = label + field['desc'] = desc + return field + + def cancel(self): + self['type'] = 'cancel' + + def del_fields(self): + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + self.xml.remove(fieldXML) + + def del_instructions(self): + instsXML = self.xml.findall('{%s}instructions') + for instXML in instsXML: + self.xml.remove(instXML) + + def del_items(self): + itemsXML = self.xml.find('{%s}item' % self.namespace) + for itemXML in itemsXML: + self.xml.remove(itemXML) + + def del_reported(self): + reportedXML = self.xml.find('{%s}reported' % self.namespace) + if reportedXML is not None: + self.xml.remove(reportedXML) + + def get_fields(self, use_dict=False): + fields = OrderedDict() + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + for fieldXML in fieldsXML: + field = FormField(xml=fieldXML) + fields[field['var']] = field + return fields + + def get_instructions(self): + instructions = '' + instsXML = self.xml.findall('{%s}instructions' % self.namespace) + return "\n".join([instXML.text for instXML in instsXML]) + + def get_items(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 get_reported(self): + fields = {} + xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, + FormField.namespace)) + for field in xml: + field = FormField(xml=field) + fields[field['var']] = field + return fields + + def get_values(self): + values = {} + fields = self['fields'] + 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 set_fields(self, fields): + del self['fields'] + if not isinstance(fields, list): + fields = fields.items() + for var, field in fields: + field['var'] = var + self.add_field(**field) + + def set_instructions(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) + inst.text = instruction + self.xml.append(inst) + + def set_items(self, items): + for item in items: + self.add_item(item) + + def set_reported(self, reported): + for var in reported: + field = reported[var] + field['var'] = var + self.add_reported(var, **field) + + def set_values(self, values): + fields = self['fields'] + for field in values: + fields[field]['value'] = values[field] + + def merge(self, other): + new = copy.copy(self) + if type(other) == dict: + new['values'] = other + return new + nfields = new['fields'] + ofields = other['fields'] + nfields.update(ofields) + new['fields'] = nfields + return new + + +Form.setType = Form.set_type +Form.addField = Form.add_field +Form.addItem = Form.add_item +Form.addReported = Form.add_reported +Form.delFields = Form.del_fields +Form.delInstructions = Form.del_instructions +Form.delItems = Form.del_items +Form.delReported = Form.del_reported +Form.getFields = Form.get_fields +Form.getInstructions = Form.get_instructions +Form.getItems = Form.get_items +Form.getReported = Form.get_reported +Form.getValues = Form.get_values +Form.setFields = Form.set_fields +Form.setInstructions = Form.set_instructions +Form.setItems = Form.set_items +Form.setReported = Form.set_reported +Form.setValues = Form.set_values diff --git a/tests/test_stanza_xep_0004.py b/tests/test_stanza_xep_0004.py index bdc4a87..22f8b77 100644 --- a/tests/test_stanza_xep_0004.py +++ b/tests/test_stanza_xep_0004.py @@ -1,4 +1,6 @@ from sleekxmpp.test import * +from sleekxmpp.thirdparty import OrderedDict + import sleekxmpp.plugins.xep_0004 as xep_0004 @@ -47,21 +49,25 @@ class TestDataForms(SleekTest): """) - form['fields'] = [('f1', {'type': 'text-single', - 'label': 'Username', - 'required': True}), - ('f2', {'type': 'text-private', - 'label': 'Password', - 'required': True}), - ('f3', {'type': 'text-multi', - 'label': 'Message', - 'value': 'Enter message.\nA long one even.'}), - ('f4', {'type': 'list-single', - 'label': 'Message Type', - 'options': [{'label': 'Cool!', - 'value': 'cool'}, - {'label': 'Urgh!', - 'value': 'urgh'}]})] + fields = OrderedDict() + fields['f1'] = {'type': 'text-single', + 'label': 'Username', + 'required': True} + fields['f2'] = {'type': 'text-private', + 'label': 'Password', + 'required': True} + fields['f3'] = {'type': 'text-multi', + 'label': 'Message', + 'value': 'Enter message.\nA long one even.'} + fields['f4'] = {'type': 'list-single', + 'label': 'Message Type', + 'options': [{'label': 'Cool!', + 'value': 'cool'}, + {'label': 'Urgh!', + 'value': 'urgh'}]} + form['fields'] = fields + + self.check(msg, """ @@ -92,9 +98,8 @@ class TestDataForms(SleekTest): msg = self.Message() form = msg['form'] - form.setFields([ - ('foo', {'type': 'text-single'}), - ('bar', {'type': 'list-multi'})]) + form.add_field(var='foo', ftype='text-single') + form.add_field(var='bar', ftype='list-multi') form.setValues({'foo': 'Foo!', 'bar': ['a', 'b']}) diff --git a/tests/test_stanza_xep_0060.py b/tests/test_stanza_xep_0060.py index d42c11b..2427b78 100644 --- a/tests/test_stanza_xep_0060.py +++ b/tests/test_stanza_xep_0060.py @@ -182,7 +182,7 @@ class TestPubsubStanzas(SleekTest): - + this thing is awesome @@ -306,42 +306,42 @@ class TestPubsubStanzas(SleekTest): - + http://jabber.org/protocol/pubsub#node_config - + leaf - - + + 1 - + 1 - - - + + + 1 - - - + + + 10 - + 1 - + open - + publishers - + never - + From 0050c5112428939346397a4d2edfeb313c0fd497 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Fri, 12 Aug 2011 16:32:09 -0700 Subject: [PATCH 24/25] updated pubsub plugin to use stanzas --- sleekxmpp/plugins/xep_0060/pubsub.py | 371 +++++------------- sleekxmpp/plugins/xep_0060/stanza/pubsub.py | 16 +- .../plugins/xep_0060/stanza/pubsub_owner.py | 15 +- 3 files changed, 110 insertions(+), 292 deletions(-) diff --git a/sleekxmpp/plugins/xep_0060/pubsub.py b/sleekxmpp/plugins/xep_0060/pubsub.py index e199be0..06d148e 100644 --- a/sleekxmpp/plugins/xep_0060/pubsub.py +++ b/sleekxmpp/plugins/xep_0060/pubsub.py @@ -19,295 +19,110 @@ class xep_0060(base.base_plugin): self.xep = '0060' self.description = 'Publish-Subscribe' - def create_node(self, jid, node, config=None, collection=False, ntype=None): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - create = ET.Element('create') - create.set('node', node) - pubsub.append(create) - configure = ET.Element('configure') - if collection: - ntype = 'collection' - #if config is None: - # submitform = self.xmpp.plugin['xep_0004'].makeForm('submit') - #else: - if config is not None: - submitform = config - if 'FORM_TYPE' in submitform.field: - submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config') + def create_node(self, jid, node, config=None, ntype=None): + iq = IQ(sto=jid, stype='set', sfrom=self.xmpp.jid) + iq['pubsub']['create']['node'] = node + if ntype is None: + ntype = 'leaf' + if config is not None: + if 'FORM_TYPE' in submitform.field: + config.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config') else: - submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') - if ntype: - if 'pubsub#node_type' in submitform.field: - submitform.field['pubsub#node_type'].setValue(ntype) - else: - submitform.addField('pubsub#node_type', value=ntype) - else: - if 'pubsub#node_type' in submitform.field: - submitform.field['pubsub#node_type'].setValue('leaf') - else: - submitform.addField('pubsub#node_type', value='leaf') - submitform['type'] = 'submit' - configure.append(submitform.xml) - pubsub.append(configure) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is False or result is None or result['type'] == 'error': return False - return True + config.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') + if 'pubsub#node_type' in submitform.field: + config.field['pubsub#node_type'].setValue(ntype) + else: + config.addField('pubsub#node_type', value=ntype) + iq['pubsub']['configure']['form'] = config + return iq.send() - def subscribe(self, jid, node, bare=True, subscribee=None): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - subscribe = ET.Element('subscribe') - subscribe.attrib['node'] = node - if subscribee is None: - if bare: - subscribe.attrib['jid'] = self.xmpp.boundjid.bare - else: - subscribe.attrib['jid'] = self.xmpp.boundjid.full - else: - subscribe.attrib['jid'] = subscribee - pubsub.append(subscribe) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is False or result is None or result['type'] == 'error': return False - return True + def subscribe(self, jid, node, bare=True, subscribee=None): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') + iq['pubsub']['subscribe']['node'] = node + if subscribee is None: + if bare: + iq['pubsub']['subscribe']['jid'] = self.xmpp.jid.bare + else: + iq['pubsub']['subscribe']['jid'] = self.xmpp.jid.full + else: + iq['pubsub']['subscribe']['jid'] = subscribee + return iq.send() - def unsubscribe(self, jid, node, bare=True, subscribee=None): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - unsubscribe = ET.Element('unsubscribe') - unsubscribe.attrib['node'] = node - if subscribee is None: - if bare: - unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare - else: - unsubscribe.attrib['jid'] = self.xmpp.boundjid.full - else: - unsubscribe.attrib['jid'] = subscribee - pubsub.append(unsubscribe) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is False or result is None or result['type'] == 'error': return False - return True + def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') + iq['pubsub']['unsubscribe']['node'] = node + if subscribee is None: + if bare: + iq['pubsub']['unsubscribe']['jid'] = self.xmpp.jid.bare + else: + iq['pubsub']['unsubscribe']['jid'] = self.xmpp.jid.full + else: + iq['pubsub']['unsubscribe']['jid'] = subscribee + if subid is not None: + iq['pubsub']['unsubscribe']['subid'] = subid + return iq.send() - def getNodeConfig(self, jid, node=None): # if no node, then grab default - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - if node is not None: - configure = ET.Element('configure') - configure.attrib['node'] = node - else: - configure = ET.Element('default') - pubsub.append(configure) - #TODO: Add configure support. - iq = self.xmpp.makeIqGet() - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - #self.xmpp.add_handler("" % id, self.handlerCreateNodeResponse) - result = iq.send() - if result is None or result == False or result['type'] == 'error': - log.warning("got error instead of config") - return False - if node is not None: - form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x') - else: - form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x') - if not form or form is None: - log.error("No form found.") - return False - return Form(xml=form) + def get_node_config(self, jid, node=None): # if no node, then grab default + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') + if node is None: + iq['pubsub_owner']['default'] + else: + iq['pubsub_owner']['configure']['node'] = node + return iq.send() - def getNodeSubscriptions(self, jid, node): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - subscriptions = ET.Element('subscriptions') - subscriptions.attrib['node'] = node - pubsub.append(subscriptions) - iq = self.xmpp.makeIqGet() - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is None or result == False or result['type'] == 'error': - log.warning("got error instead of config") - return False - else: - results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription') - if results is None: - return False - subs = {} - for sub in results: - subs[sub.get('jid')] = sub.get('subscription') - return subs + def get_node_subscriptions(self, jid, node): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') + iq['pubsub_owner']['subscriptions']['node'] = node + return iq.send() - def getNodeAffiliations(self, jid, node): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - affiliations = ET.Element('affiliations') - affiliations.attrib['node'] = node - pubsub.append(affiliations) - iq = self.xmpp.makeIqGet() - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is None or result == False or result['type'] == 'error': - log.warning("got error instead of config") - return False - else: - results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation') - if results is None: - return False - subs = {} - for sub in results: - subs[sub.get('jid')] = sub.get('affiliation') - return subs + def get_node_affiliations(self, jid, node): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') + iq['pubsub_owner']['affiliations']['node'] = node + return iq.send() - def deleteNode(self, jid, node): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - iq = self.xmpp.makeIqSet() - delete = ET.Element('delete') - delete.attrib['node'] = node - pubsub.append(delete) - iq.append(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - result = iq.send() - if result is not None and result is not False and result['type'] != 'error': - return True - else: - return False + def delete_node(self, jid, node): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') + iq['pubsub_owner']['delete']['node'] = node + return iq.send() + def set_node_config(self, jid, node, config): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') + iq['pubsub_owner']['configure']['node'] = node + iq['pubsub_owner']['configure']['config'] = config + return iq.send() - def setNodeConfig(self, jid, node, config): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - configure = ET.Element('configure') - configure.attrib['node'] = node - config = config.getXML('submit') - configure.append(config) - pubsub.append(configure) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is None or result['type'] == 'error': - return False - return True + def publish(self, jid, node, items=[]): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') + iq['pubsub']['publish']['node'] = node + for id, payload in items: + item = stanza.pubsub.Item() + if id is not None: + item['id'] = id + item['payload'] = payload + iq['pubsub']['publish'].append(item) + return iq.send() - def setItem(self, jid, node, items=[]): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - publish = ET.Element('publish') - publish.attrib['node'] = node - for pub_item in items: - id, payload = pub_item - item = ET.Element('item') - if id is not None: - item.attrib['id'] = id - item.append(payload) - publish.append(item) - pubsub.append(publish) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is None or result is False or result['type'] == 'error': return False - return True + def retract(self, jid, node, item): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') + iq['pubsub']['retract']['node'] = node + item = stanza.pubsub.Item() + item['id'] = item + iq['pubsub']['retract'].append(item) + return iq.send() - def addItem(self, jid, node, items=[]): - return self.setItem(jid, node, items) - - def deleteItem(self, jid, node, item): - pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') - retract = ET.Element('retract') - retract.attrib['node'] = node - itemn = ET.Element('item') - itemn.attrib['id'] = item - retract.append(itemn) - pubsub.append(retract) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is None or result is False or result['type'] == 'error': return False - return True - - def getNodes(self, jid): - response = self.xmpp.plugin['xep_0030'].getItems(jid) - items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') - nodes = {} - if items is not None and items is not False: - for item in items: - nodes[item.get('node')] = item.get('name') - return nodes + def get_nodes(self, jid): + return self.xmpp.plugin['xep_0030'].get_items(jid) def getItems(self, jid, node): - response = self.xmpp.plugin['xep_0030'].getItems(jid, node) - items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') - nodeitems = [] - if items is not None and items is not False: - for item in items: - nodeitems.append(item.get('node')) - return nodeitems - - def addNodeToCollection(self, jid, child, parent=''): - config = self.getNodeConfig(jid, child) - if not config or config is None: - self.lasterror = "Config Error" - return False - try: - config.field['pubsub#collection'].setValue(parent) - except KeyError: - log.warning("pubsub#collection doesn't exist in config, trying to add it") - config.addField('pubsub#collection', value=parent) - if not self.setNodeConfig(jid, child, config): - return False - return True - - def modifyAffiliation(self, ps_jid, node, user_jid, affiliation): - if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'): - raise TypeError - pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') - affs = ET.Element('affiliations') - affs.attrib['node'] = node - aff = ET.Element('affiliation') - aff.attrib['jid'] = user_jid - aff.attrib['affiliation'] = affiliation - affs.append(aff) - pubsub.append(affs) - iq = self.xmpp.makeIqSet(pubsub) - iq.attrib['to'] = ps_jid - iq.attrib['from'] = self.xmpp.boundjid.full - id = iq['id'] - result = iq.send() - if result is None or result is False or result['type'] == 'error': - return False - return True - - def addNodeToCollection(self, jid, child, parent=''): - config = self.getNodeConfig(jid, child) - if not config or config is None: - self.lasterror = "Config Error" - return False - try: - config.field['pubsub#collection'].setValue(parent) - except KeyError: - log.warning("pubsub#collection doesn't exist in config, trying to add it") - config.addField('pubsub#collection', value=parent) - if not self.setNodeConfig(jid, child, config): - return False - return True - - def removeNodeFromCollection(self, jid, child): - self.addNodeToCollection(jid, child, '') + return self.xmpp.plugin['xep_0030'].get_items(jid, node) + def modify_affiliation(self, jid, node, affiliation, user_jid=None): + iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') + iq['pubsub_owner']['affiliations'] + aff = stanza.pubsub.Affiliation() + aff['node'] = node + if user_jid is not None: + aff['jid'] = user_jid + aff['affiliation'] = affiliation + iq['pubsub_owner']['affiliations'].append(aff) + return iq.send() diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py index 9665594..68e4f95 100644 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py +++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py @@ -23,9 +23,15 @@ class Affiliation(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'affiliation' plugin_attrib = name - interfaces = set(('node', 'affiliation')) + interfaces = set(('node', 'affiliation', 'jid')) plugin_attrib_map = {} plugin_tag_map = {} + + def setJid(self, value): + self._setAttr('jid', str(value)) + + def getJid(self): + return JID(self._getAttr('jid')) class Affiliations(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' @@ -36,12 +42,6 @@ class Affiliations(ElementBase): plugin_tag_map = {} subitem = (Affiliation,) - def append(self, affiliation): - if not isinstance(affiliation, Affiliation): - raise TypeError - self.xml.append(affiliation.xml) - return self.iterables.append(affiliation) - registerStanzaPlugin(Pubsub, Affiliations) @@ -164,7 +164,7 @@ class Unsubscribe(ElementBase): namespace = 'http://jabber.org/protocol/pubsub' name = 'unsubscribe' plugin_attrib = name - interfaces = set(('node', 'jid')) + interfaces = set(('node', 'jid', 'subid')) plugin_attrib_map = {} plugin_tag_map = {} diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py index a90780c..a8ced8a 100644 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py +++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py @@ -65,10 +65,19 @@ class OwnerAffiliation(Affiliation): plugin_tag_map = {} class OwnerConfigure(Configure): + name = 'configure' + plugin_attrib = 'configure' namespace = 'http://jabber.org/protocol/pubsub#owner' interfaces = set(('node', 'config')) plugin_attrib_map = {} plugin_tag_map = {} + + def getConfig(self): + return self['form'] + + def setConfig(self, value): + self['form'].setStanzaValues(value.getStanzaValues()) + return self registerStanzaPlugin(PubsubOwner, OwnerConfigure) @@ -78,12 +87,6 @@ class OwnerDefault(OwnerConfigure): plugin_attrib_map = {} plugin_tag_map = {} - def getConfig(self): - return self['form'] - - def setConfig(self, value): - self['form'].setStanzaValues(value.getStanzaValues()) - return self registerStanzaPlugin(PubsubOwner, OwnerDefault) registerStanzaPlugin(OwnerDefault, xep_0004.Form) From 88184ff9556774b1be3dc7fcb97f1f71803d2d61 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Fri, 12 Aug 2011 16:35:36 -0700 Subject: [PATCH 25/25] fixed indenting and merged in exceptions branch --- sleekxmpp/plugins/xep_0060/pubsub.py | 42 +-- sleekxmpp/plugins/xep_0060/stanza/pubsub.py | 302 +++++++++--------- .../plugins/xep_0060/stanza/pubsub_owner.py | 178 +++++------ 3 files changed, 261 insertions(+), 261 deletions(-) diff --git a/sleekxmpp/plugins/xep_0060/pubsub.py b/sleekxmpp/plugins/xep_0060/pubsub.py index 06d148e..5536206 100644 --- a/sleekxmpp/plugins/xep_0060/pubsub.py +++ b/sleekxmpp/plugins/xep_0060/pubsub.py @@ -11,13 +11,13 @@ log = logging.getLogger(__name__) class xep_0060(base.base_plugin): - """ - XEP-0060 Publish Subscribe - """ + """ + XEP-0060 Publish Subscribe + """ - def plugin_init(self): - self.xep = '0060' - self.description = 'Publish-Subscribe' + def plugin_init(self): + self.xep = '0060' + self.description = 'Publish-Subscribe' def create_node(self, jid, node, config=None, ntype=None): iq = IQ(sto=jid, stype='set', sfrom=self.xmpp.jid) @@ -26,9 +26,9 @@ class xep_0060(base.base_plugin): ntype = 'leaf' if config is not None: if 'FORM_TYPE' in submitform.field: - config.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config') - else: - config.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') + config.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config') + else: + config.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') if 'pubsub#node_type' in submitform.field: config.field['pubsub#node_type'].setValue(ntype) else: @@ -48,7 +48,7 @@ class xep_0060(base.base_plugin): iq['pubsub']['subscribe']['jid'] = subscribee return iq.send() - def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None): + def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') iq['pubsub']['unsubscribe']['node'] = node if subscribee is None: @@ -62,7 +62,7 @@ class xep_0060(base.base_plugin): iq['pubsub']['unsubscribe']['subid'] = subid return iq.send() - def get_node_config(self, jid, node=None): # if no node, then grab default + def get_node_config(self, jid, node=None): # if no node, then grab default iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') if node is None: iq['pubsub_owner']['default'] @@ -70,28 +70,28 @@ class xep_0060(base.base_plugin): iq['pubsub_owner']['configure']['node'] = node return iq.send() - def get_node_subscriptions(self, jid, node): + def get_node_subscriptions(self, jid, node): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') iq['pubsub_owner']['subscriptions']['node'] = node return iq.send() - def get_node_affiliations(self, jid, node): + def get_node_affiliations(self, jid, node): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') iq['pubsub_owner']['affiliations']['node'] = node return iq.send() - def delete_node(self, jid, node): + def delete_node(self, jid, node): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get') iq['pubsub_owner']['delete']['node'] = node return iq.send() - def set_node_config(self, jid, node, config): + def set_node_config(self, jid, node, config): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') iq['pubsub_owner']['configure']['node'] = node iq['pubsub_owner']['configure']['config'] = config return iq.send() - def publish(self, jid, node, items=[]): + def publish(self, jid, node, items=[]): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') iq['pubsub']['publish']['node'] = node for id, payload in items: @@ -102,7 +102,7 @@ class xep_0060(base.base_plugin): iq['pubsub']['publish'].append(item) return iq.send() - def retract(self, jid, node, item): + def retract(self, jid, node, item): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') iq['pubsub']['retract']['node'] = node item = stanza.pubsub.Item() @@ -110,11 +110,11 @@ class xep_0060(base.base_plugin): iq['pubsub']['retract'].append(item) return iq.send() - def get_nodes(self, jid): - return self.xmpp.plugin['xep_0030'].get_items(jid) + def get_nodes(self, jid): + return self.xmpp.plugin['xep_0030'].get_items(jid) - def getItems(self, jid, node): - return self.xmpp.plugin['xep_0030'].get_items(jid, node) + def getItems(self, jid, node): + return self.xmpp.plugin['xep_0030'].get_items(jid, node) def modify_affiliation(self, jid, node, affiliation, user_jid=None): iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set') diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py index 68e4f95..d9e5505 100644 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub.py +++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub.py @@ -9,236 +9,236 @@ from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting class Pubsub(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'pubsub' - plugin_attrib = 'pubsub' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'pubsub' + plugin_attrib = 'pubsub' + interfaces = set(tuple()) + plugin_attrib_map = {} + plugin_tag_map = {} registerStanzaPlugin(Iq, Pubsub) class Affiliation(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'affiliation' - plugin_attrib = name - interfaces = set(('node', 'affiliation', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} - + namespace = 'http://jabber.org/protocol/pubsub' + name = 'affiliation' + plugin_attrib = name + interfaces = set(('node', 'affiliation', 'jid')) + plugin_attrib_map = {} + plugin_tag_map = {} + def setJid(self, value): - self._setAttr('jid', str(value)) + self._setAttr('jid', str(value)) - def getJid(self): - return JID(self._getAttr('jid')) + def getJid(self): + return JID(self._getAttr('jid')) class Affiliations(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'affiliations' - plugin_attrib = 'affiliations' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Affiliation,) + namespace = 'http://jabber.org/protocol/pubsub' + name = 'affiliations' + plugin_attrib = 'affiliations' + interfaces = set(tuple()) + plugin_attrib_map = {} + plugin_tag_map = {} + subitem = (Affiliation,) registerStanzaPlugin(Pubsub, Affiliations) class Subscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscription' - plugin_attrib = name - interfaces = set(('jid', 'node', 'subscription', 'subid')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'subscription' + plugin_attrib = name + interfaces = set(('jid', 'node', 'subscription', 'subid')) + plugin_attrib_map = {} + plugin_tag_map = {} - def setjid(self, value): - self._setattr('jid', str(value)) + def setjid(self, value): + self._setattr('jid', str(value)) - def getjid(self): - return jid(self._getattr('jid')) + def getjid(self): + return jid(self._getattr('jid')) registerStanzaPlugin(Pubsub, Subscription) class Subscriptions(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscriptions' - plugin_attrib = 'subscriptions' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Subscription,) + namespace = 'http://jabber.org/protocol/pubsub' + name = 'subscriptions' + plugin_attrib = 'subscriptions' + interfaces = set(tuple()) + plugin_attrib_map = {} + plugin_tag_map = {} + subitem = (Subscription,) registerStanzaPlugin(Pubsub, Subscriptions) class SubscribeOptions(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscribe-options' - plugin_attrib = 'suboptions' - plugin_attrib_map = {} - plugin_tag_map = {} - interfaces = set(('required',)) + namespace = 'http://jabber.org/protocol/pubsub' + name = 'subscribe-options' + plugin_attrib = 'suboptions' + plugin_attrib_map = {} + plugin_tag_map = {} + interfaces = set(('required',)) registerStanzaPlugin(Subscription, SubscribeOptions) class Item(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'item' - plugin_attrib = name - interfaces = set(('id', 'payload')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'item' + plugin_attrib = name + interfaces = set(('id', 'payload')) + plugin_attrib_map = {} + plugin_tag_map = {} - def setPayload(self, value): - self.xml.append(value) + def setPayload(self, value): + self.xml.append(value) - def getPayload(self): - childs = self.xml.getchildren() - if len(childs) > 0: - return childs[0] + def getPayload(self): + childs = self.xml.getchildren() + if len(childs) > 0: + return childs[0] - def delPayload(self): - for child in self.xml.getchildren(): - self.xml.remove(child) + def delPayload(self): + for child in self.xml.getchildren(): + self.xml.remove(child) class Items(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'items' - plugin_attrib = 'items' - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Item,) + namespace = 'http://jabber.org/protocol/pubsub' + name = 'items' + plugin_attrib = 'items' + interfaces = set(('node',)) + plugin_attrib_map = {} + plugin_tag_map = {} + subitem = (Item,) registerStanzaPlugin(Pubsub, Items) class Create(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'create' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'create' + plugin_attrib = name + interfaces = set(('node',)) + plugin_attrib_map = {} + plugin_tag_map = {} registerStanzaPlugin(Pubsub, Create) #class Default(ElementBase): -# namespace = 'http://jabber.org/protocol/pubsub' -# name = 'default' -# plugin_attrib = name -# interfaces = set(('node', 'type')) -# plugin_attrib_map = {} -# plugin_tag_map = {} +# namespace = 'http://jabber.org/protocol/pubsub' +# name = 'default' +# plugin_attrib = name +# interfaces = set(('node', 'type')) +# plugin_attrib_map = {} +# plugin_tag_map = {} # -# def getType(self): -# t = self._getAttr('type') -# if not t: t == 'leaf' -# return t +# def getType(self): +# t = self._getAttr('type') +# if not t: t == 'leaf' +# return t # #registerStanzaPlugin(Pubsub, Default) class Publish(Items): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'publish' - plugin_attrib = name - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = (Item,) + namespace = 'http://jabber.org/protocol/pubsub' + name = 'publish' + plugin_attrib = name + interfaces = set(('node',)) + plugin_attrib_map = {} + plugin_tag_map = {} + subitem = (Item,) registerStanzaPlugin(Pubsub, Publish) class Retract(Items): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'retract' - plugin_attrib = name - interfaces = set(('node', 'notify')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'retract' + plugin_attrib = name + interfaces = set(('node', 'notify')) + plugin_attrib_map = {} + plugin_tag_map = {} registerStanzaPlugin(Pubsub, Retract) class Unsubscribe(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'unsubscribe' - plugin_attrib = name - interfaces = set(('node', 'jid', 'subid')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'unsubscribe' + plugin_attrib = name + interfaces = set(('node', 'jid', 'subid')) + plugin_attrib_map = {} + plugin_tag_map = {} - def setJid(self, value): - self._setAttr('jid', str(value)) + def setJid(self, value): + self._setAttr('jid', str(value)) - def getJid(self): - return JID(self._getAttr('jid')) + def getJid(self): + return JID(self._getAttr('jid')) registerStanzaPlugin(Pubsub, Unsubscribe) class Subscribe(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'subscribe' - plugin_attrib = name - interfaces = set(('node', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'subscribe' + plugin_attrib = name + interfaces = set(('node', 'jid')) + plugin_attrib_map = {} + plugin_tag_map = {} - def setJid(self, value): - self._setAttr('jid', str(value)) + def setJid(self, value): + self._setAttr('jid', str(value)) - def getJid(self): - return JID(self._getAttr('jid')) + def getJid(self): + return JID(self._getAttr('jid')) registerStanzaPlugin(Pubsub, Subscribe) class Configure(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'configure' - plugin_attrib = name - interfaces = set(('node', 'type')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'configure' + plugin_attrib = name + interfaces = set(('node', 'type')) + plugin_attrib_map = {} + plugin_tag_map = {} - def getType(self): - t = self._getAttr('type') - if not t: t == 'leaf' - return t + def getType(self): + t = self._getAttr('type') + if not t: t == 'leaf' + return t registerStanzaPlugin(Pubsub, Configure) registerStanzaPlugin(Configure, xep_0004.Form) class Options(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub' - name = 'options' - plugin_attrib = 'options' - interfaces = set(('jid', 'node', 'options')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub' + name = 'options' + plugin_attrib = 'options' + interfaces = set(('jid', 'node', 'options')) + plugin_attrib_map = {} + plugin_tag_map = {} - def __init__(self, *args, **kwargs): - ElementBase.__init__(self, *args, **kwargs) + def __init__(self, *args, **kwargs): + ElementBase.__init__(self, *args, **kwargs) - def getOptions(self): - config = self.xml.find('{jabber:x:data}x') - form = xep_0004.Form() - if config is not None: - form.fromXML(config) - return form + def getOptions(self): + config = self.xml.find('{jabber:x:data}x') + form = xep_0004.Form() + if config is not None: + form.fromXML(config) + return form - def setOptions(self, value): - self.xml.append(value.getXML()) - return self + def setOptions(self, value): + self.xml.append(value.getXML()) + return self - def delOptions(self): - config = self.xml.find('{jabber:x:data}x') - self.xml.remove(config) + def delOptions(self): + config = self.xml.find('{jabber:x:data}x') + self.xml.remove(config) - def setJid(self, value): - self._setAttr('jid', str(value)) + def setJid(self, value): + self._setAttr('jid', str(value)) - def getJid(self): - return JID(self._getAttr('jid')) + def getJid(self): + return JID(self._getAttr('jid')) registerStanzaPlugin(Pubsub, Options) registerStanzaPlugin(Subscribe, Options) diff --git a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py index a8ced8a..201dc90 100644 --- a/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py +++ b/sleekxmpp/plugins/xep_0060/stanza/pubsub_owner.py @@ -9,147 +9,147 @@ from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation, Configure, Subscriptions class PubsubOwner(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'pubsub' - plugin_attrib = 'pubsub_owner' - interfaces = set(tuple()) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + name = 'pubsub' + plugin_attrib = 'pubsub_owner' + interfaces = set(tuple()) + plugin_attrib_map = {} + plugin_tag_map = {} registerStanzaPlugin(Iq, PubsubOwner) class DefaultConfig(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'default' - plugin_attrib = 'default' - interfaces = set(('node', 'type', 'config')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + name = 'default' + plugin_attrib = 'default' + interfaces = set(('node', 'type', 'config')) + plugin_attrib_map = {} + plugin_tag_map = {} - def __init__(self, *args, **kwargs): - ElementBase.__init__(self, *args, **kwargs) + def __init__(self, *args, **kwargs): + ElementBase.__init__(self, *args, **kwargs) - def getType(self): - t = self._getAttr('type') - if not t: t = 'leaf' - return t + def getType(self): + t = self._getAttr('type') + if not t: t = 'leaf' + return t - def getConfig(self): - return self['form'] + def getConfig(self): + return self['form'] - def setConfig(self, value): - self['form'].setStanzaValues(value.getStanzaValues()) - return self + def setConfig(self, value): + self['form'].setStanzaValues(value.getStanzaValues()) + return self registerStanzaPlugin(PubsubOwner, DefaultConfig) registerStanzaPlugin(DefaultConfig, xep_0004.Form) class OwnerAffiliations(Affiliations): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + interfaces = set(('node')) + plugin_attrib_map = {} + plugin_tag_map = {} - def append(self, affiliation): - if not isinstance(affiliation, OwnerAffiliation): - raise TypeError - self.xml.append(affiliation.xml) - return self.affiliations.append(affiliation) + def append(self, affiliation): + if not isinstance(affiliation, OwnerAffiliation): + raise TypeError + self.xml.append(affiliation.xml) + return self.affiliations.append(affiliation) registerStanzaPlugin(PubsubOwner, OwnerAffiliations) class OwnerAffiliation(Affiliation): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('affiliation', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + interfaces = set(('affiliation', 'jid')) + plugin_attrib_map = {} + plugin_tag_map = {} class OwnerConfigure(Configure): name = 'configure' plugin_attrib = 'configure' - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node', 'config')) - plugin_attrib_map = {} - plugin_tag_map = {} - + namespace = 'http://jabber.org/protocol/pubsub#owner' + interfaces = set(('node', 'config')) + plugin_attrib_map = {} + plugin_tag_map = {} + def getConfig(self): - return self['form'] + return self['form'] - def setConfig(self, value): - self['form'].setStanzaValues(value.getStanzaValues()) - return self + def setConfig(self, value): + self['form'].setStanzaValues(value.getStanzaValues()) + return self registerStanzaPlugin(PubsubOwner, OwnerConfigure) class OwnerDefault(OwnerConfigure): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node', 'config')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + interfaces = set(('node', 'config')) + plugin_attrib_map = {} + plugin_tag_map = {} registerStanzaPlugin(PubsubOwner, OwnerDefault) registerStanzaPlugin(OwnerDefault, xep_0004.Form) class OwnerDelete(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'delete' - plugin_attrib = 'delete' - plugin_attrib_map = {} - plugin_tag_map = {} - interfaces = set(('node',)) + namespace = 'http://jabber.org/protocol/pubsub#owner' + name = 'delete' + plugin_attrib = 'delete' + plugin_attrib_map = {} + plugin_tag_map = {} + interfaces = set(('node',)) registerStanzaPlugin(PubsubOwner, OwnerDelete) class OwnerPurge(ElementBase, OptionalSetting): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'purge' - plugin_attrib = name - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + name = 'purge' + plugin_attrib = name + plugin_attrib_map = {} + plugin_tag_map = {} registerStanzaPlugin(PubsubOwner, OwnerPurge) class OwnerRedirect(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'redirect' - plugin_attrib = name - interfaces = set(('node', 'jid')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + name = 'redirect' + plugin_attrib = name + interfaces = set(('node', 'jid')) + plugin_attrib_map = {} + plugin_tag_map = {} - def setJid(self, value): - self._setAttr('jid', str(value)) + def setJid(self, value): + self._setAttr('jid', str(value)) - def getJid(self): - return JID(self._getAttr('jid')) + def getJid(self): + return JID(self._getAttr('jid')) registerStanzaPlugin(OwnerDelete, OwnerRedirect) class OwnerSubscriptions(Subscriptions): - namespace = 'http://jabber.org/protocol/pubsub#owner' - interfaces = set(('node',)) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + interfaces = set(('node',)) + plugin_attrib_map = {} + plugin_tag_map = {} - def append(self, subscription): - if not isinstance(subscription, OwnerSubscription): - raise TypeError - self.xml.append(subscription.xml) - return self.subscriptions.append(subscription) + def append(self, subscription): + if not isinstance(subscription, OwnerSubscription): + raise TypeError + self.xml.append(subscription.xml) + return self.subscriptions.append(subscription) registerStanzaPlugin(PubsubOwner, OwnerSubscriptions) class OwnerSubscription(ElementBase): - namespace = 'http://jabber.org/protocol/pubsub#owner' - name = 'subscription' - plugin_attrib = name - interfaces = set(('jid', 'subscription')) - plugin_attrib_map = {} - plugin_tag_map = {} + namespace = 'http://jabber.org/protocol/pubsub#owner' + name = 'subscription' + plugin_attrib = name + interfaces = set(('jid', 'subscription')) + plugin_attrib_map = {} + plugin_tag_map = {} - def setJid(self, value): - self._setAttr('jid', str(value)) + def setJid(self, value): + self._setAttr('jid', str(value)) - def getJid(self): - return JID(self._getAttr('from')) + def getJid(self): + return JID(self._getAttr('from'))