Compare commits

..

66 Commits

Author SHA1 Message Date
Tom Nichols
8bdfa77024 Merge branch 'hacks' of git@github.com:tomstrummer/SleekXMPP into hacks 2010-06-28 11:10:34 -04:00
Tom Nichols
15ac3e9fba race condition where we were transitioning to 'disconnected' and immediately reconnecting in another thread before the socket.close call occurred. Now we're locking the state machine until the disconnect routine completes. 2010-06-28 11:06:26 -04:00
Tom Nichols
e8d37b409c make the scheduler a daemon thread to prevent hanging when the main thread exits. 2010-06-28 11:04:18 -04:00
Tom Nichols
898f96f265 print the traceback if we can't load a plugin for some reason 2010-06-28 11:03:46 -04:00
Tom Nichols
4fccd77685 Merge branch 'hacks' of git@github.com:tomstrummer/SleekXMPP into hacks 2010-06-08 10:40:15 -04:00
Thom Nichols
bf2bf29fc6 fixed mis-named variable, doc typo and using conformant Condition methods. 2010-06-08 09:02:51 -04:00
Thom Nichols
34dc236126 added documentation for transition_ctx and removed some superfluous comment lines 2010-06-07 14:41:42 -04:00
Thom Nichols
9464736551 added __str__ 2010-06-07 13:58:15 -04:00
Thom Nichols
47f1fb1690 context manager now returns a boolean 'result' as the context variable to indicate whether the transition timed out or if you are actually locked when entering the context body 2010-06-07 13:43:37 -04:00
Thom Nichols
66cf0c2021 context manager is working but there's a fatal flaw: inside the body of the 'with' statement, there's no way to tell whether or not the transition occurred or timed out. 2010-06-07 13:16:02 -04:00
Thom Nichols
e7c37c4ec5 connect uses the new function-on-state-transition so when the connect method returns you are guaranteed to be either in the 'connected' or 'disconnected' state. Could remove the 'connecting' state except uses it. 2010-06-04 17:00:51 -04:00
Thom Nichols
919c8c5633 tweaked connectTCP call slightly to reduce possibility of 'connecting' state limbo 2010-06-03 15:21:26 -04:00
Thom Nichols
f54501a346 added function execution on transition, and more unit tests. 2010-06-03 14:12:06 -04:00
Thom Nichols
d20cd6b3e6 added function execution on transition, and more unit tests. 2010-06-03 13:51:11 -04:00
Thom Nichols
da6e1e47dc whups, somehow I lost the 'connecting' lock in connect() 2010-06-03 08:09:09 -04:00
Thom Nichols
2f0f18a8c6 added function to retrieve the current state 2010-06-03 08:07:56 -04:00
Thom Nichols
1c32668e18 fixed quiesce algorithm; state transition if connect fails; note about use_tls instance variable. 2010-06-03 07:47:27 -04:00
Tom Nichols
77bff9cce7 Merge branch 'hacks' of git@github.com:tomstrummer/SleekXMPP into hacks 2010-06-02 15:45:51 -04:00
Thom Nichols
1f3cfb98f1 Merge branch 'master' into hacks 2010-06-02 14:18:46 -04:00
Thom Nichols
4295a66c70 reconnection quiesce logic 2010-06-02 14:18:09 -04:00
Thom Nichols
8227affd7f removed unnecessary flags and arguments from disconnect method 2010-06-02 14:17:36 -04:00
Thom Nichols
3a2f989c5e Merge branch 'master' into hacks 2010-06-02 14:15:07 -04:00
Nathan Fritz
85a2715c7d hack fix for session before bind 2010-06-03 01:30:24 +08:00
Nathan Fritz
b03e6168a8 if binding and session are advertised in the same go, do session first 2010-06-03 01:30:23 +08:00
Brian Beggs
2a43f59a58 added try/catch block to plugin loading 2010-06-03 01:29:49 +08:00
Brian Beggs
184f7cb8a4 moddified plugin loading so plugins located outside of the plugins directory in sleek may be loaded. Added optional argument pluginModule that is a string that represents the module the desired plugin should be loaded from.
An exception on plugin loading now also will not cause the program to exit.  The exception is caught and loading of other plugins contains.
2010-06-03 01:29:49 +08:00
Brian Beggs
e1aa4d0b93 Added .pydevproject to the .gitignore 2010-06-03 01:29:48 +08:00
Thom Nichols
7930ed22f2 overhauled state machine. Now allows for atomic transitions.
Next step: atomic function calls (and maybe 'handlers') on state transition.
2010-06-02 12:39:54 -04:00
Thom Nichols
060b4c3938 Merge branch 'hacks' of github.com:tomstrummer/SleekXMPP 2010-06-01 22:55:01 -04:00
Thom Nichols
49f5767aea merged changes from fritzy 2010-06-01 22:54:30 -04:00
Thom Nichols
4eb210bff5 fixed some major reconnection errors 2010-06-01 22:51:49 -04:00
Thom Nichols
1780ca900a merged a lot of fritzy's changes 2010-06-01 22:40:37 -04:00
Nathan Fritz
e6c2fde283 included jobs plugin 2010-06-01 22:07:53 +08:00
Nathan Fritz
ecf902bf16 Scheduler waits too longer, and pubsubstate registration was backwards 2010-06-01 22:07:53 +08:00
Lance stout
d76c0931ef Added missing 'internal-server-error' condition to error stanza interface. 2010-06-01 22:07:53 +08:00
Lance stout
e18793152f Touched up the style of creating an Iq stanza. 2010-06-01 22:07:53 +08:00
Lance stout
e388680269 Added 'resource-constraint' to the list of error conditions. 2010-06-01 22:07:53 +08:00
Lance Stout
bee42e4a2f Added unit tests for the new XEP-0030 stanza objects. All pass.
(cherry picked from commit e1b814f27bf160f20bb30c315ca30769d217482d)
2010-06-01 22:07:53 +08:00
Lance Stout
8e3227ae5e Updated the XEP-0030 plugin to work with stanza objects instead of manipulating XML directly.
Four new events have been added:
  disco_info - A disco#info result has been received
  disco_info_request - A disco#info request has been received
  disco_items - A disco#items result has been received
  disco_items_request - A disco#items request has been received

For disco_info_request and disco_items_request two default handlers are registered. These handlers will only run if they are the only handler for these two events so that multiple responses are not returned and cause errors.

In your own handlers for these two events, you can call the default handlers to preserve the static node behaviour as so:
  self.plugin['xep_0030'].handle_disco_info(iq, True)

The forwarded=True will disable the check for other registered handlers.

Agents can now dynamically respond to disco requests by using these events.
(cherry picked from commit 0fc3381492a8bd75e6a9858539a972334881d8ff)
2010-06-01 22:07:53 +08:00
Nathan Fritz
257bcadd96 control-c fixes 2010-06-01 22:07:52 +08:00
Nathan Fritz
3e5cdc8664 added pubsubjobs test 2010-06-01 22:07:52 +08:00
Nathan Fritz
194e6bcb51 added pubsub state stanzas and scheduled events 2010-06-01 22:07:52 +08:00
Nathan Fritz
2e7024419a adding scheduler 2010-06-01 22:07:52 +08:00
Nathan Fritz
5235313aab added muc room to readme 2010-06-01 22:07:51 +08:00
Nathan Fritz
a2719b0bb0 plugins now are checked for post_init having ran when process() is called 2010-06-01 22:07:51 +08:00
Hernan E Grecco
71ad715caa Changed example.py to register first Xep_0030.
This a simple fix to prevent getting a key error as many plugins add
features to Xep_0030. A better fix would be to call pos_init after all
 plugins are loaded. An even better fix would be to define dependencies
for each plugin and registering on demand.
2010-06-01 22:07:51 +08:00
Hernan E Grecco
d452085049 Fixed error registering a plugin. To add a feature to another plugin, it should look into xmpp.plugin dict 2010-06-01 22:07:51 +08:00
Nathan Fritz
8b3b8aca9e updated README, index fix for component 2010-06-01 22:07:51 +08:00
Lance Stout
e00dea7c0c Added a flag to registerPlugin to control calling the plugin's post_init method. 2010-06-01 22:07:51 +08:00
Lance Stout
520bf72e11 Modified the return values for several methods so that they can be chained.
For example:

    iq.reply().error().setPayload(something.xml).send()
2010-06-01 22:07:51 +08:00
Lance Stout
040f426f1a Added the error attribute 'code' to the Error object interface. 2010-06-01 22:07:51 +08:00
Nathan Fritz
226b0e4297 added plugin indexing to components 2010-06-01 22:07:50 +08:00
Nathan Fritz
0b2cd176b1 added test_events and testing new del_event_handler 2010-06-01 22:07:50 +08:00
Lance Stout
56b5cbe5b1 Added del_event_handler to remove handler functions for a given event.
All registered handlers for the event which use the given function will
be removed.

Using this method allows agents to reconfigure their behaviour on the fly
without needing to add extra state information to event handling functions.
2010-06-01 22:07:50 +08:00
Thom Nichols
3e83b16a58 Merge branch 'hacks' of github.com:tomstrummer/SleekXMPP 2010-05-18 16:11:27 -04:00
Tom Nichols
de4d611d30 fixed SRV query - should use dns.rdatatype.SRV 2010-05-14 11:22:17 -04:00
Tom Nichols
e8d0fc37dc updated ignore file 2010-05-14 11:21:53 -04:00
Tom Nichols
3f41fdd231 fixed SRV query - should use dns.rdatatype.SRV 2010-05-13 14:39:32 -04:00
Tom Nichols
8e95ae2948 attempt to add support for self-signed certificate certs 2010-05-13 13:49:00 -04:00
Tom Nichols
341c110b6a Merge branch 'master' of git@github.com:tomstrummer/SleekXMPP into hacks 2010-05-13 13:48:27 -04:00
Nathan Fritz
7522839141 added test for unsolicided unavailable presence and fixed bug to make it pass 2010-05-14 01:47:19 +08:00
Nathan Fritz
4c410dd48a fixed a rather large memory leak 2010-05-14 01:47:19 +08:00
Tom Nichols
a92075a659 merged 2010-05-12 16:54:01 -04:00
Tom Nichols
7552efee5c some reconnetion fixes 2010-05-12 16:51:14 -04:00
Tom Nichols
6bc6ebb95d updated ignore file 2010-05-12 16:46:23 -04:00
Brian Beggs
e0c32b6d9b Fixes for disconnection problems detailed in http://github.com/fritzy/SleekXMPP/issues/#issue/20
Fixes to both ClientXMPP & xmlstream.  ClientXMPP was not tracking the changes to authenticated and sessionstarted after the client was disconnected.

xmlstream had some funkyness with state in the _process method that was cleaned up and hopefully made a little cleaner.

Also changed a DNS issue that was occuring that rendered me unable to disconnect.  I would recieve the following error upon reconnect.
Exception in thread process:
Exception in thread process:
Traceback (most recent call last):
  File "/usr/local/lib/python2.6/threading.py", line 532, in __bootstrap_inner
    self.run()
  File "/usr/local/lib/python2.6/threading.py", line 484, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/xmlstream/xmlstream.py", line 202, in _process
    self.reconnect()
  File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/__init__.py", line 134, in reconnect
    XMLStream.reconnect(self)
  File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/xmlstream/xmlstream.py", line 289, in reconnect
    self.connect()
  File "/home/macdiesel/tmp/workspace/SleekXMPP/sleekxmpp/__init__.py", line 99, in connect
    answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, "SRV")
  File "/usr/local/lib/python2.6/site-packages/dns/resolver.py", line 732, in query
    return get_default_resolver().query(qname, rdtype, rdclass, tcp, source)
  File "/usr/local/lib/python2.6/site-packages/dns/resolver.py", line 617, in query
    source=source)
  File "/usr/local/lib/python2.6/site-packages/dns/query.py", line 113, in udp
    wire = q.to_wire()
  File "/usr/local/lib/python2.6/site-packages/dns/message.py", line 404, in to_wire
    r.add_question(rrset.name, rrset.rdtype, rrset.rdclass)
  File "/usr/local/lib/python2.6/site-packages/dns/renderer.py", line 152, in add_question
    self.output.write(struct.pack("!HH", rdtype, rdclass))
TypeError: unsupported operand type(s) for &: 'unicode' and 'long'

Seems I was getting this error when calling line 99 in ClientXMPP.  You can't bit-shift a 1 and a string and this is why this error is coming up. I removed the "SRV" argument and used the default of 1.  not sure exactly what this should be so it may need to be fixed back before it's merged back to trunk.

The line in question:
answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, "SRV")
2010-05-13 04:43:25 +08:00
218 changed files with 5522 additions and 25704 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
*.pyc
.project
build/
*.swp
.pydevproject
.settings

10
INSTALL
View File

@@ -1,12 +1,8 @@
Pre-requisites:
- Python 3.1 or 2.6
Python 3.1 or 2.6
Install:
> python3 setup.py install
python3 setup.py install
Root install:
> sudo python3 setup.py install
To test:
> cd examples
> python echo_client.py -v -j [USER@example.com] -p [PASSWORD]
sudo python3 setup.py install

123
LICENSE
View File

@@ -1,125 +1,4 @@
Copyright (c) 2010 Nathanael C. Fritz
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.
Licences of Bundled Third Pary Code
-----------------------------------
dateutil - Extensions to the standard python 2.3+ datetime module.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
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_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
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
Copyright (c) 2010 ICRL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

39
MANIFEST Normal file
View File

@@ -0,0 +1,39 @@
setup.py
sleekxmpp/__init__.py
sleekxmpp/basexmpp.py
sleekxmpp/clientxmpp.py
sleekxmpp/example.py
sleekxmpp/plugins/__init__.py
sleekxmpp/plugins/base.py
sleekxmpp/plugins/gmail_notify.py
sleekxmpp/plugins/xep_0004.py
sleekxmpp/plugins/xep_0009.py
sleekxmpp/plugins/xep_0030.py
sleekxmpp/plugins/xep_0045.py
sleekxmpp/plugins/xep_0050.py
sleekxmpp/plugins/xep_0060.py
sleekxmpp/plugins/xep_0078.py
sleekxmpp/plugins/xep_0086.py
sleekxmpp/plugins/xep_0092.py
sleekxmpp/plugins/xep_0199.py
sleekxmpp/stanza/__init__.py
sleekxmpp/stanza/iq.py
sleekxmpp/stanza/message.py
sleekxmpp/stanza/presence.py
sleekxmpp/xmlstream/__init__.py
sleekxmpp/xmlstream/stanzabase.py
sleekxmpp/xmlstream/statemachine.py
sleekxmpp/xmlstream/test.py
sleekxmpp/xmlstream/testclient.py
sleekxmpp/xmlstream/xmlstream.py
sleekxmpp/xmlstream/handler/__init__.py
sleekxmpp/xmlstream/handler/base.py
sleekxmpp/xmlstream/handler/callback.py
sleekxmpp/xmlstream/handler/waiter.py
sleekxmpp/xmlstream/handler/xmlcallback.py
sleekxmpp/xmlstream/handler/xmlwaiter.py
sleekxmpp/xmlstream/matcher/__init__.py
sleekxmpp/xmlstream/matcher/base.py
sleekxmpp/xmlstream/matcher/many.py
sleekxmpp/xmlstream/matcher/xmlmask.py
sleekxmpp/xmlstream/matcher/xpath.py

8
README
View File

@@ -4,11 +4,6 @@ Hosted at http://wiki.github.com/fritzy/SleekXMPP/
Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre
If you're coming here from The Definitive Guide, please read http://wiki.github.com/fritzy/SleekXMPP/xmpp-the-definitive-guide
Requirements:
We try to keep requirements to a minimum, but we suggest that you install http://dnspython.org although it isn't strictly required.
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.
SleekXMPP has several design goals/philosophies:
- Low number of dependencies.
- Every XEP as a plugin.
@@ -42,9 +37,6 @@ Main Author: Nathan Fritz fritz@netflint.net
Contributors: Kevin Smith & Lance Stout
Patches: Remko Tronçon
Dave Cridland, for his Suelta SASL library.
Feel free to add fritzy@netflint.net to your roster for direct support and comments.
Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion.
Join sleek@conference.jabber.org for groupchat discussion.

48
example.py Normal file
View File

@@ -0,0 +1,48 @@
# coding=utf8
import sleekxmpp
import logging
from optparse import OptionParser
import time
import sys
if sys.version_info < (3,0):
reload(sys)
sys.setdefaultencoding('utf8')
class Example(sleekxmpp.ClientXMPP):
def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.message)
def start(self, event):
self.getRoster()
self.sendPresence()
def message(self, msg):
msg.reply("Thanks for sending\n%(body)s" % msg).send()
if __name__ == '__main__':
#parse command line arguements
optp = OptionParser()
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)
optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
opts,args = optp.parse_args()
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
xmpp = Example('user@gmail.com/sleekxmpp', 'password')
xmpp.registerPlugin('xep_0030')
xmpp.registerPlugin('xep_0004')
xmpp.registerPlugin('xep_0060')
xmpp.registerPlugin('xep_0199')
if xmpp.connect(('talk.google.com', 5222)):
xmpp.process(threaded=False)
print("done")
else:
print("Unable to connect.")

View File

@@ -1,199 +0,0 @@
#!/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 CommandBot(sleekxmpp.ClientXMPP):
"""
A simple SleekXMPP bot that provides a basic
adhoc command.
"""
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)
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()
# We add the command after session_start has fired
# to ensure that the correct full JID is used.
# If using a component, may also pass jid keyword parameter.
self['xep_0050'].add_command(node='greeting',
name='Greeting',
handler=self._handle_command)
def _handle_command(self, iq, session):
"""
Respond to the intial request for a command.
Arguments:
iq -- The iq stanza containing the command request.
session -- A dictionary of data relevant to the command
session. Additional, custom data may be saved
here to persist across handler callbacks.
"""
form = self['xep_0004'].makeForm('form', 'Greeting')
form.addField(var='greeting',
ftype='text-single',
label='Your greeting')
session['payload'] = form
session['next'] = self._handle_command_complete
session['has_next'] = False
# Other useful session values:
# session['to'] -- The JID that received the
# command request.
# session['from'] -- The JID that sent the
# command request.
# session['has_next'] = True -- There are more steps to complete
# session['allow_complete'] = True -- Allow user to finish immediately
# and possibly skip steps
# session['cancel'] = handler -- Assign a handler for if the user
# cancels the command.
# session['notes'] = [ -- Add informative notes about the
# ('info', 'Info message'), command's results.
# ('warning', 'Warning message'),
# ('error', 'Error message')]
return session
def _handle_command_complete(self, payload, session):
"""
Process a command result from the user.
Arguments:
payload -- Either a single item, such as a form, or a list
of items or forms if more than one form was
provided to the user. The payload may be any
stanza, such as jabber:x:oob for out of band
data, or jabber:x:data for typical data forms.
session -- A dictionary of data relevant to the command
session. Additional, custom data may be saved
here to persist across handler callbacks.
"""
# In this case (as is typical), the payload is a form
form = payload
greeting = form['values']['greeting']
self.send_message(mto=session['from'],
mbody="%s, World!" % greeting)
# Having no return statement is the same as unsetting the 'payload'
# and 'next' session values and returning the session.
# Unless it is the final step, always return the session dictionary.
session['payload'] = None
session['next'] = None
return session
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 CommandBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = CommandBot(opts.jid, opts.password)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0050') # Adhoc Commands
# 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.")

View File

@@ -1,208 +0,0 @@
#!/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 CommandUserBot(sleekxmpp.ClientXMPP):
"""
A simple SleekXMPP bot that uses the adhoc command
provided by the adhoc_provider.py example.
"""
def __init__(self, jid, password, other, greeting):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.command_provider = other
self.greeting = greeting
# 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)
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()
# We first create a session dictionary containing:
# 'next' -- the handler to execute on a successful response
# 'error' -- the handler to execute if an error occurs
# The session may also contain custom data.
session = {'greeting': self.greeting,
'next': self._command_start,
'error': self._command_error}
self['xep_0050'].start_command(jid=self.command_provider,
node='greeting',
session=session)
def message(self, msg):
"""
Process incoming message stanzas.
Arguments:
msg -- The received message stanza.
"""
logging.info(msg['body'])
def _command_start(self, iq, session):
"""
Process the initial command result.
Arguments:
iq -- The iq stanza containing the command result.
session -- A dictionary of data relevant to the command
session. Additional, custom data may be saved
here to persist across handler callbacks.
"""
# The greeting command provides a form with a single field:
# <x xmlns="jabber:x:data" type="form">
# <field var="greeting"
# type="text-single"
# label="Your greeting" />
# </x>
form = self['xep_0004'].makeForm(ftype='submit')
form.addField(var='greeting',
value=session['greeting'])
session['payload'] = form
# We don't need to process the next result.
session['next'] = None
# Other options include using:
# continue_command() -- Continue to the next step in the workflow
# cancel_command() -- Stop command execution.
self['xep_0050'].complete_command(session)
def _command_error(self, iq, session):
"""
Process an error that occurs during command execution.
Arguments:
iq -- The iq stanza containing the error.
session -- A dictionary of data relevant to the command
session. Additional, custom data may be saved
here to persist across handler callbacks.
"""
logging.error("COMMAND: %s %s" % (iq['error']['condition'],
iq['error']['text']))
# Terminate the command's execution and clear its session.
# The session will automatically be cleared if no error
# handler is provided.
self['xep_0050'].terminate_command(session)
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")
optp.add_option("-o", "--other", dest="other",
help="JID providing commands")
optp.add_option("-g", "--greeting", dest="greeting",
help="Greeting")
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: ")
if opts.other is None:
opts.other = raw_input("JID Providing Commands: ")
if opts.greeting is None:
opts.other = raw_input("Greeting: ")
# Setup the CommandBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = CommandUserBot(opts.jid, opts.password, opts.other, opts.greeting)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0050') # Adhoc Commands
# 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.")

View File

@@ -1,10 +0,0 @@
<config xmlns="sleekxmpp:config">
<jid>component.localhost</jid>
<secret>ssshh</secret>
<server>localhost</server>
<port>8888</port>
<query xmlns="jabber:iq:roster">
<item jid="user@example.com" subscription="both" />
</query>
</config>

View File

@@ -1,190 +0,0 @@
#!/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
from optparse import OptionParser
import sleekxmpp
from sleekxmpp.componentxmpp import ComponentXMPP
from sleekxmpp.stanza.roster import Roster
from sleekxmpp.xmlstream import ElementBase
from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin
# 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 Config(ElementBase):
"""
In order to make loading and manipulating an XML config
file easier, we will create a custom stanza object for
our config XML file contents. See the documentation
on stanza objects for more information on how to create
and use stanza objects and stanza plugins.
We will reuse the IQ roster query stanza to store roster
information since it already exists.
Example config XML:
<config xmlns="sleekxmpp:config">
<jid>component.localhost</jid>
<secret>ssshh</secret>
<server>localhost</server>
<port>8888</port>
<query xmlns="jabber:iq:roster">
<item jid="user@example.com" subscription="both" />
</query>
</config>
"""
name = "config"
namespace = "sleekxmpp:config"
interfaces = set(('jid', 'secret', 'server', 'port'))
sub_interfaces = interfaces
registerStanzaPlugin(Config, Roster)
class ConfigComponent(ComponentXMPP):
"""
A simple SleekXMPP component that uses an external XML
file to store its configuration data. To make testing
that the component works, it will also echo messages sent
to it.
"""
def __init__(self, config):
"""
Create a ConfigComponent.
Arguments:
config -- The XML contents of the config file.
config_file -- The XML config file object itself.
"""
ComponentXMPP.__init__(self, config['jid'],
config['secret'],
config['server'],
config['port'])
# Store the roster information.
self.roster = config['roster']['items']
# The session_start event will be triggered when
# the component 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
# broadcast any needed initial presence stanzas.
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.
The typical action for the session_start event in a component
is to broadcast presence stanzas to all subscribers to the
component. Note that the component does not have a roster
provided by the XMPP server. In this case, we have possibly
saved a roster in the component's configuration file.
Since the component may use any number of JIDs, you should
also include the JID that is sending the presence.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
for jid in self.roster:
if self.roster[jid]['subscription'] != 'none':
self.sendPresence(pfrom=self.jid, pto=jid)
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.
Since a component may send messages from any number of JIDs,
it is best to always include a from JID.
Arguments:
msg -- The received message stanza. See the documentation
for stanza objects and the Message stanza to see
how it may be used.
"""
# The reply method will use the messages 'to' JID as the
# outgoing reply's 'from' JID.
msg.reply("Thanks for sending\n%(body)s" % msg).send()
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)
# Component name and secret options.
optp.add_option("-c", "--config", help="path to config file",
dest="config", default="config.xml")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
# Load configuration data.
config_file = open(opts.config, 'r+')
config_data = "\n".join([line for line in config_file])
config = Config(xml=ET.fromstring(config_data))
config_file.close()
# Setup the ConfigComponent and register plugins. Note that while plugins
# may have interdependencies, the order in which you register them does
# not matter.
xmpp = ConfigComponent(config)
xmpp.registerPlugin('xep_0030') # Service Discovery
xmpp.registerPlugin('xep_0004') # Data Forms
xmpp.registerPlugin('xep_0060') # PubSub
xmpp.registerPlugin('xep_0199') # XMPP Ping
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
xmpp.process(threaded=False)
print("Done")
else:
print("Unable to connect.")

View File

@@ -1,198 +0,0 @@
#!/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 time
import logging
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 Disco(sleekxmpp.ClientXMPP):
"""
A demonstration for using basic service discovery.
Send a disco#info and disco#items request to a JID/node combination,
and print out the results.
May also request only particular info categories such as just features,
or just items.
"""
def __init__(self, jid, password, target_jid, target_node='', get=''):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
# Using service discovery requires the XEP-0030 plugin.
self.register_plugin('xep_0030')
self.get = get
self.target_jid = target_jid
self.target_node = target_node
# Values to control which disco entities are reported
self.info_types = ['', 'all', 'info', 'identities', 'features']
self.identity_types = ['', 'all', 'info', 'identities']
self.feature_types = ['', 'all', 'info', 'features']
self.items_types = ['', 'all', 'items']
# 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)
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.
In this case, we send disco#info and disco#items
stanzas to the requested JID and print the results.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.get_roster()
self.send_presence()
if self.get in self.info_types:
# By using block=True, the result stanza will be
# returned. Execution will block until the reply is
# received. Non-blocking options would be to listen
# for the disco_info event, or passing a handler
# function using the callback parameter.
info = self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node,
block=True)
if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
items = self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node,
block=True)
else:
logging.error("Invalid disco request type.")
self.disconnect()
return
header = 'XMPP Service Discovery: %s' % self.target_jid
print(header)
print('-' * len(header))
if self.target_node != '':
print('Node: %s' % self.target_node)
print('-' * len(header))
if self.get in self.identity_types:
print('Identities:')
for identity in info['disco_info']['identities']:
print(' - %s' % str(identity))
if self.get in self.feature_types:
print('Features:')
for feature in info['disco_info']['features']:
print(' - %s' % feature)
if self.get in self.items_types:
print('Items:')
for item in items['disco_items']['items']:
print(' - %s' % str(item))
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
optp = OptionParser()
optp.version = '%%prog 0.1'
optp.usage = "Usage: %%prog [options] %s <jid> [<node>]" % \
'all|info|items|identities|features'
optp.add_option('-q','--quiet', help='set logging to ERROR',
action='store_const',
dest='loglevel',
const=logging.ERROR,
default=logging.ERROR)
optp.add_option('-d','--debug', help='set logging to DEBUG',
action='store_const',
dest='loglevel',
const=logging.DEBUG,
default=logging.ERROR)
optp.add_option('-v','--verbose', help='set logging to COMM',
action='store_const',
dest='loglevel',
const=5,
default=logging.ERROR)
# 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 len(args) < 2:
optp.print_help()
exit()
if len(args) == 2:
args = (args[0], args[1], '')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
# Setup the Disco browser.
xmpp = Disco(opts.jid, opts.password, args[1], args[2], args[0])
# 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)
else:
print("Unable to connect.")

View File

@@ -1,142 +0,0 @@
#!/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 EchoBot(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()
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()
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 = EchoBot(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.")

View File

@@ -1,186 +0,0 @@
#!/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
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 MUCBot(sleekxmpp.ClientXMPP):
"""
A simple SleekXMPP bot that will greets those
who enter the room, and acknowledge any messages
that mentions the bot's nickname.
"""
def __init__(self, jid, password, room, nick):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.room = room
self.nick = nick
# 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 groupchat_message event is triggered whenever a message
# stanza is received from any chat room. If you also also
# register a handler for the 'message' event, MUC messages
# will be processed by both handlers.
self.add_event_handler("groupchat_message", self.muc_message)
# The groupchat_presence event is triggered whenever a
# presence stanza is received from any chat room, including
# any presences you send yourself. To limit event handling
# to a single room, use the events muc::room@server::presence,
# muc::room@server::got_online, or muc::room@server::got_offline.
self.add_event_handler("muc::%s::got_online" % self.room,
self.muc_online)
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.getRoster()
self.sendPresence()
self.plugin['xep_0045'].joinMUC(self.room,
self.nick,
# If a room password is needed, use:
# password=the_room_password,
wait=True)
def muc_message(self, msg):
"""
Process incoming message stanzas from any chat room. Be aware
that if you also have any handlers for the 'message' event,
message stanzas may be processed by both handlers, so check
the 'type' attribute when using a 'message' event handler.
Whenever the bot's nickname is mentioned, respond to
the message.
IMPORTANT: Always check that a message is not from yourself,
otherwise you will create an infinite loop responding
to your own messages.
This handler will reply to messages that mention
the bot's nickname.
Arguments:
msg -- The received message stanza. See the documentation
for stanza objects and the Message stanza to see
how it may be used.
"""
if msg['mucnick'] != self.nick and self.nick in msg['body']:
self.send_message(mto=msg['from'].bare,
mbody="I heard that, %s." % msg['mucnick'],
mtype='groupchat')
def muc_online(self, presence):
"""
Process a presence stanza from a chat room. In this case,
presences from users that have just come online are
handled by sending a welcome message that includes
the user's nickname and role in the room.
Arguments:
presence -- The received presence stanza. See the
documentation for the Presence stanza
to see how else it may be used.
"""
if presence['muc']['nick'] != self.nick:
self.send_message(mto=presence['from'].bare,
mbody="Hello, %s %s" % (presence['muc']['role'],
presence['muc']['nick']),
mtype='groupchat')
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")
optp.add_option("-r", "--room", dest="room",
help="MUC room to join")
optp.add_option("-n", "--nick", dest="nick",
help="MUC nickname")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if None in [opts.jid, opts.password, opts.room, opts.nick]:
optp.print_help()
sys.exit(1)
# Setup the MUCBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = MUCBot(opts.jid, opts.password, opts.room, opts.nick)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0045') # Multi-User Chat
xmpp.register_plugin('xep_0199') # XMPP Ping
# 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.")

View File

@@ -1,140 +0,0 @@
#!/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 PingTest(sleekxmpp.ClientXMPP):
"""
A simple SleekXMPP bot that will send a ping request
to a given JID.
"""
def __init__(self, jid, password, pingjid):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
if pingjid is None:
pingjid = self.jid
self.pingjid = pingjid
# 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)
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()
result = self['xep_0199'].send_ping(self.pingjid,
timeout=10,
errorfalse=True)
logging.info("Pinging...")
if result is False:
logging.info("Couldn't ping.")
self.disconnect()
sys.exit(1)
else:
logging.info("Success! RTT: %s" % str(result))
self.disconnect()
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)
optp.add_option('-t', '--pingto', help='set jid to ping',
action='store', type='string', dest='pingjid',
default=None)
# 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 PingTest and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = PingTest(opts.jid, opts.password, opts.pingjid)
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.")

View File

@@ -1,167 +0,0 @@
#!/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 EchoBot(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()
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()
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")
optp.add_option("--phost", dest="proxy_host",
help="Proxy hostname")
optp.add_option("--pport", dest="proxy_port",
help="Proxy port")
optp.add_option("--puser", dest="proxy_user",
help="Proxy username")
optp.add_option("--ppass", dest="proxy_pass",
help="Proxy password")
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: ")
if opts.proxy_host is None:
opts.proxy_host = raw_input("Proxy host: ")
if opts.proxy_port is None:
opts.proxy_port = raw_input("Proxy port: ")
if opts.proxy_user is None:
opts.proxy_user = raw_input("Proxy username: ")
if opts.proxy_pass is None and opts.proxy_user:
opts.proxy_pass = getpass.getpass("Proxy 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 = EchoBot(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"
xmpp.use_proxy = True
xmpp.proxy_config = {
'host': opts.proxy_host,
'port': int(opts.proxy_port),
'username': opts.proxy_user,
'password': opts.proxy_pass}
# 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.")

View File

@@ -1,44 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
ANY_ALL, Future
import time
class Boomerang(Endpoint):
def FQN(self):
return 'boomerang'
@remote
def throw(self):
print "Duck!"
def main():
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
session.new_handler(ANY_ALL, Boomerang)
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
callback = Future()
boomerang.async(callback).throw()
time.sleep(10)
session.close()
if __name__ == '__main__':
main()

View File

@@ -1,53 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
ANY_ALL
import threading
import time
class Thermostat(Endpoint):
def FQN(self):
return 'thermostat'
def __init(self, initial_temperature):
self._temperature = initial_temperature
self._event = threading.Event()
@remote
def set_temperature(self, temperature):
return NotImplemented
@remote
def get_temperature(self):
return NotImplemented
@remote(False)
def release(self):
return NotImplemented
def main():
session = Remote.new_session('operator@xmpp.org/rpc', '*****')
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
print("Current temperature is %s" % thermostat.get_temperature())
thermostat.set_temperature(20)
time.sleep(10)
session.close()
if __name__ == '__main__':
main()

View File

@@ -1,52 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
ANY_ALL
import threading
class Thermostat(Endpoint):
def FQN(self):
return 'thermostat'
def __init(self, initial_temperature):
self._temperature = initial_temperature
self._event = threading.Event()
@remote
def set_temperature(self, temperature):
print("Setting temperature to %s" % temperature)
self._temperature = temperature
@remote
def get_temperature(self):
return self._temperature
@remote(False)
def release(self):
self._event.set()
def wait_for_release(self):
self._event.wait()
def main():
session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
thermostat.wait_for_release()
session.close()
if __name__ == '__main__':
main()

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2011 Nathanael C. Fritz
# Copyright (C) 2007-2008 Nathanael C. Fritz
# All Rights Reserved
#
# This software is licensed as described in the README file,
@@ -12,71 +12,42 @@
from distutils.core import setup
import sys
import sleekxmpp
# if 'cygwin' in sys.platform.lower():
# min_version = '0.6c6'
# else:
# min_version = '0.6a9'
#
#
# try:
# use_setuptools(min_version=min_version)
# except TypeError:
# # locally installed ez_setup won't have min_version
# use_setuptools()
#
#
# from setuptools import setup, find_packages, Extension, Feature
VERSION = sleekxmpp.__version__
VERSION = '0.2.3.1'
DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
with open('README') as readme:
LONG_DESCRIPTION = '\n'.join(readme)
LONG_DESCRIPTION = """
SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).
"""
CLASSIFIERS = [ 'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'License :: OSI Approved :: MIT',
'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',
]
packages = [ 'sleekxmpp',
'sleekxmpp/stanza',
'sleekxmpp/test',
'sleekxmpp/xmlstream',
'sleekxmpp/xmlstream/matcher',
'sleekxmpp/xmlstream/handler',
'sleekxmpp/plugins',
'sleekxmpp/plugins/xep_0009',
'sleekxmpp/plugins/xep_0009/stanza',
'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza',
'sleekxmpp/plugins/xep_0050',
'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',
'sleekxmpp/features/feature_starttls',
'sleekxmpp/features/feature_bind',
'sleekxmpp/features/feature_session',
'sleekxmpp/thirdparty',
'sleekxmpp/thirdparty/suelta',
'sleekxmpp/thirdparty/suelta/mechanisms',
]
packages = [ 'sleekxmpp',
'sleekxmpp/plugins',
'sleekxmpp/stanza',
'sleekxmpp/xmlstream',
'sleekxmpp/xmlstream/matcher',
'sleekxmpp/xmlstream/handler' ]
if sys.version_info < (3, 0):
packages.append('sleekxmpp/xmlstream/tostring26')
else:
packages.append('sleekxmpp/xmlstream/tostring')
setup(
name = "sleekxmpp",
@@ -85,10 +56,10 @@ setup(
long_description = LONG_DESCRIPTION,
author = 'Nathanael Fritz',
author_email = 'fritzy [at] netflint.net',
url = 'http://github.com/fritzy/SleekXMPP',
url = 'http://code.google.com/p/sleekxmpp',
license = 'MIT',
platforms = [ 'any' ],
packages = packages,
packages = packages,
requires = [ 'tlslite', 'pythondns' ],
)

View File

@@ -1,19 +1,275 @@
#!/usr/bin/python2.5
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
See the file license.txt for copying permission.
"""
from __future__ import absolute_import, unicode_literals
from . basexmpp import basexmpp
from xml.etree import cElementTree as ET
from . xmlstream.xmlstream import XMLStream
from . xmlstream.xmlstream import RestartStream
from . xmlstream.matcher.xmlmask import MatchXMLMask
from . xmlstream.matcher.xpath import MatchXPath
from . xmlstream.matcher.many import MatchMany
from . xmlstream.handler.callback import Callback
from . xmlstream.stanzabase import StanzaBase
from . xmlstream import xmlstream as xmlstreammod
from . stanza.message import Message
from . stanza.iq import Iq
import time
import logging
import base64
import sys
import random
import copy
from . import plugins
#from . import stanza
srvsupport = True
try:
import dns.resolver
import dns.rdatatype
except ImportError:
srvsupport = False
from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.clientxmpp import ClientXMPP
from sleekxmpp.componentxmpp import ComponentXMPP
from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
__version__ = '1.0beta6.1'
__version_info__ = (1, 0, 0, 'beta6', 1)
#class PresenceStanzaType(object):
#
# def fromXML(self, xml):
# self.ptype = xml.get('type')
class ClientXMPP(basexmpp, XMLStream):
"""SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True):
global srvsupport
XMLStream.__init__(self)
self.default_ns = 'jabber:client'
basexmpp.__init__(self)
self.plugin_config = plugin_config
self.escape_quotes = escape_quotes
self.set_jid(jid)
self.server = None
self.port = 5222 # not used if DNS SRV is used
self.plugin_whitelist = plugin_whitelist
self.auto_reconnect = True
self.srvsupport = srvsupport
self.password = password
self.registered_features = []
self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.domain,self.default_ns)
self.stream_footer = "</stream:stream>"
#self.map_namespace('http://etherx.jabber.org/streams', 'stream')
#self.map_namespace('jabber:client', '')
self.features = []
#TODO: Use stream state here
self.authenticated = False
self.sessionstarted = False
self.bound = False
self.bindfail = False
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True)
self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True)
self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource)
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session)
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
#self.register_plugins()
def __getitem__(self, key):
if key in self.plugin:
return self.plugin[key]
else:
logging.warning("""Plugin "%s" is not loaded.""" % key)
return False
def get(self, key, default):
return self.plugin.get(key, default)
def connect(self, host=None, port=None):
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
the JID server."""
if self.state['connected']: return True
if host:
self.server = host
if port is None: port = self.port
else:
if not self.srvsupport:
logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using domain from JID.")
else:
logging.debug("Since no address is supplied, attempting SRV lookup.")
try:
answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.domain,
dns.rdatatype.SRV )
except dns.resolver.NXDOMAIN:
logging.debug("No appropriate SRV record found. Using JID server name.")
else:
# pick a random answer, weighted by priority
# there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway
# suggestions are welcome
addresses = {}
intmax = 0
priorities = []
for answer in answers:
intmax += answer.priority
addresses[intmax] = (answer.target.to_text()[:-1], answer.port)
priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort()
picked = random.randint(0, intmax)
for priority in priorities:
if picked <= priority:
(host,port) = addresses[priority]
break
# if SRV lookup was successful, we aren't using a particular server.
self.server = None
if not host:
# if all else fails take server from JID.
(host,port) = (self.domain, self.port)
self.server = None
logging.debug('Attempting connection to %s:%d', host, port )
#TODO option to not use TLS?
result = XMLStream.connect(self, host, port, use_tls=True)
if result:
self.event("connected")
else:
logging.warning("Failed to connect")
self.event("disconnected")
return result
# overriding reconnect and disconnect so that we can get some events
# should events be part of or required by xmlstream? Maybe that would be cleaner
def reconnect(self):
self.disconnect(reconnect=True)
def disconnect(self, reconnect=False):
self.event("disconnected")
self.authenticated = False
self.sessionstarted = False
XMLStream.disconnect(self, reconnect)
def registerFeature(self, mask, pointer, breaker = False):
"""Register a stream feature."""
self.registered_features.append((MatchXMLMask(mask), pointer, breaker))
def updateRoster(self, jid, name=None, subscription=None, groups=[]):
"""Add or change a roster item."""
iq = self.Iq().setValues({'type': 'set'})
iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
#self.send(iq, self.Iq().setValues({'id': iq['id']}))
r = iq.send()
return r['type'] == 'result'
def getRoster(self):
"""Request the roster be sent."""
iq = self.Iq().setValues({'type': 'get'}).enable('roster').send()
self._handleRoster(iq, request=True)
def _handleStreamFeatures(self, features):
logging.debug('handling stream features')
self.features = []
for sub in features.xml:
self.features.append(sub.tag)
for subelement in features.xml:
for feature in self.registered_features:
if feature[0].match(subelement):
#if self.maskcmp(subelement, feature[0], True):
# This calls the feature handler & optionally breaks
if feature[1](subelement) and feature[2]: #if breaker, don't continue
return True
def handler_starttls(self, xml):
logging.debug( 'TLS start handler; SSL support: %s', self.ssl_support )
if not self.authenticated and self.ssl_support:
_stanza = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />"
if not self.event_handlers.get(_stanza,None): # don't add handler > once
self.add_handler( _stanza, self.handler_tls_start, instream=True )
self.sendXML(xml)
return True
else:
logging.warning("The module tlslite is required in to some servers, and has not been found.")
return False
def handler_tls_start(self, xml):
logging.debug("Starting TLS")
if self.startTLS():
raise RestartStream()
def handler_sasl_auth(self, xml):
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
return False
logging.debug("Starting SASL Auth")
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True)
self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, instream=True)
sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism')
if len(sasl_mechs):
for sasl_mech in sasl_mechs:
self.features.append("sasl:%s" % sasl_mech.text)
if 'sasl:PLAIN' in self.features:
if sys.version_info < (3,0):
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8'))
else:
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8'))
else:
logging.error("No appropriate login method.")
self.disconnect()
#if 'sasl:DIGEST-MD5' in self.features:
# self._auth_digestmd5()
return True
def handler_auth_success(self, xml):
logging.debug("Authentication successful.")
self.authenticated = True
self.features = []
raise RestartStream()
def handler_auth_fail(self, xml):
logging.warning("Authentication failed.")
self.disconnect()
self.event("failed_auth")
def handler_bind_resource(self, xml):
logging.debug("Requesting resource: %s" % self.resource)
iq = self.Iq(stype='set')
res = ET.Element('resource')
res.text = self.resource
xml.append(res)
iq.append(xml)
response = iq.send()
#response = self.send(iq, self.Iq(sid=iq['id']))
self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
self.bound = True
logging.info("Node set to: %s" % self.fulljid)
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail:
logging.debug("Established Session")
self.sessionstarted = True
self.event("session_start")
def handler_start_session(self, xml):
if self.authenticated and self.bound:
iq = self.makeIqSet(xml)
response = iq.send()
logging.debug("Established Session")
self.sessionstarted = True
self.event("session_start")
else:
#bind probably hasn't happened yet
self.bindfail = True
def _handleRoster(self, iq, request=False):
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
for jid in iq['roster']['items']:
if not jid in self.roster:
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
self.roster[jid].update(iq['roster']['items'][jid])
if iq['type'] == 'set':
self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster'))
self.event("roster_update", iq)

File diff suppressed because it is too large Load Diff

View File

@@ -1,335 +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 absolute_import, unicode_literals
import logging
import base64
import sys
import hashlib
import random
import threading
import sleekxmpp
from sleekxmpp import plugins
from sleekxmpp import stanza
from sleekxmpp import features
from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.stanza import *
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
# Flag indicating if DNS SRV records are available for use.
SRV_SUPPORT = True
try:
import dns.resolver
except:
SRV_SUPPORT = False
log = logging.getLogger(__name__)
class ClientXMPP(BaseXMPP):
"""
SleekXMPP's client class. ( 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:
Methods:
connect -- Overrides XMLStream.connect.
del_roster_item -- Delete a roster item.
get_roster -- Retrieve the roster from the server.
register_feature -- Register a stream feature.
update_roster -- Update a roster item.
"""
def __init__(self, jid, password, ssl=False, plugin_config={},
plugin_whitelist=[], escape_quotes=True):
"""
Create a new SleekXMPP client.
Arguments:
jid -- The JID of the XMPP user account.
password -- The password for the XMPP user account.
ssl -- Deprecated.
plugin_config -- A dictionary of plugin configurations.
plugin_whitelist -- A list of approved plugins that will be loaded
when calling register_plugins.
escape_quotes -- Deprecated.
"""
BaseXMPP.__init__(self, 'jabber:client')
self.set_jid(jid)
self.password = password
self.escape_quotes = escape_quotes
self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist
self.srv_support = SRV_SUPPORT
self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns,
"xmlns='%s'" % self.default_ns)
self.stream_footer = "</stream:stream>"
self.features = set()
self._stream_feature_handlers = {}
self._stream_feature_order = []
#TODO: Use stream state here
self.authenticated = False
self.sessionstarted = False
self.bound = False
self.bindfail = False
self.add_event_handler('connected', self._handle_connected)
self.register_stanza(StreamFeatures)
self.register_handler(
Callback('Stream Features',
MatchXPath('{%s}features' % self.stream_ns),
self._handle_stream_features))
self.register_handler(
Callback('Roster Update',
MatchXPath('{%s}iq/{%s}query' % (
self.default_ns,
'jabber:iq:roster')),
self._handle_roster))
# Setup default stream features
self.register_plugin('feature_starttls')
self.register_plugin('feature_mechanisms')
self.register_plugin('feature_bind')
self.register_plugin('feature_session')
def connect(self, address=tuple(), reattempt=True, use_tls=True):
"""
Connect to the XMPP server.
When no address is given, a SRV lookup for the server will
be attempted. If that fails, the server user in the JID
will be used.
Arguments:
address -- A tuple containing the server's host and port.
reattempt -- If True, reattempt the connection if an
error occurs. Defaults to True.
use_tls -- Indicates if TLS should be used for the
connection. Defaults to True.
"""
self.session_started_event.clear()
if not address or len(address) < 2:
if not self.srv_support:
log.debug("Did not supply (address, port) to connect" + \
" to and no SRV support is installed" + \
" (http://www.dnspython.org)." + \
" Continuing to attempt connection, using" + \
" server hostname from JID.")
else:
log.debug("Since no address is supplied," + \
"attempting SRV lookup.")
try:
xmpp_srv = "_xmpp-client._tcp.%s" % self.boundjid.host
answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV)
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
log.debug("No appropriate SRV record found." + \
" Using JID server name.")
except (dns.exception.Timeout,):
log.debug("DNS resolution timed out.")
else:
# Pick a random server, weighted by priority.
addresses = {}
intmax = 0
topprio = 65535
for answer in answers:
topprio = min(topprio, answer.priority)
for answer in answers:
if answer.priority == topprio:
intmax += answer.weight
addresses[intmax] = (answer.target.to_text()[:-1],
answer.port)
#python3 returns a generator for dictionary keys
items = [x for x in addresses.keys()]
items.sort()
picked = random.randint(0, intmax)
for item in items:
if picked <= item:
address = addresses[item]
break
if not address:
# If all else fails, use the server from the JID.
address = (self.boundjid.host, 5222)
return XMLStream.connect(self, address[0], address[1],
use_tls=use_tls, reattempt=reattempt)
def register_feature(self, name, handler, restart=False, order=5000):
"""
Register a stream feature.
Arguments:
name -- The name of the stream feature.
handler -- The function to execute if the feature is received.
restart -- Indicates if feature processing should halt with
this feature. Defaults to False.
order -- The relative ordering in which the feature should
be negotiated. Lower values will be attempted
earlier when available.
"""
self._stream_feature_handlers[name] = (handler, restart)
self._stream_feature_order.append((order, name))
self._stream_feature_order.sort()
def update_roster(self, jid, name=None, subscription=None, groups=[],
block=True, timeout=None, callback=None):
"""
Add or change a roster item.
Arguments:
jid -- The JID of the entry to modify.
name -- The user's nickname for this JID.
subscription -- The subscription status. May be one of
'to', 'from', 'both', or 'none'. If set
to 'remove', the entry will be deleted.
groups -- The roster groups that contain this item.
block -- Specify if the roster request will block
until a response is received, or a timeout
occurs. Defaults to True.
timeout -- The length of time (in seconds) to wait
for a response before continuing if blocking
is used. Defaults to self.response_timeout.
callback -- Optional reference to a stream handler function.
Will be executed when the roster is received.
Implies block=False.
"""
iq = self.Iq()
iq['type'] = 'set'
iq['roster']['items'] = {jid: {'name': name,
'subscription': subscription,
'groups': groups}}
response = iq.send(block, timeout, callback)
if response in [False, None] or not isinstance(response, Iq):
return response
return response['type'] == 'result'
def del_roster_item(self, jid):
"""
Remove an item from the roster by setting its subscription
status to 'remove'.
Arguments:
jid -- The JID of the item to remove.
"""
return self.update_roster(jid, subscription='remove')
def get_roster(self, block=True, timeout=None, callback=None):
"""
Request the roster from the server.
Arguments:
block -- Specify if the roster request will block until a
response is received, or a timeout occurs.
Defaults to True.
timeout -- The length of time (in seconds) to wait for a response
before continuing if blocking is used.
Defaults to self.response_timeout.
callback -- Optional reference to a stream handler function. Will
be executed when the roster is received.
Implies block=False.
"""
iq = self.Iq()
iq['type'] = 'get'
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:
return self._handle_roster(response, request=True)
def _handle_connected(self, event=None):
#TODO: Use stream state here
self.authenticated = False
self.sessionstarted = False
self.bound = False
self.bindfail = False
self.features = set()
def session_timeout():
if not self.session_started_event.isSet():
log.debug("Session start has taken more than 15 seconds")
self.disconnect(reconnect=self.auto_reconnect)
self.schedule("session timeout checker", 15, session_timeout)
def _handle_stream_features(self, features):
"""
Process the received stream features.
Arguments:
features -- The features stanza.
"""
for order, name in self._stream_feature_order:
if name in features['features']:
handler, restart = self._stream_feature_handlers[name]
if handler(features) and restart:
# Don't continue if the feature requires
# restarting the XML stream.
return True
def _handle_roster(self, iq, request=False):
"""
Update the roster after receiving a roster stanza.
Arguments:
iq -- The roster stanza.
request -- Indicates if this stanza is a response
to a request for the roster.
"""
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
for jid in iq['roster']['items']:
if not jid in self.roster:
self.roster[jid] = {'groups': [],
'name': '',
'subscription': 'none',
'presence': {},
'in_roster': True}
self.roster[jid].update(iq['roster']['items'][jid])
self.event('roster_received', iq)
self.event("roster_update", iq)
if iq['type'] == 'set':
iq.reply()
iq.enable('roster')
iq.send()
return True
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
ClientXMPP.updateRoster = ClientXMPP.update_roster
ClientXMPP.delRosterItem = ClientXMPP.del_roster_item
ClientXMPP.getRoster = ClientXMPP.get_roster
ClientXMPP.registerFeature = ClientXMPP.register_feature

View File

@@ -0,0 +1,41 @@
import sleekxmpp.componentxmpp
import logging
from optparse import OptionParser
import time
class Example(sleekxmpp.componentxmpp.ComponentXMPP):
def __init__(self, jid, password):
sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'vm1', 5230)
self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.message)
def start(self, event):
#self.getRoster()
#self.sendPresence(pto='admin@tigase.netflint.net/sarkozy')
#self.sendPresence(pto='tigase.netflint.net')
pass
def message(self, event):
self.sendMessage("%s/%s" % (event['jid'], event['resource']), "Thanks for sending me, \"%s\"." % event['message'], mtype=event['type'])
if __name__ == '__main__':
#parse command line arguements
optp = OptionParser()
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)
optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
opts,args = optp.parse_args()
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
xmpp = Example('component.vm1', 'secreteating')
xmpp.registerPlugin('xep_0004')
xmpp.registerPlugin('xep_0030')
xmpp.registerPlugin('xep_0060')
xmpp.registerPlugin('xep_0199')
if xmpp.connect():
xmpp.process(threaded=False)
print("done")
else:
print("Unable to connect.")

198
sleekxmpp/componentxmpp.py Normal file → Executable file
View File

@@ -1,142 +1,88 @@
#!/usr/bin/python2.6
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
See the file license.txt for copying permission.
"""
from __future__ import absolute_import
from . basexmpp import basexmpp
from xml.etree import cElementTree as ET
from . xmlstream.xmlstream import XMLStream
from . xmlstream.xmlstream import RestartStream
from . xmlstream.matcher.xmlmask import MatchXMLMask
from . xmlstream.matcher.xpath import MatchXPath
from . xmlstream.matcher.many import MatchMany
from . xmlstream.handler.callback import Callback
from . xmlstream.stanzabase import StanzaBase
from . xmlstream import xmlstream as xmlstreammod
import time
import logging
import base64
import sys
import random
import copy
from . import plugins
from . import stanza
import hashlib
from sleekxmpp import plugins
from sleekxmpp import stanza
from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream import StanzaBase, ET
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
srvsupport = True
try:
import dns.resolver
except ImportError:
srvsupport = False
log = logging.getLogger(__name__)
class ComponentXMPP(basexmpp, XMLStream):
"""SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False):
XMLStream.__init__(self)
if use_jc_ns:
self.default_ns = 'jabber:client'
else:
self.default_ns = 'jabber:component:accept'
basexmpp.__init__(self)
self.auto_authorize = None
self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid
self.stream_footer = "</stream:stream>"
self.server_host = host
self.server_port = port
self.set_jid(jid)
self.secret = secret
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
def __getitem__(self, key):
if key in self.plugin:
return self.plugin[key]
else:
logging.warning("""Plugin "%s" is not loaded.""" % key)
return False
def get(self, key, default):
return self.plugin.get(key, default)
def incoming_filter(self, xmlobj):
if xmlobj.tag.startswith('{jabber:client}'):
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
for sub in xmlobj:
self.incoming_filter(sub)
return xmlobj
class ComponentXMPP(BaseXMPP):
"""
SleekXMPP's basic XMPP server component.
Use only for good, not for evil.
Methods:
connect -- Overrides XMLStream.connect.
incoming_filter -- Overrides XMLStream.incoming_filter.
start_stream_handler -- Overrides XMLStream.start_stream_handler.
"""
def __init__(self, jid, secret, host, port,
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
"""
Arguments:
jid -- The JID of the component.
secret -- The secret or password for the component.
host -- The server accepting the component.
port -- The port used to connect to the server.
plugin_config -- A dictionary of plugin configurations.
plugin_whitelist -- A list of desired plugins to load
when using register_plugins.
use_js_ns -- Indicates if the 'jabber:client' namespace
should be used instead of the standard
'jabber:component:accept' namespace.
Defaults to False.
"""
if use_jc_ns:
default_ns = 'jabber:client'
else:
default_ns = 'jabber:component:accept'
BaseXMPP.__init__(self, default_ns)
self.auto_authorize = None
self.stream_header = "<stream:stream %s %s to='%s'>" % (
'xmlns="jabber:component:accept"',
'xmlns:stream="%s"' % self.stream_ns,
jid)
self.stream_footer = "</stream:stream>"
self.server_host = host
self.server_port = port
self.set_jid(jid)
self.secret = secret
self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist
self.is_component = True
self.register_handler(
Callback('Handshake',
MatchXPath('{jabber:component:accept}handshake'),
self._handle_handshake))
def connect(self):
"""
Connect to the server.
Overrides XMLStream.connect.
"""
log.debug("Connecting to %s:%s" % (self.server_host,
self.server_port))
return XMLStream.connect(self, self.server_host,
self.server_port)
def incoming_filter(self, xml):
"""
Pre-process incoming XML stanzas by converting any 'jabber:client'
namespaced elements to the component's default namespace.
Overrides XMLStream.incoming_filter.
Arguments:
xml -- The XML stanza to pre-process.
"""
if xml.tag.startswith('{jabber:client}'):
xml.tag = xml.tag.replace('jabber:client', self.default_ns)
# The incoming_filter call is only made on top level stanza
# elements. So we manually continue filtering on sub-elements.
for sub in xml:
self.incoming_filter(sub)
return xml
def start_stream_handler(self, xml):
"""
Once the streams are established, attempt to handshake
with the server to be accepted as a component.
Overrides XMLStream.start_stream_handler.
Arguments:
xml -- The incoming stream's root element.
"""
# Construct a hash of the stream ID and the component secret.
sid = xml.get('id', '')
pre_hash = '%s%s' % (sid, self.secret)
if sys.version_info >= (3, 0):
# Handle Unicode byte encoding in Python 3.
pre_hash = bytes(pre_hash, 'utf-8')
handshake = ET.Element('{jabber:component:accept}handshake')
handshake.text = hashlib.sha1(pre_hash).hexdigest().lower()
self.send_xml(handshake, now=True)
def _handle_handshake(self, xml):
"""
The handshake has been accepted.
Arguments:
xml -- The reply handshake stanza.
"""
self.session_started_event.set()
self.event("session_start")
def start_stream_handler(self, xml):
sid = xml.get('id', '')
handshake = ET.Element('{jabber:component:accept}handshake')
if sys.version_info < (3,0):
handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower()
else:
handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
self.sendXML(handshake)
def _handleHandshake(self, xml):
self.event("session_start")
def connect(self):
logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)

View File

@@ -3,52 +3,14 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
See the file license.txt for copying permission.
"""
class XMPPError(Exception):
"""
A generic exception that may be raised while processing an XMPP stanza
to indicate that an error response stanza should be sent.
The exception method for stanza objects extending RootStanza will create
an error stanza and initialize any additional substanzas using the
extension information included in the exception.
Meant for use in SleekXMPP plugins and applications using SleekXMPP.
"""
def __init__(self, condition='undefined-condition', text=None, etype=None,
extension=None, extension_ns=None, extension_args=None,
clear=True):
"""
Create a new XMPPError exception.
Extension information can be included to add additional XML elements
to the generated error stanza.
Arguments:
condition -- The XMPP defined error condition.
text -- Human readable text describing the error.
etype -- The XMPP error type, such as cancel or modify.
extension -- Tag name of the extension's XML content.
extension_ns -- XML namespace of the extensions' XML content.
extension_args -- Content and attributes for the extension
element. Same as the additional arguments to
the ET.Element constructor.
clear -- Indicates if the stanza's contents should be
removed before replying with an error.
Defaults to True.
"""
if extension_args is None:
extension_args = {}
self.condition = condition
self.text = text
self.etype = etype
self.clear = clear
self.extension = extension
self.extension_ns = extension_ns
self.extension_args = extension_args
def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None):
self.condition = condition
self.text = text
self.etype = etype
self.extension = extension
self.extension_ns = extension_ns
self.extension_args = extension_args

View File

@@ -1,9 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
__all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind']

View File

@@ -1,10 +0,0 @@
"""
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.features.feature_bind.bind import feature_bind
from sleekxmpp.features.feature_bind.stanza import Bind

View File

@@ -1,64 +0,0 @@
"""
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
from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.features.feature_bind import stanza
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.plugins.base import base_plugin
log = logging.getLogger(__name__)
class feature_bind(base_plugin):
def plugin_init(self):
self.name = 'Bind Resource'
self.rfc = '6120'
self.description = 'Resource Binding Stream Feature'
self.stanza = stanza
self.xmpp.register_feature('bind',
self._handle_bind_resource,
restart=False,
order=10000)
register_stanza_plugin(Iq, stanza.Bind)
register_stanza_plugin(StreamFeatures, stanza.Bind)
def _handle_bind_resource(self, features):
"""
Handle requesting a specific resource.
Arguments:
features -- The stream features stanza.
"""
log.debug("Requesting resource: %s" % self.xmpp.boundjid.resource)
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('bind')
if self.xmpp.boundjid.resource:
iq['bind']['resource'] = self.xmpp.boundjid.resource
response = iq.send(now=True)
self.xmpp.set_jid(response['bind']['jid'])
self.xmpp.bound = True
self.xmpp.features.add('bind')
log.info("Node set to: %s" % self.xmpp.boundjid.full)
if 'session' not in features['features']:
log.debug("Established Session")
self.xmpp.sessionstarted = True
self.xmpp.session_started_event.set()
self.xmpp.event("session_start")

View File

@@ -1,22 +0,0 @@
"""
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.stanza import Iq, StreamFeatures
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class Bind(ElementBase):
"""
"""
name = 'bind'
namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
interfaces = set(('resource', 'jid'))
sub_interfaces = interfaces
plugin_attrib = 'bind'

View File

@@ -1,13 +0,0 @@
"""
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.features.feature_mechanisms.mechanisms import feature_mechanisms
from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms
from sleekxmpp.features.feature_mechanisms.stanza import Auth
from sleekxmpp.features.feature_mechanisms.stanza import Success
from sleekxmpp.features.feature_mechanisms.stanza import Failure

View File

@@ -1,126 +0,0 @@
"""
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
from sleekxmpp.thirdparty import suelta
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.features.feature_mechanisms import stanza
log = logging.getLogger(__name__)
class feature_mechanisms(base_plugin):
def plugin_init(self):
self.name = 'SASL Mechanisms'
self.rfc = '6120'
self.description = "SASL Stream Feature"
self.stanza = stanza
def tls_active():
return 'starttls' in self.xmpp.features
def basic_callback(mech, values):
if 'username' in values:
values['username'] = self.xmpp.boundjid.user
if 'password' in values:
values['password'] = self.xmpp.password
mech.fulfill(values)
sasl_callback = self.config.get('sasl_callback', None)
if sasl_callback is None:
sasl_callback = basic_callback
self.mech = None
self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp',
username=self.xmpp.boundjid.user,
sec_query=suelta.sec_query_allow,
request_values=sasl_callback,
tls_active=tls_active)
register_stanza_plugin(StreamFeatures, stanza.Mechanisms)
self.xmpp.register_stanza(stanza.Success)
self.xmpp.register_stanza(stanza.Failure)
self.xmpp.register_stanza(stanza.Auth)
self.xmpp.register_stanza(stanza.Challenge)
self.xmpp.register_stanza(stanza.Response)
self.xmpp.register_handler(
Callback('SASL Success',
MatchXPath(stanza.Success.tag_name()),
self._handle_success,
instream=True,
once=True))
self.xmpp.register_handler(
Callback('SASL Failure',
MatchXPath(stanza.Failure.tag_name()),
self._handle_fail,
instream=True,
once=True))
self.xmpp.register_handler(
Callback('SASL Challenge',
MatchXPath(stanza.Challenge.tag_name()),
self._handle_challenge))
self.xmpp.register_feature('mechanisms',
self._handle_sasl_auth,
restart=True,
order=self.config.get('order', 100))
def _handle_sasl_auth(self, features):
"""
Handle authenticating using SASL.
Arguments:
features -- The stream features stanza.
"""
if 'mechanisms' in self.xmpp.features:
# SASL authentication has already succeeded, but the
# server has incorrectly offered it again.
return False
mech_list = features['mechanisms']
self.mech = self.sasl.choose_mechanism(mech_list)
if self.mech is not None:
resp = stanza.Auth(self.xmpp)
resp['mechanism'] = self.mech.name
resp['value'] = self.mech.process()
resp.send(now=True)
else:
log.error("No appropriate login method.")
self.xmpp.event("no_auth", direct=True)
self.xmpp.disconnect()
return True
def _handle_challenge(self, stanza):
"""SASL challenge received. Process and send response."""
resp = self.stanza.Response(self.xmpp)
resp['value'] = self.mech.process(stanza['value'])
resp.send(now=True)
def _handle_success(self, stanza):
"""SASL authentication succeeded. Restart the stream."""
self.xmpp.authenticated = True
self.xmpp.features.add('mechanisms')
raise RestartStream()
def _handle_fail(self, stanza):
"""SASL authentication failed. Disconnect and shutdown."""
log.info("Authentication failed: %s" % stanza['condition'])
self.xmpp.event("failed_auth", stanza, direct=True)
self.xmpp.disconnect()
return True

View File

@@ -1,15 +0,0 @@
"""
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.features.feature_mechanisms.stanza.mechanisms import Mechanisms
from sleekxmpp.features.feature_mechanisms.stanza.auth import Auth
from sleekxmpp.features.feature_mechanisms.stanza.success import Success
from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure
from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge
from sleekxmpp.features.feature_mechanisms.stanza.response import Response

View File

@@ -1,39 +0,0 @@
"""
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 base64
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class Auth(StanzaBase):
"""
"""
name = 'auth'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('mechanism', 'value'))
plugin_attrib = name
def setup(self, xml):
StanzaBase.setup(self, xml)
self.xml.tag = self.tag_name()
def get_value(self):
return base64.b64decode(bytes(self.xml.text))
def set_value(self, values):
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
def del_value(self):
self.xml.text = ''

View File

@@ -1,39 +0,0 @@
"""
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 base64
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class Challenge(StanzaBase):
"""
"""
name = 'challenge'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('value',))
plugin_attrib = name
def setup(self, xml):
StanzaBase.setup(self, xml)
self.xml.tag = self.tag_name()
def get_value(self):
return base64.b64decode(bytes(self.xml.text))
def set_value(self, values):
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
def del_value(self):
self.xml.text = ''

View File

@@ -1,78 +0,0 @@
"""
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.stanza import StreamFeatures
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class Failure(StanzaBase):
"""
"""
name = 'failure'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('condition', 'text'))
plugin_attrib = name
sub_interfaces = set(('text',))
conditions = set(('aborted', 'account-disabled', 'credentials-expired',
'encryption-required', 'incorrect-encoding', 'invalid-authzid',
'invalid-mechanism', 'malformed-request', 'mechansism-too-weak',
'not-authorized', 'temporary-auth-failure'))
def setup(self, xml=None):
"""
Populate the stanza object using an optional XML object.
Overrides ElementBase.setup.
Sets a default error type and condition, and changes the
parent stanza's type to 'error'.
Arguments:
xml -- Use an existing XML object for the stanza's values.
"""
# StanzaBase overrides self.namespace
self.namespace = Failure.namespace
if StanzaBase.setup(self, xml):
#If we had to generate XML then set default values.
self['condition'] = 'not-authorized'
self.xml.tag = self.tag_name()
def get_condition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren():
if "{%s}" % self.namespace in child.tag:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
return cond
return 'not-authorized'
def set_condition(self, value):
"""
Set the tag name of the condition element.
Arguments:
value -- The tag name of the condition element.
"""
if value in self.conditions:
del self['condition']
self.xml.append(ET.Element("{%s}%s" % (self.namespace, value)))
return self
def del_condition(self):
"""Remove the condition element."""
for child in self.xml.getchildren():
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:
self.xml.remove(child)
return self

View File

@@ -1,55 +0,0 @@
"""
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.stanza import StreamFeatures
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class Mechanisms(ElementBase):
"""
"""
name = 'mechanisms'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('mechanisms', 'required'))
plugin_attrib = name
is_extension = True
def get_required(self):
"""
"""
return True
def get_mechanisms(self):
"""
"""
results = []
mechs = self.findall('{%s}mechanism' % self.namespace)
if mechs:
for mech in mechs:
results.append(mech.text)
return results
def set_mechanisms(self, values):
"""
"""
self.del_mechanisms()
for val in values:
mech = ET.Element('{%s}mechanism' % self.namespace)
mech.text = val
self.append(mech)
def del_mechanisms(self):
"""
"""
mechs = self.findall('{%s}mechanism' % self.namespace)
if mechs:
for mech in mechs:
self.xml.remove(mech)

View File

@@ -1,39 +0,0 @@
"""
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 base64
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class Response(StanzaBase):
"""
"""
name = 'response'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('value',))
plugin_attrib = name
def setup(self, xml):
StanzaBase.setup(self, xml)
self.xml.tag = self.tag_name()
def get_value(self):
return base64.b64decode(bytes(self.xml.text))
def set_value(self, values):
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
def del_value(self):
self.xml.text = ''

View File

@@ -1,26 +0,0 @@
"""
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.stanza import StreamFeatures
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class Success(StanzaBase):
"""
"""
name = 'success'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set()
plugin_attrib = name
def setup(self, xml):
StanzaBase.setup(self, xml)
self.xml.tag = self.tag_name()

View File

@@ -1,10 +0,0 @@
"""
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.features.feature_session.session import feature_session
from sleekxmpp.features.feature_session.stanza import Session

View File

@@ -1,56 +0,0 @@
"""
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
from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.features.feature_session import stanza
log = logging.getLogger(__name__)
class feature_session(base_plugin):
def plugin_init(self):
self.name = 'Start Session'
self.rfc = '3920'
self.description = 'Start Session Stream Feature'
self.stanza = stanza
self.xmpp.register_feature('session',
self._handle_start_session,
restart=False,
order=10001)
register_stanza_plugin(Iq, stanza.Session)
register_stanza_plugin(StreamFeatures, stanza.Session)
def _handle_start_session(self, features):
"""
Handle the start of the session.
Arguments:
feature -- The stream features element.
"""
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('session')
response = iq.send(now=True)
self.xmpp.features.add('session')
log.debug("Established Session")
self.xmpp.sessionstarted = True
self.xmpp.session_started_event.set()
self.xmpp.event("session_start")

View File

@@ -1,21 +0,0 @@
"""
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.stanza import Iq, StreamFeatures
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class Session(ElementBase):
"""
"""
name = 'session'
namespace = 'urn:ietf:params:xml:ns:xmpp-session'
interfaces = set()
plugin_attrib = 'session'

View File

@@ -1,10 +0,0 @@
"""
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.features.feature_starttls.starttls import feature_starttls
from sleekxmpp.features.feature_starttls.stanza import *

View File

@@ -1,47 +0,0 @@
"""
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.stanza import StreamFeatures
from sleekxmpp.xmlstream import StanzaBase, ElementBase
from sleekxmpp.xmlstream import register_stanza_plugin
class STARTTLS(ElementBase):
"""
"""
name = 'starttls'
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
interfaces = set(('required',))
plugin_attrib = name
def get_required(self):
"""
"""
return True
class Proceed(StanzaBase):
"""
"""
name = 'proceed'
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
interfaces = set()
class Failure(StanzaBase):
"""
"""
name = 'failure'
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
interfaces = set()

View File

@@ -1,70 +0,0 @@
"""
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
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.features.feature_starttls import stanza
log = logging.getLogger(__name__)
class feature_starttls(base_plugin):
def plugin_init(self):
self.name = "STARTTLS"
self.rfc = '6120'
self.description = "STARTTLS Stream Feature"
self.stanza = stanza
self.xmpp.register_handler(
Callback('STARTTLS Proceed',
MatchXPath(stanza.Proceed.tag_name()),
self._handle_starttls_proceed,
instream=True))
self.xmpp.register_feature('starttls',
self._handle_starttls,
restart=True,
order=self.config.get('order', 0))
self.xmpp.register_stanza(stanza.Proceed)
self.xmpp.register_stanza(stanza.Failure)
register_stanza_plugin(StreamFeatures, stanza.STARTTLS)
def _handle_starttls(self, features):
"""
Handle notification that the server supports TLS.
Arguments:
features -- The stream:features element.
"""
if 'starttls' in self.xmpp.features:
# We have already negotiated TLS, but the server is
# offering it again, against spec.
return False
elif not self.xmpp.use_tls:
return False
elif self.xmpp.ssl_support:
self.xmpp.send(features['starttls'], now=True)
return True
else:
log.warning("The module tlslite is required to log in" +\
" to some servers, and has not been found.")
return False
def _handle_starttls_proceed(self, proceed):
"""Restart the XML stream when TLS is accepted."""
log.debug("Starting TLS")
if self.xmpp.start_tls():
self.xmpp.features.add('starttls')
raise RestartStream()

View File

@@ -1,11 +1,20 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
__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']
__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060']

View File

@@ -1,91 +1,36 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
class base_plugin(object):
"""
The base_plugin class serves as a base for user created plugins
that provide support for existing or experimental XEPS.
Each plugin has a dictionary for configuration options, as well
as a name and description.
The lifecycle of a plugin is:
1. The plugin is instantiated during registration.
2. Once the XML stream begins processing, the method
plugin_init() is called (if the plugin is configured
as enabled with {'enable': True}).
3. After all plugins have been initialized, the
method post_init() is called.
Recommended event handlers:
session_start -- Plugins which require the use of the current
bound JID SHOULD wait for the session_start
event to perform any initialization (or
resetting). This is a transitive recommendation,
plugins that use other plugins which use the
bound JID should also wait for session_start
before making such calls.
session_end -- If the plugin keeps any per-session state,
such as joined MUC rooms, such state SHOULD
be cleared when the session_end event is raised.
Attributes:
xep -- The XEP number the plugin implements, if any.
description -- A short description of the plugin, typically
the long name of the implemented XEP.
xmpp -- The main SleekXMPP instance.
config -- A dictionary of custom configuration values.
The value 'enable' is special and controls
whether or not the plugin is initialized
after registration.
post_initted -- Executed after all plugins have been initialized
to handle any cross-plugin interactions, such as
registering service discovery items.
enable -- Indicates that the plugin is enabled for use and
will be initialized after registration.
Methods:
plugin_init -- Initialize the plugin state.
post_init -- Handle any cross-plugin concerns.
"""
def __init__(self, xmpp, config=None):
"""
Instantiate a new plugin and store the given configuration.
Arguments:
xmpp -- The main SleekXMPP instance.
config -- A dictionary of configuration values.
"""
if config is None:
config = {}
self.xep = None
self.rfc = None
self.description = 'Base Plugin'
self.xmpp = xmpp
self.config = config
self.post_inited = False
self.enable = config.get('enable', True)
if self.enable:
self.plugin_init()
def plugin_init(self):
"""
Initialize plugin state, such as registering any stream or
event handlers, or new stanza types.
"""
pass
def post_init(self):
"""
Perform any cross-plugin interactions, such as registering
service discovery identities or items.
"""
self.post_inited = True
def __init__(self, xmpp, config):
self.xep = 'base'
self.description = 'Base Plugin'
self.xmpp = xmpp
self.config = config
self.post_inited = False
self.enable = config.get('enable', True)
if self.enable:
self.plugin_init()
def plugin_init(self):
pass
def post_init(self):
self.post_inited = True

View File

@@ -1,149 +1,57 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
import logging
from __future__ import with_statement
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.iq import Iq
log = logging.getLogger(__name__)
class GmailQuery(ElementBase):
namespace = 'google:mail:notify'
name = 'query'
plugin_attrib = 'gmail'
interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
def getSearch(self):
return self['q']
def setSearch(self, search):
self['q'] = search
def delSearch(self):
del self['q']
class MailBox(ElementBase):
namespace = 'google:mail:notify'
name = 'mailbox'
plugin_attrib = 'mailbox'
interfaces = set(('result-time', 'total-matched', 'total-estimate',
'url', 'threads', 'matched', 'estimate'))
def getThreads(self):
threads = []
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
MailThread.name)):
threads.append(MailThread(xml=threadXML, parent=None))
return threads
def getMatched(self):
return self['total-matched']
def getEstimate(self):
return self['total-estimate'] == '1'
class MailThread(ElementBase):
namespace = 'google:mail:notify'
name = 'mail-thread-info'
plugin_attrib = 'thread'
interfaces = set(('tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'))
sub_interfaces = set(('labels', 'subject', 'snippet'))
def getSenders(self):
senders = []
sendersXML = self.xml.find('{%s}senders' % self.namespace)
if sendersXML is not None:
for senderXML in sendersXML.findall('{%s}sender' % self.namespace):
senders.append(MailSender(xml=senderXML, parent=None))
return senders
class MailSender(ElementBase):
namespace = 'google:mail:notify'
name = 'sender'
plugin_attrib = 'sender'
interfaces = set(('address', 'name', 'originator', 'unread'))
def getOriginator(self):
return self.xml.attrib.get('originator', '0') == '1'
def getUnread(self):
return self.xml.attrib.get('unread', '0') == '1'
class NewMail(ElementBase):
namespace = 'google:mail:notify'
name = 'new-mail'
plugin_attrib = 'new-mail'
import logging
from xml.etree import cElementTree as ET
import traceback
import time
class gmail_notify(base.base_plugin):
"""
Google Talk: Gmail Notifications
"""
def plugin_init(self):
self.description = 'Google Talk: Gmail Notifications'
self.xmpp.registerHandler(
Callback('Gmail Result',
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
MailBox.namespace,
MailBox.name)),
self.handle_gmail))
self.xmpp.registerHandler(
Callback('Gmail New Mail',
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
NewMail.namespace,
NewMail.name)),
self.handle_new_mail))
registerStanzaPlugin(Iq, GmailQuery)
registerStanzaPlugin(Iq, MailBox)
registerStanzaPlugin(Iq, NewMail)
self.last_result_time = None
def handle_gmail(self, iq):
mailbox = iq['mailbox']
approx = ' approximately' if mailbox['estimated'] else ''
log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched']))
self.last_result_time = mailbox['result-time']
self.xmpp.event('gmail_messages', iq)
def handle_new_mail(self, iq):
log.info("Gmail: New emails received!")
self.xmpp.event('gmail_notify')
self.checkEmail()
def getEmail(self, query=None):
return self.search(query)
def checkEmail(self):
return self.search(newer=self.last_result_time)
def search(self, query=None, newer=None):
if query is None:
log.info("Gmail: Checking for new emails")
else:
log.info('Gmail: Searching for emails matching: "%s"' % query)
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = self.xmpp.boundjid.bare
iq['gmail']['q'] = query
iq['gmail']['newer-than-time'] = newer
return iq.send()
def plugin_init(self):
self.description = 'Google Talk Gmail Notification'
self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
self.emails = []
def handler_gmailcheck(self, payload):
#TODO XEP 30 should cache results and have getFeature
result = self.xmpp['xep_0030'].getInfo(self.xmpp.server)
features = []
for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'):
features.append(feature.get('var'))
if 'google:mail:notify' in features:
logging.debug("Server supports Gmail Notify")
self.xmpp.add_handler("<iq type='set' xmlns='%s'><new-mail xmlns='google:mail:notify' /></iq>" % self.xmpp.default_ns, self.handler_notify)
self.getEmail()
def handler_notify(self, xml):
logging.info("New Gmail recieved!")
self.xmpp.event('gmail_notify')
def getEmail(self):
iq = self.xmpp.makeIqGet()
iq.attrib['from'] = self.xmpp.fulljid
iq.attrib['to'] = self.xmpp.jid
self.xmpp.makeIqQuery(iq, 'google:mail:notify')
emails = iq.send()
mailbox = emails.find('{google:mail:notify}mailbox')
total = int(mailbox.get('total-matched', 0))
logging.info("%s New Gmail Messages" % total)

View File

@@ -2,19 +2,15 @@ from . import base
import logging
from xml.etree import cElementTree as ET
log = logging.getLogger(__name__)
class jobs(base.base_plugin):
def plugin_init(self):
self.xep = 'pubsubjob'
self.description = "Job distribution over Pubsub"
def post_init(self):
pass
#TODO add event
def createJobNode(self, host, jid, node, config=None):
pass
@@ -24,7 +20,7 @@ class jobs(base.base_plugin):
def claimJob(self, host, node, jobid, ifrom=None):
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed'))
def unclaimJob(self, host, node, jobid):
def unclaimJob(self, jobid):
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
def finishJob(self, host, node, jobid, payload=None):
@@ -42,8 +38,7 @@ class jobs(base.base_plugin):
iq['psstate']['item'] = jobid
iq['psstate']['payload'] = state
result = iq.send()
if result is None or type(result) == bool or result['type'] != 'result':
log.error("Unable to change %s:%s to %s" % (node, jobid, state))
if result is None or result['type'] != 'result':
return False
return True

View File

@@ -1,421 +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 . import base
import logging
from xml.etree import cElementTree as ET
import copy
import logging
#TODO support item groups and results
log = logging.getLogger(__name__)
class old_0004(base.base_plugin):
def plugin_init(self):
self.xep = '0004'
self.description = '*Deprecated Data Forms'
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form')
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
log.warning("This implementation of XEP-0004 is deprecated.")
def handler_message_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("message_form", object)
def handler_presence_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("presence_form", object)
def handle_form(self, xml):
xmlform = xml.find('{jabber:x:data}x')
object = self.buildForm(xmlform)
self.xmpp.event("message_xform", object)
return object
def buildForm(self, xml):
form = Form(ftype=xml.attrib['type'])
form.fromXML(xml)
return form
def makeForm(self, ftype='form', title='', instructions=''):
return Form(self.xmpp, ftype, title, instructions)
class FieldContainer(object):
def __init__(self, stanza = 'form'):
self.fields = []
self.field = {}
self.stanza = stanza
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
self.field[var] = FormField(var, ftype, label, desc, required, value)
self.fields.append(self.field[var])
return self.field[var]
def buildField(self, xml):
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
self.fields.append(self.field[xml.get('var', '__unnamed__')])
self.field[xml.get('var', '__unnamed__')].buildField(xml)
def buildContainer(self, xml):
self.stanza = xml.tag
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
def getXML(self, ftype):
container = ET.Element(self.stanza)
for field in self.fields:
container.append(field.getXML(ftype))
return container
class Form(FieldContainer):
types = ('form', 'submit', 'cancel', 'result')
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
if not ftype in self.types:
raise ValueError("Invalid Form Type")
FieldContainer.__init__(self)
self.xmpp = xmpp
self.type = ftype
self.title = title
self.instructions = instructions
self.reported = []
self.items = []
def merge(self, form2):
form1 = Form(ftype=self.type)
form1.fromXML(self.getXML(self.type))
for field in form2.fields:
if not field.var in form1.field:
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
else:
form1.field[field.var].value = field.value
for option, label in field.options:
if (option, label) not in form1.field[field.var].options:
form1.fields[field.var].addOption(option, label)
return form1
def copy(self):
newform = Form(ftype=self.type)
newform.fromXML(self.getXML(self.type))
return newform
def update(self, form):
values = form.getValues()
for var in values:
if var in self.fields:
self.fields[var].setValue(self.fields[var])
def getValues(self):
result = {}
for field in self.fields:
value = field.value
if len(value) == 1:
value = value[0]
result[field.var] = value
return result
def setValues(self, values={}):
for field in values:
if field in self.field:
if isinstance(values[field], list) or isinstance(values[field], tuple):
for value in values[field]:
self.field[field].setValue(value)
else:
self.field[field].setValue(values[field])
def fromXML(self, xml):
self.buildForm(xml)
def addItem(self):
newitem = FieldContainer('item')
self.items.append(newitem)
return newitem
def buildItem(self, xml):
newitem = self.addItem()
newitem.buildContainer(xml)
def addReported(self):
reported = FieldContainer('reported')
self.reported.append(reported)
return reported
def buildReported(self, xml):
reported = self.addReported()
reported.buildContainer(xml)
def setTitle(self, title):
self.title = title
def setInstructions(self, instructions):
self.instructions = instructions
def setType(self, ftype):
self.type = ftype
def getXMLMessage(self, to):
msg = self.xmpp.makeMessage(to)
msg.append(self.getXML())
return msg
def buildForm(self, xml):
self.type = xml.get('type', 'form')
if xml.find('{jabber:x:data}title') is not None:
self.setTitle(xml.find('{jabber:x:data}title').text)
if xml.find('{jabber:x:data}instructions') is not None:
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
for reported in xml.findall('{jabber:x:data}reported'):
self.buildReported(reported)
for item in xml.findall('{jabber:x:data}item'):
self.buildItem(item)
#def getXML(self, tostring = False):
def getXML(self, ftype=None):
if ftype:
self.type = ftype
form = ET.Element('{jabber:x:data}x')
form.attrib['type'] = self.type
if self.title and self.type in ('form', 'result'):
title = ET.Element('{jabber:x:data}title')
title.text = self.title
form.append(title)
if self.instructions and self.type == 'form':
instructions = ET.Element('{jabber:x:data}instructions')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXML(self.type))
for reported in self.reported:
form.append(reported.getXML('{jabber:x:data}reported'))
for item in self.items:
form.append(item.getXML(self.type))
#if tostring:
# form = self.xmpp.tostring(form)
return form
def getXHTML(self):
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
if self.title:
title = ET.Element('h2')
title.text = self.title
form.append(title)
if self.instructions:
instructions = ET.Element('p')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXHTML())
for field in self.reported:
form.append(field.getXHTML())
for field in self.items:
form.append(field.getXHTML())
return form
def makeSubmit(self):
self.setType('submit')
class FormField(object):
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
lbtypes = ('fixed', 'text-multi')
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
if not ftype in self.types:
raise ValueError("Invalid Field Type")
self.type = ftype
self.var = var
self.label = label
self.desc = desc
self.options = []
self.required = False
self.value = []
if self.type in self.listtypes:
self.islist = True
else:
self.islist = False
if self.type in self.lbtypes:
self.islinebreak = True
else:
self.islinebreak = False
if value:
self.setValue(value)
def addOption(self, value, label):
if self.islist:
self.options.append((value, label))
else:
raise ValueError("Cannot add options to non-list type field.")
def setTrue(self):
if self.type == 'boolean':
self.value = [True]
def setFalse(self):
if self.type == 'boolean':
self.value = [False]
def require(self):
self.required = True
def setDescription(self, desc):
self.desc = desc
def setValue(self, value):
if self.type == 'boolean':
if value in ('1', 1, True, 'true', 'True', 'yes'):
value = True
else:
value = False
if self.islinebreak and value is not None:
self.value += value.split('\n')
else:
if len(self.value) and (not self.islist or self.type == 'list-single'):
self.value = [value]
else:
self.value.append(value)
def delValue(self, value):
if type(self.value) == type([]):
try:
idx = self.value.index(value)
if idx != -1:
self.value.pop(idx)
except ValueError:
pass
else:
self.value = ''
def setAnswer(self, value):
self.setValue(value)
def buildField(self, xml):
self.type = xml.get('type', 'text-single')
self.label = xml.get('label', '')
for option in xml.findall('{jabber:x:data}option'):
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
for value in xml.findall('{jabber:x:data}value'):
self.setValue(value.text)
if xml.find('{jabber:x:data}required') is not None:
self.require()
if xml.find('{jabber:x:data}desc') is not None:
self.setDescription(xml.find('{jabber:x:data}desc').text)
def getXML(self, ftype):
field = ET.Element('{jabber:x:data}field')
if ftype != 'result':
field.attrib['type'] = self.type
if self.type != 'fixed':
if self.var:
field.attrib['var'] = self.var
if self.label:
field.attrib['label'] = self.label
if ftype == 'form':
for option in self.options:
optionxml = ET.Element('{jabber:x:data}option')
optionxml.attrib['label'] = option[1]
optionval = ET.Element('{jabber:x:data}value')
optionval.text = option[0]
optionxml.append(optionval)
field.append(optionxml)
if self.required:
required = ET.Element('{jabber:x:data}required')
field.append(required)
if self.desc:
desc = ET.Element('{jabber:x:data}desc')
desc.text = self.desc
field.append(desc)
for value in self.value:
valuexml = ET.Element('{jabber:x:data}value')
if value is True or value is False:
if value:
valuexml.text = '1'
else:
valuexml.text = '0'
else:
valuexml.text = value
field.append(valuexml)
return field
def getXHTML(self):
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
if self.label:
label = ET.Element('p')
label.text = "%s: " % self.label
else:
label = ET.Element('p')
label.text = "%s: " % self.var
field.append(label)
if self.type == 'boolean':
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
if len(self.value) and self.value[0] in (True, 'true', '1'):
formf.attrib['checked'] = 'checked'
elif self.type == 'fixed':
formf = ET.Element('p')
try:
formf.text = ', '.join(self.value)
except:
pass
field.append(formf)
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type == 'hidden':
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type in ('jid-multi', 'list-multi'):
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
optf.text = option[1]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(option)
elif self.type in ('jid-single', 'text-single'):
formf = ET.Element('input', {'type': 'text', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
elif self.type == 'list-single':
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0]})
optf.text = option[1]
if not optf.text:
optf.text = option[0]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(optf)
elif self.type == 'text-multi':
formf = ET.Element('textarea', {'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
if not formf.text:
formf.text = ' '
elif self.type == 'text-private':
formf = ET.Element('input', {'type': 'password', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
label.append(formf)
return field

View File

@@ -0,0 +1,585 @@
from .. xmlstream.stanzabase import ElementBase, ET, JID
from .. stanza.iq import Iq
from .. stanza.message import Message
from .. basexmpp import basexmpp
from .. xmlstream.xmlstream import XMLStream
import logging
from . import xep_0004
def stanzaPlugin(stanza, plugin):
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
class PubsubState(ElementBase):
namespace = 'http://jabber.org/protocol/psstate'
name = 'state'
plugin_attrib = 'psstate'
interfaces = set(('node', 'item', 'payload'))
plugin_attrib_map = {}
plugin_tag_map = {}
def setPayload(self, value):
self.xml.append(value)
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)
stanzaPlugin(Iq, PubsubState)
class PubsubStateEvent(ElementBase):
namespace = 'http://jabber.org/protocol/psstate#event'
name = 'event'
plugin_attrib = 'psstate_event'
intefaces = set(tuple())
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(Message, PubsubStateEvent)
stanzaPlugin(PubsubStateEvent, PubsubState)
class Pubsub(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'pubsub'
plugin_attrib = 'pubsub'
interfaces = set(tuple())
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(Iq, Pubsub)
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 = {}
stanzaPlugin(Iq, PubsubOwner)
class Affiliation(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'affiliation'
plugin_attrib = name
interfaces = set(('node', 'affiliation'))
plugin_attrib_map = {}
plugin_tag_map = {}
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,)
def append(self, affiliation):
if not isinstance(affiliation, Affiliation):
raise TypeError
self.xml.append(affiliation.xml)
return self.iterables.append(affiliation)
stanzaPlugin(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 = {}
def setjid(self, value):
self._setattr('jid', str(value))
def getjid(self):
return jid(self._getattr('jid'))
stanzaPlugin(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,)
stanzaPlugin(Pubsub, Subscriptions)
class OptionalSetting(object):
interfaces = set(('required',))
def setRequired(self, value):
value = bool(value)
if value and not self['required']:
self.xml.append(ET.Element("{%s}required" % self.namespace))
elif not value and self['required']:
self.delRequired()
def getRequired(self):
required = self.xml.find("{%s}required" % self.namespace)
if required is not None:
return True
else:
return False
def delRequired(self):
required = self.xml.find("{%s}required" % self.namespace)
if required is not None:
self.xml.remove(required)
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',))
stanzaPlugin(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 = {}
def setPayload(self, value):
self.xml.append(value)
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)
class Items(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'items'
plugin_attrib = 'items'
interfaces = set(tuple())
plugin_attrib_map = {}
plugin_tag_map = {}
subitem = (Item,)
stanzaPlugin(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 = {}
stanzaPlugin(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 = {}
#
# def getType(self):
# t = self._getAttr('type')
# if not t: t == 'leaf'
# return t
#
#stanzaPlugin(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,)
stanzaPlugin(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 = {}
stanzaPlugin(Pubsub, Retract)
class Unsubscribe(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'unsubscribe'
plugin_attrib = name
interfaces = set(('node', '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 Subscribe(ElementBase):
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 getJid(self):
return JID(self._getAttr('jid'))
stanzaPlugin(Pubsub, Subscribe)
class Configure(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'configure'
plugin_attrib = name
interfaces = set(('node', 'type', 'config'))
plugin_attrib_map = {}
plugin_tag_map = {}
def getType(self):
t = self._getAttr('type')
if not t: t == 'leaf'
return t
def getConfig(self):
config = self.xml.find('{jabber:x:data}x')
form = xep_0004.Form()
if config is not None:
form.fromXML(config)
return form
def setConfig(self, value):
self.xml.append(value.getXML())
return self
def delConfig(self):
config = self.xml.find('{jabber:x:data}x')
self.xml.remove(config)
stanzaPlugin(Pubsub, Configure)
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 = {}
def __init__(self, *args, **kwargs):
ElementBase.__init__(self, *args, **kwargs)
def getConfig(self):
config = self.xml.find('{jabber:x:data}x')
form = xep_0004.Form()
if config is not None:
form.fromXML(config)
return form
def setConfig(self, value):
self.xml.append(value.getXML())
return self
def delConfig(self):
config = self.xml.find('{jabber:x:data}x')
self.xml.remove(config)
def getType(self):
t = self._getAttr('type')
if not t: t = 'leaf'
return t
stanzaPlugin(PubsubOwner, DefaultConfig)
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 = {}
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 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 setJid(self, value):
self._setAttr('jid', str(value))
def getJid(self):
return JID(self._getAttr('jid'))
stanzaPlugin(Pubsub, Options)
stanzaPlugin(Subscribe, Options)
class OwnerAffiliations(Affiliations):
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)
stanzaPlugin(PubsubOwner, OwnerAffiliations)
class OwnerAffiliation(Affiliation):
namespace = 'http://jabber.org/protocol/pubsub#owner'
interfaces = set(('affiliation', 'jid'))
plugin_attrib_map = {}
plugin_tag_map = {}
class OwnerConfigure(Configure):
namespace = 'http://jabber.org/protocol/pubsub#owner'
interfaces = set(('node', 'config'))
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(PubsubOwner, OwnerConfigure)
class OwnerDefault(OwnerConfigure):
namespace = 'http://jabber.org/protocol/pubsub#owner'
interfaces = set(('node', 'config'))
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(PubsubOwner, OwnerDefault)
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',))
stanzaPlugin(PubsubOwner, OwnerDelete)
class OwnerPurge(ElementBase, OptionalSetting):
namespace = 'http://jabber.org/protocol/pubsub#owner'
name = 'purge'
plugin_attrib = name
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(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 = {}
def setJid(self, value):
self._setAttr('jid', str(value))
def getJid(self):
return JID(self._getAttr('jid'))
stanzaPlugin(OwnerDelete, OwnerRedirect)
class OwnerSubscriptions(Subscriptions):
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)
stanzaPlugin(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 = {}
def setJid(self, value):
self._setAttr('jid', str(value))
def getJid(self):
return JID(self._getAttr('from'))
class Event(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'event'
plugin_attrib = 'pubsub_event'
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(Message, Event)
class EventItem(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'item'
plugin_attrib = 'item'
interfaces = set(('id', 'payload'))
plugin_attrib_map = {}
plugin_tag_map = {}
def setPayload(self, value):
self.xml.append(value)
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)
class EventRetract(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'retract'
plugin_attrib = 'retract'
interfaces = set(('id',))
plugin_attrib_map = {}
plugin_tag_map = {}
class EventItems(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'items'
plugin_attrib = 'items'
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
subitem = (EventItem, EventRetract)
stanzaPlugin(Event, EventItems)
class EventCollection(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'collection'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(Event, EventCollection)
class EventAssociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'associate'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(EventCollection, EventAssociate)
class EventDisassociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'disassociate'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(EventCollection, EventDisassociate)
class EventConfiguration(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'configuration'
plugin_attrib = name
interfaces = set(('node', 'config'))
plugin_attrib_map = {}
plugin_tag_map = {}
def getConfig(self):
config = self.xml.find('{jabber:x:data}x')
form = xep_0004.Form()
if config is not None:
form.fromXML(config)
return form
def setConfig(self, value):
self.xml.append(value.getXML())
return self
def delConfig(self):
config = self.xml.find('{jabber:x:data}x')
self.xml.remove(config)
stanzaPlugin(Event, EventConfiguration)
class EventPurge(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'purge'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
stanzaPlugin(Event, EventPurge)
class EventSubscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'subscription'
plugin_attrib = name
interfaces = set(('node','expiry', 'jid', 'subid', 'subscription'))
plugin_attrib_map = {}
plugin_tag_map = {}
def setJid(self, value):
self._setAttr('jid', str(value))
def getJid(self):
return JID(self._getAttr('jid'))
stanzaPlugin(Event, EventSubscription)

View File

@@ -1,395 +1,427 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
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',))
import logging
from xml.etree import cElementTree as ET
import copy
#TODO support item groups and results
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
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
def handle_form(self, message):
self.xmpp.event("message_xform", message)
def handler_message_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("message_form", object)
def handler_presence_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("presence_form", object)
def handle_form(self, xml):
xmlform = xml.find('{jabber:x:data}x')
object = self.buildForm(xmlform)
self.xmpp.event("message_xform", object)
return object
def buildForm(self, xml):
return Form(xml=xml)
form = Form(ftype=xml.attrib['type'])
form.fromXML(xml)
return form
def makeForm(self, ftype='form', title='', instructions=''):
return Form(self.xmpp, ftype, title, instructions)
class FieldContainer(object):
def __init__(self, stanza = 'form'):
self.fields = []
self.field = {}
self.stanza = stanza
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
self.field[var] = FormField(var, ftype, label, desc, required, value)
self.fields.append(self.field[var])
return self.field[var]
def buildField(self, xml):
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
self.fields.append(self.field[xml.get('var', '__unnamed__')])
self.field[xml.get('var', '__unnamed__')].buildField(xml)
def buildContainer(self, xml):
self.stanza = xml.tag
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
def getXML(self, ftype):
container = ET.Element(self.stanza)
for field in self.fields:
container.append(field.getXML(ftype))
return container
class Form(FieldContainer):
types = ('form', 'submit', 'cancel', 'result')
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
if not ftype in self.types:
raise ValueError("Invalid Form Type")
FieldContainer.__init__(self)
self.xmpp = xmpp
self.type = ftype
self.title = title
self.instructions = instructions
self.reported = []
self.items = []
def merge(self, form2):
form1 = Form(ftype=self.type)
form1.fromXML(self.getXML(self.type))
for field in form2.fields:
if not field.var in form1.field:
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
else:
form1.field[field.var].value = field.value
for option, label in field.options:
if (option, label) not in form1.field[field.var].options:
form1.fields[field.var].addOption(option, label)
return form1
def copy(self):
newform = Form(ftype=self.type)
newform.fromXML(self.getXML(self.type))
return newform
def update(self, form):
values = form.getValues()
for var in values:
if var in self.fields:
self.fields[var].setValue(self.fields[var])
def getValues(self):
result = {}
for field in self.fields:
value = field.value
if len(value) == 1:
value = value[0]
result[field.var] = value
return result
def setValues(self, values={}):
for field in values:
if field in self.field:
if isinstance(values[field], list) or isinstance(values[field], tuple):
for value in values[field]:
self.field[field].setValue(value)
else:
self.field[field].setValue(values[field])
def fromXML(self, xml):
self.buildForm(xml)
def addItem(self):
newitem = FieldContainer('item')
self.items.append(newitem)
return newitem
def buildItem(self, xml):
newitem = self.addItem()
newitem.buildContainer(xml)
def addReported(self):
reported = FieldContainer('reported')
self.reported.append(reported)
return reported
def buildReported(self, xml):
reported = self.addReported()
reported.buildContainer(xml)
def setTitle(self, title):
self.title = title
def setInstructions(self, instructions):
self.instructions = instructions
def setType(self, ftype):
self.type = ftype
def getXMLMessage(self, to):
msg = self.xmpp.makeMessage(to)
msg.append(self.getXML())
return msg
def buildForm(self, xml):
self.type = xml.get('type', 'form')
if xml.find('{jabber:x:data}title') is not None:
self.setTitle(xml.find('{jabber:x:data}title').text)
if xml.find('{jabber:x:data}instructions') is not None:
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
for reported in xml.findall('{jabber:x:data}reported'):
self.buildReported(reported)
for item in xml.findall('{jabber:x:data}item'):
self.buildItem(item)
#def getXML(self, tostring = False):
def getXML(self, ftype=None):
if ftype:
self.type = ftype
form = ET.Element('{jabber:x:data}x')
form.attrib['type'] = self.type
if self.title and self.type in ('form', 'result'):
title = ET.Element('{jabber:x:data}title')
title.text = self.title
form.append(title)
if self.instructions and self.type == 'form':
instructions = ET.Element('{jabber:x:data}instructions')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXML(self.type))
for reported in self.reported:
form.append(reported.getXML('{jabber:x:data}reported'))
for item in self.items:
form.append(item.getXML(self.type))
#if tostring:
# form = self.xmpp.tostring(form)
return form
def getXHTML(self):
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
if self.title:
title = ET.Element('h2')
title.text = self.title
form.append(title)
if self.instructions:
instructions = ET.Element('p')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXHTML())
for field in self.reported:
form.append(field.getXHTML())
for field in self.items:
form.append(field.getXHTML())
return form
def makeSubmit(self):
self.setType('submit')
class FormField(object):
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
lbtypes = ('fixed', 'text-multi')
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
if not ftype in self.types:
raise ValueError("Invalid Field Type")
self.type = ftype
self.var = var
self.label = label
self.desc = desc
self.options = []
self.required = False
self.value = []
if self.type in self.listtypes:
self.islist = True
else:
self.islist = False
if self.type in self.lbtypes:
self.islinebreak = True
else:
self.islinebreak = False
if value:
self.setValue(value)
def addOption(self, value, label):
if self.islist:
self.options.append((value, label))
else:
raise ValueError("Cannot add options to non-list type field.")
def setTrue(self):
if self.type == 'boolean':
self.value = [True]
def setFalse(self):
if self.type == 'boolean':
self.value = [False]
def require(self):
self.required = True
def setDescription(self, desc):
self.desc = desc
def setValue(self, value):
if self.type == 'boolean':
if value in ('1', 1, True, 'true', 'True', 'yes'):
value = True
else:
value = False
if self.islinebreak and value is not None:
self.value += value.split('\n')
else:
if len(self.value) and (not self.islist or self.type == 'list-single'):
self.value = [value]
else:
self.value.append(value)
def delValue(self, value):
if type(self.value) == type([]):
try:
idx = self.value.index(value)
if idx != -1:
self.value.pop(idx)
except ValueError:
pass
else:
self.value = ''
def setAnswer(self, value):
self.setValue(value)
def buildField(self, xml):
self.type = xml.get('type', 'text-single')
self.label = xml.get('label', '')
for option in xml.findall('{jabber:x:data}option'):
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
for value in xml.findall('{jabber:x:data}value'):
self.setValue(value.text)
if xml.find('{jabber:x:data}required') is not None:
self.require()
if xml.find('{jabber:x:data}desc') is not None:
self.setDescription(xml.find('{jabber:x:data}desc').text)
def getXML(self, ftype):
field = ET.Element('{jabber:x:data}field')
if ftype != 'result':
field.attrib['type'] = self.type
if self.type != 'fixed':
if self.var:
field.attrib['var'] = self.var
if self.label:
field.attrib['label'] = self.label
if ftype == 'form':
for option in self.options:
optionxml = ET.Element('{jabber:x:data}option')
optionxml.attrib['label'] = option[1]
optionval = ET.Element('{jabber:x:data}value')
optionval.text = option[0]
optionxml.append(optionval)
field.append(optionxml)
if self.required:
required = ET.Element('{jabber:x:data}required')
field.append(required)
if self.desc:
desc = ET.Element('{jabber:x:data}desc')
desc.text = self.desc
field.append(desc)
for value in self.value:
valuexml = ET.Element('{jabber:x:data}value')
if value is True or value is False:
if value:
valuexml.text = '1'
else:
valuexml.text = '0'
else:
valuexml.text = value
field.append(valuexml)
return field
def getXHTML(self):
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
if self.label:
label = ET.Element('p')
label.text = "%s: " % self.label
else:
label = ET.Element('p')
label.text = "%s: " % self.var
field.append(label)
if self.type == 'boolean':
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
if len(self.value) and self.value[0] in (True, 'true', '1'):
formf.attrib['checked'] = 'checked'
elif self.type == 'fixed':
formf = ET.Element('p')
try:
formf.text = ', '.join(self.value)
except:
pass
field.append(formf)
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type == 'hidden':
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type in ('jid-multi', 'list-multi'):
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
optf.text = option[1]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(option)
elif self.type in ('jid-single', 'text-single'):
formf = ET.Element('input', {'type': 'text', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
elif self.type == 'list-single':
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0]})
optf.text = option[1]
if not optf.text:
optf.text = option[0]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(optf)
elif self.type == 'text-multi':
formf = ET.Element('textarea', {'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
if not formf.text:
formf.text = ' '
elif self.type == 'text-private':
formf = ET.Element('input', {'type': 'password', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
label.append(formf)
return field

View File

@@ -178,12 +178,9 @@ class xep_0009(base.base_plugin):
def plugin_init(self):
self.xep = '0009'
self.description = 'Jabber-RPC'
self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
self._callMethod, name='Jabber RPC Call')
self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
self._callResult, name='Jabber RPC Result')
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
self._callError, name='Jabber RPC Error')
self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", self._callMethod)
self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", self._callResult)
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", self._callError)
self.entries = {}
self.activeCalls = []

View File

@@ -1,11 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0009 import stanza
from sleekxmpp.plugins.xep_0009.rpc import xep_0009
from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse

View File

@@ -1,166 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from xml.etree import cElementTree as ET
import base64
import logging
import time
log = logging.getLogger(__name__)
_namespace = 'jabber:iq:rpc'
def fault2xml(fault):
value = dict()
value['faultCode'] = fault['code']
value['faultString'] = fault['string']
fault = ET.Element("fault", {'xmlns': _namespace})
fault.append(_py2xml((value)))
return fault
def xml2fault(params):
vals = []
for value in params.findall('{%s}value' % _namespace):
vals.append(_xml2py(value))
fault = dict()
fault['code'] = vals[0]['faultCode']
fault['string'] = vals[0]['faultString']
return fault
def py2xml(*args):
params = ET.Element("{%s}params" % _namespace)
for x in args:
param = ET.Element("{%s}param" % _namespace)
param.append(_py2xml(x))
params.append(param) #<params><param>...
return params
def _py2xml(*args):
for x in args:
val = ET.Element("value")
if x is None:
nil = ET.Element("nil")
val.append(nil)
elif type(x) is int:
i4 = ET.Element("i4")
i4.text = str(x)
val.append(i4)
elif type(x) is bool:
boolean = ET.Element("boolean")
boolean.text = str(int(x))
val.append(boolean)
elif type(x) is str:
string = ET.Element("string")
string.text = x
val.append(string)
elif type(x) is float:
double = ET.Element("double")
double.text = str(x)
val.append(double)
elif type(x) is rpcbase64:
b64 = ET.Element("Base64")
b64.text = x.encoded()
val.append(b64)
elif type(x) is rpctime:
iso = ET.Element("dateTime.iso8601")
iso.text = str(x)
val.append(iso)
elif type(x) in (list, tuple):
array = ET.Element("array")
data = ET.Element("data")
for y in x:
data.append(_py2xml(y))
array.append(data)
val.append(array)
elif type(x) is dict:
struct = ET.Element("struct")
for y in x.keys():
member = ET.Element("member")
name = ET.Element("name")
name.text = y
member.append(name)
member.append(_py2xml(x[y]))
struct.append(member)
val.append(struct)
return val
def xml2py(params):
namespace = 'jabber:iq:rpc'
vals = []
for param in params.findall('{%s}param' % namespace):
vals.append(_xml2py(param.find('{%s}value' % namespace)))
return vals
def _xml2py(value):
namespace = 'jabber:iq:rpc'
if value.find('{%s}nil' % namespace) is not None:
return None
if value.find('{%s}i4' % namespace) is not None:
return int(value.find('{%s}i4' % namespace).text)
if value.find('{%s}int' % namespace) is not None:
return int(value.find('{%s}int' % namespace).text)
if value.find('{%s}boolean' % namespace) is not None:
return bool(value.find('{%s}boolean' % namespace).text)
if value.find('{%s}string' % namespace) is not None:
return value.find('{%s}string' % namespace).text
if value.find('{%s}double' % namespace) is not None:
return float(value.find('{%s}double' % namespace).text)
if value.find('{%s}Base64') is not None:
return rpcbase64(value.find('Base64' % namespace).text)
if value.find('{%s}dateTime.iso8601') is not None:
return rpctime(value.find('{%s}dateTime.iso8601'))
if value.find('{%s}struct' % namespace) is not None:
struct = {}
for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace):
struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace))
return struct
if value.find('{%s}array' % namespace) is not None:
array = []
for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
array.append(_xml2py(val))
return array
raise ValueError()
class rpcbase64(object):
def __init__(self, data):
#base 64 encoded string
self.data = data
def decode(self):
return base64.decodestring(self.data)
def __str__(self):
return self.decode()
def encoded(self):
return self.data
class rpctime(object):
def __init__(self,data=None):
#assume string data is in iso format YYYYMMDDTHH:MM:SS
if type(data) is str:
self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
elif type(data) is time.struct_time:
self.timestamp = data
elif data is None:
self.timestamp = time.gmtime()
else:
raise ValueError()
def iso8601(self):
#return a iso8601 string
return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
def __str__(self):
return self.iso8601()

View File

@@ -1,739 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from binding import py2xml, xml2py, xml2fault, fault2xml
from threading import RLock
import abc
import inspect
import logging
import sleekxmpp
import sys
import threading
import traceback
log = logging.getLogger(__name__)
def _intercept(method, name, public):
def _resolver(instance, *args, **kwargs):
log.debug("Locally calling %s.%s with arguments %s." % (instance.FQN(), method.__name__, args))
try:
value = method(instance, *args, **kwargs)
if value == NotImplemented:
raise InvocationException("Local handler does not implement %s.%s!" % (instance.FQN(), method.__name__))
return value
except InvocationException:
raise
except Exception as e:
raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
_resolver._rpc = public
_resolver._rpc_name = method.__name__ if name is None else name
return _resolver
def remote(function_argument, public = True):
'''
Decorator for methods which are remotely callable. This decorator
works in conjunction with classes which extend ABC Endpoint.
Example:
@remote
def remote_method(arg1, arg2)
Arguments:
function_argument -- a stand-in for either the actual method
OR a new name (string) for the method. In that case the
method is considered mapped:
Example:
@remote("new_name")
def remote_method(arg1, arg2)
public -- A flag which indicates if this method should be part
of the known dictionary of remote methods. Defaults to True.
Example:
@remote(False)
def remote_method(arg1, arg2)
Note: renaming and revising (public vs. private) can be combined.
Example:
@remote("new_name", False)
def remote_method(arg1, arg2)
'''
if hasattr(function_argument, '__call__'):
return _intercept(function_argument, None, public)
else:
if not isinstance(function_argument, basestring):
if not isinstance(function_argument, bool):
raise Exception('Expected an RPC method name or visibility modifier!')
else:
def _wrap_revised(function):
function = _intercept(function, None, function_argument)
return function
return _wrap_revised
def _wrap_remapped(function):
function = _intercept(function, function_argument, public)
return function
return _wrap_remapped
class ACL:
'''
An Access Control List (ACL) is a list of rules, which are evaluated
in order until a match is found. The policy of the matching rule
is then applied.
Rules are 3-tuples, consisting of a policy enumerated type, a JID
expression and a RCP resource expression.
Examples:
[ (ACL.ALLOW, '*', '*') ] allow everyone everything, no restrictions
[ (ACL.DENY, '*', '*') ] deny everyone everything, no restrictions
[ (ACL.ALLOW, 'test@xmpp.org/unit', 'test.*'),
(ACL.DENY, '*', '*') ] deny everyone everything, except named
JID, which is allowed access to endpoint 'test' only.
The use of wildcards is allowed in expressions, as follows:
'*' everyone, or everything (= all endpoints and methods)
'test@xmpp.org/*' every JID regardless of JID resource
'*@xmpp.org/rpc' every JID from domain xmpp.org with JID res 'rpc'
'frank@*' every 'frank', regardless of domain or JID res
'system.*' all methods of endpoint 'system'
'*.reboot' all methods reboot regardless of endpoint
'''
ALLOW = True
DENY = False
@classmethod
def check(cls, rules, jid, resource):
if rules is None:
return cls.DENY # No rules means no access!
for rule in rules:
policy = cls._check(rule, jid, resource)
if policy is not None:
return policy
return cls.DENY # By default if not rule matches, deny access.
@classmethod
def _check(cls, rule, jid, resource):
if cls._match(jid, rule[1]) and cls._match(resource, rule[2]):
return rule[0]
else:
return None
@classmethod
def _next_token(cls, expression, index):
new_index = expression.find('*', index)
if new_index == 0:
return ''
else:
if new_index == -1:
return expression[index : ]
else:
return expression[index : new_index]
@classmethod
def _match(cls, value, expression):
#! print "_match [VALUE] %s [EXPR] %s" % (value, expression)
index = 0
position = 0
while index < len(expression):
token = cls._next_token(expression, index)
#! print "[TOKEN] '%s'" % token
size = len(token)
if size > 0:
token_index = value.find(token, position)
if token_index == -1:
return False
else:
#! print "[INDEX-OF] %s" % token_index
position = token_index + len(token)
pass
if size == 0:
index += 1
else:
index += size
#! print "index %s position %s" % (index, position)
return True
ANY_ALL = [ (ACL.ALLOW, '*', '*') ]
class RemoteException(Exception):
'''
Base exception for RPC. This exception is raised when a problem
occurs in the network layer.
'''
def __init__(self, message="", cause=None):
'''
Initializes a new RemoteException.
Arguments:
message -- The message accompanying this exception.
cause -- The underlying cause of this exception.
'''
self._message = message
self._cause = cause
pass
def __str__(self):
return repr(self._message)
def get_message(self):
return self._message
def get_cause(self):
return self._cause
class InvocationException(RemoteException):
'''
Exception raised when a problem occurs during the remote invocation
of a method.
'''
pass
class AuthorizationException(RemoteException):
'''
Exception raised when the caller is not authorized to invoke the
remote method.
'''
pass
class TimeoutException(Exception):
'''
Exception raised when the synchronous execution of a method takes
longer than the given threshold because an underlying asynchronous
reply did not arrive in time.
'''
pass
class Callback(object):
'''
A base class for callback handlers.
'''
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def set_value(self, value):
return NotImplemented
@abc.abstractproperty
def cancel_with_error(self, exception):
return NotImplemented
class Future(Callback):
'''
Represents the result of an asynchronous computation.
'''
def __init__(self):
'''
Initializes a new Future.
'''
self._value = None
self._exception = None
self._event = threading.Event()
pass
def set_value(self, value):
'''
Sets the value of this Future. Once the value is set, a caller
blocked on get_value will be able to continue.
'''
self._value = value
self._event.set()
def get_value(self, timeout=None):
'''
Gets the value of this Future. This call will block until
the result is available, or until an optional timeout expires.
When this Future is cancelled with an error,
Arguments:
timeout -- The maximum waiting time to obtain the value.
'''
self._event.wait(timeout)
if self._exception:
raise self._exception
if not self._event.is_set():
raise TimeoutException
return self._value
def is_done(self):
'''
Returns true if a value has been returned.
'''
return self._event.is_set()
def cancel_with_error(self, exception):
'''
Cancels the Future because of an error. Once cancelled, a
caller blocked on get_value will be able to continue.
'''
self._exception = exception
self._event.set()
class Endpoint(object):
'''
The Endpoint class is an abstract base class for all objects
participating in an RPC-enabled XMPP network.
A user subclassing this class is required to implement the method:
FQN(self)
where FQN stands for Fully Qualified Name, an unambiguous name
which specifies which object an RPC call refers to. It is the
first part in a RPC method name '<fqn>.<method>'.
'''
__metaclass__ = abc.ABCMeta
def __init__(self, session, target_jid):
'''
Initialize a new Endpoint. This constructor should never be
invoked by a user, instead it will be called by the factories
which instantiate the RPC-enabled objects, of which only
the classes are provided by the user.
Arguments:
session -- An RPC session instance.
target_jid -- the identity of the remote XMPP entity.
'''
self.session = session
self.target_jid = target_jid
@abc.abstractproperty
def FQN(self):
return NotImplemented
def get_methods(self):
'''
Returns a dictionary of all RPC method names provided by this
class. This method returns the actual method names as found
in the class definition which have been decorated with:
@remote
def some_rpc_method(arg1, arg2)
Unless:
(1) the name has been remapped, in which case the new
name will be returned.
@remote("new_name")
def some_rpc_method(arg1, arg2)
(2) the method is set to hidden
@remote(False)
def some_hidden_method(arg1, arg2)
'''
result = dict()
for function in dir(self):
test_attr = getattr(self, function, None)
try:
if test_attr._rpc:
result[test_attr._rpc_name] = test_attr
except Exception:
pass
return result
class Proxy(Endpoint):
'''
Implementation of the Proxy pattern which is intended to wrap
around Endpoints in order to intercept calls, marshall them and
forward them to the remote object.
'''
def __init__(self, endpoint, callback = None):
'''
Initializes a new Proxy.
Arguments:
endpoint -- The endpoint which is proxified.
'''
self._endpoint = endpoint
self._callback = callback
def __getattribute__(self, name, *args):
if name in ('__dict__', '_endpoint', 'async', '_callback'):
return object.__getattribute__(self, name)
else:
attribute = self._endpoint.__getattribute__(name)
if hasattr(attribute, '__call__'):
try:
if attribute._rpc:
def _remote_call(*args, **kwargs):
log.debug("Remotely calling '%s.%s' with arguments %s." % (self._endpoint.FQN(), attribute._rpc_name, args))
return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs)
return _remote_call
except:
pass # If the attribute doesn't exist, don't care!
return attribute
def async(self, callback):
return Proxy(self._endpoint, callback)
def get_endpoint(self):
'''
Returns the proxified endpoint.
'''
return self._endpoint
def FQN(self):
return self._endpoint.FQN()
class JabberRPCEntry(object):
def __init__(self, endpoint_FQN, call):
self._endpoint_FQN = endpoint_FQN
self._call = call
def call_method(self, args):
return_value = self._call(*args)
if return_value is None:
return return_value
else:
return self._return(return_value)
def get_endpoint_FQN(self):
return self._endpoint_FQN
def _return(self, *args):
return args
class RemoteSession(object):
'''
A context object for a Jabber-RPC session.
'''
def __init__(self, client, session_close_callback):
'''
Initializes a new RPC session.
Arguments:
client -- The SleekXMPP client associated with this session.
session_close_callback -- A callback called when the
session is closed.
'''
self._client = client
self._session_close_callback = session_close_callback
self._event = threading.Event()
self._entries = {}
self._callbacks = {}
self._acls = {}
self._lock = RLock()
def _wait(self):
self._event.wait()
def _notify(self, event):
log.debug("RPC Session as %s started." % self._client.boundjid.full)
self._client.sendPresence()
self._event.set()
pass
def _register_call(self, endpoint, method, name=None):
'''
Registers a method from an endpoint as remotely callable.
'''
if name is None:
name = method.__name__
key = "%s.%s" % (endpoint, name)
log.debug("Registering call handler for %s (%s)." % (key, method))
with self._lock:
if key in self._entries:
raise KeyError("A handler for %s has already been regisered!" % endpoint)
self._entries[key] = JabberRPCEntry(endpoint, method)
return key
def _register_acl(self, endpoint, acl):
log.debug("Registering ACL %s for endpoint %s." % (repr(acl), endpoint))
with self._lock:
self._acls[endpoint] = acl
def _register_callback(self, pid, callback):
with self._lock:
self._callbacks[pid] = callback
def forget_callback(self, callback):
with self._lock:
pid = self._find_key(self._callbacks, callback)
if pid is not None:
del self._callback[pid]
else:
raise ValueError("Unknown callback!")
pass
def _find_key(self, dict, value):
"""return the key of dictionary dic given the value"""
search = [k for k, v in dict.iteritems() if v == value]
if len(search) == 0:
return None
else:
return search[0]
def _unregister_call(self, key):
#removes the registered call
with self._lock:
if self._entries[key]:
del self._entries[key]
else:
raise ValueError()
def new_proxy(self, target_jid, endpoint_cls):
'''
Instantiates a new proxy object, which proxies to a remote
endpoint. This method uses a class reference without
constructor arguments to instantiate the proxy.
Arguments:
target_jid -- the XMPP entity ID hosting the endpoint.
endpoint_cls -- The remote (duck) type.
'''
try:
argspec = inspect.getargspec(endpoint_cls.__init__)
args = [None] * (len(argspec[0]) - 1)
result = endpoint_cls(*args)
Endpoint.__init__(result, self, target_jid)
return Proxy(result)
except:
traceback.print_exc(file=sys.stdout)
def new_handler(self, acl, handler_cls, *args, **kwargs):
'''
Instantiates a new handler object, which is called remotely
by others. The user can control the effect of the call by
implementing the remote method in the local endpoint class. The
returned reference can be called locally and will behave as a
regular instance.
Arguments:
acl -- Access control list (see ACL class)
handler_clss -- The local (duck) type.
*args -- Constructor arguments for the local type.
**kwargs -- Constructor keyworded arguments for the local
type.
'''
argspec = inspect.getargspec(handler_cls.__init__)
base_argspec = inspect.getargspec(Endpoint.__init__)
if(argspec == base_argspec):
result = handler_cls(self, self._client.boundjid.full)
else:
result = handler_cls(*args, **kwargs)
Endpoint.__init__(result, self, self._client.boundjid.full)
method_dict = result.get_methods()
for method_name, method in method_dict.iteritems():
#!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
self._register_call(result.FQN(), method, method_name)
self._register_acl(result.FQN(), acl)
return result
# def is_available(self, targetCls, pto):
# return self._client.is_available(pto)
def _call_remote(self, pto, pmethod, callback, *arguments):
iq = self._client.plugin['xep_0009'].make_iq_method_call(pto, pmethod, py2xml(*arguments))
pid = iq['id']
if callback is None:
future = Future()
self._register_callback(pid, future)
iq.send()
return future.get_value(30)
else:
log.debug("[RemoteSession] _call_remote %s" % callback)
self._register_callback(pid, callback)
iq.send()
def close(self):
'''
Closes this session.
'''
self._client.disconnect(False)
self._session_close_callback()
def _on_jabber_rpc_method_call(self, iq):
iq.enable('rpc_query')
params = iq['rpc_query']['method_call']['params']
args = xml2py(params)
pmethod = iq['rpc_query']['method_call']['method_name']
try:
with self._lock:
entry = self._entries[pmethod]
rules = self._acls[entry.get_endpoint_FQN()]
if ACL.check(rules, iq['from'], pmethod):
return_value = entry.call_method(args)
else:
raise AuthorizationException("Unauthorized access to %s from %s!" % (pmethod, iq['from']))
if return_value is None:
return_value = ()
response = self._client.plugin['xep_0009'].make_iq_method_response(iq['id'], iq['from'], py2xml(*return_value))
response.send()
except InvocationException as ie:
fault = dict()
fault['code'] = 500
fault['string'] = ie.get_message()
self._client.plugin['xep_0009']._send_fault(iq, fault2xml(fault))
except AuthorizationException as ae:
log.error(ae.get_message())
error = self._client.plugin['xep_0009']._forbidden(iq)
error.send()
except Exception as e:
if isinstance(e, KeyError):
log.error("No handler available for %s!" % pmethod)
error = self._client.plugin['xep_0009']._item_not_found(iq)
else:
traceback.print_exc(file=sys.stderr)
log.error("An unexpected problem occurred invoking method %s!" % pmethod)
error = self._client.plugin['xep_0009']._undefined_condition(iq)
#! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e
error.send()
def _on_jabber_rpc_method_response(self, iq):
iq.enable('rpc_query')
args = xml2py(iq['rpc_query']['method_response']['params'])
pid = iq['id']
with self._lock:
callback = self._callbacks[pid]
del self._callbacks[pid]
if(len(args) > 0):
callback.set_value(args[0])
else:
callback.set_value(None)
pass
def _on_jabber_rpc_method_response2(self, iq):
iq.enable('rpc_query')
if iq['rpc_query']['method_response']['fault'] is not None:
self._on_jabber_rpc_method_fault(iq)
else:
args = xml2py(iq['rpc_query']['method_response']['params'])
pid = iq['id']
with self._lock:
callback = self._callbacks[pid]
del self._callbacks[pid]
if(len(args) > 0):
callback.set_value(args[0])
else:
callback.set_value(None)
pass
def _on_jabber_rpc_method_fault(self, iq):
iq.enable('rpc_query')
fault = xml2fault(iq['rpc_query']['method_response']['fault'])
pid = iq['id']
with self._lock:
callback = self._callbacks[pid]
del self._callbacks[pid]
e = {
500: InvocationException
}[fault['code']](fault['string'])
callback.cancel_with_error(e)
def _on_jabber_rpc_error(self, iq):
pid = iq['id']
pmethod = self._client.plugin['xep_0009']._extract_method(iq['rpc_query'])
code = iq['error']['code']
type = iq['error']['type']
condition = iq['error']['condition']
#! print("['REMOTE.PY']._BINDING_handle_remote_procedure_error -> ERROR! ERROR! ERROR! Condition is '%s'" % condition)
with self._lock:
callback = self._callbacks[pid]
del self._callbacks[pid]
e = {
'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),
}[condition]
if e is None:
RemoteException("An unexpected exception occurred at %s!" % iq['from'])
callback.cancel_with_error(e)
class Remote(object):
'''
Bootstrap class for Jabber-RPC sessions. New sessions are openend
with an existing XMPP client, or one is instantiated on demand.
'''
_instance = None
_sessions = dict()
_lock = threading.RLock()
@classmethod
def new_session_with_client(cls, client, callback=None):
'''
Opens a new session with a given client.
Arguments:
client -- An XMPP client.
callback -- An optional callback which can be used to track
the starting state of the session.
'''
with Remote._lock:
if(client.boundjid.bare in cls._sessions):
raise RemoteException("There already is a session associated with these credentials!")
else:
cls._sessions[client.boundjid.bare] = client;
def _session_close_callback():
with Remote._lock:
del cls._sessions[client.boundjid.bare]
result = RemoteSession(client, _session_close_callback)
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call)
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response)
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault)
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error)
if callback is None:
start_event_handler = result._notify
else:
start_event_handler = callback
client.add_event_handler("session_start", start_event_handler)
if client.connect():
client.process(threaded=True)
else:
raise RemoteException("Could not connect to XMPP server!")
pass
if callback is None:
result._wait()
return result
@classmethod
def new_session(cls, jid, password, callback=None):
'''
Opens a new session and instantiates a new XMPP client.
Arguments:
jid -- The XMPP JID for logging in.
password -- The password for logging in.
callback -- An optional callback which can be used to track
the starting state of the session.
'''
client = sleekxmpp.ClientXMPP(jid, password)
#? Register plug-ins.
client.registerPlugin('xep_0004') # Data Forms
client.registerPlugin('xep_0009') # Jabber-RPC
client.registerPlugin('xep_0030') # Service Discovery
client.registerPlugin('xep_0060') # PubSub
client.registerPlugin('xep_0199') # XMPP Ping
return cls.new_session_with_client(client, callback)

View File

@@ -1,221 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins import base
from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
from sleekxmpp.stanza.iq import Iq
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
from xml.etree import cElementTree as ET
import logging
log = logging.getLogger(__name__)
class xep_0009(base.base_plugin):
def plugin_init(self):
self.xep = '0009'
self.description = 'Jabber-RPC'
#self.stanza = sleekxmpp.plugins.xep_0009.stanza
register_stanza_plugin(Iq, RPCQuery)
register_stanza_plugin(RPCQuery, MethodCall)
register_stanza_plugin(RPCQuery, MethodResponse)
self.xmpp.registerHandler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
self._handle_method_call)
)
self.xmpp.registerHandler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
self._handle_method_response)
)
self.xmpp.registerHandler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
self._handle_error)
)
self.xmpp.add_event_handler('jabber_rpc_method_call', self._on_jabber_rpc_method_call)
self.xmpp.add_event_handler('jabber_rpc_method_response', self._on_jabber_rpc_method_response)
self.xmpp.add_event_handler('jabber_rpc_method_fault', self._on_jabber_rpc_method_fault)
self.xmpp.add_event_handler('jabber_rpc_error', self._on_jabber_rpc_error)
self.xmpp.add_event_handler('error', self._handle_error)
#self.activeCalls = []
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
self.xmpp.plugin['xep_0030'].add_identity('automation','rpc')
def make_iq_method_call(self, pto, pmethod, params):
iq = self.xmpp.makeIqSet()
iq.attrib['to'] = pto
iq.attrib['from'] = self.xmpp.boundjid.full
iq.enable('rpc_query')
iq['rpc_query']['method_call']['method_name'] = pmethod
iq['rpc_query']['method_call']['params'] = params
return iq;
def make_iq_method_response(self, pid, pto, params):
iq = self.xmpp.makeIqResult(pid)
iq.attrib['to'] = pto
iq.attrib['from'] = self.xmpp.boundjid.full
iq.enable('rpc_query')
iq['rpc_query']['method_response']['params'] = params
return iq
def make_iq_method_response_fault(self, pid, pto, params):
iq = self.xmpp.makeIqResult(pid)
iq.attrib['to'] = pto
iq.attrib['from'] = self.xmpp.boundjid.full
iq.enable('rpc_query')
iq['rpc_query']['method_response']['params'] = None
iq['rpc_query']['method_response']['fault'] = params
return iq
# def make_iq_method_error(self, pto, pid, pmethod, params, code, type, condition):
# iq = self.xmpp.makeIqError(pid)
# iq.attrib['to'] = pto
# iq.attrib['from'] = self.xmpp.boundjid.full
# iq['error']['code'] = code
# iq['error']['type'] = type
# iq['error']['condition'] = condition
# iq['rpc_query']['method_call']['method_name'] = pmethod
# iq['rpc_query']['method_call']['params'] = params
# return iq
def _item_not_found(self, iq):
payload = iq.get_payload()
iq.reply().error().set_payload(payload);
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
return iq
def _undefined_condition(self, iq):
payload = iq.get_payload()
iq.reply().error().set_payload(payload)
iq['error']['code'] = '500'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'undefined-condition'
return iq
def _forbidden(self, iq):
payload = iq.get_payload()
iq.reply().error().set_payload(payload)
iq['error']['code'] = '403'
iq['error']['type'] = 'auth'
iq['error']['condition'] = 'forbidden'
return iq
def _recipient_unvailable(self, iq):
payload = iq.get_payload()
iq.reply().error().set_payload(payload)
iq['error']['code'] = '404'
iq['error']['type'] = 'wait'
iq['error']['condition'] = 'recipient-unavailable'
return iq
def _handle_method_call(self, iq):
type = iq['type']
if type == 'set':
log.debug("Incoming Jabber-RPC call from %s" % iq['from'])
self.xmpp.event('jabber_rpc_method_call', iq)
else:
if type == 'error' and ['rpc_query'] is None:
self.handle_error(iq)
else:
log.debug("Incoming Jabber-RPC error from %s" % iq['from'])
self.xmpp.event('jabber_rpc_error', iq)
def _handle_method_response(self, iq):
if iq['rpc_query']['method_response']['fault'] is not None:
log.debug("Incoming Jabber-RPC fault from %s" % iq['from'])
#self._on_jabber_rpc_method_fault(iq)
self.xmpp.event('jabber_rpc_method_fault', iq)
else:
log.debug("Incoming Jabber-RPC response from %s" % iq['from'])
self.xmpp.event('jabber_rpc_method_response', iq)
def _handle_error(self, iq):
print("['XEP-0009']._handle_error -> ERROR! Iq is '%s'" % iq)
print("#######################")
print("### NOT IMPLEMENTED ###")
print("#######################")
def _on_jabber_rpc_method_call(self, iq, forwarded=False):
"""
A default handler for Jabber-RPC method call. If another
handler is registered, this one will defer and not run.
If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal.
"""
if not forwarded and self.xmpp.event_handled('jabber_rpc_method_call') > 1:
return
# Reply with error by default
error = self.client.plugin['xep_0009']._item_not_found(iq)
error.send()
def _on_jabber_rpc_method_response(self, iq, forwarded=False):
"""
A default handler for Jabber-RPC method response. If another
handler is registered, this one will defer and not run.
If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal.
"""
if not forwarded and self.xmpp.event_handled('jabber_rpc_method_response') > 1:
return
error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
error.send()
def _on_jabber_rpc_method_fault(self, iq, forwarded=False):
"""
A default handler for Jabber-RPC fault response. If another
handler is registered, this one will defer and not run.
If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal.
"""
if not forwarded and self.xmpp.event_handled('jabber_rpc_method_fault') > 1:
return
error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
error.send()
def _on_jabber_rpc_error(self, iq, forwarded=False):
"""
A default handler for Jabber-RPC error response. If another
handler is registered, this one will defer and not run.
If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal.
"""
if not forwarded and self.xmpp.event_handled('jabber_rpc_error') > 1:
return
error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload())
error.send()
def _send_fault(self, iq, fault_xml): #
fault = self.make_iq_method_response_fault(iq['id'], iq['from'], fault_xml)
fault.send()
def _send_error(self, iq):
print("['XEP-0009']._send_error -> ERROR! Iq is '%s'" % iq)
print("#######################")
print("### NOT IMPLEMENTED ###")
print("#######################")
def _extract_method(self, stanza):
xml = ET.fromstring("%s" % stanza)
return xml.find("./methodCall/methodName").text

View File

@@ -1,64 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.stanzabase import ElementBase
from xml.etree import cElementTree as ET
class RPCQuery(ElementBase):
name = 'query'
namespace = 'jabber:iq:rpc'
plugin_attrib = 'rpc_query'
interfaces = set(())
subinterfaces = set(())
plugin_attrib_map = {}
plugin_tag_map = {}
class MethodCall(ElementBase):
name = 'methodCall'
namespace = 'jabber:iq:rpc'
plugin_attrib = 'method_call'
interfaces = set(('method_name', 'params'))
subinterfaces = set(())
plugin_attrib_map = {}
plugin_tag_map = {}
def get_method_name(self):
return self._get_sub_text('methodName')
def set_method_name(self, value):
return self._set_sub_text('methodName', value)
def get_params(self):
return self.xml.find('{%s}params' % self.namespace)
def set_params(self, params):
self.append(params)
class MethodResponse(ElementBase):
name = 'methodResponse'
namespace = 'jabber:iq:rpc'
plugin_attrib = 'method_response'
interfaces = set(('params', 'fault'))
subinterfaces = set(())
plugin_attrib_map = {}
plugin_tag_map = {}
def get_params(self):
return self.xml.find('{%s}params' % self.namespace)
def set_params(self, params):
self.append(params)
def get_fault(self):
return self.xml.find('{%s}fault' % self.namespace)
def set_fault(self, fault):
self.append(fault)

View File

@@ -1,9 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse

View File

@@ -1,118 +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 datetime import datetime
import logging
from . import base
from .. stanza.iq import Iq
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin
log = logging.getLogger(__name__)
class LastActivity(ElementBase):
name = 'query'
namespace = 'jabber:iq:last'
plugin_attrib = 'last_activity'
interfaces = set(('seconds', 'status'))
def get_seconds(self):
return int(self._get_attr('seconds'))
def set_seconds(self, value):
self._set_attr('seconds', str(value))
def get_status(self):
return self.xml.text
def set_status(self, value):
self.xml.text = str(value)
def del_status(self):
self.xml.text = ''
class xep_0012(base.base_plugin):
"""
XEP-0012 Last Activity
"""
def plugin_init(self):
self.description = "Last Activity"
self.xep = "0012"
self.xmpp.registerHandler(
Callback('Last Activity',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
LastActivity.namespace)),
self.handle_last_activity_query))
register_stanza_plugin(Iq, LastActivity)
self.xmpp.add_event_handler('last_activity_request', self.handle_last_activity)
def post_init(self):
base.base_plugin.post_init(self)
if self.xmpp.is_component:
# We are a component, so we track the uptime
self.xmpp.add_event_handler("session_start", self._reset_uptime)
self._start_datetime = datetime.now()
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last')
def _reset_uptime(self, event):
self._start_datetime = datetime.now()
def handle_last_activity_query(self, iq):
if iq['type'] == 'get':
log.debug("Last activity requested by %s" % iq['from'])
self.xmpp.event('last_activity_request', iq)
elif iq['type'] == 'result':
log.debug("Last activity result from %s" % iq['from'])
self.xmpp.event('last_activity', iq)
def handle_last_activity(self, iq):
jid = iq['from']
if self.xmpp.is_component:
# Send the uptime
result = LastActivity()
td = (datetime.now() - self._start_datetime)
result['seconds'] = td.seconds + td.days * 24 * 3600
reply = iq.reply().setPayload(result.xml).send()
else:
barejid = JID(jid).bare
if barejid in self.xmpp.roster and ( self.xmpp.roster[barejid]['subscription'] in ('from', 'both') or
barejid == self.xmpp.boundjid.bare ):
# We don't know how to calculate it
iq.reply().error().setPayload(iq['last_activity'].xml)
iq['error']['code'] = '503'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'service-unavailable'
iq.send()
else:
iq.reply().error().setPayload(iq['last_activity'].xml)
iq['error']['code'] = '403'
iq['error']['type'] = 'auth'
iq['error']['condition'] = 'forbidden'
iq.send()
def get_last_activity(self, jid):
"""Query the LastActivity of jid and return it in seconds"""
iq = self.xmpp.makeIqGet()
query = LastActivity()
iq.append(query.xml)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq.get('id')
result = iq.send()
if result and result is not None and result.get('type', 'error') != 'error':
return result['last_activity']['seconds']
else:
return False

View File

@@ -0,0 +1,324 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file license.txt for copying permissio
"""
import logging
from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import ElementBase, ET, JID
from .. stanza.iq import Iq
class DiscoInfo(ElementBase):
namespace = 'http://jabber.org/protocol/disco#info'
name = 'query'
plugin_attrib = 'disco_info'
interfaces = set(('node', 'features', 'identities'))
def getFeatures(self):
features = []
featuresXML = self.xml.findall('{%s}feature' % self.namespace)
for feature in featuresXML:
features.append(feature.attrib['var'])
return features
def setFeatures(self, features):
self.delFeatures()
for name in features:
self.addFeature(name)
def delFeatures(self):
featuresXML = self.xml.findall('{%s}feature' % self.namespace)
for feature in featuresXML:
self.xml.remove(feature)
def addFeature(self, feature):
featureXML = ET.Element('{%s}feature' % self.namespace,
{'var': feature})
self.xml.append(featureXML)
def delFeature(self, feature):
featuresXML = self.xml.findall('{%s}feature' % self.namespace)
for featureXML in featuresXML:
if featureXML.attrib['var'] == feature:
self.xml.remove(featureXML)
def getIdentities(self):
ids = []
idsXML = self.xml.findall('{%s}identity' % self.namespace)
for idXML in idsXML:
idData = (idXML.attrib['category'],
idXML.attrib['type'],
idXML.attrib.get('name', ''))
ids.append(idData)
return ids
def setIdentities(self, ids):
self.delIdentities()
for idData in ids:
self.addIdentity(*idData)
def delIdentities(self):
idsXML = self.xml.findall('{%s}identity' % self.namespace)
for idXML in idsXML:
self.xml.remove(idXML)
def addIdentity(self, category, id_type, name=''):
idXML = ET.Element('{%s}identity' % self.namespace,
{'category': category,
'type': id_type,
'name': name})
self.xml.append(idXML)
def delIdentity(self, category, id_type, name=''):
idsXML = self.xml.findall('{%s}identity' % self.namespace)
for idXML in idsXML:
idData = (idXML.attrib['category'],
idXML.attrib['type'])
delId = (category, id_type)
if idData == delId:
self.xml.remove(idXML)
class DiscoItems(ElementBase):
namespace = 'http://jabber.org/protocol/disco#items'
name = 'query'
plugin_attrib = 'disco_items'
interfaces = set(('node', 'items'))
def getItems(self):
items = []
itemsXML = self.xml.findall('{%s}item' % self.namespace)
for item in itemsXML:
itemData = (item.attrib['jid'],
item.attrib.get('node'),
item.attrib.get('name'))
items.append(itemData)
return items
def setItems(self, items):
self.delItems()
for item in items:
self.addItem(*item)
def delItems(self):
itemsXML = self.xml.findall('{%s}item' % self.namespace)
for item in itemsXML:
self.xml.remove(item)
def addItem(self, jid, node='', name=''):
itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid})
if name:
itemXML.attrib['name'] = name
if node:
itemXML.attrib['node'] = node
self.xml.append(itemXML)
def delItem(self, jid, node=''):
itemsXML = self.xml.findall('{%s}item' % self.namespace)
for itemXML in itemsXML:
itemData = (itemXML.attrib['jid'],
itemXML.attrib.get('node', ''))
itemDel = (jid, node)
if itemData == itemDel:
self.xml.remove(itemXML)
class DiscoNode(object):
"""
Collection object for grouping info and item information
into nodes.
"""
def __init__(self, name):
self.name = name
self.info = DiscoInfo()
self.items = DiscoItems()
# This is a bit like poor man's inheritance, but
# to simplify adding information to the node we
# map node functions to either the info or items
# stanza objects.
#
# We don't want to make DiscoNode inherit from
# DiscoInfo and DiscoItems because DiscoNode is
# not an actual stanza, and doing so would create
# confusion and potential bugs.
self._map(self.items, 'items', ['get', 'set', 'del'])
self._map(self.items, 'item', ['add', 'del'])
self._map(self.info, 'identities', ['get', 'set', 'del'])
self._map(self.info, 'identity', ['add', 'del'])
self._map(self.info, 'features', ['get', 'set', 'del'])
self._map(self.info, 'feature', ['add', 'del'])
def isEmpty(self):
"""
Test if the node contains any information. Useful for
determining if a node can be deleted.
"""
ids = self.getIdentities()
features = self.getFeatures()
items = self.getItems()
if not ids and not features and not items:
return True
return False
def _map(self, obj, interface, access):
"""
Map functions of the form obj.accessInterface
to self.accessInterface for each given access type.
"""
interface = interface.title()
for access_type in access:
method = access_type + interface
if hasattr(obj, method):
setattr(self, method, getattr(obj, method))
class xep_0030(base.base_plugin):
"""
XEP-0030 Service Discovery
"""
def plugin_init(self):
self.xep = '0030'
self.description = 'Service Discovery'
self.xmpp.registerHandler(
Callback('Disco Items',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
DiscoItems.namespace)),
self.handle_item_query))
self.xmpp.registerHandler(
Callback('Disco Info',
MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,
DiscoInfo.namespace)),
self.handle_info_query))
self.xmpp.stanzaPlugin(Iq, DiscoInfo)
self.xmpp.stanzaPlugin(Iq, DiscoItems)
self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items)
self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info)
self.nodes = {'main': DiscoNode('main')}
def add_node(self, node):
if node not in self.nodes:
self.nodes[node] = DiscoNode(node)
def del_node(self, node):
if node in self.nodes:
del self.nodes[node]
def handle_item_query(self, iq):
if iq['type'] == 'get':
logging.debug("Items requested by %s" % iq['from'])
self.xmpp.event('disco_items_request', iq)
elif iq['type'] == 'result':
logging.debug("Items result from %s" % iq['from'])
self.xmpp.event('disco_items', iq)
def handle_info_query(self, iq):
if iq['type'] == 'get':
logging.debug("Info requested by %s" % iq['from'])
self.xmpp.event('disco_info_request', iq)
elif iq['type'] == 'result':
logging.debug("Info result from %s" % iq['from'])
self.xmpp.event('disco_info', iq)
def handle_disco_info(self, iq, forwarded=False):
"""
A default handler for disco#info requests. If another
handler is registered, this one will defer and not run.
"""
handlers = self.xmpp.event_handlers['disco_info_request']
if not forwarded and len(handlers) > 1:
return
node_name = iq['disco_info']['node']
if not node_name:
node_name = 'main'
logging.debug("Using default handler for disco#info on node '%s'." % node_name)
if node_name in self.nodes:
node = self.nodes[node_name]
iq.reply().setPayload(node.info.xml).send()
else:
logging.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_info'].xml)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
iq.send()
def handle_disco_items(self, iq, forwarded=False):
"""
A default handler for disco#items requests. If another
handler is registered, this one will defer and not run.
If this handler is called by your own custom handler with
forwarded set to True, then it will run as normal.
"""
handlers = self.xmpp.event_handlers['disco_items_request']
if not forwarded and len(handlers) > 1:
return
node_name = iq['disco_items']['node']
if not node_name:
node_name = 'main'
logging.debug("Using default handler for disco#items on node '%s'." % node_name)
if node_name in self.nodes:
node = self.nodes[node_name]
iq.reply().setPayload(node.items.xml).send()
else:
logging.debug("Node %s requested, but does not exist." % node_name)
iq.reply().error().setPayload(iq['disco_items'].xml)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'
iq.send()
# Older interface methods for backwards compatibility
def getInfo(self, jid, node=''):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = jid
iq['from'] = self.xmpp.fulljid
iq['disco_info']['node'] = node
iq.send()
def getItems(self, jid, node=''):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = jid
iq['from'] = self.xmpp.fulljid
iq['disco_items']['node'] = node
iq.send()
def add_feature(self, feature, node='main'):
self.add_node(node)
self.nodes[node].addFeature(feature)
def add_identity(self, category='', itype='', name='', node='main'):
self.add_node(node)
self.nodes[node].addIdentity(category=category,
id_type=itype,
name=name)
def add_item(self, jid=None, name='', node='main', subnode=''):
self.add_node(node)
self.add_node(subnode)
if jid is None:
jid = self.xmpp.fulljid
self.nodes[node].addItem(jid=jid, name=name, node=subnode)

View File

@@ -1,12 +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.
"""
from sleekxmpp.plugins.xep_0030 import stanza
from sleekxmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems
from sleekxmpp.plugins.xep_0030.static import StaticDisco
from sleekxmpp.plugins.xep_0030.disco import xep_0030

View File

@@ -1,623 +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 sleekxmpp
from sleekxmpp import Iq
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems, StaticDisco
log = logging.getLogger(__name__)
class xep_0030(base_plugin):
"""
XEP-0030: Service Discovery
Service discovery in XMPP allows entities to discover information about
other agents in the network, such as the feature sets supported by a
client, or signposts to other, related entities.
Also see <http://www.xmpp.org/extensions/xep-0030.html>.
The XEP-0030 plugin works using a hierarchy of dynamic
node handlers, ranging from global handlers to specific
JID+node handlers. The default set of handlers operate
in a static manner, storing disco information in memory.
However, custom handlers may use any available backend
storage mechanism desired, such as SQLite or Redis.
Node handler hierarchy:
JID | Node | Level
---------------------
None | None | Global
Given | None | All nodes for the JID
None | Given | Node on self.xmpp.boundjid
Given | Given | A single node
Stream Handlers:
Disco Info -- Any Iq stanze that includes a query with the
namespace http://jabber.org/protocol/disco#info.
Disco Items -- Any Iq stanze that includes a query with the
namespace http://jabber.org/protocol/disco#items.
Events:
disco_info -- Received a disco#info Iq query result.
disco_items -- Received a disco#items Iq query result.
disco_info_query -- Received a disco#info Iq query request.
disco_items_query -- Received a disco#items Iq query request.
Attributes:
stanza -- A reference to the module containing the
stanza classes provided by this plugin.
static -- Object containing the default set of
static node handlers.
default_handlers -- A dictionary mapping operations to the default
global handler (by default, the static handlers).
xmpp -- The main SleekXMPP object.
Methods:
set_node_handler -- Assign a handler to a JID/node combination.
del_node_handler -- Remove a handler from a JID/node combination.
get_info -- Retrieve disco#info data, locally or remote.
get_items -- Retrieve disco#items data, locally or remote.
set_identities --
set_features --
set_items --
del_items --
del_identity --
del_feature --
del_item --
add_identity --
add_feature --
add_item --
"""
def plugin_init(self):
"""
Start the XEP-0030 plugin.
"""
self.xep = '0030'
self.description = 'Service Discovery'
self.stanza = sleekxmpp.plugins.xep_0030.stanza
self.xmpp.register_handler(
Callback('Disco Info',
StanzaPath('iq/disco_info'),
self._handle_disco_info))
self.xmpp.register_handler(
Callback('Disco Items',
StanzaPath('iq/disco_items'),
self._handle_disco_items))
register_stanza_plugin(Iq, DiscoInfo)
register_stanza_plugin(Iq, DiscoItems)
self.static = StaticDisco(self.xmpp)
self._disco_ops = ['get_info', 'set_identities', 'set_features',
'get_items', 'set_items', 'del_items',
'add_identity', 'del_identity', 'add_feature',
'del_feature', 'add_item', 'del_item',
'del_identities', 'del_features']
self.default_handlers = {}
self._handlers = {}
for op in self._disco_ops:
self._add_disco_op(op, getattr(self.static, op))
def post_init(self):
"""Handle cross-plugin dependencies."""
base_plugin.post_init(self)
if 'xep_0059' in self.xmpp.plugin:
register_stanza_plugin(DiscoItems,
self.xmpp['xep_0059'].stanza.Set)
def _add_disco_op(self, op, default_handler):
self.default_handlers[op] = default_handler
self._handlers[op] = {'global': default_handler,
'jid': {},
'node': {}}
def set_node_handler(self, htype, jid=None, node=None, handler=None):
"""
Add a node handler for the given hierarchy level and
handler type.
Node handlers are ordered in a hierarchy where the
most specific handler is executed. Thus, a fallback,
global handler can be used for the majority of cases
with a few node specific handler that override the
global behavior.
Node handler hierarchy:
JID | Node | Level
---------------------
None | None | Global
Given | None | All nodes for the JID
None | Given | Node on self.xmpp.boundjid
Given | Given | A single node
Handler types:
get_info
get_items
set_identities
set_features
set_items
del_items
del_identities
del_identity
del_feature
del_features
del_item
add_identity
add_feature
add_item
Arguments:
htype -- The operation provided by the handler.
jid -- The JID the handler applies to. May be narrowed
further if a node is given.
node -- The particular node the handler is for. If no JID
is given, then the self.xmpp.boundjid.full is
assumed.
handler -- The handler function to use.
"""
if htype not in self._disco_ops:
return
if jid is None and node is None:
self._handlers[htype]['global'] = handler
elif node is None:
self._handlers[htype]['jid'][jid] = handler
elif jid is None:
if self.xmpp.is_component:
jid = self.xmpp.boundjid.full
else:
jid = self.xmpp.boundjid.bare
self._handlers[htype]['node'][(jid, node)] = handler
else:
self._handlers[htype]['node'][(jid, node)] = handler
def del_node_handler(self, htype, jid, node):
"""
Remove a handler type for a JID and node combination.
The next handler in the hierarchy will be used if one
exists. If removing the global handler, make sure that
other handlers exist to process existing nodes.
Node handler hierarchy:
JID | Node | Level
---------------------
None | None | Global
Given | None | All nodes for the JID
None | Given | Node on self.xmpp.boundjid
Given | Given | A single node
Arguments:
htype -- The type of handler to remove.
jid -- The JID from which to remove the handler.
node -- The node from which to remove the handler.
"""
self.set_node_handler(htype, jid, node, None)
def restore_defaults(self, jid=None, node=None, handlers=None):
"""
Change all or some of a node's handlers to the default
handlers. Useful for manually overriding the contents
of a node that would otherwise be handled by a JID level
or global level dynamic handler.
The default is to use the built-in static handlers, but that
may be changed by modifying self.default_handlers.
Arguments:
jid -- The JID owning the node to modify.
node -- The node to change to using static handlers.
handlers -- Optional list of handlers to change to the
default version. If provided, only these
handlers will be changed. Otherwise, all
handlers will use the default version.
"""
if handlers is None:
handlers = self._disco_ops
for op in handlers:
self.del_node_handler(op, jid, node)
self.set_node_handler(op, jid, node, self.default_handlers[op])
def get_info(self, jid=None, node=None, local=False, **kwargs):
"""
Retrieve the disco#info results from a given JID/node combination.
Info may be retrieved from both local resources and remote agents;
the local parameter indicates if the information should be gathered
by executing the local node handlers, or if a disco#info stanza
must be generated and sent.
If requesting items from a local JID/node, then only a DiscoInfo
stanza will be returned. Otherwise, an Iq stanza will be returned.
Arguments:
jid -- Request info from this JID.
node -- The particular node to query.
local -- If true, then the query is for a JID/node
combination handled by this Sleek instance and
no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the
remove JID to retrieve the info.
ifrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for
a reply. If None, then wait indefinitely. The
timeout value is only used when block=True.
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
"""
if local or jid is None:
log.debug("Looking up local disco#info data " + \
"for %s, node %s." % (jid, node))
info = self._run_node_handler('get_info', jid, node, kwargs)
return self._fix_default_info(info)
iq = self.xmpp.Iq()
# Check dfrom parameter for backwards compatibility
iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
iq['to'] = jid
iq['type'] = 'get'
iq['disco_info']['node'] = node if node else ''
return iq.send(timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None))
def get_items(self, jid=None, node=None, local=False, **kwargs):
"""
Retrieve the disco#items results from a given JID/node combination.
Items may be retrieved from both local resources and remote agents;
the local parameter indicates if the items should be gathered by
executing the local node handlers, or if a disco#items stanza must
be generated and sent.
If requesting items from a local JID/node, then only a DiscoItems
stanza will be returned. Otherwise, an Iq stanza will be returned.
Arguments:
jid -- Request info from this JID.
node -- The particular node to query.
local -- If true, then the query is for a JID/node
combination handled by this Sleek instance and
no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the
remove JID to retrieve the items.
ifrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for
a reply. If None, then wait indefinitely.
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
iterator -- If True, return a result set iterator using
the XEP-0059 plugin, if the plugin is loaded.
Otherwise the parameter is ignored.
"""
if local or jid is None:
return self._run_node_handler('get_items', jid, node, kwargs)
iq = self.xmpp.Iq()
# Check dfrom parameter for backwards compatibility
iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
iq['to'] = jid
iq['type'] = 'get'
iq['disco_items']['node'] = node if node else ''
if kwargs.get('iterator', False) and self.xmpp['xep_0059']:
return self.xmpp['xep_0059'].iterate(iq, 'disco_items')
else:
return iq.send(timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None))
def set_items(self, jid=None, node=None, **kwargs):
"""
Set or replace all items for the specified JID/node combination.
The given items must be in a list or set where each item is a
tuple of the form: (jid, node, name).
Arguments:
jid -- The JID to modify.
node -- Optional node to modify.
items -- A series of items in tuple format.
"""
self._run_node_handler('set_items', jid, node, kwargs)
def del_items(self, jid=None, node=None, **kwargs):
"""
Remove all items from the given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- Optional node to modify.
"""
self._run_node_handler('del_items', jid, node, kwargs)
def add_item(self, jid='', name='', node=None, subnode='', ijid=None):
"""
Add a new item element to the given JID/node combination.
Each item is required to have a JID, but may also specify
a node value to reference non-addressable entities.
Arguments:
jid -- The JID for the item.
name -- Optional name for the item.
node -- The node to modify.
subnode -- Optional node for the item.
ijid -- The JID to modify.
"""
if not jid:
jid = self.xmpp.boundjid.full
kwargs = {'ijid': jid,
'name': name,
'inode': subnode}
self._run_node_handler('add_item', ijid, node, kwargs)
def del_item(self, jid=None, node=None, **kwargs):
"""
Remove a single item from the given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
ijid -- The item's JID.
inode -- The item's node.
"""
self._run_node_handler('del_item', jid, node, kwargs)
def add_identity(self, category='', itype='', name='',
node=None, jid=None, lang=None):
"""
Add a new identity to the given JID/node combination.
Each identity must be unique in terms of all four identity
components: category, type, name, and language.
Multiple, identical category/type pairs are allowed only
if the xml:lang values are different. Likewise, multiple
category/type/xml:lang pairs are allowed so long as the
names are different. A category and type is always required.
Arguments:
category -- The identity's category.
itype -- The identity's type.
name -- Optional name for the identity.
lang -- Optional two-letter language code.
node -- The node to modify.
jid -- The JID to modify.
"""
kwargs = {'category': category,
'itype': itype,
'name': name,
'lang': lang}
self._run_node_handler('add_identity', jid, node, kwargs)
def add_feature(self, feature, node=None, jid=None):
"""
Add a feature to a JID/node combination.
Arguments:
feature -- The namespace of the supported feature.
node -- The node to modify.
jid -- The JID to modify.
"""
kwargs = {'feature': feature}
self._run_node_handler('add_feature', jid, node, kwargs)
def del_identity(self, jid=None, node=None, **kwargs):
"""
Remove an identity from the given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
category -- The identity's category.
itype -- The identity's type value.
name -- Optional, human readable name for the identity.
lang -- Optional, the identity's xml:lang value.
"""
self._run_node_handler('del_identity', jid, node, kwargs)
def del_feature(self, jid=None, node=None, **kwargs):
"""
Remove a feature from a given JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
feature -- The feature's namespace.
"""
self._run_node_handler('del_feature', jid, node, kwargs)
def set_identities(self, jid=None, node=None, **kwargs):
"""
Add or replace all identities for the given JID/node combination.
The identities must be in a set where each identity is a tuple
of the form: (category, type, lang, name)
Arguments:
jid -- The JID to modify.
node -- The node to modify.
identities -- A set of identities in tuple form.
lang -- Optional, xml:lang value.
"""
self._run_node_handler('set_identities', jid, node, kwargs)
def del_identities(self, jid=None, node=None, **kwargs):
"""
Remove all identities for a JID/node combination.
If a language is specified, only identities using that
language will be removed.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
lang -- Optional. If given, only remove identities
using this xml:lang value.
"""
self._run_node_handler('del_identities', jid, node, kwargs)
def set_features(self, jid=None, node=None, **kwargs):
"""
Add or replace the set of supported features
for a JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
features -- The new set of supported features.
"""
self._run_node_handler('set_features', jid, node, kwargs)
def del_features(self, jid=None, node=None, **kwargs):
"""
Remove all features from a JID/node combination.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
"""
self._run_node_handler('del_features', jid, node, kwargs)
def _run_node_handler(self, htype, jid, node, data={}):
"""
Execute the most specific node handler for the given
JID/node combination.
Arguments:
htype -- The handler type to execute.
jid -- The JID requested.
node -- The node requested.
data -- Optional, custom data to pass to the handler.
"""
if jid is None:
if self.xmpp.is_component:
jid = self.xmpp.boundjid.full
else:
jid = self.xmpp.boundjid.bare
if node is None:
node = ''
if self._handlers[htype]['node'].get((jid, node), False):
return self._handlers[htype]['node'][(jid, node)](jid, node, data)
elif self._handlers[htype]['jid'].get(jid, False):
return self._handlers[htype]['jid'][jid](jid, node, data)
elif self._handlers[htype]['global']:
return self._handlers[htype]['global'](jid, node, data)
else:
return None
def _handle_disco_info(self, iq):
"""
Process an incoming disco#info stanza. If it is a get
request, find and return the appropriate identities
and features. If it is an info result, fire the
disco_info event.
Arguments:
iq -- The incoming disco#items stanza.
"""
if iq['type'] == 'get':
log.debug("Received disco info query from " + \
"<%s> to <%s>." % (iq['from'], iq['to']))
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
info = self._run_node_handler('get_info',
jid,
iq['disco_info']['node'],
iq)
iq.reply()
if info:
info = self._fix_default_info(info)
iq.set_payload(info.xml)
iq.send()
elif iq['type'] == 'result':
log.debug("Received disco info result from" + \
"%s to %s." % (iq['from'], iq['to']))
self.xmpp.event('disco_info', iq)
def _handle_disco_items(self, iq):
"""
Process an incoming disco#items stanza. If it is a get
request, find and return the appropriate items. If it
is an items result, fire the disco_items event.
Arguments:
iq -- The incoming disco#items stanza.
"""
if iq['type'] == 'get':
log.debug("Received disco items query from " + \
"<%s> to <%s>." % (iq['from'], iq['to']))
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
items = self._run_node_handler('get_items',
jid,
iq['disco_items']['node'])
iq.reply()
if items:
iq.set_payload(items.xml)
iq.send()
elif iq['type'] == 'result':
log.debug("Received disco items result from" + \
"%s to %s." % (iq['from'], iq['to']))
self.xmpp.event('disco_items', iq)
def _fix_default_info(self, info):
"""
Disco#info results for a JID are required to include at least
one identity and feature. As a default, if no other identity is
provided, SleekXMPP will use either the generic component or the
bot client identity. A the standard disco#info feature will also be
added if no features are provided.
Arguments:
info -- The disco#info quest (not the full Iq stanza) to modify.
"""
if not info['node']:
if not info['identities']:
if self.xmpp.is_component:
log.debug("No identity found for this entity." + \
"Using default component identity.")
info.add_identity('component', 'generic')
else:
log.debug("No identity found for this entity." + \
"Using default client identity.")
info.add_identity('client', 'bot')
if not info['features']:
log.debug("No features found for this entity." + \
"Using default disco#info feature.")
info.add_feature(info.namespace)
return info
# Retain some backwards compatibility
xep_0030.getInfo = xep_0030.get_info
xep_0030.getItems = xep_0030.get_items
xep_0030.make_static = xep_0030.restore_defaults

View File

@@ -1,10 +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.
"""
from sleekxmpp.plugins.xep_0030.stanza.info import DiscoInfo
from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems

View File

@@ -1,262 +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.
"""
from sleekxmpp.xmlstream import ElementBase, ET
class DiscoInfo(ElementBase):
"""
XMPP allows for users and agents to find the identities and features
supported by other entities in the XMPP network through service discovery,
or "disco". In particular, the "disco#info" query type for <iq> stanzas is
used to request the list of identities and features offered by a JID.
An identity is a combination of a category and type, such as the 'client'
category with a type of 'pc' to indicate the agent is a human operated
client with a GUI, or a category of 'gateway' with a type of 'aim' to
identify the agent as a gateway for the legacy AIM protocol. See
<http://xmpp.org/registrar/disco-categories.html> for a full list of
accepted category and type combinations.
Features are simply a set of the namespaces that identify the supported
features. For example, a client that supports service discovery will
include the feature 'http://jabber.org/protocol/disco#info'.
Since clients and components may operate in several roles at once, identity
and feature information may be grouped into "nodes". If one were to write
all of the identities and features used by a client, then node names would
be like section headings.
Example disco#info stanzas:
<iq type="get">
<query xmlns="http://jabber.org/protocol/disco#info" />
</iq>
<iq type="result">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" type="bot" name="SleekXMPP Bot" />
<feature var="http://jabber.org/protocol/disco#info" />
<feature var="jabber:x:data" />
<feature var="urn:xmpp:ping" />
</query>
</iq>
Stanza Interface:
node -- The name of the node to either
query or return info from.
identities -- A set of 4-tuples, where each tuple contains
the category, type, xml:lang, and name
of an identity.
features -- A set of namespaces for features.
Methods:
add_identity -- Add a new, single identity.
del_identity -- Remove a single identity.
get_identities -- Return all identities in tuple form.
set_identities -- Use multiple identities, each given in tuple form.
del_identities -- Remove all identities.
add_feature -- Add a single feature.
del_feature -- Remove a single feature.
get_features -- Return a list of all features.
set_features -- Use a given list of features.
del_features -- Remove all features.
"""
name = 'query'
namespace = 'http://jabber.org/protocol/disco#info'
plugin_attrib = 'disco_info'
interfaces = set(('node', 'features', 'identities'))
lang_interfaces = set(('identities',))
# Cache identities and features
_identities = set()
_features = set()
def setup(self, xml=None):
"""
Populate the stanza object using an optional XML object.
Overrides ElementBase.setup
Caches identity and feature information.
Arguments:
xml -- Use an existing XML object for the stanza's values.
"""
ElementBase.setup(self, xml)
self._identities = set([id[0:3] for id in self['identities']])
self._features = self['features']
def add_identity(self, category, itype, name=None, lang=None):
"""
Add a new identity element. Each identity must be unique
in terms of all four identity components.
Multiple, identical category/type pairs are allowed only
if the xml:lang values are different. Likewise, multiple
category/type/xml:lang pairs are allowed so long as the names
are different. In any case, a category and type are required.
Arguments:
category -- The general category to which the agent belongs.
itype -- A more specific designation with the category.
name -- Optional human readable name for this identity.
lang -- Optional standard xml:lang value.
"""
identity = (category, itype, lang)
if identity not in self._identities:
self._identities.add(identity)
id_xml = ET.Element('{%s}identity' % self.namespace)
id_xml.attrib['category'] = category
id_xml.attrib['type'] = itype
if lang:
id_xml.attrib['{%s}lang' % self.xml_ns] = lang
if name:
id_xml.attrib['name'] = name
self.xml.append(id_xml)
return True
return False
def del_identity(self, category, itype, name=None, lang=None):
"""
Remove a given identity.
Arguments:
category -- The general category to which the agent belonged.
itype -- A more specific designation with the category.
name -- Optional human readable name for this identity.
lang -- Optional, standard xml:lang value.
"""
identity = (category, itype, lang)
if identity in self._identities:
self._identities.remove(identity)
for id_xml in self.findall('{%s}identity' % self.namespace):
id = (id_xml.attrib['category'],
id_xml.attrib['type'],
id_xml.attrib.get('{%s}lang' % self.xml_ns, None))
if id == identity:
self.xml.remove(id_xml)
return True
return False
def get_identities(self, lang=None):
"""
Return a set of all identities in tuple form as so:
(category, type, lang, name)
If a language was specified, only return identities using
that language.
Arguments:
lang -- Optional, standard xml:lang value.
"""
identities = set()
for id_xml in self.findall('{%s}identity' % self.namespace):
xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None)
if lang is None or xml_lang == lang:
identities.add((
id_xml.attrib['category'],
id_xml.attrib['type'],
id_xml.attrib.get('{%s}lang' % self.xml_ns, None),
id_xml.attrib.get('name', None)))
return identities
def set_identities(self, identities, lang=None):
"""
Add or replace all identities. The identities must be a in set
where each identity is a tuple of the form:
(category, type, lang, name)
If a language is specifified, any identities using that language
will be removed to be replaced with the given identities.
NOTE: An identity's language will not be changed regardless of
the value of lang.
Arguments:
identities -- A set of identities in tuple form.
lang -- Optional, standard xml:lang value.
"""
self.del_identities(lang)
for identity in identities:
category, itype, lang, name = identity
self.add_identity(category, itype, name, lang)
def del_identities(self, lang=None):
"""
Remove all identities. If a language was specified, only
remove identities using that language.
Arguments:
lang -- Optional, standard xml:lang value.
"""
for id_xml in self.findall('{%s}identity' % self.namespace):
if lang is None:
self.xml.remove(id_xml)
elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang:
self._identities.remove((
id_xml.attrib['category'],
id_xml.attrib['type'],
id_xml.attrib.get('{%s}lang' % self.xml_ns, None)))
self.xml.remove(id_xml)
def add_feature(self, feature):
"""
Add a single, new feature.
Arguments:
feature -- The namespace of the supported feature.
"""
if feature not in self._features:
self._features.add(feature)
feature_xml = ET.Element('{%s}feature' % self.namespace)
feature_xml.attrib['var'] = feature
self.xml.append(feature_xml)
return True
return False
def del_feature(self, feature):
"""
Remove a single feature.
Arguments:
feature -- The namespace of the removed feature.
"""
if feature in self._features:
self._features.remove(feature)
for feature_xml in self.findall('{%s}feature' % self.namespace):
if feature_xml.attrib['var'] == feature:
self.xml.remove(feature_xml)
return True
return False
def get_features(self):
"""Return the set of all supported features."""
features = set()
for feature_xml in self.findall('{%s}feature' % self.namespace):
features.add(feature_xml.attrib['var'])
return features
def set_features(self, features):
"""
Add or replace the set of supported features.
Arguments:
features -- The new set of supported features.
"""
self.del_features()
for feature in features:
self.add_feature(feature)
def del_features(self):
"""Remove all features."""
self._features = set()
for feature_xml in self.findall('{%s}feature' % self.namespace):
self.xml.remove(feature_xml)

View File

@@ -1,136 +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.
"""
from sleekxmpp.xmlstream import ElementBase, ET
class DiscoItems(ElementBase):
"""
Example disco#items stanzas:
<iq type="get">
<query xmlns="http://jabber.org/protocol/disco#items" />
</iq>
<iq type="result">
<query xmlns="http://jabber.org/protocol/disco#items">
<item jid="chat.example.com"
node="xmppdev"
name="XMPP Dev" />
<item jid="chat.example.com"
node="sleekdev"
name="SleekXMPP Dev" />
</query>
</iq>
Stanza Interface:
node -- The name of the node to either
query or return info from.
items -- A list of 3-tuples, where each tuple contains
the JID, node, and name of an item.
Methods:
add_item -- Add a single new item.
del_item -- Remove a single item.
get_items -- Return all items.
set_items -- Set or replace all items.
del_items -- Remove all items.
"""
name = 'query'
namespace = 'http://jabber.org/protocol/disco#items'
plugin_attrib = 'disco_items'
interfaces = set(('node', 'items'))
# Cache items
_items = set()
def setup(self, xml=None):
"""
Populate the stanza object using an optional XML object.
Overrides ElementBase.setup
Caches item information.
Arguments:
xml -- Use an existing XML object for the stanza's values.
"""
ElementBase.setup(self, xml)
self._items = set([item[0:2] for item in self['items']])
def add_item(self, jid, node=None, name=None):
"""
Add a new item element. Each item is required to have a
JID, but may also specify a node value to reference
non-addressable entitities.
Arguments:
jid -- The JID for the item.
node -- Optional additional information to reference
non-addressable items.
name -- Optional human readable name for the item.
"""
if (jid, node) not in self._items:
self._items.add((jid, node))
item_xml = ET.Element('{%s}item' % self.namespace)
item_xml.attrib['jid'] = jid
if name:
item_xml.attrib['name'] = name
if node:
item_xml.attrib['node'] = node
self.xml.append(item_xml)
return True
return False
def del_item(self, jid, node=None):
"""
Remove a single item.
Arguments:
jid -- JID of the item to remove.
node -- Optional extra identifying information.
"""
if (jid, node) in self._items:
for item_xml in self.findall('{%s}item' % self.namespace):
item = (item_xml.attrib['jid'],
item_xml.attrib.get('node', None))
if item == (jid, node):
self.xml.remove(item_xml)
return True
return False
def get_items(self):
"""Return all items."""
items = set()
for item_xml in self.findall('{%s}item' % self.namespace):
item = (item_xml.attrib['jid'],
item_xml.attrib.get('node'),
item_xml.attrib.get('name'))
items.add(item)
return items
def set_items(self, items):
"""
Set or replace all items. The given items must be in a
list or set where each item is a tuple of the form:
(jid, node, name)
Arguments:
items -- A series of items in tuple format.
"""
self.del_items()
for item in items:
jid, node, name = item
self.add_item(jid, node, name)
def del_items(self):
"""Remove all items."""
self._items = set()
for item_xml in self.findall('{%s}item' % self.namespace):
self.xml.remove(item_xml)

View File

@@ -1,265 +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 sleekxmpp
from sleekxmpp import Iq
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems
log = logging.getLogger(__name__)
class StaticDisco(object):
"""
While components will likely require fully dynamic handling
of service discovery information, most clients and simple bots
only need to manage a few disco nodes that will remain mostly
static.
StaticDisco provides a set of node handlers that will store
static sets of disco info and items in memory.
Attributes:
nodes -- A dictionary mapping (JID, node) tuples to a dict
containing a disco#info and a disco#items stanza.
xmpp -- The main SleekXMPP object.
"""
def __init__(self, xmpp):
"""
Create a static disco interface. Sets of disco#info and
disco#items are maintained for every given JID and node
combination. These stanzas are used to store disco
information in memory without any additional processing.
Arguments:
xmpp -- The main SleekXMPP object.
"""
self.nodes = {}
self.xmpp = xmpp
def add_node(self, jid=None, node=None):
"""
Create a new set of stanzas for the provided
JID and node combination.
Arguments:
jid -- The JID that will own the new stanzas.
node -- The node that will own the new stanzas.
"""
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if (jid, node) not in self.nodes:
self.nodes[(jid, node)] = {'info': DiscoInfo(),
'items': DiscoItems()}
self.nodes[(jid, node)]['info']['node'] = node
self.nodes[(jid, node)]['items']['node'] = node
# =================================================================
# Node Handlers
#
# Each handler accepts three arguments: jid, node, and data.
# The jid and node parameters together determine the set of
# info and items stanzas that will be retrieved or added.
# The data parameter is a dictionary with additional paramters
# that will be passed to other calls.
def get_info(self, jid, node, data):
"""
Return the stored info data for the requested JID/node combination.
The data parameter is not used.
"""
if (jid, node) not in self.nodes:
if not node:
return DiscoInfo()
else:
raise XMPPError(condition='item-not-found')
else:
return self.nodes[(jid, node)]['info']
def del_info(self, jid, node, data):
"""
Reset the info stanza for a given JID/node combination.
The data parameter is not used.
"""
if (jid, node) in self.nodes:
self.nodes[(jid, node)]['info'] = DiscoInfo()
def get_items(self, jid, node, data):
"""
Return the stored items data for the requested JID/node combination.
The data parameter is not used.
"""
if (jid, node) not in self.nodes:
if not node:
return DiscoInfo()
else:
raise XMPPError(condition='item-not-found')
else:
return self.nodes[(jid, node)]['items']
def set_items(self, jid, node, data):
"""
Replace the stored items data for a JID/node combination.
The data parameter may provided:
items -- A set of items in tuple format.
"""
items = data.get('items', set())
self.add_node(jid, node)
self.nodes[(jid, node)]['items']['items'] = items
def del_items(self, jid, node, data):
"""
Reset the items stanza for a given JID/node combination.
The data parameter is not used.
"""
if (jid, node) in self.nodes:
self.nodes[(jid, node)]['items'] = DiscoItems()
def add_identity(self, jid, node, data):
"""
Add a new identity to te JID/node combination.
The data parameter may provide:
category -- The general category to which the agent belongs.
itype -- A more specific designation with the category.
name -- Optional human readable name for this identity.
lang -- Optional standard xml:lang value.
"""
self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
data.get('lang', None))
def set_identities(self, jid, node, data):
"""
Add or replace all identities for a JID/node combination.
The data parameter should include:
identities -- A list of identities in tuple form:
(category, type, name, lang)
"""
identities = data.get('identities', set())
self.add_node(jid, node)
self.nodes[(jid, node)]['info']['identities'] = identities
def del_identity(self, jid, node, data):
"""
Remove an identity from a JID/node combination.
The data parameter may provide:
category -- The general category to which the agent belonged.
itype -- A more specific designation with the category.
name -- Optional human readable name for this identity.
lang -- Optional, standard xml:lang value.
"""
if (jid, node) not in self.nodes:
return
self.nodes[(jid, node)]['info'].del_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
data.get('lang', None))
def del_identities(self, jid, node, data):
"""
Remove all identities from a JID/node combination.
The data parameter is not used.
"""
if (jid, node) not in self.nodes:
return
del self.nodes[(jid, node)]['info']['identities']
def add_feature(self, jid, node, data):
"""
Add a feature to a JID/node combination.
The data parameter should include:
feature -- The namespace of the supported feature.
"""
self.add_node(jid, node)
self.nodes[(jid, node)]['info'].add_feature(data.get('feature', ''))
def set_features(self, jid, node, data):
"""
Add or replace all features for a JID/node combination.
The data parameter should include:
features -- The new set of supported features.
"""
features = data.get('features', set())
self.add_node(jid, node)
self.nodes[(jid, node)]['info']['features'] = features
def del_feature(self, jid, node, data):
"""
Remove a feature from a JID/node combination.
The data parameter should include:
feature -- The namespace of the removed feature.
"""
if (jid, node) not in self.nodes:
return
self.nodes[(jid, node)]['info'].del_feature(data.get('feature', ''))
def del_features(self, jid, node, data):
"""
Remove all features from a JID/node combination.
The data parameter is not used.
"""
if (jid, node) not in self.nodes:
return
del self.nodes[(jid, node)]['info']['features']
def add_item(self, jid, node, data):
"""
Add an item to a JID/node combination.
The data parameter may include:
ijid -- The JID for the item.
inode -- Optional additional information to reference
non-addressable items.
name -- Optional human readable name for the item.
"""
self.add_node(jid, node)
self.nodes[(jid, node)]['items'].add_item(
data.get('ijid', ''),
node=data.get('inode', ''),
name=data.get('name', ''))
def del_item(self, jid, node, data):
"""
Remove an item from a JID/node combination.
The data parameter may include:
ijid -- JID of the item to remove.
inode -- Optional extra identifying information.
"""
if (jid, node) in self.nodes:
self.nodes[(jid, node)]['items'].del_item(
data.get('ijid', ''),
node=data.get('inode', None))

View File

@@ -1,161 +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
from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.message import Message
class Addresses(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'addresses'
plugin_attrib = 'addresses'
interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False):
address = Address(parent=self)
address['type'] = atype
address['jid'] = jid
address['node'] = node
address['uri'] = uri
address['desc'] = desc
address['delivered'] = delivered
return address
def getAddresses(self, atype=None):
addresses = []
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if atype is None or addrXML.attrib.get('type') == atype:
addresses.append(Address(xml=addrXML, parent=None))
return addresses
def setAddresses(self, addresses, set_type=None):
self.delAddresses(set_type)
for addr in addresses:
addr = dict(addr)
# Remap 'type' to 'atype' to match the add method
if set_type is not None:
addr['type'] = set_type
curr_type = addr.get('type', None)
if curr_type is not None:
del addr['type']
addr['atype'] = curr_type
self.addAddress(**addr)
def delAddresses(self, atype=None):
if atype is None:
return
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if addrXML.attrib.get('type') == atype:
self.xml.remove(addrXML)
# --------------------------------------------------------------
def delBcc(self):
self.delAddresses('bcc')
def delCc(self):
self.delAddresses('cc')
def delNoreply(self):
self.delAddresses('noreply')
def delReplyroom(self):
self.delAddresses('replyroom')
def delReplyto(self):
self.delAddresses('replyto')
def delTo(self):
self.delAddresses('to')
# --------------------------------------------------------------
def getBcc(self):
return self.getAddresses('bcc')
def getCc(self):
return self.getAddresses('cc')
def getNoreply(self):
return self.getAddresses('noreply')
def getReplyroom(self):
return self.getAddresses('replyroom')
def getReplyto(self):
return self.getAddresses('replyto')
def getTo(self):
return self.getAddresses('to')
# --------------------------------------------------------------
def setBcc(self, addresses):
self.setAddresses(addresses, 'bcc')
def setCc(self, addresses):
self.setAddresses(addresses, 'cc')
def setNoreply(self, addresses):
self.setAddresses(addresses, 'noreply')
def setReplyroom(self, addresses):
self.setAddresses(addresses, 'replyroom')
def setReplyto(self, addresses):
self.setAddresses(addresses, 'replyto')
def setTo(self, addresses):
self.setAddresses(addresses, 'to')
class Address(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'address'
plugin_attrib = 'address'
interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri'))
address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def getDelivered(self):
return self.xml.attrib.get('delivered', False)
def setDelivered(self, delivered):
if delivered:
self.xml.attrib['delivered'] = "true"
else:
del self['delivered']
def setUri(self, uri):
if uri:
del self['jid']
del self['node']
self.xml.attrib['uri'] = uri
elif 'uri' in self.xml.attrib:
del self.xml.attrib['uri']
class xep_0033(base.base_plugin):
"""
XEP-0033: Extended Stanza Addressing
"""
def plugin_init(self):
self.xep = '0033'
self.description = 'Extended Stanza Addressing'
registerStanzaPlugin(Message, Addresses)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace)

View File

@@ -1,353 +1,316 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID
from .. xmlstream.stanzabase import ElementBase, JID
from .. stanza.presence import Presence
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.matcher.xmlmask import MatchXMLMask
log = logging.getLogger(__name__)
class MUCPresence(ElementBase):
name = 'x'
namespace = 'http://jabber.org/protocol/muc#user'
plugin_attrib = 'muc'
interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room'))
affiliations = set(('', ))
roles = set(('', ))
name = 'x'
namespace = 'http://jabber.org/protocol/muc#user'
plugin_attrib = 'muc'
interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room'))
affiliations = set(('', ))
roles = set(('', ))
def getXMLItem(self):
item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
if item is None:
item = ET.Element('{http://jabber.org/protocol/muc#user}item')
self.xml.append(item)
return item
def getXMLItem(self):
item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
if item is None:
item = ET.Element('{http://jabber.org/protocol/muc#user}item')
self.xml.append(item)
return item
def getAffiliation(self):
#TODO if no affilation, set it to the default and return default
item = self.getXMLItem()
return item.get('affiliation', '')
def setAffiliation(self, value):
item = self.getXMLItem()
#TODO check for valid affiliation
item.attrib['affiliation'] = value
return self
def delAffiliation(self):
item = self.getXMLItem()
#TODO set default affiliation
if 'affiliation' in item.attrib: del item.attrib['affiliation']
return self
def getJid(self):
item = self.getXMLItem()
return JID(item.get('jid', ''))
def setJid(self, value):
item = self.getXMLItem()
if not isinstance(value, str):
value = str(value)
item.attrib['jid'] = value
return self
def delJid(self):
item = self.getXMLItem()
if 'jid' in item.attrib: del item.attrib['jid']
return self
def getRole(self):
item = self.getXMLItem()
#TODO get default role, set default role if none
return item.get('role', '')
def setRole(self, value):
item = self.getXMLItem()
#TODO check for valid role
item.attrib['role'] = value
return self
def delRole(self):
item = self.getXMLItem()
#TODO set default role
if 'role' in item.attrib: del item.attrib['role']
return self
def getNick(self):
return self.parent()['from'].resource
def getRoom(self):
return self.parent()['from'].bare
def setNick(self, value):
log.warning("Cannot set nick through mucpresence plugin.")
return self
def setRoom(self, value):
log.warning("Cannot set room through mucpresence plugin.")
return self
def delNick(self):
log.warning("Cannot delete nick through mucpresence plugin.")
return self
def delRoom(self):
log.warning("Cannot delete room through mucpresence plugin.")
return self
def getAffiliation(self):
#TODO if no affilation, set it to the default and return default
item = self.getXMLItem()
return item.get('affiliation', '')
def setAffiliation(self, value):
item = self.getXMLItem()
#TODO check for valid affiliation
item.attrib['affiliation'] = value
return self
def delAffiliation(self):
item = self.getXMLItem()
#TODO set default affiliation
if 'affiliation' in item.attrib: del item.attrib['affiliation']
return self
def getJid(self):
item = self.getXMLItem()
return JID(item.get('jid', ''))
def setJid(self, value):
item = self.getXMLItem()
if not isinstance(value, str):
value = str(value)
item.attrib['jid'] = value
return self
def delJid(self):
item = self.getXMLItem()
if 'jid' in item.attrib: del item.attrib['jid']
return self
def getRole(self):
item = self.getXMLItem()
#TODO get default role, set default role if none
return item.get('role', '')
def setRole(self, value):
item = self.getXMLItem()
#TODO check for valid role
item.attrib['role'] = value
return self
def delRole(self):
item = self.getXMLItem()
#TODO set default role
if 'role' in item.attrib: del item.attrib['role']
return self
def getNick(self):
return self.parent()['from'].resource
def getRoom(self):
return self.parent()['from'].bare
def setNick(self, value):
logging.warning("Cannot set nick through mucpresence plugin.")
return self
def setRoom(self, value):
logging.warning("Cannot set room through mucpresence plugin.")
return self
def delNick(self):
logging.warning("Cannot delete nick through mucpresence plugin.")
return self
def delRoom(self):
logging.warning("Cannot delete room through mucpresence plugin.")
return self
class xep_0045(base.base_plugin):
"""
Implements XEP-0045 Multi User Chat
"""
"""
Impliments XEP-0045 Multi User Chat
"""
def plugin_init(self):
self.rooms = {}
self.ourNicks = {}
self.xep = '0045'
self.description = 'Multi User Chat'
# load MUC support in presence stanzas
self.xmpp.stanzaPlugin(Presence, MUCPresence)
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
def handle_groupchat_presence(self, pr):
""" Handle a presence in a muc.
"""
if pr['muc']['room'] not in self.rooms.keys():
return
entry = pr['muc'].getValues()
if pr['type'] == 'unavailable':
del self.rooms[entry['room']][entry['nick']]
else:
self.rooms[entry['room']][entry['nick']] = entry
logging.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry))
self.xmpp.event("groupchat_presence", pr)
def handle_groupchat_message(self, msg):
""" Handle a message event in a muc.
"""
self.xmpp.event('groupchat_message', msg)
def jidInRoom(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return True
return False
def plugin_init(self):
self.rooms = {}
self.ourNicks = {}
self.xep = '0045'
self.description = 'Multi User Chat'
# load MUC support in presence stanzas
registerStanzaPlugin(Presence, MUCPresence)
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{http://jabber.org/protocol/muc#user}x/invite" % self.xmpp.default_ns), self.handle_groupchat_invite))
def getRoomForm(self, room, ifrom=None):
iq = self.xmpp.makeIqGet()
iq['to'] = room
if ifrom is not None:
iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
iq.append(query)
result = iq.send()
if result['type'] == 'error':
return False
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if xform is None: return False
form = self.xmpp.plugin['xep_0004'].buildForm(xform)
return form
def configureRoom(self, room, form=None, ifrom=None):
if form is None:
form = self.getRoomForm(room, ifrom=ifrom)
#form = self.xmpp.plugin['xep_0004'].makeForm(ftype='submit')
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
iq = self.xmpp.makeIqSet()
iq['to'] = room
if ifrom is not None:
iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
form = form.getXML('submit')
query.append(form)
iq.append(query)
result = iq.send()
if result['type'] == 'error':
return False
return True
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None):
""" Join the specified room, requesting 'maxhistory' lines of history.
"""
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow)
x = ET.Element('{http://jabber.org/protocol/muc}x')
if password:
passelement = ET.Element('password')
passelement.text = password
x.append(passelement)
history = ET.Element('history')
history.attrib['maxstanzas'] = maxhistory
x.append(history)
stanza.append(x)
if not wait:
self.xmpp.send(stanza)
else:
#wait for our own room presence back
expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)})
self.xmpp.send(stanza, expect)
self.rooms[room] = {}
self.ourNicks[room] = nick
def destroy(self, room, reason='', altroom = '', ifrom=None):
iq = self.xmpp.makeIqSet()
if ifrom is not None:
iq['from'] = ifrom
iq['to'] = room
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
destroy = ET.Element('destroy')
if altroom:
destroy.attrib['jid'] = altroom
xreason = ET.Element('reason')
xreason.text = reason
destroy.append(xreason)
query.append(destroy)
iq.append(query)
r = iq.send()
if r is False or r['type'] == 'error':
return False
return True
def handle_groupchat_invite(self, inv):
""" Handle an invite into a muc.
"""
logging.debug("MUC invite to %s from %s: %s" % (inv['from'], inv["from"], inv))
if inv['from'] not in self.rooms.keys():
self.xmpp.event("groupchat_invite", inv)
def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
""" Change room affiliation."""
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
if nick is not None:
item = ET.Element('item', {'affiliation':affiliation, 'nick':nick})
else:
item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
query.append(item)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
result = iq.send()
if result is False or result['type'] != 'result':
raise ValueError
return True
def invite(self, room, jid, reason=''):
""" Invite a jid to a room."""
msg = self.xmpp.makeMessage(room)
msg['from'] = self.xmpp.jid
x = ET.Element('{http://jabber.org/protocol/muc#user}x')
invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
if reason:
rxml = ET.Element('reason')
rxml.text = reason
invite.append(rxml)
x.append(invite)
msg.append(x)
self.xmpp.send(msg)
def handle_groupchat_presence(self, pr):
""" Handle a presence in a muc.
"""
got_offline = False
got_online = False
if pr['muc']['room'] not in self.rooms.keys():
return
entry = pr['muc'].getStanzaValues()
entry['show'] = pr['show']
entry['status'] = pr['status']
if pr['type'] == 'unavailable':
if entry['nick'] in self.rooms[entry['room']]:
del self.rooms[entry['room']][entry['nick']]
got_offline = True
else:
if entry['nick'] not in self.rooms[entry['room']]:
got_online = True
self.rooms[entry['room']][entry['nick']] = entry
log.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry))
self.xmpp.event("groupchat_presence", pr)
self.xmpp.event("muc::%s::presence" % entry['room'], pr)
if got_offline:
self.xmpp.event("muc::%s::got_offline" % entry['room'], pr)
if got_online:
self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
def handle_groupchat_message(self, msg):
""" Handle a message event in a muc.
"""
self.xmpp.event('groupchat_message', msg)
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
def handle_groupchat_subject(self, msg):
""" Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room)
"""
self.xmpp.event('groupchat_subject', msg)
def jidInRoom(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return True
return False
def getNick(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return nick
def getRoomForm(self, room, ifrom=None):
iq = self.xmpp.makeIqGet()
iq['to'] = room
if ifrom is not None:
iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
iq.append(query)
result = iq.send()
if result['type'] == 'error':
return False
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if xform is None: return False
form = self.xmpp.plugin['old_0004'].buildForm(xform)
return form
def configureRoom(self, room, form=None, ifrom=None):
if form is None:
form = self.getRoomForm(room, ifrom=ifrom)
#form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
iq = self.xmpp.makeIqSet()
iq['to'] = room
if ifrom is not None:
iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
form = form.getXML('submit')
query.append(form)
iq.append(query)
result = iq.send()
if result['type'] == 'error':
return False
return True
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None):
""" Join the specified room, requesting 'maxhistory' lines of history.
"""
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow)
x = ET.Element('{http://jabber.org/protocol/muc}x')
if password:
passelement = ET.Element('password')
passelement.text = password
x.append(passelement)
if maxhistory:
history = ET.Element('history')
if maxhistory == "0":
history.attrib['maxchars'] = maxhistory
else:
history.attrib['maxstanzas'] = maxhistory
x.append(history)
stanza.append(x)
if not wait:
self.xmpp.send(stanza)
else:
#wait for our own room presence back
expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)})
self.xmpp.send(stanza, expect)
self.rooms[room] = {}
self.ourNicks[room] = nick
def destroy(self, room, reason='', altroom = '', ifrom=None):
iq = self.xmpp.makeIqSet()
if ifrom is not None:
iq['from'] = ifrom
iq['to'] = room
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
destroy = ET.Element('destroy')
if altroom:
destroy.attrib['jid'] = altroom
xreason = ET.Element('reason')
xreason.text = reason
destroy.append(xreason)
query.append(destroy)
iq.append(query)
r = iq.send()
if r is False or r['type'] == 'error':
return False
return True
def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
""" Change room affiliation."""
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
if nick is not None:
item = ET.Element('item', {'affiliation':affiliation, 'nick':nick})
else:
item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
query.append(item)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
result = iq.send()
if result is False or result['type'] != 'result':
raise ValueError
return True
def invite(self, room, jid, reason='', mfrom=''):
""" Invite a jid to a room."""
msg = self.xmpp.makeMessage(room)
msg['from'] = mfrom
x = ET.Element('{http://jabber.org/protocol/muc#user}x')
invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid})
if reason:
rxml = ET.Element('reason')
rxml.text = reason
invite.append(rxml)
x.append(invite)
msg.append(x)
self.xmpp.send(msg)
def leaveMUC(self, room, nick, msg=''):
""" Leave the specified room.
"""
if msg:
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg)
else:
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
del self.rooms[room]
def getRoomConfig(self, room, ifrom=''):
iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
iq['to'] = room
iq['from'] = ifrom
result = iq.send()
if result is None or result['type'] != 'result':
raise ValueError
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if form is None:
raise ValueError
return self.xmpp.plugin['xep_0004'].buildForm(form)
def cancelConfig(self, room):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
iq.send()
def setRoomConfig(self, room, config, ifrom=''):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = config.getXML('submit')
query.append(x)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
iq['from'] = ifrom
iq.send()
def getJoinedRooms(self):
return self.rooms.keys()
def getOurJidInRoom(self, roomJid):
""" Return the jid we're using in a room.
"""
return "%s/%s" % (roomJid, self.ourNicks[roomJid])
def getJidProperty(self, room, nick, jidProperty):
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation'
If not found, return None.
"""
if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
return self.rooms[room][nick][jidProperty]
else:
return None
def getRoster(self, room):
""" Get the list of nicks in a room.
"""
if room not in self.rooms.keys():
return None
return self.rooms[room].keys()
def leaveMUC(self, room, nick):
""" Leave the specified room.
"""
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
del self.rooms[room]
def getRoomConfig(self, room):
iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
iq['to'] = room
result = iq.send()
if result is None or result['type'] != 'result':
raise ValueError
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if form is None:
raise ValueError
return self.xmpp.plugin['xep_0004'].buildForm(form)
def cancelConfig(self, room):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x)
iq = self.xmpp.makeIqSet(query)
iq.send()
def setRoomConfig(self, room, config):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = config.getXML('submit')
query.append(x)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
iq.send()
def getJoinedRooms(self):
return self.rooms.keys()
def getOurJidInRoom(self, roomJid):
""" Return the jid we're using in a room.
"""
return "%s/%s" % (roomJid, self.ourNicks[roomJid])
def getJidProperty(self, room, nick, jidProperty):
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation'
If not found, return None.
"""
if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
return self.rooms[room][nick][jidProperty]
else:
return None
def getRoster(self, room):
""" Get the list of nicks in a room.
"""
if room not in self.rooms.keys():
return None
return self.rooms[room].keys()

View File

@@ -1,17 +1,30 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import traceback
import time
class old_0050(base.base_plugin):
class xep_0050(base.base_plugin):
"""
XEP-0050 Ad-Hoc Commands
"""
@@ -19,11 +32,11 @@ class old_0050(base.base_plugin):
def plugin_init(self):
self.xep = '0050'
self.description = 'Ad-Hoc Commands'
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, threaded=True)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete)
self.commands = {}
self.sessions = {}
self.sd = self.xmpp.plugin['xep_0030']
@@ -70,7 +83,7 @@ class old_0050(base.base_plugin):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['old_0004'].makeForm('result')
results = self.xmpp.plugin['xep_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x'))
pointer(results,sessionid)
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[]))
@@ -81,7 +94,7 @@ class old_0050(base.base_plugin):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['old_0004'].makeForm('result')
results = self.xmpp.plugin['xep_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x'))
form, npointer, next = pointer(results,sessionid)
self.sessions[sessionid]['next'] = npointer
@@ -110,7 +123,7 @@ class old_0050(base.base_plugin):
if not id:
id = self.xmpp.getNewId()
iq = self.xmpp.makeIqResult(id)
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
iq.attrib['to'] = to
command = ET.Element('{http://jabber.org/protocol/commands}command')
command.attrib['node'] = node

View File

@@ -1,10 +0,0 @@
"""
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_0050.stanza import Command
from sleekxmpp.plugins.xep_0050.adhoc import xep_0050

View File

@@ -1,593 +0,0 @@
"""
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 logging
import time
from sleekxmpp import Iq
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0050 import stanza
from sleekxmpp.plugins.xep_0050 import Command
log = logging.getLogger(__name__)
class xep_0050(base_plugin):
"""
XEP-0050: Ad-Hoc Commands
XMPP's Adhoc Commands provides a generic workflow mechanism for
interacting with applications. The result is similar to menu selections
and multi-step dialogs in normal desktop applications. Clients do not
need to know in advance what commands are provided by any particular
application or agent. While adhoc commands provide similar functionality
to Jabber-RPC, adhoc commands are used primarily for human interaction.
Also see <http://xmpp.org/extensions/xep-0050.html>
Configuration Values:
threaded -- Indicates if command events should be threaded.
Defaults to True.
Events:
command_execute -- Received a command with action="execute"
command_next -- Received a command with action="next"
command_complete -- Received a command with action="complete"
command_cancel -- Received a command with action="cancel"
Attributes:
threaded -- Indicates if command events should be threaded.
Defaults to True.
commands -- A dictionary mapping JID/node pairs to command
names and handlers.
sessions -- A dictionary or equivalent backend mapping
session IDs to dictionaries containing data
relevant to a command's session.
Methods:
plugin_init -- Overrides base_plugin.plugin_init
post_init -- Overrides base_plugin.post_init
new_session -- Return a new session ID.
prep_handlers -- Placeholder. May call with a list of handlers
to prepare them for use with the session storage
backend, if needed.
set_backend -- Replace the default session storage with some
external storage mechanism, such as a database.
The provided backend wrapper must be able to
act using the same syntax as a dictionary.
add_command -- Add a command for use by external entitites.
get_commands -- Retrieve a list of commands provided by a
remote agent.
send_command -- Send a command request to a remote agent.
start_command -- Command user API: initiate a command session
continue_command -- Command user API: proceed to the next step
cancel_command -- Command user API: cancel a command
complete_command -- Command user API: finish a command
terminate_command -- Command user API: delete a command's session
"""
def plugin_init(self):
"""Start the XEP-0050 plugin."""
self.xep = '0050'
self.description = 'Ad-Hoc Commands'
self.stanza = stanza
self.threaded = self.config.get('threaded', True)
self.commands = {}
self.sessions = self.config.get('session_db', {})
self.xmpp.register_handler(
Callback("Ad-Hoc Execute",
StanzaPath('iq@type=set/command'),
self._handle_command))
self.xmpp.register_handler(
Callback("Ad-Hoc Result",
StanzaPath('iq@type=result/command'),
self._handle_command_result))
self.xmpp.register_handler(
Callback("Ad-Hoc Error",
StanzaPath('iq@type=error/command'),
self._handle_command_result))
register_stanza_plugin(Iq, stanza.Command)
self.xmpp.add_event_handler('command_execute',
self._handle_command_start,
threaded=self.threaded)
self.xmpp.add_event_handler('command_next',
self._handle_command_next,
threaded=self.threaded)
self.xmpp.add_event_handler('command_cancel',
self._handle_command_cancel,
threaded=self.threaded)
self.xmpp.add_event_handler('command_complete',
self._handle_command_complete,
threaded=self.threaded)
def post_init(self):
"""Handle cross-plugin interactions."""
base_plugin.post_init(self)
self.xmpp['xep_0030'].add_feature(Command.namespace)
def set_backend(self, db):
"""
Replace the default session storage dictionary with
a generic, external data storage mechanism.
The replacement backend must be able to interact through
the same syntax and interfaces as a normal dictionary.
Arguments:
db -- The new session storage mechanism.
"""
self.sessions = db
def prep_handlers(self, handlers, **kwargs):
"""
Prepare a list of functions for use by the backend service.
Intended to be replaced by the backend service as needed.
Arguments:
handlers -- A list of function pointers
**kwargs -- Any additional parameters required by the backend.
"""
pass
# =================================================================
# Server side (command provider) API
def add_command(self, jid=None, node=None, name='', handler=None):
"""
Make a new command available to external entities.
Access control may be implemented in the provided handler.
Command workflow is done across a sequence of command handlers. The
first handler is given the intial Iq stanza of the request in order
to support access control. Subsequent handlers are given only the
payload items of the command. All handlers will receive the command's
session data.
Arguments:
jid -- The JID that will expose the command.
node -- The node associated with the command.
name -- A human readable name for the command.
handler -- A function that will generate the response to the
initial command request, as well as enforcing any
access control policies.
"""
if jid is None:
jid = self.xmpp.boundjid
elif not isinstance(jid, JID):
jid = JID(jid)
item_jid = jid.full
# Client disco uses only the bare JID
if self.xmpp.is_component:
jid = jid.full
else:
jid = jid.bare
self.xmpp['xep_0030'].add_identity(category='automation',
itype='command-list',
name='Ad-Hoc commands',
node=Command.namespace,
jid=jid)
self.xmpp['xep_0030'].add_item(jid=item_jid,
name=name,
node=Command.namespace,
subnode=node,
ijid=jid)
self.xmpp['xep_0030'].add_identity(category='automation',
itype='command-node',
name=name,
node=node,
jid=jid)
self.xmpp['xep_0030'].add_feature(Command.namespace, None, jid)
self.commands[(item_jid, node)] = (name, handler)
def new_session(self):
"""Return a new session ID."""
return str(time.time()) + '-' + self.xmpp.new_id()
def _handle_command(self, iq):
"""Raise command events based on the command action."""
self.xmpp.event('command_%s' % iq['command']['action'], iq)
def _handle_command_start(self, iq):
"""
Process an initial request to execute a command.
Arguments:
iq -- The command execution request.
"""
sessionid = self.new_session()
node = iq['command']['node']
key = (iq['to'].full, node)
name, handler = self.commands.get(key, ('Not found', None))
if not handler:
log.debug('Command not found: %s, %s' % (key, self.commands))
initial_session = {'id': sessionid,
'from': iq['from'],
'to': iq['to'],
'node': node,
'payload': None,
'interfaces': '',
'payload_classes': None,
'notes': None,
'has_next': False,
'allow_complete': False,
'allow_prev': False,
'past': [],
'next': None,
'prev': None,
'cancel': None}
session = handler(iq, initial_session)
self._process_command_response(iq, session)
def _handle_command_next(self, iq):
"""
Process a request for the next step in the workflow
for a command with multiple steps.
Arguments:
iq -- The command continuation request.
"""
sessionid = iq['command']['sessionid']
session = self.sessions[sessionid]
handler = session['next']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
session = handler(results, session)
self._process_command_response(iq, session)
def _process_command_response(self, iq, session):
"""
Generate a command reply stanza based on the
provided session data.
Arguments:
iq -- The command request stanza.
session -- A dictionary of relevant session data.
"""
sessionid = session['id']
payload = session['payload']
if not isinstance(payload, list):
payload = [payload]
session['interfaces'] = [item.plugin_attrib for item in payload]
session['payload_classes'] = [item.__class__ for item in payload]
self.sessions[sessionid] = session
for item in payload:
register_stanza_plugin(Command, item.__class__, iterable=True)
iq.reply()
iq['command']['node'] = session['node']
iq['command']['sessionid'] = session['id']
if session['next'] is None:
iq['command']['actions'] = []
iq['command']['status'] = 'completed'
elif session['has_next']:
actions = ['next']
if session['allow_complete']:
actions.append('complete')
if session['allow_prev']:
actions.append('prev')
iq['command']['actions'] = actions
iq['command']['status'] = 'executing'
else:
iq['command']['actions'] = ['complete']
iq['command']['status'] = 'executing'
iq['command']['notes'] = session['notes']
for item in payload:
iq['command'].append(item)
iq.send()
def _handle_command_cancel(self, iq):
"""
Process a request to cancel a command's execution.
Arguments:
iq -- The command cancellation request.
"""
node = iq['command']['node']
sessionid = iq['command']['sessionid']
session = self.sessions[sessionid]
handler = session['cancel']
if handler:
handler(iq, session)
try:
del self.sessions[sessionid]
except:
pass
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['status'] = 'canceled'
iq['command']['notes'] = session['notes']
iq.send()
def _handle_command_complete(self, iq):
"""
Process a request to finish the execution of command
and terminate the workflow.
All data related to the command session will be removed.
Arguments:
iq -- The command completion request.
"""
node = iq['command']['node']
sessionid = iq['command']['sessionid']
session = self.sessions[sessionid]
handler = session['next']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
if handler:
handler(results, session)
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['actions'] = []
iq['command']['status'] = 'completed'
iq['command']['notes'] = session['notes']
iq.send()
del self.sessions[sessionid]
# =================================================================
# Client side (command user) API
def get_commands(self, jid, **kwargs):
"""
Return a list of commands provided by a given JID.
Arguments:
jid -- The JID to query for commands.
local -- If true, then the query is for a JID/node
combination handled by this Sleek instance and
no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the
remove JID to retrieve the items.
ifrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for
a reply. If None, then wait indefinitely.
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
iterator -- If True, return a result set iterator using
the XEP-0059 plugin, if the plugin is loaded.
Otherwise the parameter is ignored.
"""
return self.xmpp['xep_0030'].get_items(jid=jid,
node=Command.namespace,
**kwargs)
def send_command(self, jid, node, ifrom=None, action='execute',
payload=None, sessionid=None, **kwargs):
"""
Create and send a command stanza, without using the provided
workflow management APIs.
Arguments:
jid -- The JID to send the command request or result.
node -- The node for the command.
ifrom -- Specify the sender's JID.
action -- May be one of: execute, cancel, complete,
or cancel.
payload -- Either a list of payload items, or a single
payload item such as a data form.
sessionid -- The current session's ID value.
block -- Specify if the send call will block until a
response is received, or a timeout occurs.
Defaults to True.
timeout -- The length of time (in seconds) to wait for a
response before exiting the send call
if blocking is used. Defaults to
sleekxmpp.xmlstream.RESPONSE_TIMEOUT
callback -- Optional reference to a stream handler
function. Will be executed when a reply
stanza is received.
"""
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = jid
if ifrom:
iq['from'] = ifrom
iq['command']['node'] = node
iq['command']['action'] = action
if sessionid is not None:
iq['command']['sessionid'] = sessionid
if payload is not None:
if not isinstance(payload, list):
payload = [payload]
for item in payload:
iq['command'].append(item)
return iq.send(**kwargs)
def start_command(self, jid, node, session, ifrom=None):
"""
Initiate executing a command provided by a remote agent.
The workflow provided is always non-blocking.
The provided session dictionary should contain:
next -- A handler for processing the command result.
error -- A handler for processing any error stanzas
generated by the request.
Arguments:
jid -- The JID to send the command request.
node -- The node for the desired command.
session -- A dictionary of relevant session data.
ifrom -- Optionally specify the sender's JID.
"""
session['jid'] = jid
session['node'] = node
session['timestamp'] = time.time()
session['payload'] = None
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = jid
if ifrom:
iq['from'] = ifrom
session['from'] = ifrom
iq['command']['node'] = node
iq['command']['action'] = 'execute'
sessionid = 'client:pending_' + iq['id']
session['id'] = sessionid
self.sessions[sessionid] = session
iq.send(block=False)
def continue_command(self, session):
"""
Execute the next action of the command.
Arguments:
session -- All stored data relevant to the current
command session.
"""
sessionid = 'client:' + session['id']
self.sessions[sessionid] = session
self.send_command(session['jid'],
session['node'],
ifrom=session.get('from', None),
action='next',
payload=session.get('payload', None),
sessionid=session['id'])
def cancel_command(self, session):
"""
Cancel the execution of a command.
Arguments:
session -- All stored data relevant to the current
command session.
"""
sessionid = 'client:' + session['id']
self.sessions[sessionid] = session
self.send_command(session['jid'],
session['node'],
ifrom=session.get('from', None),
action='cancel',
payload=session.get('payload', None),
sessionid=session['id'])
def complete_command(self, session):
"""
Finish the execution of a command workflow.
Arguments:
session -- All stored data relevant to the current
command session.
"""
sessionid = 'client:' + session['id']
self.sessions[sessionid] = session
self.send_command(session['jid'],
session['node'],
ifrom=session.get('from', None),
action='complete',
payload=session.get('payload', None),
sessionid=session['id'])
def terminate_command(self, session):
"""
Delete a command's session after a command has completed
or an error has occured.
Arguments:
session -- All stored data relevant to the current
command session.
"""
try:
del self.sessions[session['id']]
except:
pass
def _handle_command_result(self, iq):
"""
Process the results of a command request.
Will execute the 'next' handler stored in the session
data, or the 'error' handler depending on the Iq's type.
Arguments:
iq -- The command response.
"""
sessionid = 'client:' + iq['command']['sessionid']
pending = False
if sessionid not in self.sessions:
pending = True
pendingid = 'client:pending_' + iq['id']
if pendingid not in self.sessions:
return
sessionid = pendingid
session = self.sessions[sessionid]
sessionid = 'client:' + iq['command']['sessionid']
session['id'] = iq['command']['sessionid']
self.sessions[sessionid] = session
if pending:
del self.sessions[pendingid]
handler_type = 'next'
if iq['type'] == 'error':
handler_type = 'error'
handler = session.get(handler_type, None)
if handler:
handler(iq, session)
elif iq['type'] == 'error':
self.terminate_command(session)
if iq['command']['status'] == 'completed':
self.terminate_command(session)

View File

@@ -1,185 +0,0 @@
"""
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 Command(ElementBase):
"""
XMPP's Adhoc Commands provides a generic workflow mechanism for
interacting with applications. The result is similar to menu selections
and multi-step dialogs in normal desktop applications. Clients do not
need to know in advance what commands are provided by any particular
application or agent. While adhoc commands provide similar functionality
to Jabber-RPC, adhoc commands are used primarily for human interaction.
Also see <http://xmpp.org/extensions/xep-0050.html>
Example command stanzas:
<iq type="set">
<command xmlns="http://jabber.org/protocol/commands"
node="run_foo"
action="execute" />
</iq>
<iq type="result">
<command xmlns="http://jabber.org/protocol/commands"
node="run_foo"
sessionid="12345"
status="executing">
<actions>
<complete />
</actions>
<note type="info">Information!</note>
<x xmlns="jabber:x:data">
<field var="greeting"
type="text-single"
label="Greeting" />
</x>
</command>
</iq>
Stanza Interface:
action -- The action to perform.
actions -- The set of allowable next actions.
node -- The node associated with the command.
notes -- A list of tuples for informative notes.
sessionid -- A unique identifier for a command session.
status -- May be one of: canceled, completed, or executing.
Attributes:
actions -- A set of allowed action values.
statuses -- A set of allowed status values.
next_actions -- A set of allowed next action names.
Methods:
get_action -- Return the requested action.
get_actions -- Return the allowable next actions.
set_actions -- Set the allowable next actions.
del_actions -- Remove the current set of next actions.
get_notes -- Return a list of informative note data.
set_notes -- Set informative notes.
del_notes -- Remove any note data.
add_note -- Add a single note.
"""
name = 'command'
namespace = 'http://jabber.org/protocol/commands'
plugin_attrib = 'command'
interfaces = set(('action', 'sessionid', 'node',
'status', 'actions', 'notes'))
actions = set(('cancel', 'complete', 'execute', 'next', 'prev'))
statuses = set(('canceled', 'completed', 'executing'))
next_actions = set(('prev', 'next', 'complete'))
def get_action(self):
"""
Return the value of the action attribute.
If the Iq stanza's type is "set" then use a default
value of "execute".
"""
if self.parent()['type'] == 'set':
return self._get_attr('action', default='execute')
return self._get_attr('action')
def set_actions(self, values):
"""
Assign the set of allowable next actions.
Arguments:
values -- A list containing any combination of:
'prev', 'next', and 'complete'
"""
self.del_actions()
if values:
self._set_sub_text('{%s}actions' % self.namespace, '', True)
actions = self.find('{%s}actions' % self.namespace)
for val in values:
if val in self.next_actions:
action = ET.Element('{%s}%s' % (self.namespace, val))
actions.append(action)
def get_actions(self):
"""
Return the set of allowable next actions.
"""
actions = []
actions_xml = self.find('{%s}actions' % self.namespace)
if actions_xml is not None:
for action in self.next_actions:
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
action))
if action_xml is not None:
actions.append(action)
return actions
def del_actions(self):
"""
Remove all allowable next actions.
"""
self._del_sub('{%s}actions' % self.namespace)
def get_notes(self):
"""
Return a list of note information.
Example:
[('info', 'Some informative data'),
('warning', 'Use caution'),
('error', 'The command ran, but had errors')]
"""
notes = []
notes_xml = self.findall('{%s}note' % self.namespace)
for note in notes_xml:
notes.append((note.attrib.get('type', 'info'),
note.text))
return notes
def set_notes(self, notes):
"""
Add multiple notes to the command result.
Each note is a tuple, with the first item being one of:
'info', 'warning', or 'error', and the second item being
any human readable message.
Example:
[('info', 'Some informative data'),
('warning', 'Use caution'),
('error', 'The command ran, but had errors')]
Arguments:
notes -- A list of tuples of note information.
"""
self.del_notes()
for note in notes:
self.add_note(note[1], note[0])
def del_notes(self):
"""
Remove all notes associated with the command result.
"""
notes_xml = self.findall('{%s}note' % self.namespace)
for note in notes_xml:
self.xml.remove(note)
def add_note(self, msg='', ntype='info'):
"""
Add a single note annotation to the command.
Arguments:
msg -- A human readable message.
ntype -- One of: 'info', 'warning', 'error'
"""
xml = ET.Element('{%s}note' % self.namespace)
xml.attrib['type'] = ntype
xml.text = msg
self.xml.append(xml)

View File

@@ -1,10 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0059.stanza import Set
from sleekxmpp.plugins.xep_0059.rsm import ResultIterator, xep_0059

View File

@@ -1,119 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
import sleekxmpp
from sleekxmpp import Iq
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0059 import Set
log = logging.getLogger(__name__)
class ResultIterator():
"""
An iterator for Result Set Managment
"""
def __init__(self, query, interface, amount=10, start=None, reverse=False):
"""
Arguments:
query -- The template query
interface -- The substanza of the query, for example disco_items
amount -- The max amounts of items to request per iteration
start -- From which item id to start
reverse -- If True, page backwards through the results
Example:
q = Iq()
q['to'] = 'pubsub.example.com'
q['disco_items']['node'] = 'blog'
for i in ResultIterator(q, 'disco_items', '10'):
print i['disco_items']['items']
"""
self.query = query
self.amount = amount
self.start = start
self.interface = interface
self.reverse = reverse
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
"""
Return the next page of results from a query.
Note: If using backwards paging, then the next page of
results will be the items before the current page
of items.
"""
self.query[self.interface]['rsm']['before'] = self.reverse
self.query['id'] = self.query.stream.new_id()
self.query[self.interface]['rsm']['max'] = str(self.amount)
if self.start and self.reverse:
self.query[self.interface]['rsm']['before'] = self.start
elif self.start:
self.query[self.interface]['rsm']['after'] = self.start
r = self.query.send(block=True)
if not r or not r[self.interface]['rsm']['first'] and \
not r[self.interface]['rsm']['last']:
raise StopIteration
if self.reverse:
self.start = r[self.interface]['rsm']['first']
else:
self.start = r[self.interface]['rsm']['last']
return r
class xep_0059(base_plugin):
"""
XEP-0050: Result Set Management
"""
def plugin_init(self):
"""
Start the XEP-0059 plugin.
"""
self.xep = '0059'
self.description = 'Result Set Management'
self.stanza = sleekxmpp.plugins.xep_0059.stanza
def post_init(self):
"""Handle inter-plugin dependencies."""
base_plugin.post_init(self)
self.xmpp['xep_0030'].add_feature(Set.namespace)
def iterate(self, stanza, interface):
"""
Create a new result set iterator for a given stanza query.
Arguments:
stanza -- A stanza object to serve as a template for
queries made each iteration. For example, a
basic disco#items query.
interface -- The name of the substanza to which the
result set management stanza should be
appended. For example, for disco#items queries
the interface 'disco_items' should be used.
"""
return ResultIterator(stanza, interface)

View File

@@ -1,108 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase, ET
from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems
class Set(ElementBase):
"""
XEP-0059 (Result Set Managment) can be used to manage the
results of queries. For example, limiting the number of items
per response or starting at certain positions.
Example set stanzas:
<iq type="get">
<query xmlns="http://jabber.org/protocol/disco#items">
<set xmlns="http://jabber.org/protocol/rsm">
<max>2</max>
</set>
</query>
</iq>
<iq type="result">
<query xmlns="http://jabber.org/protocol/disco#items">
<item jid="conference.example.com" />
<item jid="pubsub.example.com" />
<set xmlns="http://jabber.org/protocol/rsm">
<first>conference.example.com</first>
<last>pubsub.example.com</last>
</set>
</query>
</iq>
Stanza Interface:
first_index -- The index attribute of <first>
after -- The id defining from which item to start
before -- The id defining from which item to
start when browsing backwards
max -- Max amount per response
first -- Id for the first item in the response
last -- Id for the last item in the response
index -- Used to set an index to start from
count -- The number of remote items available
Methods:
set_first_index -- Sets the index attribute for <first> and
creates the element if it doesn't exist
get_first_index -- Returns the value of the index
attribute for <first>
del_first_index -- Removes the index attribute for <first>
but keeps the element
set_before -- Sets the value of <before>, if the value is True
then the element will be created without a value
get_before -- Returns the value of <before>, if it is
empty it will return True
"""
namespace = 'http://jabber.org/protocol/rsm'
name = 'set'
plugin_attrib = 'rsm'
sub_interfaces = set(('first', 'after', 'before', 'count',
'index', 'last', 'max'))
interfaces = set(('first_index', 'first', 'after', 'before',
'count', 'index', 'last', 'max'))
def set_first_index(self, val):
fi = self.find("{%s}first" % (self.namespace))
if fi is not None:
if val:
fi.attrib['index'] = val
else:
del fi.attrib['index']
elif val:
fi = ET.Element("{%s}first" % (self.namespace))
fi.attrib['index'] = val
self.xml.append(fi)
def get_first_index(self):
fi = self.find("{%s}first" % (self.namespace))
if fi is not None:
return fi.attrib.get('index', '')
def del_first_index(self):
fi = self.xml.find("{%s}first" % (self.namespace))
if fi is not None:
del fi.attrib['index']
def set_before(self, val):
b = self.xml.find("{%s}before" % (self.namespace))
if b is None and val == True:
self._set_sub_text('{%s}before' % self.namespace, '', True)
else:
self._set_sub_text('{%s}before' % self.namespace, val)
def get_before(self):
b = self.xml.find("{%s}before" % (self.namespace))
if b is not None and not b.text:
return True
elif b is not None:
return b.text
else:
return None

View File

@@ -2,13 +2,8 @@ from __future__ import with_statement
from . import base
import logging
#from xml.etree import cElementTree as ET
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
from .. xmlstream.stanzabase import ElementBase, ET
from . import stanza_pubsub
from . xep_0004 import Form
log = logging.getLogger(__name__)
class xep_0060(base.base_plugin):
"""
@@ -18,7 +13,7 @@ class xep_0060(base.base_plugin):
def plugin_init(self):
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')
@@ -46,57 +41,56 @@ class xep_0060(base.base_plugin):
submitform.field['pubsub#node_type'].setValue('leaf')
else:
submitform.addField('pubsub#node_type', value='leaf')
submitform['type'] = 'submit'
configure.append(submitform.xml)
configure.append(submitform.getXML('submit'))
pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
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):
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
subscribe.attrib['jid'] = self.xmpp.jid
else:
subscribe.attrib['jid'] = self.xmpp.boundjid.full
subscribe.attrib['jid'] = self.xmpp.fulljid
else:
subscribe.attrib['jid'] = subscribee
pubsub.append(subscribe)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
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, 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
unsubscribe.attrib['jid'] = self.xmpp.jid
else:
unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
unsubscribe.attrib['jid'] = self.xmpp.fulljid
else:
unsubscribe.attrib['jid'] = subscribee
pubsub.append(unsubscribe)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
id = iq['id']
result = iq.send()
if result is False or result is None or result['type'] == 'error': return False
return True
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:
@@ -109,22 +103,22 @@ class xep_0060(base.base_plugin):
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
id = iq['id']
#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
logging.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.")
logging.error("No form found.")
return False
return Form(xml=form)
return self.xmpp.plugin['xep_0004'].buildForm(form)
def getNodeSubscriptions(self, jid, node):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
subscriptions = ET.Element('subscriptions')
@@ -133,11 +127,11 @@ class xep_0060(base.base_plugin):
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
logging.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')
@@ -156,11 +150,11 @@ class xep_0060(base.base_plugin):
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
logging.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')
@@ -179,14 +173,14 @@ class xep_0060(base.base_plugin):
pubsub.append(delete)
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
result = iq.send()
if result is not None and result is not False and result['type'] != 'error':
return True
else:
return False
def setNodeConfig(self, jid, node, config):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
configure = ET.Element('configure')
@@ -196,13 +190,13 @@ class xep_0060(base.base_plugin):
pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
id = iq['id']
result = iq.send()
if result is None or result['type'] == 'error':
if result is None or result['type'] == 'error':
return False
return True
def setItem(self, jid, node, items=[]):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
publish = ET.Element('publish')
@@ -217,12 +211,12 @@ class xep_0060(base.base_plugin):
pubsub.append(publish)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error': return False
return True
def addItem(self, jid, node, items=[]):
return self.setItem(jid, node, items)
@@ -236,12 +230,12 @@ class xep_0060(base.base_plugin):
pubsub.append(retract)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
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')
@@ -250,7 +244,7 @@ class xep_0060(base.base_plugin):
for item in items:
nodes[item.get('node')] = item.get('name')
return nodes
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')
@@ -268,7 +262,7 @@ class xep_0060(base.base_plugin):
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
log.warning("pubsub#collection doesn't exist in config, trying to add it")
logging.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
@@ -287,7 +281,7 @@ class xep_0060(base.base_plugin):
pubsub.append(affs)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = ps_jid
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['from'] = self.xmpp.fulljid
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error':
@@ -302,7 +296,7 @@ class xep_0060(base.base_plugin):
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
log.warning("pubsub#collection doesn't exist in config, trying to add it")
logging.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

View File

@@ -1,2 +0,0 @@
from sleekxmpp.plugins.xep_0060.pubsub import xep_0060
from sleekxmpp.plugins.xep_0060 import stanza

View File

@@ -1,313 +0,0 @@
from __future__ import with_statement
from sleekxmpp.plugins import base
import logging
#from xml.etree import cElementTree as ET
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
from sleekxmpp.plugins.xep_0060 import stanza
from sleekxmpp.plugins.xep_0004 import Form
log = logging.getLogger(__name__)
class xep_0060(base.base_plugin):
"""
XEP-0060 Publish Subscribe
"""
def plugin_init(self):
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')
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
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 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 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("<iq id='%s'/>" % 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 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 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 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 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 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 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 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, '')

View File

@@ -1,3 +0,0 @@
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Pubsub, Affiliation, Affiliations, Subscription, Subscriptions, SubscribeOptions, Item, Items, Create, Publish, Retract, Unsubscribe, Subscribe, Configure, Options, PubsubState, PubsubStateEvent
from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import PubsubOwner, DefaultConfig, OwnerAffiliations, OwnerAffiliation, OwnerConfigure, OwnerDefault, OwnerDelete, OwnerPurge, OwnerRedirect, OwnerSubscriptions, OwnerSubscription
from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import Event, EventItem, EventRetract, EventItems, EventCollection, EventAssociate, EventDisassociate, EventConfiguration, EventPurge, EventSubscription

View File

@@ -1,24 +0,0 @@
from xml.etree import cElementTree as ET
class OptionalSetting(object):
interfaces = set(('required',))
def setRequired(self, value):
value = bool(value)
if value and not self['required']:
self.xml.append(ET.Element("{%s}required" % self.namespace))
elif not value and self['required']:
self.delRequired()
def getRequired(self):
required = self.xml.find("{%s}required" % self.namespace)
if required is not None:
return True
else:
return False
def delRequired(self):
required = self.xml.find("{%s}required" % self.namespace)
if required is not None:
self.xml.remove(required)

View File

@@ -1,277 +0,0 @@
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from sleekxmpp.stanza.iq import Iq
from sleekxmpp.stanza.message import Message
from sleekxmpp.basexmpp import basexmpp
from sleekxmpp.xmlstream.xmlstream import XMLStream
import logging
from sleekxmpp.plugins import xep_0004
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 = {}
registerStanzaPlugin(Iq, Pubsub)
class Affiliation(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'affiliation'
plugin_attrib = name
interfaces = set(('node', 'affiliation'))
plugin_attrib_map = {}
plugin_tag_map = {}
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,)
def append(self, affiliation):
if not isinstance(affiliation, Affiliation):
raise TypeError
self.xml.append(affiliation.xml)
return self.iterables.append(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 = {}
def setjid(self, value):
self._setattr('jid', str(value))
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,)
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',))
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 = {}
def setPayload(self, value):
self.xml.append(value)
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)
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,)
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 = {}
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 = {}
#
# 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,)
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 = {}
registerStanzaPlugin(Pubsub, Retract)
class Unsubscribe(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'unsubscribe'
plugin_attrib = name
interfaces = set(('node', 'jid'))
plugin_attrib_map = {}
plugin_tag_map = {}
def setJid(self, value):
self._setAttr('jid', str(value))
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 = {}
def setJid(self, value):
self._setAttr('jid', str(value))
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 = {}
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 = {}
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 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 setJid(self, value):
self._setAttr('jid', str(value))
def getJid(self):
return JID(self._getAttr('jid'))
registerStanzaPlugin(Pubsub, Options)
registerStanzaPlugin(Subscribe, Options)
class PubsubState(ElementBase):
namespace = 'http://jabber.org/protocol/psstate'
name = 'state'
plugin_attrib = 'psstate'
interfaces = set(('node', 'item', 'payload'))
plugin_attrib_map = {}
plugin_tag_map = {}
def setPayload(self, value):
self.xml.append(value)
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)
registerStanzaPlugin(Iq, PubsubState)
class PubsubStateEvent(ElementBase):
namespace = 'http://jabber.org/protocol/psstate#event'
name = 'event'
plugin_attrib = 'psstate_event'
intefaces = set(tuple())
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(Message, PubsubStateEvent)
registerStanzaPlugin(PubsubStateEvent, PubsubState)

View File

@@ -1,124 +0,0 @@
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from sleekxmpp.stanza.iq import Iq
from sleekxmpp.stanza.message import Message
from sleekxmpp.basexmpp import basexmpp
from sleekxmpp.xmlstream.xmlstream import XMLStream
import logging
from sleekxmpp.plugins import xep_0004
class Event(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'event'
plugin_attrib = 'pubsub_event'
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(Message, Event)
class EventItem(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'item'
plugin_attrib = 'item'
interfaces = set(('id', 'payload'))
plugin_attrib_map = {}
plugin_tag_map = {}
def setPayload(self, value):
self.xml.append(value)
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)
class EventRetract(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'retract'
plugin_attrib = 'retract'
interfaces = set(('id',))
plugin_attrib_map = {}
plugin_tag_map = {}
class EventItems(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'items'
plugin_attrib = 'items'
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
subitem = (EventItem, EventRetract)
registerStanzaPlugin(Event, EventItems)
class EventCollection(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'collection'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(Event, EventCollection)
class EventAssociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'associate'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(EventCollection, EventAssociate)
class EventDisassociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'disassociate'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(EventCollection, EventDisassociate)
class EventConfiguration(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'configuration'
plugin_attrib = name
interfaces = set(('node', 'config'))
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(Event, EventConfiguration)
registerStanzaPlugin(EventConfiguration, xep_0004.Form)
class EventPurge(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'purge'
plugin_attrib = name
interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(Event, EventPurge)
class EventSubscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'subscription'
plugin_attrib = name
interfaces = set(('node','expiry', 'jid', 'subid', 'subscription'))
plugin_attrib_map = {}
plugin_tag_map = {}
def setJid(self, value):
self._setAttr('jid', str(value))
def getJid(self):
return JID(self._getAttr('jid'))
registerStanzaPlugin(Event, EventSubscription)

View File

@@ -1,152 +0,0 @@
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from sleekxmpp.stanza.iq import Iq
from sleekxmpp.stanza.message import Message
from sleekxmpp.basexmpp import basexmpp
from sleekxmpp.xmlstream.xmlstream import XMLStream
import logging
from sleekxmpp.plugins import xep_0004
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 = {}
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 = {}
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 getConfig(self):
return self['form']
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 = {}
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 = {}
class OwnerConfigure(Configure):
namespace = 'http://jabber.org/protocol/pubsub#owner'
interfaces = set(('node', 'config'))
plugin_attrib_map = {}
plugin_tag_map = {}
registerStanzaPlugin(PubsubOwner, OwnerConfigure)
class OwnerDefault(OwnerConfigure):
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, 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',))
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 = {}
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 = {}
def setJid(self, value):
self._setAttr('jid', str(value))
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 = {}
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 = {}
def setJid(self, value):
self._setAttr('jid', str(value))
def getJid(self):
return JID(self._getAttr('from'))

View File

@@ -1,11 +0,0 @@
"""
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_0066 import stanza
from sleekxmpp.plugins.xep_0066.stanza import OOB, OOBTransfer
from sleekxmpp.plugins.xep_0066.oob import xep_0066

View File

@@ -1,154 +0,0 @@
"""
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 logging
from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0066 import stanza
log = logging.getLogger(__name__)
class xep_0066(base_plugin):
"""
XEP-0066: Out-of-Band Data
Out-of-Band Data is a basic method for transferring files between
XMPP agents. The URL of the resource in question is sent to the receiving
entity, which then downloads the resource before responding to the OOB
request. OOB is also used as a generic means to transmit URLs in other
stanzas to indicate where to find additional information.
Also see <http://www.xmpp.org/extensions/xep-0066.html>.
Events:
oob_transfer -- Raised when a request to download a resource
has been received.
Methods:
send_oob -- Send a request to another entity to download a file
or other addressable resource.
"""
def plugin_init(self):
"""Start the XEP-0066 plugin."""
self.xep = '0066'
self.description = 'Out-of-Band Transfer'
self.stanza = stanza
self.url_handlers = {'global': self._default_handler,
'jid': {}}
register_stanza_plugin(Iq, stanza.OOBTransfer)
register_stanza_plugin(Message, stanza.OOB)
register_stanza_plugin(Presence, stanza.OOB)
self.xmpp.register_handler(
Callback('OOB Transfer',
StanzaPath('iq@type=set/oob_transfer'),
self._handle_transfer))
def post_init(self):
"""Handle cross-plugin dependencies."""
base_plugin.post_init(self)
self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace)
self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace)
def register_url_handler(self, jid=None, handler=None):
"""
Register a handler to process download requests, either for all
JIDs or a single JID.
Arguments:
jid -- If None, then set the handler as a global default.
handler -- If None, then remove the existing handler for the
given JID, or reset the global handler if the JID
is None.
"""
if jid is None:
if handler is not None:
self.url_handlers['global'] = handler
else:
self.url_handlers['global'] = self._default_handler
else:
if handler is not None:
self.url_handlers['jid'][jid] = handler
else:
del self.url_handlers['jid'][jid]
def send_oob(self, to, url, desc=None, ifrom=None, **iqargs):
"""
Initiate a basic file transfer by sending the URL of
a file or other resource.
Arguments:
url -- The URL of the resource to transfer.
desc -- An optional human readable description of the item
that is to be transferred.
ifrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for
a reply. If None, then wait indefinitely.
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
"""
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = to
if ifrom:
iq['from'] = ifrom
iq['oob_transfer']['url'] = url
iq['oob_transfer']['desc'] = desc
return iq.send(**iqargs)
def _run_url_handler(self, iq):
"""
Execute the appropriate handler for a transfer request.
Arguments:
iq -- The Iq stanza containing the OOB transfer request.
"""
if iq['to'] in self.url_handlers['jid']:
return self.url_handlers['jid'][jid](iq)
else:
if self.url_handlers['global']:
self.url_handlers['global'](iq)
else:
raise XMPPError('service-unavailable')
def _default_handler(self, iq):
"""
As a safe default, don't actually download files.
Register a new handler using self.register_url_handler to
screen requests and download files.
Arguments:
iq -- The Iq stanza containing the OOB transfer request.
"""
raise XMPPError('service-unavailable')
def _handle_transfer(self, iq):
"""
Handle receiving an out-of-band transfer request.
Arguments:
iq -- An Iq stanza containing an OOB transfer request.
"""
log.debug('Received out-of-band data request for %s from %s:' % (
iq['oob_transfer']['url'], iq['from']))
self._run_url_handler(iq)
iq.reply().send()

View File

@@ -1,33 +0,0 @@
"""
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
class OOBTransfer(ElementBase):
"""
"""
name = 'query'
namespace = 'jabber:iq:oob'
plugin_attrib = 'oob_transfer'
interfaces = set(('url', 'desc', 'sid'))
sub_interfaces = set(('url', 'desc'))
class OOB(ElementBase):
"""
"""
name = 'x'
namespace = 'jabber:x:oob'
plugin_attrib = 'oob'
interfaces = set(('url', 'desc'))
sub_interfaces = interfaces

View File

@@ -1,9 +1,21 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from __future__ import with_statement
from xml.etree import cElementTree as ET
@@ -12,9 +24,6 @@ import hashlib
from . import base
log = logging.getLogger(__name__)
class xep_0078(base.base_plugin):
"""
XEP-0078 NON-SASL Authentication
@@ -26,17 +35,17 @@ class xep_0078(base.base_plugin):
#disabling until I fix conflict with PLAIN
#self.xmpp.registerFeature("<auth xmlns='http://jabber.org/features/iq-auth'/>", 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")
logging.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
auth_request.attrib['to'] = self.xmpp.server
username = ET.Element('username')
username.text = self.xmpp.username
auth_request_query.append(username)
@@ -50,12 +59,12 @@ class xep_0078(base.base_plugin):
query.append(username)
query.append(resource)
if rquery.find('{jabber:iq:auth}digest') is None:
log.warning("Authenticating via jabber:iq:auth Plain.")
logging.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")
logging.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)
@@ -67,6 +76,6 @@ class xep_0078(base.base_plugin):
self.xmpp.sessionstarted = True
self.xmpp.event("session_start")
else:
log.info("Authentication failed")
logging.info("Authentication failed")
self.xmpp.disconnect()
self.xmpp.event("failed_auth")

View File

@@ -1,206 +0,0 @@
"""
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 logging
import datetime as dt
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.thirdparty import tzutc, tzoffset, parse_iso
# =====================================================================
# To make it easier for stanzas without direct access to plugin objects
# to use the XEP-0082 utility methods, we will define them as top-level
# functions and then just reference them in the plugin itself.
def parse(time_str):
"""
Convert a string timestamp into a datetime object.
Arguments:
time_str -- A formatted timestamp string.
"""
return parse_iso(time_str)
def format_date(time_obj):
"""
Return a formatted string version of a date object.
Format:
YYYY-MM-DD
Arguments:
time_obj -- A date or datetime object.
"""
if isinstance(time_obj, dt.datetime):
time_obj = time_obj.date()
return time_obj.isoformat()
def format_time(time_obj):
"""
Return a formatted string version of a time object.
format:
hh:mm:ss[.sss][TZD]
arguments:
time_obj -- A time or datetime object.
"""
if isinstance(time_obj, dt.datetime):
time_obj = time_obj.timetz()
timestamp = time_obj.isoformat()
if time_obj.tzinfo == tzutc():
timestamp = timestamp[:-6]
return '%sZ' % timestamp
return timestamp
def format_datetime(time_obj):
"""
Return a formatted string version of a datetime object.
Format:
YYYY-MM-DDThh:mm:ss[.sss]TZD
arguments:
time_obj -- A datetime object.
"""
timestamp = time_obj.isoformat('T')
if time_obj.tzinfo == tzutc():
timestamp = timestamp[:-6]
return '%sZ' % timestamp
return timestamp
def date(year=None, month=None, day=None):
"""
Create a date only timestamp for the given instant.
Unspecified components default to their current counterparts.
Arguments:
year -- Integer value of the year (4 digits)
month -- Integer value of the month
day -- Integer value of the day of the month.
"""
today = dt.datetime.today()
if year is None:
year = today.year
if month is None:
month = today.month
if day is None:
day = today.day
return format_date(dt.date(year, month, day))
def time(hour=None, min=None, sec=None, micro=None, offset=None):
"""
Create a time only timestamp for the given instant.
Unspecified components default to their current counterparts.
Arguments:
hour -- Integer value of the hour.
min -- Integer value of the number of minutes.
sec -- Integer value of the number of seconds.
micro -- Integer value of the number of microseconds.
offset -- Either a positive or negative number of seconds
to offset from UTC to match a desired timezone,
or a tzinfo object.
"""
now = dt.datetime.utcnow()
if hour is None:
hour = now.hour
if min is None:
min = now.minute
if sec is None:
sec = now.second
if micro is None:
micro = now.microsecond
if offset is None:
offset = tzutc()
elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset)
time = dt.time(hour, min, sec, micro, offset)
return format_time(time)
def datetime(year=None, month=None, day=None, hour=None,
min=None, sec=None, micro=None, offset=None,
separators=True):
"""
Create a datetime timestamp for the given instant.
Unspecified components default to their current counterparts.
Arguments:
year -- Integer value of the year (4 digits)
month -- Integer value of the month
day -- Integer value of the day of the month.
hour -- Integer value of the hour.
min -- Integer value of the number of minutes.
sec -- Integer value of the number of seconds.
micro -- Integer value of the number of microseconds.
offset -- Either a positive or negative number of seconds
to offset from UTC to match a desired timezone,
or a tzinfo object.
"""
now = dt.datetime.utcnow()
if year is None:
year = now.year
if month is None:
month = now.month
if day is None:
day = now.day
if hour is None:
hour = now.hour
if min is None:
min = now.minute
if sec is None:
sec = now.second
if micro is None:
micro = now.microsecond
if offset is None:
offset = tzutc()
elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset)
date = dt.datetime(year, month, day, hour,
min, sec, micro, offset)
return format_datetime(date)
class xep_0082(base_plugin):
"""
XEP-0082: XMPP Date and Time Profiles
XMPP uses a subset of the formats allowed by ISO 8601 as a matter of
pragmatism based on the relatively few formats historically used by
the XMPP.
Also see <http://www.xmpp.org/extensions/xep-0082.html>.
Methods:
date -- Create a time stamp using the Date profile.
datetime -- Create a time stamp using the DateTime profile.
time -- Create a time stamp using the Time profile.
format_date -- Format an existing date object.
format_datetime -- Format an existing datetime object.
format_time -- Format an existing time object.
parse -- Convert a time string into a Python datetime object.
"""
def plugin_init(self):
"""Start the XEP-0082 plugin."""
self.xep = '0082'
self.description = 'XMPP Date and Time Profiles'
self.date = date
self.datetime = datetime
self.time = time
self.format_date = format_date
self.format_datetime = format_datetime
self.format_time = format_time
self.parse = parse

View File

@@ -1,10 +0,0 @@
"""
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 permissio
"""
from sleekxmpp.plugins.xep_0085.stanza import ChatState
from sleekxmpp.plugins.xep_0085.chat_states import xep_0085

View File

@@ -1,49 +0,0 @@
"""
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 permissio
"""
import logging
import sleekxmpp
from sleekxmpp.stanza import Message
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0085 import stanza, ChatState
log = logging.getLogger(__name__)
class xep_0085(base_plugin):
"""
XEP-0085 Chat State Notifications
"""
def plugin_init(self):
self.xep = '0085'
self.description = 'Chat State Notifications'
self.stanza = stanza
for state in ChatState.states:
self.xmpp.register_handler(
Callback('Chat State: %s' % state,
StanzaPath('message@chat_state=%s' % state),
self._handle_chat_state))
register_stanza_plugin(Message, ChatState)
def post_init(self):
base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace)
def _handle_chat_state(self, msg):
state = msg['chat_state']
log.debug("Chat State: %s, %s" % (state, msg['from'].jid))
self.xmpp.event('chatstate_%s' % state, msg)

View File

@@ -1,73 +0,0 @@
"""
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 permissio
"""
import sleekxmpp
from sleekxmpp.xmlstream import ElementBase, ET
class ChatState(ElementBase):
"""
Example chat state stanzas:
<message>
<active xmlns="http://jabber.org/protocol/chatstates" />
</message>
<message>
<paused xmlns="http://jabber.org/protocol/chatstates" />
</message>
Stanza Interfaces:
chat_state
Attributes:
states
Methods:
get_chat_state
set_chat_state
del_chat_state
"""
name = ''
namespace = 'http://jabber.org/protocol/chatstates'
plugin_attrib = 'chat_state'
interfaces = set(('chat_state',))
is_extension = True
states = set(('active', 'composing', 'gone', 'inactive', 'paused'))
def setup(self, xml=None):
self.xml = ET.Element('')
return True
def get_chat_state(self):
parent = self.parent()
for state in self.states:
state_xml = parent.find('{%s}%s' % (self.namespace, state))
if state_xml is not None:
self.xml = state_xml
return state
return ''
def set_chat_state(self, state):
self.del_chat_state()
parent = self.parent()
if state in self.states:
self.xml = ET.Element('{%s}%s' % (self.namespace, state))
parent.append(self.xml)
elif state not in [None, '']:
raise ValueError('Invalid chat state')
def del_chat_state(self):
parent = self.parent()
for state in self.states:
state_xml = parent.find('{%s}%s' % (self.namespace, state))
if state_xml is not None:
self.xml = ET.Element('')
parent.xml.remove(state_xml)

View File

@@ -0,0 +1,49 @@
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
class xep_0086(base.base_plugin):
"""
XEP-0086 Error Condition Mappings
"""
def plugin_init(self):
self.xep = '0086'
self.description = 'Error Condition Mappings'
self.error_map = {
'bad-request':('modify','400'),
'conflict':('cancel','409'),
'feature-not-implemented':('cancel','501'),
'forbidden':('auth','403'),
'gone':('modify','302'),
'internal-server-error':('wait','500'),
'item-not-found':('cancel','404'),
'jid-malformed':('modify','400'),
'not-acceptable':('modify','406'),
'not-allowed':('cancel','405'),
'not-authorized':('auth','401'),
'payment-required':('auth','402'),
'recipient-unavailable':('wait','404'),
'redirect':('modify','302'),
'registration-required':('auth','407'),
'remote-server-not-found':('cancel','404'),
'remote-server-timeout':('wait','504'),
'resource-constraint':('wait','500'),
'service-unavailable':('cancel','503'),
'subscription-required':('auth','407'),
'undefined-condition':(None,'500'),
'unexpected-request':('wait','400')
}
def makeError(self, condition, cdata=None, errorType=None, text=None, customElem=None):
conditionElem = self.xmpp.makeStanzaErrorCondition(condition, cdata)
if errorType is None:
error = self.xmpp.makeStanzaError(conditionElem, self.error_map[condition][0], self.error_map[condition][1], text, customElem)
else:
error = self.xmpp.makeStanzaError(conditionElem, errorType, self.error_map[condition][1], text, customElem)
error.append(conditionElem)
return error

View File

@@ -1,10 +0,0 @@
"""
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_0086.stanza import LegacyError
from sleekxmpp.plugins.xep_0086.legacy_error import xep_0086

View File

@@ -1,42 +0,0 @@
"""
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.stanza import Error
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0086 import stanza, LegacyError
class xep_0086(base_plugin):
"""
XEP-0086: Error Condition Mappings
Older XMPP implementations used code based error messages, similar
to HTTP response codes. Since then, error condition elements have
been introduced. XEP-0086 provides a mapping between the new
condition elements and a combination of error types and the older
response codes.
Also see <http://xmpp.org/extensions/xep-0086.html>.
Configuration Values:
override -- Indicates if applying legacy error codes should
be done automatically. Defaults to True.
If False, then inserting legacy error codes can
be done using:
iq['error']['legacy']['condition'] = ...
"""
def plugin_init(self):
self.xep = '0086'
self.description = 'Error Condition Mappings'
self.stanza = stanza
register_stanza_plugin(Error, LegacyError,
overrides=self.config.get('override', True))

View File

@@ -1,91 +0,0 @@
"""
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.stanza import Error
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class LegacyError(ElementBase):
"""
Older XMPP implementations used code based error messages, similar
to HTTP response codes. Since then, error condition elements have
been introduced. XEP-0086 provides a mapping between the new
condition elements and a combination of error types and the older
response codes.
Also see <http://xmpp.org/extensions/xep-0086.html>.
Example legacy error stanzas:
<error xmlns="jabber:client" code="501" type="cancel">
<feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
<error code="402" type="auth">
<payment-required
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
Attributes:
error_map -- A map of error conditions to error types and
code values.
Methods:
setup -- Overrides ElementBase.setup
set_condition -- Remap the type and code interfaces when a
condition is set.
"""
name = 'legacy'
namespace = Error.namespace
plugin_attrib = name
interfaces = set(('condition',))
overrides = ['set_condition']
error_map = {'bad-request': ('modify','400'),
'conflict': ('cancel','409'),
'feature-not-implemented': ('cancel','501'),
'forbidden': ('auth','403'),
'gone': ('modify','302'),
'internal-server-error': ('wait','500'),
'item-not-found': ('cancel','404'),
'jid-malformed': ('modify','400'),
'not-acceptable': ('modify','406'),
'not-allowed': ('cancel','405'),
'not-authorized': ('auth','401'),
'payment-required': ('auth','402'),
'recipient-unavailable': ('wait','404'),
'redirect': ('modify','302'),
'registration-required': ('auth','407'),
'remote-server-not-found': ('cancel','404'),
'remote-server-timeout': ('wait','504'),
'resource-constraint': ('wait','500'),
'service-unavailable': ('cancel','503'),
'subscription-required': ('auth','407'),
'undefined-condition': (None,'500'),
'unexpected-request': ('wait','400')}
def setup(self, xml):
"""Don't create XML for the plugin."""
self.xml = ET.Element('')
def set_condition(self, value):
"""
Set the error type and code based on the given error
condition value.
Arguments:
value -- The new error condition.
"""
self.parent().set_condition(value)
error_data = self.error_map.get(value, None)
if error_data is not None:
if error_data[0] is not None:
self.parent()['type'] = error_data[0]
self.parent()['code'] = error_data[1]

View File

@@ -0,0 +1,68 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz
This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
from xml.etree import cElementTree as ET
from . import base
from .. xmlstream.handler.xmlwaiter import XMLWaiter
class xep_0092(base.base_plugin):
"""
XEP-0092 Software Version
"""
def plugin_init(self):
self.description = "Software Version"
self.xep = "0092"
self.name = self.config.get('name', 'SleekXMPP')
self.version = self.config.get('version', '0.1-dev')
self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
def report_version(self, xml):
iq = self.xmpp.makeIqResult(xml.get('id', 'unknown'))
iq.attrib['to'] = xml.get('from', self.xmpp.server)
query = ET.Element('{jabber:iq:version}query')
name = ET.Element('name')
name.text = self.name
version = ET.Element('version')
version.text = self.version
query.append(name)
query.append(version)
iq.append(query)
self.xmpp.send(iq)
def getVersion(self, jid):
iq = self.xmpp.makeIqGet()
query = ET.Element('{jabber:iq:version}query')
iq.append(query)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.fulljid
id = iq.get('id')
result = iq.send()
if result and result is not None and result.get('type', 'error') != 'error':
qry = result.find('{jabber:iq:version}query')
version = {}
for child in qry.getchildren():
version[child.tag.split('}')[-1]] = child.text
return version
else:
return False

View File

@@ -1,11 +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.
"""
from sleekxmpp.plugins.xep_0092 import stanza
from sleekxmpp.plugins.xep_0092.stanza import Version
from sleekxmpp.plugins.xep_0092.version import xep_0092

View File

@@ -1,42 +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 sleekxmpp.xmlstream import ElementBase, ET
class Version(ElementBase):
"""
XMPP allows for an agent to advertise the name and version of the
underlying software libraries, as well as the operating system
that the agent is running on.
Example version stanzas:
<iq type="get">
<query xmlns="jabber:iq:version" />
</iq>
<iq type="result">
<query xmlns="jabber:iq:version">
<name>SleekXMPP</name>
<version>1.0</version>
<os>Linux</os>
</query>
</iq>
Stanza Interface:
name -- The human readable name of the software.
version -- The specific version of the software.
os -- The name of the operating system running the program.
"""
name = 'query'
namespace = 'jabber:iq:version'
plugin_attrib = 'software_version'
interfaces = set(('name', 'version', 'os'))
sub_interfaces = interfaces

View File

@@ -1,88 +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.
"""
import logging
import sleekxmpp
from sleekxmpp import Iq
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0092 import Version
log = logging.getLogger(__name__)
class xep_0092(base_plugin):
"""
XEP-0092: Software Version
"""
def plugin_init(self):
"""
Start the XEP-0092 plugin.
"""
self.xep = "0092"
self.description = "Software Version"
self.stanza = sleekxmpp.plugins.xep_0092.stanza
self.name = self.config.get('name', 'SleekXMPP')
self.version = self.config.get('version', sleekxmpp.__version__)
self.os = self.config.get('os', '')
self.getVersion = self.get_version
self.xmpp.register_handler(
Callback('Software Version',
StanzaPath('iq@type=get/software_version'),
self._handle_version))
register_stanza_plugin(Iq, Version)
def post_init(self):
"""
Handle cross-plugin dependencies.
"""
base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
def _handle_version(self, iq):
"""
Respond to a software version query.
Arguments:
iq -- The Iq stanza containing the software version query.
"""
iq.reply()
iq['software_version']['name'] = self.name
iq['software_version']['version'] = self.version
iq['software_version']['os'] = self.os
iq.send()
def get_version(self, jid, ifrom=None):
"""
Retrieve the software version of a remote agent.
Arguments:
jid -- The JID of the entity to query.
"""
iq = self.xmpp.Iq()
iq['to'] = jid
if ifrom:
iq['from'] = ifrom
iq['type'] = 'get'
iq['query'] = Version.namespace
result = iq.send()
if result and result['type'] != 'error':
return result['software_version'].values
return False

View File

@@ -1,10 +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.
"""
from sleekxmpp.plugins.xep_0128.static import StaticExtendedDisco
from sleekxmpp.plugins.xep_0128.extended_disco import xep_0128

Some files were not shown because too many files have changed in this diff Show More