pax_global_header 0000666 0000000 0000000 00000000064 11577753404 0014527 g ustar 00root root 0000000 0000000 52 comment=d8d9e8df16c07bd13bbac72e4445a2930407b244
fritzy-SleekXMPP-5c4ee57/ 0000775 0000000 0000000 00000000000 11577753404 0015247 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/.gitignore 0000664 0000000 0000000 00000000015 11577753404 0017233 0 ustar 00root root 0000000 0000000 *.pyc
build/
fritzy-SleekXMPP-5c4ee57/INSTALL 0000664 0000000 0000000 00000000317 11577753404 0016301 0 ustar 00root root 0000000 0000000 Pre-requisites:
- Python 3.1 or 2.6
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]
fritzy-SleekXMPP-5c4ee57/LICENSE 0000664 0000000 0000000 00000002046 11577753404 0016256 0 ustar 00root root 0000000 0000000 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.
fritzy-SleekXMPP-5c4ee57/README 0000664 0000000 0000000 00000004071 11577753404 0016131 0 ustar 00root root 0000000 0000000 SleekXMPP is an XMPP library written for Python 3.1+ (with 2.6 compatibility).
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.
- Rewarding to work with.
The goals for 1.0 include (and we're getting close):
- Nearly Full test coverage of stanzas.
- Wide range of functional tests.
- Stanza objects for all interaction with the stream
- Documentation on using and extending SleekXMPP.
- Complete documentation on all implemented stanza objects
- Documentation on all examples used in XMPP: The Definitive Guide
1.1 will include:
- More functional and unit tests
- PEP-8 compliance
- XEP-225 support
Since 0.2, here's the Changelog:
- MANY bugfixes
- Re-implementation of handlers/threading to greatly simplify and remove bugs (no more spawning threads in handlers)
- Stanza objects for jabber:client and all implemented XEPs
- Raising XMPPError for jabber:client and extended errors in handlers
- Robust error handling and better insurance of iq responses
- Stanza objects have made life a lot easier!
- Massive audit/cleanup.
Credits
----------------
Main Author: Nathan Fritz fritz@netflint.net
Contributors: Kevin Smith & Lance Stout
Patches: Remko Tronçon
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.
fritzy-SleekXMPP-5c4ee57/conn_tests/ 0000775 0000000 0000000 00000000000 11577753404 0017426 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/conn_tests/test_pubsubjobs.py 0000664 0000000 0000000 00000015417 11577753404 0023225 0 ustar 00root root 0000000 0000000 import logging
import sleekxmpp
from optparse import OptionParser
from xml.etree import cElementTree as ET
import os
import time
import sys
import unittest
import sleekxmpp.plugins.xep_0004
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
from sleekxmpp.xmlstream.handler.waiter import Waiter
try:
import configparser
except ImportError:
import ConfigParser as configparser
try:
import queue
except ImportError:
import Queue as queue
class TestClient(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)
self.waitforstart = queue.Queue()
def start(self, event):
self.getRoster()
self.sendPresence()
self.waitforstart.put(True)
class TestPubsubServer(unittest.TestCase):
statev = {}
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
def setUp(self):
pass
def test001getdefaultconfig(self):
"""Get the default node config"""
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2')
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3')
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4')
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5')
result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost)
self.statev['defaultconfig'] = result
self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form))
def test002createdefaultnode(self):
"""Create a node without config"""
self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1'))
def test003deletenode(self):
"""Delete recently created node"""
self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1'))
def test004createnode(self):
"""Create a node with a config"""
self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open')
self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True)
self.statev['defaultconfig'].field['pubsub#persist_items'].setValue(True)
self.statev['defaultconfig'].field['pubsub#presence_based_delivery'].setValue(True)
p = self.xmpp2.Presence()
p['to'] = self.pshost
p.send()
self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig'], ntype='job'))
def test005reconfigure(self):
"""Retrieving node config and reconfiguring"""
nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2')
self.failUnless(nconfig, "No configuration returned")
#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues()))
self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match")
self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig))
def test006subscribetonode(self):
"""Subscribe to node from account 2"""
self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2"))
def test007publishitem(self):
"""Publishing item"""
item = ET.Element('{http://netflint.net/protocol/test}test')
w = Waiter('wait publish', StanzaPath('message/pubsub_event/items'))
self.xmpp2.registerHandler(w)
#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),))
result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test1', item)
msg = w.wait(5) # got to get a result in 5 seconds
self.failUnless(msg != False, "Account #2 did not get message event")
#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),))
result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test2', item)
w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items'))
self.xmpp2.registerHandler(w)
self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test1')
msg = w.wait(5) # got to get a result in 5 seconds
self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test2')
self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test1')
self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test2')
print result
#need to add check for update
def test900cleanup(self):
"Cleaning up"
#self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.")
time.sleep(10)
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")
optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use")
optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use")
opts,args = optp.parse_args()
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
#load xml config
logging.info("Loading config file: %s" % opts.configfile)
config = configparser.RawConfigParser()
config.read(opts.configfile)
#init
logging.info("Account 1 is %s" % config.get('account1', 'jid'))
xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
logging.info("Account 2 is %s" % config.get('account2', 'jid'))
xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
xmpp1.registerPlugin('xep_0004')
xmpp1.registerPlugin('xep_0030')
xmpp1.registerPlugin('xep_0060')
xmpp1.registerPlugin('xep_0199')
xmpp1.registerPlugin('jobs')
xmpp2.registerPlugin('xep_0004')
xmpp2.registerPlugin('xep_0030')
xmpp2.registerPlugin('xep_0060')
xmpp2.registerPlugin('xep_0199')
xmpp2.registerPlugin('jobs')
if not config.get('account1', 'server'):
# we don't know the server, but the lib can probably figure it out
xmpp1.connect()
else:
xmpp1.connect((config.get('account1', 'server'), 5222))
xmpp1.process(threaded=True)
#init
if not config.get('account2', 'server'):
# we don't know the server, but the lib can probably figure it out
xmpp2.connect()
else:
xmpp2.connect((config.get('account2', 'server'), 5222))
xmpp2.process(threaded=True)
TestPubsubServer.xmpp1 = xmpp1
TestPubsubServer.xmpp2 = xmpp2
TestPubsubServer.pshost = config.get('settings', 'pubsub')
xmpp1.waitforstart.get(True)
xmpp2.waitforstart.get(True)
testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer)
alltests_suite = unittest.TestSuite([testsuite])
result = unittest.TextTestRunner(verbosity=2).run(alltests_suite)
xmpp1.disconnect()
xmpp2.disconnect()
fritzy-SleekXMPP-5c4ee57/conn_tests/test_pubsubserver.py 0000664 0000000 0000000 00000023034 11577753404 0023570 0 ustar 00root root 0000000 0000000 import logging
import sleekxmpp
from optparse import OptionParser
from xml.etree import cElementTree as ET
import os
import time
import sys
import unittest
import sleekxmpp.plugins.xep_0004
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
from sleekxmpp.xmlstream.handler.waiter import Waiter
try:
import configparser
except ImportError:
import ConfigParser as configparser
try:
import queue
except ImportError:
import Queue as queue
class TestClient(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)
self.waitforstart = queue.Queue()
def start(self, event):
self.getRoster()
self.sendPresence()
self.waitforstart.put(True)
class TestPubsubServer(unittest.TestCase):
statev = {}
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
def setUp(self):
pass
def test001getdefaultconfig(self):
"""Get the default node config"""
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2')
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3')
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4')
self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5')
result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost)
self.statev['defaultconfig'] = result
self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form))
def test002createdefaultnode(self):
"""Create a node without config"""
self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1'))
def test003deletenode(self):
"""Delete recently created node"""
self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1'))
def test004createnode(self):
"""Create a node with a config"""
self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open')
self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True)
self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig']))
def test005reconfigure(self):
"""Retrieving node config and reconfiguring"""
nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2')
self.failUnless(nconfig, "No configuration returned")
#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues()))
self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match")
self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig))
def test006subscribetonode(self):
"""Subscribe to node from account 2"""
self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2"))
def test007publishitem(self):
"""Publishing item"""
item = ET.Element('{http://netflint.net/protocol/test}test')
w = Waiter('wait publish', StanzaPath('message/pubsub_event/items'))
self.xmpp2.registerHandler(w)
result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),))
msg = w.wait(5) # got to get a result in 5 seconds
self.failUnless(msg != False, "Account #2 did not get message event")
self.failUnless(result)
#need to add check for update
def test008updateitem(self):
"""Updating item"""
item = ET.Element('{http://netflint.net/protocol/test}test', {'someattr': 'hi there'})
w = Waiter('wait publish', StanzaPath('message/pubsub_event/items'))
self.xmpp2.registerHandler(w)
result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),))
msg = w.wait(5) # got to get a result in 5 seconds
self.failUnless(msg != False, "Account #2 did not get message event")
self.failUnless(result)
#need to add check for update
def test009deleteitem(self):
"""Deleting item"""
w = Waiter('wait retract', StanzaPath('message/pubsub_event/items@node=testnode2'))
self.xmpp2.registerHandler(w)
result = self.xmpp1['xep_0060'].deleteItem(self.pshost, "testnode2", "test1")
self.failUnless(result, "Got error when deleting item.")
msg = w.wait(1)
self.failUnless(msg != False, "Did not get retract notice.")
def test010unsubscribenode(self):
"Unsubscribing Account #2"
self.failUnless(self.xmpp2['xep_0060'].unsubscribe(self.pshost, "testnode2"), "Got error response when unsubscribing.")
def test011createcollectionnode(self):
"Create a collection node w/ Account #2"
self.failUnless(self.xmpp2['xep_0060'].create_node(self.pshost, "testnode3", self.statev['defaultconfig'], True), "Could not create collection node")
def test012subscribecollection(self):
"Subscribe Account #1 to collection"
self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode3"))
def test013assignnodetocollection(self):
"Assign node to collection"
self.failUnless(self.xmpp2['xep_0060'].addNodeToCollection(self.pshost, 'testnode2', 'testnode3'))
def test014publishcollection(self):
"""Publishing item to collection child"""
item = ET.Element('{http://netflint.net/protocol/test}test')
w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items@node=testnode2'))
self.xmpp1.registerHandler(w)
result = self.xmpp2['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),))
msg = w.wait(5) # got to get a result in 5 seconds
self.failUnless(msg != False, "Account #1 did not get message event: perhaps node was advertised incorrectly?")
self.failUnless(result)
# def test016speedtest(self):
# "Uncached speed test"
# import time
# start = time.time()
# for y in range(0, 50000, 1000):
# start2 = time.time()
# for x in range(y, y+1000):
# self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode4", subscribee="testuser%s@whatever" % x))
# print time.time() - start2
# seconds = time.time() - start
# print "--", seconds
# print "---------"
# time.sleep(15)
# self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4'), "Could not delete non-cached test node")
# def test015speedtest(self):
# "cached speed test"
# result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost)
# self.statev['defaultconfig'] = result
# self.statev['defaultconfig'].field['pubsub#node_type'].setValue("leaf")
# self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(True)
# self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode4', self.statev['defaultconfig']))
# self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(False)
# self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode5', self.statev['defaultconfig']))
# start = time.time()
# for y in range(0, 50000, 1000):
# start2 = time.time()
# for x in range(y, y+1000):
# self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode5", subscribee="testuser%s@whatever" % x))
# print time.time() - start2
# seconds = time.time() - start
# print "--", seconds
def test900cleanup(self):
"Cleaning up"
self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.")
self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3'), "Could not delete collection node")
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")
optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use")
optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use")
opts,args = optp.parse_args()
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
#load xml config
logging.info("Loading config file: %s" % opts.configfile)
config = configparser.RawConfigParser()
config.read(opts.configfile)
#init
logging.info("Account 1 is %s" % config.get('account1', 'jid'))
xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
logging.info("Account 2 is %s" % config.get('account2', 'jid'))
xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
xmpp1.registerPlugin('xep_0004')
xmpp1.registerPlugin('xep_0030')
xmpp1.registerPlugin('xep_0060')
xmpp1.registerPlugin('xep_0199')
xmpp2.registerPlugin('xep_0004')
xmpp2.registerPlugin('xep_0030')
xmpp2.registerPlugin('xep_0060')
xmpp2.registerPlugin('xep_0199')
if not config.get('account1', 'server'):
# we don't know the server, but the lib can probably figure it out
xmpp1.connect()
else:
xmpp1.connect((config.get('account1', 'server'), 5222))
xmpp1.process(threaded=True)
#init
if not config.get('account2', 'server'):
# we don't know the server, but the lib can probably figure it out
xmpp2.connect()
else:
xmpp2.connect((config.get('account2', 'server'), 5222))
xmpp2.process(threaded=True)
TestPubsubServer.xmpp1 = xmpp1
TestPubsubServer.xmpp2 = xmpp2
TestPubsubServer.pshost = config.get('settings', 'pubsub')
xmpp1.waitforstart.get(True)
xmpp2.waitforstart.get(True)
testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer)
alltests_suite = unittest.TestSuite([testsuite])
result = unittest.TextTestRunner(verbosity=2).run(alltests_suite)
xmpp1.disconnect()
xmpp2.disconnect()
fritzy-SleekXMPP-5c4ee57/conn_tests/testconfig.ini 0000664 0000000 0000000 00000000227 11577753404 0022275 0 ustar 00root root 0000000 0000000 [settings]
enabled=true
pubsub=pubsub.recon
[account1]
jid=fritzy@recon
pass=testing123
server=
[account2]
jid=fritzy2@recon
pass=testing123
server=
fritzy-SleekXMPP-5c4ee57/conn_tests/testpubsub.py 0000775 0000000 0000000 00000031374 11577753404 0022213 0 ustar 00root root 0000000 0000000 """
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 optparse import OptionParser
from xml.etree import cElementTree as ET
import os
import time
import sys
import Queue
import thread
class testps(sleekxmpp.ClientXMPP):
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], nodenum=0, pshost=None):
sleekxmpp.ClientXMPP.__init__(self, jid, password, ssl, plugin_config, plugin_whitelist)
self.registerPlugin('xep_0004')
self.registerPlugin('xep_0030')
self.registerPlugin('xep_0060')
self.registerPlugin('xep_0092')
self.add_handler("", self.pubsubEventHandler, name='Pubsub Event', threaded=True)
self.add_event_handler("session_start", self.start, threaded=True)
self.add_handler("", self.handleError, name='Iq Error')
self.events = Queue.Queue()
self.default_config = None
self.ps = self.plugin['xep_0060']
self.node = "pstestnode_%s"
self.pshost = pshost
if pshost is None:
self.pshost = self.boundjid.host
self.nodenum = int(nodenum)
self.leafnode = self.nodenum + 1
self.collectnode = self.nodenum + 2
self.lasterror = ''
self.sprintchars = 0
self.defaultconfig = None
self.tests = ['test_defaultConfig', 'test_createDefaultNode', 'test_getNodes', 'test_deleteNode', 'test_createWithConfig', 'test_reconfigureNode', 'test_subscribeToNode', 'test_addItem', 'test_updateItem', 'test_deleteItem', 'test_unsubscribeNode', 'test_createCollection', 'test_subscribeCollection', 'test_addNodeCollection', 'test_deleteNodeCollection', 'test_addCollectionNode', 'test_deleteCollectionNode', 'test_unsubscribeNodeCollection', 'test_deleteCollection']
self.passed = 0
self.width = 120
def start(self, event):
#TODO: make this configurable
self.getRoster()
self.sendPresence(ppriority=20)
thread.start_new(self.test_all, tuple())
def sprint(self, msg, end=False, color=False):
length = len(msg)
if color:
if color == "red":
color = "1;31"
elif color == "green":
color = "0;32"
msg = "%s%s%s" % ("\033[%sm" % color, msg, "\033[0m")
if not end:
sys.stdout.write(msg)
self.sprintchars += length
else:
self.sprint("%s%s" % ("." * (self.width - self.sprintchars - length), msg))
print('')
self.sprintchars = 0
sys.stdout.flush()
def pubsubEventHandler(self, xml):
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}item'):
self.events.put(item.get('id', '__unknown__'))
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}retract'):
self.events.put(item.get('id', '__unknown__'))
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}disassociate'):
self.events.put(item.get('node', '__unknown__'))
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}associate'):
self.events.put(item.get('node', '__unknown__'))
def handleError(self, xml):
error = xml.find('{jabber:client}error')
self.lasterror = error.getchildren()[0].tag.split('}')[-1]
def test_all(self):
print("Running Publish-Subscribe Tests")
version = self.plugin['xep_0092'].getVersion(self.pshost)
if version:
print("%s %s on %s" % (version.get('name', 'Unknown Server'), version.get('version', 'v?'), version.get('os', 'Unknown OS')))
print("=" * self.width)
for test in self.tests:
testfunc = getattr(self, test)
self.sprint("%s" % testfunc.__doc__)
if testfunc():
self.sprint("Passed", True, "green")
self.passed += 1
else:
if not self.lasterror:
self.lasterror = 'No response'
self.sprint("Failed (%s)" % self.lasterror, True, "red")
self.lasterror = ''
print("=" * self.width)
self.sprint("Cleaning up...")
#self.ps.deleteNode(self.pshost, self.node % self.nodenum)
self.ps.deleteNode(self.pshost, self.node % self.leafnode)
#self.ps.deleteNode(self.pshost, self.node % self.collectnode)
self.sprint("Done", True, "green")
self.disconnect()
self.sprint("%s" % self.passed, False, "green")
self.sprint("/%s Passed -- " % len(self.tests))
if len(self.tests) - self.passed:
self.sprint("%s" % (len(self.tests) - self.passed), False, "red")
else:
self.sprint("%s" % (len(self.tests) - self.passed), False, "green")
self.sprint(" Failed Tests")
print
#print "%s/%s Passed -- %s Failed Tests" % (self.passed, len(self.tests), len(self.tests) - self.passed)
def test_defaultConfig(self):
"Retreiving default configuration"
result = self.ps.getNodeConfig(self.pshost)
if result is False or result is None:
return False
else:
self.defaultconfig = result
try:
self.defaultconfig.field['pubsub#access_model'].setValue('open')
except KeyError:
pass
try:
self.defaultconfig.field['pubsub#notify_retract'].setValue(True)
except KeyError:
pass
return True
def test_createDefaultNode(self):
"Creating default node"
return self.ps.create_node(self.pshost, self.node % self.nodenum)
def test_getNodes(self):
"Getting list of nodes"
self.ps.getNodes(self.pshost)
self.ps.getItems(self.pshost, 'blog')
return True
def test_deleteNode(self):
"Deleting node"
return self.ps.deleteNode(self.pshost, self.node % self.nodenum)
def test_createWithConfig(self):
"Creating node with config"
if self.defaultconfig is None:
self.lasterror = "No Avail Config"
return False
return self.ps.create_node(self.pshost, self.node % self.leafnode, self.defaultconfig)
def test_reconfigureNode(self):
"Retrieving node config and reconfiguring"
nconfig = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
if nconfig == False:
return False
return self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, nconfig)
def test_subscribeToNode(self):
"Subscribing to node"
return self.ps.subscribe(self.pshost, self.node % self.leafnode)
def test_addItem(self):
"Adding item, waiting for notification"
item = ET.Element('test')
result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),))
if result == False:
return False
try:
event = self.events.get(True, 10)
except Queue.Empty:
return False
if event == 'test_node1':
return True
return False
def test_updateItem(self):
"Updating item, waiting for notification"
item = ET.Element('test')
item.attrib['crap'] = 'yup, right here'
result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),))
if result == False:
return False
try:
event = self.events.get(True, 10)
except Queue.Empty:
return False
if event == 'test_node1':
return True
return False
def test_deleteItem(self):
"Deleting item, waiting for notification"
result = self.ps.deleteItem(self.pshost, self.node % self.leafnode, 'test_node1')
if result == False:
return False
try:
event = self.events.get(True, 10)
except Queue.Empty:
self.lasterror = "No Notification"
return False
if event == 'test_node1':
return True
return False
def test_unsubscribeNode(self):
"Unsubscribing from node"
return self.ps.unsubscribe(self.pshost, self.node % self.leafnode)
def test_createCollection(self):
"Creating collection node"
return self.ps.create_node(self.pshost, self.node % self.collectnode, self.defaultconfig, True)
def test_subscribeCollection(self):
"Subscribing to collection node"
return self.ps.subscribe(self.pshost, self.node % self.collectnode)
def test_addNodeCollection(self):
"Assigning node to collection, waiting for notification"
config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#collection'].setValue(self.node % self.collectnode)
except KeyError:
self.sprint("...Missing Field...", False, "red")
config.addField('pubsub#collection', value=self.node % self.collectnode)
if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config):
return False
try:
event = self.events.get(True, 10)
except Queue.Empty:
self.lasterror = "No Notification"
return False
if event == self.node % self.leafnode:
return True
return False
def test_deleteNodeCollection(self):
"Removing node assignment to collection, waiting for notification"
config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#collection'].delValue(self.node % self.collectnode)
except KeyError:
self.sprint("...Missing Field...", False, "red")
config.addField('pubsub#collection', value='')
if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config):
return False
try:
event = self.events.get(True, 10)
except Queue.Empty:
self.lasterror = "No Notification"
return False
if event == self.node % self.leafnode:
return True
return False
def test_addCollectionNode(self):
"Assigning node from collection, waiting for notification"
config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#children'].setValue(self.node % self.leafnode)
except KeyError:
self.sprint("...Missing Field...", False, "red")
config.addField('pubsub#children', value=self.node % self.leafnode)
if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config):
return False
try:
event = self.events.get(True, 10)
except Queue.Empty:
self.lasterror = "No Notification"
return False
if event == self.node % self.leafnode:
return True
return False
def test_deleteCollectionNode(self):
"Removing node from collection, waiting for notification"
config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#children'].delValue(self.node % self.leafnode)
except KeyError:
self.sprint("...Missing Field...", False, "red")
config.addField('pubsub#children', value='')
if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config):
return False
try:
event = self.events.get(True, 10)
except Queue.Empty:
self.lasterror = "No Notification"
return False
if event == self.node % self.leafnode:
return True
return False
def test_unsubscribeNodeCollection(self):
"Unsubscribing from collection"
return self.ps.unsubscribe(self.pshost, self.node % self.collectnode)
def test_deleteCollection(self):
"Deleting collection"
return self.ps.deleteNode(self.pshost, self.node % self.collectnode)
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")
optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use")
optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use")
opts,args = optp.parse_args()
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
#load xml config
logging.info("Loading config file: %s" % opts.configfile)
config = ET.parse(os.path.expanduser(opts.configfile)).find('auth')
#init
logging.info("Logging in as %s" % config.attrib['jid'])
plugin_config = {}
plugin_config['xep_0092'] = {'name': 'SleekXMPP Example', 'version': '0.1-dev'}
plugin_config['xep_0199'] = {'keepalive': True, 'timeout': 30, 'frequency': 300}
con = testps(config.attrib['jid'], config.attrib['pass'], plugin_config=plugin_config, plugin_whitelist=[], nodenum=opts.nodenum, pshost=opts.pubsub)
if not config.get('server', None):
# we don't know the server, but the lib can probably figure it out
con.connect()
else:
con.connect((config.attrib['server'], 5222))
con.process(threaded=False)
print("")
fritzy-SleekXMPP-5c4ee57/examples/ 0000775 0000000 0000000 00000000000 11577753404 0017065 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/examples/adhoc_provider.py 0000775 0000000 0000000 00000016103 11577753404 0022433 0 ustar 00root root 0000000 0000000 #!/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.")
fritzy-SleekXMPP-5c4ee57/examples/adhoc_user.py 0000775 0000000 0000000 00000016051 11577753404 0021561 0 ustar 00root root 0000000 0000000 #!/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:
#
#
#
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.")
fritzy-SleekXMPP-5c4ee57/examples/config.xml 0000664 0000000 0000000 00000000376 11577753404 0021062 0 ustar 00root root 0000000 0000000 component.localhostssshhlocalhost8888
fritzy-SleekXMPP-5c4ee57/examples/config_component.py 0000775 0000000 0000000 00000014632 11577753404 0022777 0 ustar 00root root 0000000 0000000 #!/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:
component.localhostssshhlocalhost8888
"""
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.")
fritzy-SleekXMPP-5c4ee57/examples/disco_browser.py 0000775 0000000 0000000 00000015172 11577753404 0022314 0 ustar 00root root 0000000 0000000 #!/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 []" % \
'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.")
fritzy-SleekXMPP-5c4ee57/examples/echo_client.py 0000775 0000000 0000000 00000011300 11577753404 0021711 0 ustar 00root root 0000000 0000000 #!/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.")
fritzy-SleekXMPP-5c4ee57/examples/muc.py 0000775 0000000 0000000 00000015547 11577753404 0020242 0 ustar 00root root 0000000 0000000 #!/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.")
fritzy-SleekXMPP-5c4ee57/examples/ping.py 0000775 0000000 0000000 00000011200 11577753404 0020371 0 ustar 00root root 0000000 0000000 #!/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.")
fritzy-SleekXMPP-5c4ee57/examples/rpc_async.py 0000664 0000000 0000000 00000001472 11577753404 0021424 0 ustar 00root root 0000000 0000000 """
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()
fritzy-SleekXMPP-5c4ee57/examples/rpc_client_side.py 0000664 0000000 0000000 00000002155 11577753404 0022570 0 ustar 00root root 0000000 0000000 """
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()
fritzy-SleekXMPP-5c4ee57/examples/rpc_server_side.py 0000664 0000000 0000000 00000002171 11577753404 0022616 0 ustar 00root root 0000000 0000000 """
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()
fritzy-SleekXMPP-5c4ee57/ez_setup.py 0000664 0000000 0000000 00000021206 11577753404 0017460 0 ustar 00root root 0000000 0000000 #!python
"""Bootstrap setuptools installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from ez_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import sys
DEFAULT_VERSION = "0.6c7"
DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
md5_data = {
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
}
import sys, os
def _validate_md5(egg_name, data):
if egg_name in md5_data:
from md5 import md5
digest = md5(data).hexdigest()
if digest != md5_data[egg_name]:
print >>sys.stderr, (
"md5 validation of %s failed! (Possible download problem?)"
% egg_name
)
sys.exit(2)
return data
def use_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, min_version=None,
download_delay=15
):
"""Automatically find/download setuptools and make it available on sys.path
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end with
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
it is not already available. If `download_delay` is specified, it should
be the number of seconds that will be paused before initiating a download,
should one be required. If an older version of setuptools is installed,
this routine will print a message to ``sys.stderr`` and raise SystemExit in
an attempt to abort the calling script.
"""
try:
import setuptools
if setuptools.__version__ == '0.0.1':
print >>sys.stderr, (
"You have an obsolete version of setuptools installed. Please\n"
"remove it from your system entirely before rerunning this script."
)
sys.exit(2)
except ImportError:
egg = download_setuptools(version, download_base, to_dir, download_delay)
sys.path.insert(0, egg)
import setuptools; setuptools.bootstrap_install_from = egg
import pkg_resources
try:
if not min_version:
min_version = version
pkg_resources.require("setuptools>="+min_version)
except pkg_resources.VersionConflict, e:
# XXX could we install in a subprocess here?
print >>sys.stderr, (
"The required version of setuptools (>=%s) is not available, and\n"
"can't be installed while this script is running. Please install\n"
" a more recent version first.\n\n(Currently using %r)"
) % (min_version, e.args[0])
sys.exit(2)
def download_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
delay = 15
):
"""Download setuptools from a specified location and return its filename
`version` should be a valid setuptools version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download attempt.
"""
import urllib2, shutil
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
url = download_base + egg_name
saveto = os.path.join(to_dir, egg_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
from distutils import log
if delay:
log.warn("""
---------------------------------------------------------------------------
This script requires setuptools version %s to run (even to display
help). I will attempt to download it for you (from
%s), but
you may need to enable firewall access for this script first.
I will start the download in %d seconds.
(Note: if this machine does not have network access, please obtain the file
%s
and place it in this directory before rerunning this script.)
---------------------------------------------------------------------------""",
version, download_base, delay, url
); from time import sleep; sleep(delay)
log.warn("Downloading %s", url)
src = urllib2.urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = _validate_md5(egg_name, src.read())
dst = open(saveto,"wb"); dst.write(data)
finally:
if src: src.close()
if dst: dst.close()
return os.path.realpath(saveto)
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
try:
import setuptools
except ImportError:
egg = None
try:
egg = download_setuptools(version, delay=0)
sys.path.insert(0,egg)
from setuptools.command.easy_install import main
return main(list(argv)+[egg]) # we're done here
finally:
if egg and os.path.exists(egg):
os.unlink(egg)
else:
if setuptools.__version__ == '0.0.1':
# tell the user to uninstall obsolete version
use_setuptools(version)
req = "setuptools>="+version
import pkg_resources
try:
pkg_resources.require(req)
except pkg_resources.VersionConflict:
try:
from setuptools.command.easy_install import main
except ImportError:
from easy_install import main
main(list(argv)+[download_setuptools(delay=0)])
sys.exit(0) # try to force an exit
else:
if argv:
from setuptools.command.easy_install import main
main(argv)
else:
print "Setuptools version",version,"or greater has been installed."
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
def update_md5(filenames):
"""Update our built-in md5 registry"""
import re
from md5 import md5
for name in filenames:
base = os.path.basename(name)
f = open(name,'rb')
md5_data[base] = md5(f.read()).hexdigest()
f.close()
data = [" %r: %r,\n" % it for it in md5_data.items()]
data.sort()
repl = "".join(data)
import inspect
srcfile = inspect.getsourcefile(sys.modules[__name__])
f = open(srcfile, 'rb'); src = f.read(); f.close()
match = re.search("\nmd5_data = {\n([^}]+)}", src)
if not match:
print >>sys.stderr, "Internal error!"
sys.exit(2)
src = src[:match.start(1)] + repl + src[match.end(1):]
f = open(srcfile,'w')
f.write(src)
f.close()
if __name__=='__main__':
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
update_md5(sys.argv[2:])
else:
main(sys.argv[1:])
fritzy-SleekXMPP-5c4ee57/setup.py 0000664 0000000 0000000 00000005317 11577753404 0016767 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2008 Nathanael C. Fritz
# All Rights Reserved
#
# This software is licensed as described in the README file,
# which you should have received as part of this distribution.
#
# from ez_setup import use_setuptools
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__
DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
LONG_DESCRIPTION = """
SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).
"""
CLASSIFIERS = [ 'Intended Audience :: Developers',
'License :: OSI Approved :: MIT',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules',
]
packages = [ 'sleekxmpp',
'sleekxmpp/stanza',
'sleekxmpp/test',
'sleekxmpp/xmlstream',
'sleekxmpp/xmlstream/matcher',
'sleekxmpp/xmlstream/handler',
'sleekxmpp/thirdparty',
'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_0085',
'sleekxmpp/plugins/xep_0086',
'sleekxmpp/plugins/xep_0092',
'sleekxmpp/plugins/xep_0128',
'sleekxmpp/plugins/xep_0199',
]
if sys.version_info < (3, 0):
py_modules = ['sleekxmpp.xmlstream.tostring.tostring26']
else:
py_modules = ['sleekxmpp.xmlstream.tostring.tostring']
setup(
name = "sleekxmpp",
version = VERSION,
description = DESCRIPTION,
long_description = LONG_DESCRIPTION,
author = 'Nathanael Fritz',
author_email = 'fritzy [at] netflint.net',
url = 'http://code.google.com/p/sleekxmpp',
license = 'MIT',
platforms = [ 'any' ],
packages = packages,
py_modules = py_modules,
requires = [ 'tlslite', 'pythondns' ],
)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/ 0000775 0000000 0000000 00000000000 11577753404 0017257 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/__init__.py 0000664 0000000 0000000 00000001163 11577753404 0021371 0 ustar 00root root 0000000 0000000 """
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.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.0beta5'
__version_info__ = (1, 0, 0, 'beta5', 0)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/basexmpp.py 0000664 0000000 0000000 00000061656 11577753404 0021466 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement, unicode_literals
import sys
import copy
import logging
import sleekxmpp
from sleekxmpp import plugins
from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError
from sleekxmpp.stanza.roster import Roster
from sleekxmpp.stanza.nick import Nick
from sleekxmpp.stanza.htmlim import HTMLIM
from sleekxmpp.xmlstream import XMLStream, JID, tostring
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
log = logging.getLogger(__name__)
# In order to make sure that Unicode is handled properly
# in Python 2.x, reset the default encoding.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
class BaseXMPP(XMLStream):
"""
The BaseXMPP class adapts the generic XMLStream class for use
with XMPP. It also provides a plugin mechanism to easily extend
and add support for new XMPP features.
Attributes:
auto_authorize -- Manage automatically accepting roster
subscriptions.
auto_subscribe -- Manage automatically requesting mutual
subscriptions.
is_component -- Indicates if this stream is for an XMPP component.
jid -- The XMPP JID for this stream.
plugin -- A dictionary of loaded plugins.
plugin_config -- A dictionary of plugin configurations.
plugin_whitelist -- A list of approved plugins.
sentpresence -- Indicates if an initial presence has been sent.
roster -- A dictionary containing subscribed JIDs and
their presence statuses.
Methods:
Iq -- Factory for creating an Iq stanzas.
Message -- Factory for creating Message stanzas.
Presence -- Factory for creating Presence stanzas.
get -- Return a plugin given its name.
make_iq -- Create and initialize an Iq stanza.
make_iq_error -- Create an Iq stanza of type 'error'.
make_iq_get -- Create an Iq stanza of type 'get'.
make_iq_query -- Create an Iq stanza with a given query.
make_iq_result -- Create an Iq stanza of type 'result'.
make_iq_set -- Create an Iq stanza of type 'set'.
make_message -- Create and initialize a Message stanza.
make_presence -- Create and initialize a Presence stanza.
make_query_roster -- Create a roster query.
process -- Overrides XMLStream.process.
register_plugin -- Load and configure a plugin.
register_plugins -- Load and configure multiple plugins.
send_message -- Create and send a Message stanza.
send_presence -- Create and send a Presence stanza.
send_presence_subscribe -- Send a subscription request.
"""
def __init__(self, default_ns='jabber:client'):
"""
Adapt an XML stream for use with XMPP.
Arguments:
default_ns -- Ensure that the correct default XML namespace
is used during initialization.
"""
XMLStream.__init__(self)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
self.default_ns = default_ns
self.stream_ns = 'http://etherx.jabber.org/streams'
self.boundjid = JID("")
self.plugin = {}
self.plugin_config = {}
self.plugin_whitelist = []
self.roster = {}
self.is_component = False
self.auto_authorize = True
self.auto_subscribe = True
self.sentpresence = False
self.register_handler(
Callback('IM',
MatchXPath('{%s}message/{%s}body' % (self.default_ns,
self.default_ns)),
self._handle_message))
self.register_handler(
Callback('Presence',
MatchXPath("{%s}presence" % self.default_ns),
self._handle_presence))
self.register_handler(
Callback('Stream Error',
MatchXPath("{%s}error" % self.stream_ns),
self._handle_stream_error))
self.add_event_handler('presence_subscribe',
self._handle_subscribe)
self.add_event_handler('disconnected',
self._handle_disconnected)
# Set up the XML stream with XMPP's root stanzas.
self.register_stanza(Message)
self.register_stanza(Iq)
self.register_stanza(Presence)
self.register_stanza(StreamError)
# Initialize a few default stanza plugins.
register_stanza_plugin(Iq, Roster)
register_stanza_plugin(Message, Nick)
register_stanza_plugin(Message, HTMLIM)
def process(self, *args, **kwargs):
"""
Ensure that plugin inter-dependencies are handled before starting
event processing.
Overrides XMLStream.process.
"""
for name in self.plugin:
if not self.plugin[name].post_inited:
self.plugin[name].post_init()
return XMLStream.process(self, *args, **kwargs)
def register_plugin(self, plugin, pconfig={}, module=None):
"""
Register and configure a plugin for use in this stream.
Arguments:
plugin -- The name of the plugin class. Plugin names must
be unique.
pconfig -- A dictionary of configuration data for the plugin.
Defaults to an empty dictionary.
module -- Optional refence to the module containing the plugin
class if using custom plugins.
"""
try:
# Import the given module that contains the plugin.
if not module:
module = sleekxmpp.plugins
module = __import__("%s.%s" % (module.__name__, plugin),
globals(), locals(), [plugin])
if isinstance(module, str):
# We probably want to load a module from outside
# the sleekxmpp package, so leave out the globals().
module = __import__(module, fromlist=[plugin])
# Load the plugin class from the module.
self.plugin[plugin] = getattr(module, plugin)(self, pconfig)
# Let XEP implementing plugins have some extra logging info.
xep = ''
if hasattr(self.plugin[plugin], 'xep'):
xep = "(XEP-%s) " % self.plugin[plugin].xep
desc = (xep, self.plugin[plugin].description)
log.debug("Loaded Plugin %s%s" % desc)
except:
log.exception("Unable to load plugin: %s", plugin)
def register_plugins(self):
"""
Register and initialize all built-in plugins.
Optionally, the list of plugins loaded may be limited to those
contained in self.plugin_whitelist.
Plugin configurations stored in self.plugin_config will be used.
"""
if self.plugin_whitelist:
plugin_list = self.plugin_whitelist
else:
plugin_list = plugins.__all__
for plugin in plugin_list:
if plugin in plugins.__all__:
self.register_plugin(plugin,
self.plugin_config.get(plugin, {}))
else:
raise NameError("Plugin %s not in plugins.__all__." % plugin)
# Resolve plugin inter-dependencies.
for plugin in self.plugin:
self.plugin[plugin].post_init()
def __getitem__(self, key):
"""
Return a plugin given its name, if it has been registered.
"""
if key in self.plugin:
return self.plugin[key]
else:
log.warning("""Plugin "%s" is not loaded.""" % key)
return False
def get(self, key, default):
"""
Return a plugin given its name, if it has been registered.
"""
return self.plugin.get(key, default)
def Message(self, *args, **kwargs):
"""Create a Message stanza associated with this stream."""
return Message(self, *args, **kwargs)
def Iq(self, *args, **kwargs):
"""Create an Iq stanza associated with this stream."""
return Iq(self, *args, **kwargs)
def Presence(self, *args, **kwargs):
"""Create a Presence stanza associated with this stream."""
return Presence(self, *args, **kwargs)
def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
"""
Create a new Iq stanza with a given Id and from JID.
Arguments:
id -- An ideally unique ID value for this stanza thread.
Defaults to 0.
ifrom -- The from JID to use for this stanza.
ito -- The destination JID for this stanza.
itype -- The Iq's type, one of: get, set, result, or error.
iquery -- Optional namespace for adding a query element.
"""
iq = self.Iq()
iq['id'] = str(id)
iq['to'] = ito
iq['from'] = ifrom
iq['type'] = itype
iq['query'] = iquery
return iq
def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
"""
Create an Iq stanza of type 'get'.
Optionally, a query element may be added.
Arguments:
queryxmlns -- The namespace of the query to use.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
"""
if not iq:
iq = self.Iq()
iq['type'] = 'get'
iq['query'] = queryxmlns
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq
def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
"""
Create an Iq stanza of type 'result' with the given ID value.
Arguments:
id -- An ideally unique ID value. May use self.new_id().
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
"""
if not iq:
iq = self.Iq()
if id is None:
id = self.new_id()
iq['id'] = id
iq['type'] = 'result'
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq
def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
"""
Create an Iq stanza of type 'set'.
Optionally, a substanza may be given to use as the
stanza's payload.
Arguments:
sub -- A stanza or XML object to use as the Iq's payload.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
"""
if not iq:
iq = self.Iq()
iq['type'] = 'set'
if sub != None:
iq.append(sub)
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq
def make_iq_error(self, id, type='cancel',
condition='feature-not-implemented',
text=None, ito=None, ifrom=None, iq=None):
"""
Create an Iq stanza of type 'error'.
Arguments:
id -- An ideally unique ID value. May use self.new_id().
type -- The type of the error, such as 'cancel' or 'modify'.
Defaults to 'cancel'.
condition -- The error condition.
Defaults to 'feature-not-implemented'.
text -- A message describing the cause of the error.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
iq -- Optionally use an existing stanza instead
of generating a new one.
"""
if not iq:
iq = self.Iq()
iq['id'] = id
iq['error']['type'] = type
iq['error']['condition'] = condition
iq['error']['text'] = text
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq
def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
"""
Create or modify an Iq stanza to use the given
query namespace.
Arguments:
iq -- Optional Iq stanza to modify. A new
stanza is created otherwise.
xmlns -- The query's namespace.
ito -- The destination JID for this stanza.
ifrom -- The from JID to use for this stanza.
"""
if not iq:
iq = self.Iq()
iq['query'] = xmlns
if ito:
iq['to'] = ito
if ifrom:
iq['from'] = ifrom
return iq
def make_query_roster(self, iq=None):
"""
Create a roster query element.
Arguments:
iq -- Optional Iq stanza to modify. A new stanza
is created otherwise.
"""
if iq:
iq['query'] = 'jabber:iq:roster'
return ET.Element("{jabber:iq:roster}query")
def make_message(self, mto, mbody=None, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None):
"""
Create and initialize a new Message stanza.
Arguments:
mto -- The recipient of the message.
mbody -- The main contents of the message.
msubject -- Optional subject for the message.
mtype -- The message's type, such as 'chat' or 'groupchat'.
mhtml -- Optional HTML body content.
mfrom -- The sender of the message. If sending from a client,
be aware that some servers require that the full JID
of the sender be used.
mnick -- Optional nickname of the sender.
"""
message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
message['body'] = mbody
message['subject'] = msubject
if mnick is not None:
message['nick'] = mnick
if mhtml is not None:
message['html']['body'] = mhtml
return message
def make_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, ptype=None, pfrom=None):
"""
Create and initialize a new Presence stanza.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
ptype -- The type of presence, such as 'subscribe'.
pfrom -- The sender of the presence.
"""
presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
if pshow is not None:
presence['type'] = pshow
if pfrom is None:
presence['from'] = self.boundjid.full
presence['priority'] = ppriority
presence['status'] = pstatus
return presence
def send_message(self, mto, mbody, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None):
"""
Create, initialize, and send a Message stanza.
"""
self.makeMessage(mto, mbody, msubject, mtype,
mhtml, mfrom, mnick).send()
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, pfrom=None, ptype=None):
"""
Create, initialize, and send a Presence stanza.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
ptype -- The type of presence, such as 'subscribe'.
pfrom -- The sender of the presence.
"""
self.makePresence(pshow, pstatus, ppriority, pto,
ptype=ptype, pfrom=pfrom).send()
# Unexpected errors may occur if
if not self.sentpresence:
self.event('sent_presence')
self.sentpresence = True
def send_presence_subscription(self, pto, pfrom=None,
ptype='subscribe', pnick=None):
"""
Create, initialize, and send a Presence stanza of type 'subscribe'.
Arguments:
pto -- The recipient of a directed presence.
pfrom -- The sender of the presence.
ptype -- The type of presence. Defaults to 'subscribe'.
pnick -- Nickname of the presence's sender.
"""
presence = self.makePresence(ptype=ptype,
pfrom=pfrom,
pto=self.getjidbare(pto))
if pnick:
nick = ET.Element('{http://jabber.org/protocol/nick}nick')
nick.text = pnick
presence.append(nick)
presence.send()
@property
def jid(self):
"""
Attribute accessor for bare jid
"""
log.warning("jid property deprecated. Use boundjid.bare")
return self.boundjid.bare
@jid.setter
def jid(self, value):
log.warning("jid property deprecated. Use boundjid.bare")
self.boundjid.bare = value
@property
def fulljid(self):
"""
Attribute accessor for full jid
"""
log.warning("fulljid property deprecated. Use boundjid.full")
return self.boundjid.full
@fulljid.setter
def fulljid(self, value):
log.warning("fulljid property deprecated. Use boundjid.full")
self.boundjid.full = value
@property
def resource(self):
"""
Attribute accessor for jid resource
"""
log.warning("resource property deprecated. Use boundjid.resource")
return self.boundjid.resource
@resource.setter
def resource(self, value):
log.warning("fulljid property deprecated. Use boundjid.full")
self.boundjid.resource = value
@property
def username(self):
"""
Attribute accessor for jid usernode
"""
log.warning("username property deprecated. Use boundjid.user")
return self.boundjid.user
@username.setter
def username(self, value):
log.warning("username property deprecated. Use boundjid.user")
self.boundjid.user = value
@property
def server(self):
"""
Attribute accessor for jid host
"""
log.warning("server property deprecated. Use boundjid.host")
return self.boundjid.server
@server.setter
def server(self, value):
log.warning("server property deprecated. Use boundjid.host")
self.boundjid.server = value
def set_jid(self, jid):
"""Rip a JID apart and claim it as our own."""
log.debug("setting jid to %s" % jid)
self.boundjid.full = jid
def getjidresource(self, fulljid):
if '/' in fulljid:
return fulljid.split('/', 1)[-1]
else:
return ''
def getjidbare(self, fulljid):
return fulljid.split('/', 1)[0]
def _handle_disconnected(self, event):
"""When disconnected, reset the roster"""
self.roster = {}
def _handle_stream_error(self, error):
self.event('stream_error', error)
def _handle_message(self, msg):
"""Process incoming message stanzas."""
self.event('message', msg)
def _handle_presence(self, presence):
"""
Process incoming presence stanzas.
Update the roster with presence information.
"""
self.event("presence_%s" % presence['type'], presence)
# Check for changes in subscription state.
if presence['type'] in ('subscribe', 'subscribed',
'unsubscribe', 'unsubscribed'):
self.event('changed_subscription', presence)
return
elif not presence['type'] in ('available', 'unavailable') and \
not presence['type'] in presence.showtypes:
return
# Strip the information from the stanza.
jid = presence['from'].bare
resource = presence['from'].resource
show = presence['type']
status = presence['status']
priority = presence['priority']
was_offline = False
got_online = False
old_roster = self.roster.get(jid, {}).get(resource, {})
# Create a new roster entry if needed.
if not jid in self.roster:
self.roster[jid] = {'groups': [],
'name': '',
'subscription': 'none',
'presence': {},
'in_roster': False}
# Alias to simplify some references.
connections = self.roster[jid].get('presence', {})
# Determine if the user has just come online.
if not resource in connections:
if show == 'available' or show in presence.showtypes:
got_online = True
was_offline = True
connections[resource] = {}
if connections[resource].get('show', 'unavailable') == 'unavailable':
was_offline = True
# Update the roster's state for this JID's resource.
connections[resource] = {'show': show,
'status': status,
'priority': priority}
name = self.roster[jid].get('name', '')
# Remove unneeded state information after a resource
# disconnects. Determine if this was the last connection
# for the JID.
if show == 'unavailable':
log.debug("%s %s got offline" % (jid, resource))
del connections[resource]
if not connections and \
not self.roster[jid].get('in_roster', False):
del self.roster[jid]
if not was_offline:
self.event("got_offline", presence)
else:
return False
name = '(%s) ' % name if name else ''
# Presence state has changed.
self.event("changed_status", presence)
if got_online:
self.event("got_online", presence)
log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
show, status))
def _handle_subscribe(self, presence):
"""
Automatically managage subscription requests.
Subscription behavior is controlled by the settings
self.auto_authorize and self.auto_subscribe.
auto_auth auto_sub Result:
True True Create bi-directional subsriptions.
True False Create only directed subscriptions.
False * Decline all subscriptions.
None * Disable automatic handling and use
a custom handler.
"""
presence.reply()
presence['to'] = presence['to'].bare
# We are using trinary logic, so conditions have to be
# more explicit than usual.
if self.auto_authorize == True:
presence['type'] = 'subscribed'
presence.send()
if self.auto_subscribe:
presence['type'] = 'subscribe'
presence.send()
elif self.auto_authorize == False:
presence['type'] = 'unsubscribed'
presence.send()
# Restore the old, lowercased name for backwards compatibility.
basexmpp = BaseXMPP
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
BaseXMPP.registerPlugin = BaseXMPP.register_plugin
BaseXMPP.makeIq = BaseXMPP.make_iq
BaseXMPP.makeIqGet = BaseXMPP.make_iq_get
BaseXMPP.makeIqResult = BaseXMPP.make_iq_result
BaseXMPP.makeIqSet = BaseXMPP.make_iq_set
BaseXMPP.makeIqError = BaseXMPP.make_iq_error
BaseXMPP.makeIqQuery = BaseXMPP.make_iq_query
BaseXMPP.makeQueryRoster = BaseXMPP.make_query_roster
BaseXMPP.makeMessage = BaseXMPP.make_message
BaseXMPP.makePresence = BaseXMPP.make_presence
BaseXMPP.sendMessage = BaseXMPP.send_message
BaseXMPP.sendPresence = BaseXMPP.send_presence
BaseXMPP.sendPresenceSubscription = BaseXMPP.send_presence_subscription
fritzy-SleekXMPP-5c4ee57/sleekxmpp/clientxmpp.py 0000664 0000000 0000000 00000043362 11577753404 0022024 0 ustar 00root root 0000000 0000000 """
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
from sleekxmpp import plugins
from sleekxmpp import stanza
from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream import StanzaBase, ET
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.
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 = "" % (
self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns,
"xmlns='%s'" % self.default_ns)
self.stream_footer = ""
self.features = []
self.registered_features = []
#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_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))
self.register_feature(
"",
self._handle_starttls, True)
self.register_feature(
"",
self._handle_sasl_auth, True)
self.register_feature(
"",
self._handle_bind_resource)
self.register_feature(
"",
self._handle_start_session)
def handle_connected(self, event=None):
#TODO: Use stream state here
self.authenticated = False
self.sessionstarted = False
self.bound = False
self.bindfail = False
self.schedule("session timeout checker", 15,
self._session_timeout_check)
def _session_timeout_check(self):
if not self.session_started_event.isSet():
log.debug("Session start has taken more than 15 seconds")
self.disconnect(reconnect=self.auto_reconnect)
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
for answer in answers:
intmax += answer.priority
addresses[intmax] = (answer.target.to_text()[:-1],
answer.port)
#python3 returns a generator for dictionary keys
priorities = [x for x in addresses.keys()]
priorities.sort()
picked = random.randint(0, intmax)
for priority in priorities:
if picked <= priority:
address = addresses[priority]
break
if not address:
# If all else fails, 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, mask, pointer, breaker=False):
"""
Register a stream feature.
Arguments:
mask -- An XML string matching the feature's element.
pointer -- The function to execute if the feature is received.
breaker -- Indicates if feature processing should halt with
this feature. Defaults to False.
"""
self.registered_features.append((MatchXMLMask(mask),
pointer,
breaker))
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_stream_features(self, features):
"""
Process the received stream features.
Arguments:
features -- The features stanza.
"""
# Record all of the features.
self.features = []
for sub in features.xml:
self.features.append(sub.tag)
# Process the features.
for sub in features.xml:
for feature in self.registered_features:
mask, handler, halt = feature
if mask.match(sub):
if handler(sub) and halt:
# Don't continue if the feature was
# marked as a breaker.
return True
def _handle_starttls(self, xml):
"""
Handle notification that the server supports TLS.
Arguments:
xml -- The STARTLS proceed element.
"""
if not self.use_tls:
return False
elif not self.authenticated and self.ssl_support:
tls_ns = 'urn:ietf:params:xml:ns:xmpp-tls'
self.add_handler("" % tls_ns,
self._handle_tls_start,
name='TLS Proceed',
instream=True)
self.send_xml(xml, 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_tls_start(self, xml):
"""
Handle encrypting the stream using TLS.
Restarts the stream.
"""
log.debug("Starting TLS")
if self.start_tls():
raise RestartStream()
def _handle_sasl_auth(self, xml):
"""
Handle authenticating using SASL.
Arguments:
xml -- The SASL mechanisms stanza.
"""
if self.use_tls and \
'{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
return False
log.debug("Starting SASL Auth")
sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl'
self.add_handler("" % sasl_ns,
self._handle_auth_success,
name='SASL Sucess',
instream=True)
self.add_handler("" % sasl_ns,
self._handle_auth_fail,
name='SASL Failure',
instream=True)
sasl_mechs = xml.findall('{%s}mechanism' % sasl_ns)
if sasl_mechs:
for sasl_mech in sasl_mechs:
self.features.append("sasl:%s" % sasl_mech.text)
if 'sasl:PLAIN' in self.features and self.boundjid.user:
if sys.version_info < (3, 0):
user = bytes(self.boundjid.user)
password = bytes(self.password)
else:
user = bytes(self.boundjid.user, 'utf-8')
password = bytes(self.password, 'utf-8')
auth = base64.b64encode(b'\x00' + user + \
b'\x00' + password).decode('utf-8')
self.send("%s" % (
sasl_ns,
auth),
now=True)
elif 'sasl:ANONYMOUS' in self.features and not self.boundjid.user:
self.send("" % (
sasl_ns,
'ANONYMOUS'),
now=True)
else:
log.error("No appropriate login method.")
self.disconnect()
return True
def _handle_auth_success(self, xml):
"""
SASL authentication succeeded. Restart the stream.
Arguments:
xml -- The SASL authentication success element.
"""
self.authenticated = True
self.features = []
raise RestartStream()
def _handle_auth_fail(self, xml):
"""
SASL authentication failed. Disconnect and shutdown.
Arguments:
xml -- The SASL authentication failure element.
"""
log.info("Authentication failed.")
self.event("failed_auth", direct=True)
self.disconnect()
def _handle_bind_resource(self, xml):
"""
Handle requesting a specific resource.
Arguments:
xml -- The bind feature element.
"""
log.debug("Requesting resource: %s" % self.boundjid.resource)
xml.clear()
iq = self.Iq(stype='set')
if self.boundjid.resource:
res = ET.Element('resource')
res.text = self.boundjid.resource
xml.append(res)
iq.append(xml)
response = iq.send(now=True)
bind_ns = 'urn:ietf:params:xml:ns:xmpp-bind'
self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns,
bind_ns)).text)
self.bound = True
log.info("Node set to: %s" % self.boundjid.full)
session_ns = 'urn:ietf:params:xml:ns:xmpp-session'
if "{%s}session" % session_ns not in self.features or self.bindfail:
log.debug("Established Session")
self.sessionstarted = True
self.session_started_event.set()
self.event("session_start")
def _handle_start_session(self, xml):
"""
Handle the start of the session.
Arguments:
xml -- The session feature element.
"""
if self.authenticated and self.bound:
iq = self.makeIqSet(xml)
response = iq.send(now=True)
log.debug("Established Session")
self.sessionstarted = True
self.session_started_event.set()
self.event("session_start")
else:
# Bind probably hasn't happened yet.
self.bindfail = 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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/componentxmpp.py 0000664 0000000 0000000 00000011261 11577753404 0022541 0 ustar 00root root 0000000 0000000 """
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
import logging
import base64
import sys
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 *
log = logging.getLogger(__name__)
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 = "" % (
'xmlns="jabber:component:accept"',
'xmlns:stream="%s"' % self.stream_ns,
jid)
self.stream_footer = ""
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")
fritzy-SleekXMPP-5c4ee57/sleekxmpp/exceptions.py 0000664 0000000 0000000 00000004040 11577753404 0022010 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
class 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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/ 0000775 0000000 0000000 00000000000 11577753404 0020740 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/__init__.py 0000664 0000000 0000000 00000000607 11577753404 0023054 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
__all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
'xep_0045', 'xep_0050', 'xep_0060', 'xep_0085', 'xep_0086',
'xep_0092', 'xep_0128', 'xep_0199', 'xep_0202', 'gmail_notify']
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/base.py 0000664 0000000 0000000 00000006601 11577753404 0022227 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
class 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 = '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):
"""
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/gmail_notify.py 0000664 0000000 0000000 00000011104 11577753404 0023770 0 ustar 00root root 0000000 0000000 """
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.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'
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()
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/jobs.py 0000664 0000000 0000000 00000002715 11577753404 0022254 0 ustar 00root root 0000000 0000000 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
def createJob(self, host, node, jobid=None, payload=None):
return self.xmpp.plugin['xep_0060'].setItem(host, node, ((jobid, payload),))
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):
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
def finishJob(self, host, node, jobid, payload=None):
finished = ET.Element('{http://andyet.net/protocol/pubsubjob}finished')
if payload is not None:
finished.append(payload)
return self._setState(host, node, jobid, finished)
def _setState(self, host, node, jobid, state, ifrom=None):
iq = self.xmpp.Iq()
iq['to'] = host
if ifrom: iq['from'] = ifrom
iq['type'] = 'set'
iq['psstate']['node'] = node
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))
return False
return True
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/old_0004.py 0000664 0000000 0000000 00000027554 11577753404 0022550 0 ustar 00root root 0000000 0000000 """
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("", 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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/old_0009.py 0000664 0000000 0000000 00000017241 11577753404 0022545 0 ustar 00root root 0000000 0000000 """
XEP-0009 XMPP Remote Procedure Calls
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
import time
import base64
def py2xml(*args):
params = ET.Element("params")
for x in args:
param = ET.Element("param")
param.append(_py2xml(x))
params.append(param) #...
return params
def _py2xml(*args):
for x in args:
val = ET.Element("value")
if type(x) is int:
i4 = ET.Element("i4")
i4.text = str(x)
val.append(i4)
if 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) is list:
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):
vals = []
for param in params.findall('param'):
vals.append(_xml2py(param.find('value')))
return vals
def _xml2py(value):
if value.find('i4') is not None:
return int(value.find('i4').text)
if value.find('int') is not None:
return int(value.find('int').text)
if value.find('boolean') is not None:
return bool(value.find('boolean').text)
if value.find('string') is not None:
return value.find('string').text
if value.find('double') is not None:
return float(value.find('double').text)
if value.find('Base64') is not None:
return rpcbase64(value.find('Base64').text)
if value.find('dateTime.iso8601') is not None:
return rpctime(value.find('dateTime.iso8601'))
if value.find('struct') is not None:
struct = {}
for member in value.find('struct').findall('member'):
struct[member.find('name').text] = _xml2py(member.find('value'))
return struct
if value.find('array') is not None:
array = []
for val in value.find('array').find('data').findall('value'):
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(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()
class JabberRPCEntry(object):
def __init__(self,call):
self.call = call
self.result = None
self.error = None
self.allow = {} #{'':['',...],...}
self.deny = {}
def check_acl(self, jid, resource):
#Check for deny
if jid in self.deny.keys():
if self.deny[jid] == None or resource in self.deny[jid]:
return False
#Check for allow
if allow == None:
return True
if jid in self.allow.keys():
if self.allow[jid] == None or resource in self.allow[jid]:
return True
return False
def acl_allow(self, jid, resource):
if jid == None:
self.allow = None
elif resource == None:
self.allow[jid] = None
elif jid in self.allow.keys():
self.allow[jid].append(resource)
else:
self.allow[jid] = [resource]
def acl_deny(self, jid, resource):
if jid == None:
self.deny = None
elif resource == None:
self.deny[jid] = None
elif jid in self.deny.keys():
self.deny[jid].append(resource)
else:
self.deny[jid] = [resource]
def call_method(self, args):
ret = self.call(*args)
class xep_0009(base.base_plugin):
def plugin_init(self):
self.xep = '0009'
self.description = 'Jabber-RPC'
self.xmpp.add_handler("",
self._callMethod, name='Jabber RPC Call')
self.xmpp.add_handler("",
self._callResult, name='Jabber RPC Result')
self.xmpp.add_handler("",
self._callError, name='Jabber RPC Error')
self.entries = {}
self.activeCalls = []
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('automatition','rpc')
def register_call(self, method, name=None):
#@returns an string that can be used in acl commands.
with self.lock:
if name is None:
self.entries[method.__name__] = JabberRPCEntry(method)
return method.__name__
else:
self.entries[name] = JabberRPCEntry(method)
return name
def acl_allow(self, entry, jid=None, resource=None):
#allow the method entry to be called by the given jid and resource.
#if jid is None it will allow any jid/resource.
#if resource is None it will allow any resource belonging to the jid.
with self.lock:
if self.entries[entry]:
self.entries[entry].acl_allow(jid,resource)
else:
raise ValueError()
def acl_deny(self, entry, jid=None, resource=None):
#Note: by default all requests are denied unless allowed with acl_allow.
#If you deny an entry it will not be allowed regardless of acl_allow
with self.lock:
if self.entries[entry]:
self.entries[entry].acl_deny(jid,resource)
else:
raise ValueError()
def unregister_call(self, entry):
#removes the registered call
with self.lock:
if self.entries[entry]:
del self.entries[entry]
else:
raise ValueError()
def makeMethodCallQuery(self,pmethod,params):
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
methodCall = ET.Element('methodCall')
methodName = ET.Element('methodName')
methodName.text = pmethod
methodCall.append(methodName)
methodCall.append(params)
query.append(methodCall)
return query
def makeIqMethodCall(self,pto,pmethod,params):
iq = self.xmpp.makeIqSet()
iq.set('to',pto)
iq.append(self.makeMethodCallQuery(pmethod,params))
return iq
def makeIqMethodResponse(self,pto,pid,params):
iq = self.xmpp.makeIqResult(pid)
iq.set('to',pto)
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
methodResponse = ET.Element('methodResponse')
methodResponse.append(params)
query.append(methodResponse)
return iq
def makeIqMethodError(self,pto,id,pmethod,params,condition):
iq = self.xmpp.makeIqError(id)
iq.set('to',pto)
iq.append(self.makeMethodCallQuery(pmethod,params))
iq.append(self.xmpp['xep_0086'].makeError(condition))
return iq
def call_remote(self, pto, pmethod, *args):
#calls a remote method. Returns the id of the Iq.
pass
def _callMethod(self,xml):
pass
def _callResult(self,xml):
pass
def _callError(self,xml):
pass
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/old_0050.py 0000664 0000000 0000000 00000012751 11577753404 0022542 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import time
class old_0050(base.base_plugin):
"""
XEP-0050 Ad-Hoc Commands
"""
def plugin_init(self):
self.xep = '0050'
self.description = 'Ad-Hoc Commands'
self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None')
self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute')
self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True)
self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel')
self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete')
self.commands = {}
self.sessions = {}
self.sd = self.xmpp.plugin['xep_0030']
def post_init(self):
base.base_plugin.post_init(self)
self.sd.add_feature('http://jabber.org/protocol/commands')
def addCommand(self, node, name, form, pointer=None, multi=False):
self.sd.add_item(None, name, 'http://jabber.org/protocol/commands', node)
self.sd.add_identity('automation', 'command-node', name, node)
self.sd.add_feature('http://jabber.org/protocol/commands', node)
self.sd.add_feature('jabber:x:data', node)
self.commands[node] = (name, form, pointer, multi)
def getNewSession(self):
return str(time.time()) + '-' + self.xmpp.getNewId()
def handler_command(self, xml):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
node = in_command.get('node')
sessionid = self.getNewSession()
name, form, pointer, multi = self.commands[node]
self.sessions[sessionid] = {}
self.sessions[sessionid]['jid'] = xml.get('from')
self.sessions[sessionid]['to'] = xml.get('to')
self.sessions[sessionid]['past'] = [(form, None)]
self.sessions[sessionid]['next'] = pointer
npointer = pointer
if multi:
actions = ['next']
status = 'executing'
else:
if pointer is None:
status = 'completed'
actions = []
else:
status = 'executing'
actions = ['complete']
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
def handler_command_complete(self, xml):
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.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=[]))
del self.sessions[in_command.get('sessionid')]
def handler_command_next(self, xml):
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.fromXML(in_command.find('{jabber:x:data}x'))
form, npointer, next = pointer(results,sessionid)
self.sessions[sessionid]['next'] = npointer
self.sessions[sessionid]['past'].append((form, pointer))
actions = []
actions.append('prev')
if npointer is None:
status = 'completed'
else:
status = 'executing'
if next:
actions.append('next')
else:
actions.append('complete')
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
def handler_command_cancel(self, xml):
command = xml.find('{http://jabber.org/protocol/commands}command')
try:
del self.sessions[command.get('sessionid')]
except:
pass
self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled'))
def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]):
if not id:
id = self.xmpp.getNewId()
iq = self.xmpp.makeIqResult(id)
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['to'] = to
command = ET.Element('{http://jabber.org/protocol/commands}command')
command.attrib['node'] = node
command.attrib['status'] = status
xmlactions = ET.Element('actions')
for action in actions:
xmlactions.append(ET.Element(action))
if xmlactions:
command.append(xmlactions)
if not sessionid:
sessionid = self.getNewSession()
else:
iq.attrib['from'] = self.sessions[sessionid]['to']
command.attrib['sessionid'] = sessionid
if form is not None:
if hasattr(form,'getXML'):
form = form.getXML()
command.append(form)
iq.append(command)
return iq
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/stanza_pubsub.py 0000664 0000000 0000000 00000033156 11577753404 0024202 0 ustar 00root root 0000000 0000000 from .. xmlstream.stanzabase import registerStanzaPlugin, 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
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)
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 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 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 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',))
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 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 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 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'))
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0004.py 0000664 0000000 0000000 00000025211 11577753404 0022552 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
import copy
from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.message import Message
log = logging.getLogger(__name__)
class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values'))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
def __init__(self, *args, **kwargs):
title = None
if 'title' in kwargs:
title = kwargs['title']
del kwargs['title']
ElementBase.__init__(self, *args, **kwargs)
if title is not None:
self['title'] = title
self.field = FieldAccessor(self)
def setup(self, xml=None):
if ElementBase.setup(self, xml): #if we had to generate xml
self['type'] = 'form'
def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs):
kwtype = kwargs.get('type', None)
if kwtype is None:
kwtype = ftype
field = FormField(parent=self)
field['var'] = var
field['type'] = kwtype
field['label'] = label
field['desc'] = desc
field['required'] = required
field['value'] = value
if options is not None:
field['options'] = options
return field
def getXML(self, type='submit'):
self['type'] = type
log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
return self.xml
def fromXML(self, xml):
log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
n = Form(xml=xml)
return n
def addItem(self, values):
itemXML = ET.Element('{%s}item' % self.namespace)
self.xml.append(itemXML)
reported_vars = self['reported'].keys()
for var in reported_vars:
fieldXML = ET.Element('{%s}field' % FormField.namespace)
itemXML.append(fieldXML)
field = FormField(xml=fieldXML)
field['var'] = var
field['value'] = values.get(var, None)
def addReported(self, var, ftype=None, label='', desc='', **kwargs):
kwtype = kwargs.get('type', None)
if kwtype is None:
kwtype = ftype
reported = self.xml.find('{%s}reported' % self.namespace)
if reported is None:
reported = ET.Element('{%s}reported' % self.namespace)
self.xml.append(reported)
fieldXML = ET.Element('{%s}field' % FormField.namespace)
reported.append(fieldXML)
field = FormField(xml=fieldXML)
field['var'] = var
field['type'] = kwtype
field['label'] = label
field['desc'] = desc
return field
def cancel(self):
self['type'] = 'cancel'
def delFields(self):
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
for fieldXML in fieldsXML:
self.xml.remove(fieldXML)
def delInstructions(self):
instsXML = self.xml.findall('{%s}instructions')
for instXML in instsXML:
self.xml.remove(instXML)
def delItems(self):
itemsXML = self.xml.find('{%s}item' % self.namespace)
for itemXML in itemsXML:
self.xml.remove(itemXML)
def delReported(self):
reportedXML = self.xml.find('{%s}reported' % self.namespace)
if reportedXML is not None:
self.xml.remove(reportedXML)
def getFields(self, use_dict=False):
fields = {} if use_dict else []
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
for fieldXML in fieldsXML:
field = FormField(xml=fieldXML)
if use_dict:
fields[field['var']] = field
else:
fields.append((field['var'], field))
return fields
def getInstructions(self):
instructions = ''
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
return "\n".join([instXML.text for instXML in instsXML])
def getItems(self):
items = []
itemsXML = self.xml.findall('{%s}item' % self.namespace)
for itemXML in itemsXML:
item = {}
fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
for fieldXML in fieldsXML:
field = FormField(xml=fieldXML)
item[field['var']] = field['value']
items.append(item)
return items
def getReported(self):
fields = {}
fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
FormField.namespace))
for fieldXML in fieldsXML:
field = FormField(xml=fieldXML)
fields[field['var']] = field
return fields
def getValues(self):
values = {}
fields = self.getFields(use_dict=True)
for var in fields:
values[var] = fields[var]['value']
return values
def reply(self):
if self['type'] == 'form':
self['type'] = 'submit'
elif self['type'] == 'submit':
self['type'] = 'result'
def setFields(self, fields, default=None):
del self['fields']
for field_data in fields:
var = field_data[0]
field = field_data[1]
field['var'] = var
self.addField(**field)
def setInstructions(self, instructions):
del self['instructions']
if instructions in [None, '']:
return
instructions = instructions.split('\n')
for instruction in instructions:
inst = ET.Element('{%s}instructions' % self.namespace)
inst.text = instruction
self.xml.append(inst)
def setItems(self, items):
for item in items:
self.addItem(item)
def setReported(self, reported, default=None):
for var in reported:
field = reported[var]
field['var'] = var
self.addReported(var, **field)
def setValues(self, values):
fields = self.getFields(use_dict=True)
for field in values:
fields[field]['value'] = values[field]
def merge(self, other):
new = copy.copy(self)
if type(other) == dict:
new.setValues(other)
return new
nfields = new.getFields(use_dict=True)
ofields = other.getFields(use_dict=True)
nfields.update(ofields)
new.setFields([(x, nfields[x]) for x in nfields])
return new
class FieldAccessor(object):
def __init__(self, form):
self.form = form
def __getitem__(self, key):
return self.form.getFields(use_dict=True)[key]
def __contains__(self, key):
return key in self.form.getFields(use_dict=True)
def has_key(self, key):
return key in self.form.getFields(use_dict=True)
class FormField(ElementBase):
namespace = 'jabber:x:data'
name = 'field'
plugin_attrib = 'field'
interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var'))
sub_interfaces = set(('desc',))
field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
'list-single', 'text-multi', 'text-private', 'text-single'))
multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi'))
multi_line_types = set(('hidden', 'text-multi'))
option_types = set(('list-multi', 'list-single'))
true_values = set((True, '1', 'true'))
def addOption(self, label='', value=''):
if self['type'] in self.option_types:
opt = FieldOption(parent=self)
opt['label'] = label
opt['value'] = value
else:
raise ValueError("Cannot add options to a %s field." % self['type'])
def delOptions(self):
optsXML = self.xml.findall('{%s}option' % self.namespace)
for optXML in optsXML:
self.xml.remove(optXML)
def delRequired(self):
reqXML = self.xml.find('{%s}required' % self.namespace)
if reqXML is not None:
self.xml.remove(reqXML)
def delValue(self):
valsXML = self.xml.findall('{%s}value' % self.namespace)
for valXML in valsXML:
self.xml.remove(valXML)
def getAnswer(self):
return self.getValue()
def getOptions(self):
options = []
optsXML = self.xml.findall('{%s}option' % self.namespace)
for optXML in optsXML:
opt = FieldOption(xml=optXML)
options.append({'label': opt['label'], 'value':opt['value']})
return options
def getRequired(self):
reqXML = self.xml.find('{%s}required' % self.namespace)
return reqXML is not None
def getValue(self):
valsXML = self.xml.findall('{%s}value' % self.namespace)
if len(valsXML) == 0:
return None
elif self['type'] == 'boolean':
return valsXML[0].text in self.true_values
elif self['type'] in self.multi_value_types:
values = []
for valXML in valsXML:
if valXML.text is None:
valXML.text = ''
values.append(valXML.text)
if self['type'] == 'text-multi':
values = "\n".join(values)
return values
else:
return valsXML[0].text
def setAnswer(self, answer):
self.setValue(answer)
def setFalse(self):
self.setValue(False)
def setOptions(self, options):
for value in options:
if isinstance(value, dict):
self.addOption(**value)
else:
self.addOption(value=value)
def setRequired(self, required):
exists = self.getRequired()
if not exists and required:
self.xml.append(ET.Element('{%s}required' % self.namespace))
elif exists and not required:
self.delRequired()
def setTrue(self):
self.setValue(True)
def setValue(self, value):
self.delValue()
valXMLName = '{%s}value' % self.namespace
if self['type'] == 'boolean':
if value in self.true_values:
valXML = ET.Element(valXMLName)
valXML.text = '1'
self.xml.append(valXML)
else:
valXML = ET.Element(valXMLName)
valXML.text = '0'
self.xml.append(valXML)
elif self['type'] in self.multi_value_types or self['type'] in ['', None]:
if self['type'] in self.multi_line_types and isinstance(value, str):
value = value.split('\n')
if not isinstance(value, list):
value = [value]
for val in value:
if self['type'] in ['', None] and val in self.true_values:
val = '1'
valXML = ET.Element(valXMLName)
valXML.text = val
self.xml.append(valXML)
else:
if isinstance(value, list):
raise ValueError("Cannot add multiple values to a %s field." % self['type'])
valXML = ET.Element(valXMLName)
valXML.text = value
self.xml.append(valXML)
class FieldOption(ElementBase):
namespace = 'jabber:x:data'
name = 'option'
plugin_attrib = 'option'
interfaces = set(('label', 'value'))
sub_interfaces = set(('value',))
class xep_0004(base.base_plugin):
"""
XEP-0004: Data Forms
"""
def plugin_init(self):
self.xep = '0004'
self.description = 'Data Forms'
self.xmpp.registerHandler(
Callback('Data Form',
MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns,
Form.namespace)),
self.handle_form))
registerStanzaPlugin(FormField, FieldOption)
registerStanzaPlugin(Form, FormField)
registerStanzaPlugin(Message, Form)
def makeForm(self, ftype='form', title='', instructions=''):
f = Form()
f['type'] = ftype
f['title'] = title
f['instructions'] = instructions
return f
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
def handle_form(self, message):
self.xmpp.event("message_xform", message)
def buildForm(self, xml):
return Form(xml=xml)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/ 0000775 0000000 0000000 00000000000 11577753404 0022204 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/__init__.py 0000664 0000000 0000000 00000000575 11577753404 0024324 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/binding.py 0000664 0000000 0000000 00000012224 11577753404 0024171 0 ustar 00root root 0000000 0000000 """
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) #...
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()
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/remote.py 0000664 0000000 0000000 00000061253 11577753404 0024060 0 ustar 00root root 0000000 0000000 """
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 '.'.
'''
__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 self._entries.has_key(key):
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/rpc.py 0000664 0000000 0000000 00000021362 11577753404 0023346 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/stanza/ 0000775 0000000 0000000 00000000000 11577753404 0023504 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/stanza/RPC.py 0000664 0000000 0000000 00000003147 11577753404 0024507 0 ustar 00root root 0000000 0000000 """
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0009/stanza/__init__.py 0000664 0000000 0000000 00000000437 11577753404 0025621 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0012.py 0000664 0000000 0000000 00000010053 11577753404 0022547 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/ 0000775 0000000 0000000 00000000000 11577753404 0022176 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/__init__.py 0000664 0000000 0000000 00000000643 11577753404 0024312 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/disco.py 0000664 0000000 0000000 00000057700 11577753404 0023662 0 ustar 00root root 0000000 0000000 """
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 .
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/stanza/ 0000775 0000000 0000000 00000000000 11577753404 0023476 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/stanza/__init__.py 0000664 0000000 0000000 00000000475 11577753404 0025615 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/stanza/info.py 0000664 0000000 0000000 00000023277 11577753404 0025016 0 ustar 00root root 0000000 0000000 """
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 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
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:
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/stanza/items.py 0000664 0000000 0000000 00000010165 11577753404 0025174 0 ustar 00root root 0000000 0000000 """
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:
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0030/static.py 0000664 0000000 0000000 00000021451 11577753404 0024042 0 ustar 00root root 0000000 0000000 """
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))
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0033.py 0000664 0000000 0000000 00000010201 11577753404 0022545 0 ustar 00root root 0000000 0000000 """
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0045.py 0000664 0000000 0000000 00000031206 11577753404 0022560 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
from .. xmlstream.stanzabase import registerStanzaPlugin, 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(('', ))
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
class xep_0045(base.base_plugin):
"""
Implements 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
registerStanzaPlugin(Presence, MUCPresence)
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_message))
self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("" % 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 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 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()
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0050/ 0000775 0000000 0000000 00000000000 11577753404 0022200 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0050/__init__.py 0000664 0000000 0000000 00000000455 11577753404 0024315 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0050/adhoc.py 0000664 0000000 0000000 00000052706 11577753404 0023642 0 ustar 00root root 0000000 0000000 """
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
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0050/stanza.py 0000664 0000000 0000000 00000014131 11577753404 0024052 0 ustar 00root root 0000000 0000000 """
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
Example command stanzas:
Information!
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0059/ 0000775 0000000 0000000 00000000000 11577753404 0022211 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0059/__init__.py 0000664 0000000 0000000 00000000476 11577753404 0024331 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0059/rsm.py 0000664 0000000 0000000 00000006766 11577753404 0023403 0 ustar 00root root 0000000 0000000 """
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0059/stanza.py 0000664 0000000 0000000 00000007404 11577753404 0024070 0 ustar 00root root 0000000 0000000 """
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:
2conference.example.compubsub.example.com
Stanza Interface:
first_index -- The index attribute of
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 and
creates the element if it doesn't exist
get_first_index -- Returns the value of the index
attribute for
del_first_index -- Removes the index attribute for
but keeps the element
set_before -- Sets the value of , if the value is True
then the element will be created without a value
get_before -- Returns the value of , 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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0060.py 0000664 0000000 0000000 00000024626 11577753404 0022565 0 ustar 00root root 0000000 0000000 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 . import stanza_pubsub
from . 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("" % 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, '')
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0078.py 0000664 0000000 0000000 00000004352 11577753404 0022570 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement
from xml.etree import cElementTree as ET
import logging
import hashlib
from . import base
log = logging.getLogger(__name__)
class xep_0078(base.base_plugin):
"""
XEP-0078 NON-SASL Authentication
"""
def plugin_init(self):
self.description = "Non-SASL Authentication (broken)"
self.xep = "0078"
self.xmpp.add_event_handler("session_start", self.check_stream)
#disabling until I fix conflict with PLAIN
#self.xmpp.registerFeature("", self.auth)
self.streamid = ''
def check_stream(self, xml):
self.streamid = xml.attrib['id']
if xml.get('version', '0') != '1.0':
self.auth()
def auth(self, xml=None):
log.debug("Starting jabber:iq:auth Authentication")
auth_request = self.xmpp.makeIqGet()
auth_request_query = ET.Element('{jabber:iq:auth}query')
auth_request.attrib['to'] = self.xmpp.boundjid.host
username = ET.Element('username')
username.text = self.xmpp.username
auth_request_query.append(username)
auth_request.append(auth_request_query)
result = auth_request.send()
rquery = result.find('{jabber:iq:auth}query')
attempt = self.xmpp.makeIqSet()
query = ET.Element('{jabber:iq:auth}query')
resource = ET.Element('resource')
resource.text = self.xmpp.resource
query.append(username)
query.append(resource)
if rquery.find('{jabber:iq:auth}digest') is None:
log.warning("Authenticating via jabber:iq:auth Plain.")
password = ET.Element('password')
password.text = self.xmpp.password
query.append(password)
else:
log.debug("Authenticating via jabber:iq:auth Digest")
digest = ET.Element('digest')
digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest()
query.append(digest)
attempt.append(query)
result = attempt.send()
if result.attrib['type'] == 'result':
with self.xmpp.lock:
self.xmpp.authenticated = True
self.xmpp.sessionstarted = True
self.xmpp.event("session_start")
else:
log.info("Authentication failed")
self.xmpp.disconnect()
self.xmpp.event("failed_auth")
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0085/ 0000775 0000000 0000000 00000000000 11577753404 0022210 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0085/__init__.py 0000664 0000000 0000000 00000000463 11577753404 0024324 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0085/chat_states.py 0000664 0000000 0000000 00000002622 11577753404 0025066 0 ustar 00root root 0000000 0000000 """
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0085/stanza.py 0000664 0000000 0000000 00000003623 11577753404 0024066 0 ustar 00root root 0000000 0000000 """
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:
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)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0086/ 0000775 0000000 0000000 00000000000 11577753404 0022211 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0086/__init__.py 0000664 0000000 0000000 00000000470 11577753404 0024323 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0086/legacy_error.py 0000664 0000000 0000000 00000002666 11577753404 0025252 0 ustar 00root root 0000000 0000000 """
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 .
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))
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0086/stanza.py 0000664 0000000 0000000 00000006332 11577753404 0024067 0 ustar 00root root 0000000 0000000 """
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 .
Example legacy error stanzas:
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]
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0092/ 0000775 0000000 0000000 00000000000 11577753404 0022206 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0092/__init__.py 0000664 0000000 0000000 00000000535 11577753404 0024322 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0092/stanza.py 0000664 0000000 0000000 00000002145 11577753404 0024062 0 ustar 00root root 0000000 0000000 """
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:
SleekXMPP1.0Linux
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0092/version.py 0000664 0000000 0000000 00000004475 11577753404 0024257 0 ustar 00root root 0000000 0000000 """
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', '0.1-dev')
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0128/ 0000775 0000000 0000000 00000000000 11577753404 0022206 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0128/__init__.py 0000664 0000000 0000000 00000000502 11577753404 0024314 0 ustar 00root root 0000000 0000000 """
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
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0128/extended_disco.py 0000664 0000000 0000000 00000006450 11577753404 0025546 0 ustar 00root root 0000000 0000000 """
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.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0004 import Form
from sleekxmpp.plugins.xep_0030 import DiscoInfo
from sleekxmpp.plugins.xep_0128 import StaticExtendedDisco
class xep_0128(base_plugin):
"""
XEP-0128: Service Discovery Extensions
Allow the use of data forms to add additional identity
information to disco#info results.
Also see .
Attributes:
disco -- A reference to the XEP-0030 plugin.
static -- Object containing the default set of static
node handlers.
xmpp -- The main SleekXMPP object.
Methods:
set_extended_info -- Set extensions to a disco#info result.
add_extended_info -- Add an extension to a disco#info result.
del_extended_info -- Remove all extensions from a disco#info result.
"""
def plugin_init(self):
"""Start the XEP-0128 plugin."""
self.xep = '0128'
self.description = 'Service Discovery Extensions'
self._disco_ops = ['set_extended_info',
'add_extended_info',
'del_extended_info']
register_stanza_plugin(DiscoInfo, Form, iterable=True)
def post_init(self):
"""Handle cross-plugin dependencies."""
base_plugin.post_init(self)
self.disco = self.xmpp['xep_0030']
self.static = StaticExtendedDisco(self.disco.static)
self.disco.set_extended_info = self.set_extended_info
self.disco.add_extended_info = self.add_extended_info
self.disco.del_extended_info = self.del_extended_info
for op in self._disco_ops:
self.disco._add_disco_op(op, getattr(self.static, op))
def set_extended_info(self, jid=None, node=None, **kwargs):
"""
Set additional, extended identity information to a node.
Replaces any existing extended information.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
data -- Either a form, or a list of forms to use
as extended information, replacing any
existing extensions.
"""
self.disco._run_node_handler('set_extended_info', jid, node, kwargs)
def add_extended_info(self, jid=None, node=None, **kwargs):
"""
Add additional, extended identity information to a node.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
data -- Either a form, or a list of forms to add
as extended information.
"""
self.disco._run_node_handler('add_extended_info', jid, node, kwargs)
def del_extended_info(self, jid=None, node=None, **kwargs):
"""
Remove all extended identity information to a node.
Arguments:
jid -- The JID to modify.
node -- The node to modify.
"""
self.disco._run_node_handler('del_extended_info', jid, node, kwargs)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0128/static.py 0000664 0000000 0000000 00000003636 11577753404 0024057 0 ustar 00root root 0000000 0000000 """
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.plugins.xep_0030 import StaticDisco
log = logging.getLogger(__name__)
class StaticExtendedDisco(object):
"""
Extend the default StaticDisco implementation to provide
support for extended identity information.
"""
def __init__(self, static):
"""
Augment the default XEP-0030 static handler object.
Arguments:
static -- The default static XEP-0030 handler object.
"""
self.static = static
def set_extended_info(self, jid, node, data):
"""
Replace the extended identity data for a JID/node combination.
The data parameter may provide:
data -- Either a single data form, or a list of data forms.
"""
self.del_extended_info(jid, node, data)
self.add_extended_info(jid, node, data)
def add_extended_info(self, jid, node, data):
"""
Add additional extended identity data for a JID/node combination.
The data parameter may provide:
data -- Either a single data form, or a list of data forms.
"""
self.static.add_node(jid, node)
forms = data.get('data', [])
if not isinstance(forms, list):
forms = [forms]
for form in forms:
self.static.nodes[(jid, node)]['info'].append(form)
def del_extended_info(self, jid, node, data):
"""
Replace the extended identity data for a JID/node combination.
The data parameter is not used.
"""
if (jid, node) not in self.static.nodes:
return
info = self.static.nodes[(jid, node)]['info']
for form in info['substanza']:
info.xml.remove(form.xml)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0199/ 0000775 0000000 0000000 00000000000 11577753404 0022216 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0199/__init__.py 0000664 0000000 0000000 00000000427 11577753404 0024332 0 ustar 00root root 0000000 0000000 """
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.plugins.xep_0199.stanza import Ping
from sleekxmpp.plugins.xep_0199.ping import xep_0199
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0199/ping.py 0000664 0000000 0000000 00000012252 11577753404 0023527 0 ustar 00root root 0000000 0000000 """
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 time
import logging
import sleekxmpp
from sleekxmpp import Iq
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0199 import stanza, Ping
log = logging.getLogger(__name__)
class xep_0199(base_plugin):
"""
XEP-0199: XMPP Ping
Given that XMPP is based on TCP connections, it is possible for the
underlying connection to be terminated without the application's
awareness. Ping stanzas provide an alternative to whitespace based
keepalive methods for detecting lost connections.
Also see .
Attributes:
keepalive -- If True, periodically send ping requests
to the server. If a ping is not answered,
the connection will be reset.
frequency -- Time in seconds between keepalive pings.
Defaults to 300 seconds.
timeout -- Time in seconds to wait for a ping response.
Defaults to 30 seconds.
Methods:
send_ping -- Send a ping to a given JID, returning the
round trip time.
"""
def plugin_init(self):
"""
Start the XEP-0199 plugin.
"""
self.description = 'XMPP Ping'
self.xep = '0199'
self.stanza = stanza
self.keepalive = self.config.get('keepalive', False)
self.frequency = float(self.config.get('frequency', 300))
self.timeout = self.config.get('timeout', 30)
register_stanza_plugin(Iq, Ping)
self.xmpp.register_handler(
Callback('Ping',
StanzaPath('iq@type=get/ping'),
self._handle_ping))
if self.keepalive:
self.xmpp.add_event_handler('session_start',
self._handle_keepalive,
threaded=True)
def post_init(self):
"""Handle cross-plugin dependencies."""
base_plugin.post_init(self)
self.xmpp['xep_0030'].add_feature(Ping.namespace)
def _handle_keepalive(self, event):
"""
Begin periodic pinging of the server. If a ping is not
answered, the connection will be restarted.
The pinging interval can be adjused using self.frequency
before beginning processing.
Arguments:
event -- The session_start event.
"""
def scheduled_ping():
"""Send ping request to the server."""
log.debug("Pinging...")
resp = self.send_ping(self.xmpp.boundjid.host, self.timeout)
if resp is None or resp is False:
log.debug("Did not recieve ping back in time." + \
"Requesting Reconnect.")
self.xmpp.reconnect()
self.xmpp.schedule('Ping Keep Alive',
self.frequency,
scheduled_ping,
repeat=True)
def _handle_ping(self, iq):
"""
Automatically reply to ping requests.
Arguments:
iq -- The ping request.
"""
log.debug("Pinged by %s" % iq['from'])
iq.reply().enable('ping').send()
def send_ping(self, jid, timeout=None, errorfalse=False,
ifrom=None, block=True, callback=None):
"""
Send a ping request and calculate the response time.
Arguments:
jid -- The JID that will receive the ping.
timeout -- Time in seconds to wait for a response.
Defaults to self.timeout.
errorfalse -- Indicates if False should be returned
if an error stanza is received. Defaults
to False.
ifrom -- Specifiy the sender JID.
block -- Indicate if execution should block until
a pong response is received. Defaults
to True.
callback -- Optional handler to execute when a pong
is received. Useful in conjunction with
the option block=False.
"""
log.debug("Pinging %s" % jid)
if timeout is None:
timeout = self.timeout
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = jid
if ifrom:
iq['from'] = ifrom
iq.enable('ping')
start_time = time.clock()
resp = iq.send(block=block,
timeout=timeout,
callback=callback)
end_time = time.clock()
delay = end_time - start_time
if not block:
return None
if not resp or resp['type'] == 'error':
return False
log.debug("Pong: %s %f" % (jid, delay))
return delay
# Backwards compatibility for names
xep_0199.sendPing = xep_0199.send_ping
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0199/stanza.py 0000664 0000000 0000000 00000001457 11577753404 0024077 0 ustar 00root root 0000000 0000000 """
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 sleekxmpp
from sleekxmpp.xmlstream import ElementBase
class Ping(ElementBase):
"""
Given that XMPP is based on TCP connections, it is possible for the
underlying connection to be terminated without the application's
awareness. Ping stanzas provide an alternative to whitespace based
keepalive methods for detecting lost connections.
Example ping stanza:
Stanza Interface:
None
Methods:
None
"""
name = 'ping'
namespace = 'urn:xmpp:ping'
plugin_attrib = 'ping'
interfaces = set()
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0202.py 0000664 0000000 0000000 00000010165 11577753404 0022554 0 ustar 00root root 0000000 0000000 """
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, tzinfo
import logging
import time
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 EntityTime(ElementBase):
name = 'time'
namespace = 'urn:xmpp:time'
plugin_attrib = 'entity_time'
interfaces = set(('tzo', 'utc'))
sub_interfaces = set(('tzo', 'utc'))
#def get_tzo(self):
# TODO: Right now it returns a string but maybe it should
# return a datetime.tzinfo object or maybe a datetime.timedelta?
#pass
def set_tzo(self, tzo):
if isinstance(tzo, tzinfo):
td = datetime.now(tzo).utcoffset() # What if we are faking the time? datetime.now() shouldn't be used here'
seconds = td.seconds + td.days * 24 * 3600
sign = ('+' if seconds >= 0 else '-')
minutes = abs(seconds // 60)
tzo = '{sign}{hours:02d}:{minutes:02d}'.format(sign=sign, hours=minutes//60, minutes=minutes%60)
elif not isinstance(tzo, str):
raise TypeError('The time should be a string or a datetime.tzinfo object.')
self._set_sub_text('tzo', tzo)
def get_utc(self):
# Returns a datetime object instead the string. Is this a good idea?
value = self._get_sub_text('utc')
if '.' in value:
return datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ')
else:
return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ')
def set_utc(self, tim=None):
if isinstance(tim, datetime):
if tim.utcoffset():
tim = tim - tim.utcoffset()
tim = tim.strftime('%Y-%m-%dT%H:%M:%SZ')
elif isinstance(tim, time.struct_time):
tim = time.strftime('%Y-%m-%dT%H:%M:%SZ', tim)
elif not isinstance(tim, str):
raise TypeError('The time should be a string or a datetime.datetime or time.struct_time object.')
self._set_sub_text('utc', tim)
class xep_0202(base.base_plugin):
"""
XEP-0202 Entity Time
"""
def plugin_init(self):
self.description = "Entity Time"
self.xep = "0202"
self.xmpp.registerHandler(
Callback('Time Request',
MatchXPath('{%s}iq/{%s}time' % (self.xmpp.default_ns,
EntityTime.namespace)),
self.handle_entity_time_query))
register_stanza_plugin(Iq, EntityTime)
self.xmpp.add_event_handler('entity_time_request', self.handle_entity_time)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:time')
def handle_entity_time_query(self, iq):
if iq['type'] == 'get':
log.debug("Entity time requested by %s" % iq['from'])
self.xmpp.event('entity_time_request', iq)
elif iq['type'] == 'result':
log.debug("Entity time result from %s" % iq['from'])
self.xmpp.event('entity_time', iq)
def handle_entity_time(self, iq):
iq = iq.reply()
iq.enable('entity_time')
tzo = time.strftime('%z') # %z is not on all ANSI C libraries
tzo = tzo[:3] + ':' + tzo[3:]
iq['entity_time']['tzo'] = tzo
iq['entity_time']['utc'] = datetime.utcnow()
iq.send()
def get_entity_time(self, jid):
iq = self.xmpp.makeIqGet()
iq.enable('entity_time')
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 {'utc': result['entity_time']['utc'], 'tzo': result['entity_time']['tzo']}
else:
return False
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0249/ 0000775 0000000 0000000 00000000000 11577753404 0022212 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0249/__init__.py 0000664 0000000 0000000 00000000442 11577753404 0024323 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dalek
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0249.stanza import Invite
from sleekxmpp.plugins.xep_0249.invite import xep_0249
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0249/invite.py 0000664 0000000 0000000 00000004601 11577753404 0024063 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dalek
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
import sleekxmpp
from sleekxmpp import Message
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.xep_0249 import Invite
log = logging.getLogger(__name__)
class xep_0249(base_plugin):
"""
XEP-0249: Direct MUC Invitations
"""
def plugin_init(self):
self.xep = "0249"
self.description = "Direct MUC Invitations"
self.stanza = sleekxmpp.plugins.xep_0249.stanza
self.xmpp.register_handler(
Callback('Direct MUC Invitations',
StanzaPath('message/groupchat_invite'),
self._handle_invite))
register_stanza_plugin(Message, Invite)
def post_init(self):
base_plugin.post_init(self)
self.xmpp['xep_0030'].add_feature(Invite.namespace)
def _handle_invite(self, msg):
"""
Raise an event for all invitations received.
"""
log.debug("Received direct muc invitation from %s to room %s",
msg['from'], msg['groupchat_invite']['jid'])
self.xmpp.event('groupchat_direct_invite', msg)
def send_invitation(self, jid, roomjid, password=None,
reason=None, ifrom=None):
"""
Send a direct MUC invitation to an XMPP entity.
Arguments:
jid -- The JID of the entity that will receive
the invitation
roomjid -- the address of the groupchat room to be joined
password -- a password needed for entry into a
password-protected room (OPTIONAL).
reason -- a human-readable purpose for the invitation
(OPTIONAL).
"""
msg = self.xmpp.Message()
msg['to'] = jid
if ifrom is not None:
msg['from'] = ifrom
msg['groupchat_invite']['jid'] = roomjid
if password is not None:
msg['groupchat_invite']['password'] = password
if reason is not None:
msg['groupchat_invite']['reason'] = reason
return msg.send()
fritzy-SleekXMPP-5c4ee57/sleekxmpp/plugins/xep_0249/stanza.py 0000664 0000000 0000000 00000002172 11577753404 0024066 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Dalek
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
class Invite(ElementBase):
"""
XMPP allows for an agent in an MUC room to directly invite another
user to join the chat room (as opposed to a mediated invitation
done through the server).
Example invite stanza:
Stanza Interface:
jid -- The JID of the groupchat room
password -- The password used to gain entry in the room
(optional)
reason -- The reason for the invitation (optional)
"""
name = "x"
namespace = "jabber:x:conference"
plugin_attrib = "groupchat_invite"
interfaces = ("jid", "password", "reason")
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/ 0000775 0000000 0000000 00000000000 11577753404 0020557 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/__init__.py 0000664 0000000 0000000 00000000617 11577753404 0022674 0 ustar 00root root 0000000 0000000 """
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.stanza.error import Error
from sleekxmpp.stanza.stream_error import StreamError
from sleekxmpp.stanza.iq import Iq
from sleekxmpp.stanza.message import Message
from sleekxmpp.stanza.presence import Presence
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/atom.py 0000664 0000000 0000000 00000001146 11577753404 0022073 0 ustar 00root root 0000000 0000000 """
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
class AtomEntry(ElementBase):
"""
A simple Atom feed entry.
Stanza Interface:
title -- The title of the Atom feed entry.
summary -- The summary of the Atom feed entry.
"""
namespace = 'http://www.w3.org/2005/Atom'
name = 'entry'
plugin_attrib = 'entry'
interfaces = set(('title', 'summary'))
sub_interfaces = set(('title', 'summary'))
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/error.py 0000664 0000000 0000000 00000012136 11577753404 0022265 0 ustar 00root root 0000000 0000000 """
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, register_stanza_plugin
class Error(ElementBase):
"""
XMPP stanzas of type 'error' should include an stanza that
describes the nature of the error and how it should be handled.
Use the 'XEP-0086: Error Condition Mappings' plugin to include error
codes used in older XMPP versions.
Example error stanza:
The item was not found.
Stanza Interface:
code -- The error code used in older XMPP versions.
condition -- The name of the condition element.
text -- Human readable description of the error.
type -- Error type indicating how the error should be handled.
Attributes:
conditions -- The set of allowable error condition elements.
condition_ns -- The namespace for the condition element.
types -- A set of values indicating how the error
should be treated.
Methods:
setup -- Overrides ElementBase.setup.
get_condition -- Retrieve the name of the condition element.
set_condition -- Add a condition element.
del_condition -- Remove the condition element.
get_text -- Retrieve the contents of the element.
set_text -- Set the contents of the element.
del_text -- Remove the element.
"""
namespace = 'jabber:client'
name = 'error'
plugin_attrib = 'error'
interfaces = set(('code', 'condition', 'text', 'type'))
sub_interfaces = set(('text',))
conditions = set(('bad-request', 'conflict', 'feature-not-implemented',
'forbidden', 'gone', 'internal-server-error',
'item-not-found', 'jid-malformed', 'not-acceptable',
'not-allowed', 'not-authorized', 'payment-required',
'recipient-unavailable', 'redirect',
'registration-required', 'remote-server-not-found',
'remote-server-timeout', 'resource-constraint',
'service-unavailable', 'subscription-required',
'undefined-condition', 'unexpected-request'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
def setup(self, xml=None):
"""
Populate the stanza object using an optional XML object.
Overrides ElementBase.setup.
Sets a default error type and condition, and changes the
parent stanza's type to 'error'.
Arguments:
xml -- Use an existing XML object for the stanza's values.
"""
if ElementBase.setup(self, xml):
#If we had to generate XML then set default values.
self['type'] = 'cancel'
self['condition'] = 'feature-not-implemented'
if self.parent is not None:
self.parent()['type'] = 'error'
def get_condition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren():
if "{%s}" % self.condition_ns in child.tag:
return child.tag.split('}', 1)[-1]
return ''
def 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.condition_ns, 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
def get_text(self):
"""Retrieve the contents of the element."""
return self._get_sub_text('{%s}text' % self.condition_ns)
def set_text(self, value):
"""
Set the contents of the element.
Arguments:
value -- The new contents for the element.
"""
self._set_sub_text('{%s}text' % self.condition_ns, text=value)
return self
def del_text(self):
"""Remove the element."""
self._del_sub('{%s}text' % self.condition_ns)
return self
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
Error.getCondition = Error.get_condition
Error.setCondition = Error.set_condition
Error.delCondition = Error.del_condition
Error.getText = Error.get_text
Error.setText = Error.set_text
Error.delText = Error.del_text
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/htmlim.py 0000664 0000000 0000000 00000005166 11577753404 0022433 0 ustar 00root root 0000000 0000000 """
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.stanza import Message
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class HTMLIM(ElementBase):
"""
XEP-0071: XHTML-IM defines a method for embedding XHTML content
within a stanza so that lightweight markup can be used
to format the message contents and to create links.
Only a subset of XHTML is recommended for use with XHTML-IM.
See the full spec at 'http://xmpp.org/extensions/xep-0071.html'
for more information.
Example stanza:
Non-html message content.
HTML!
Stanza Interface:
body -- The contents of the HTML body tag.
Methods:
setup -- Overrides ElementBase.setup.
get_body -- Return the HTML body contents.
set_body -- Set the HTML body contents.
del_body -- Remove the HTML body contents.
"""
namespace = 'http://jabber.org/protocol/xhtml-im'
name = 'html'
interfaces = set(('body',))
plugin_attrib = name
def set_body(self, html):
"""
Set the contents of the HTML body.
Arguments:
html -- Either a string or XML object. If the top level
element is not with a namespace of
'http://www.w3.org/1999/xhtml', it will be wrapped.
"""
if isinstance(html, str):
html = ET.XML(html)
if html.tag != '{http://www.w3.org/1999/xhtml}body':
body = ET.Element('{http://www.w3.org/1999/xhtml}body')
body.append(html)
self.xml.append(body)
else:
self.xml.append(html)
def get_body(self):
"""Return the contents of the HTML body."""
html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
if html is None:
return ''
return html
def del_body(self):
"""Remove the HTML body contents."""
if self.parent is not None:
self.parent().xml.remove(self.xml)
register_stanza_plugin(Message, HTMLIM)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
HTMLIM.setBody = HTMLIM.set_body
HTMLIM.getBody = HTMLIM.get_body
HTMLIM.delBody = HTMLIM.del_body
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/iq.py 0000664 0000000 0000000 00000020263 11577753404 0021545 0 ustar 00root root 0000000 0000000 """
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.stanza import Error
from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import StanzaBase, ET
from sleekxmpp.xmlstream.handler import Waiter, Callback
from sleekxmpp.xmlstream.matcher import MatcherId
class Iq(RootStanza):
"""
XMPP stanzas, or info/query stanzas, are XMPP's method of
requesting and modifying information, similar to HTTP's GET and
POST methods.
Each stanza must have an 'id' value which associates the
stanza with the response stanza. XMPP entities must always
be given a response stanza with a type of 'result' after
sending a stanza of type 'get' or 'set'.
Most uses cases for stanzas will involve adding a
element whose namespace indicates the type of information
desired. However, some custom XMPP applications use stanzas
as a carrier stanza for an application-specific protocol instead.
Example Stanzas:
Friends
Stanza Interface:
query -- The namespace of the element if one exists.
Attributes:
types -- May be one of: get, set, result, or error.
Methods:
__init__ -- Overrides StanzaBase.__init__.
unhandled -- Send error if there are no handlers.
set_payload -- Overrides StanzaBase.set_payload.
set_query -- Add or modify a element.
get_query -- Return the namespace of the element.
del_query -- Remove the element.
reply -- Overrides StanzaBase.reply
send -- Overrides StanzaBase.send
"""
namespace = 'jabber:client'
name = 'iq'
interfaces = set(('type', 'to', 'from', 'id', 'query'))
types = set(('get', 'result', 'set', 'error'))
plugin_attrib = name
def __init__(self, *args, **kwargs):
"""
Initialize a new stanza with an 'id' value.
Overrides StanzaBase.__init__.
"""
StanzaBase.__init__(self, *args, **kwargs)
if self['id'] == '':
if self.stream is not None:
self['id'] = self.stream.new_id()
else:
self['id'] = '0'
def unhandled(self):
"""
Send a feature-not-implemented error if the stanza is not handled.
Overrides StanzaBase.unhandled.
"""
if self['type'] in ('get', 'set'):
self.reply()
self['error']['condition'] = 'feature-not-implemented'
self['error']['text'] = 'No handlers registered for this request.'
self.send()
def set_payload(self, value):
"""
Set the XML contents of the stanza.
Arguments:
value -- An XML object to use as the stanza's contents
"""
self.clear()
StanzaBase.set_payload(self, value)
return self
def set_query(self, value):
"""
Add or modify a element.
Query elements are differentiated by their namespace.
Arguments:
value -- The namespace of the element.
"""
query = self.xml.find("{%s}query" % value)
if query is None and value:
self.clear()
query = ET.Element("{%s}query" % value)
self.xml.append(query)
return self
def get_query(self):
"""Return the namespace of the element."""
for child in self.xml.getchildren():
if child.tag.endswith('query'):
ns = child.tag.split('}')[0]
if '{' in ns:
ns = ns[1:]
return ns
return ''
def del_query(self):
"""Remove the element."""
for child in self.xml.getchildren():
if child.tag.endswith('query'):
self.xml.remove(child)
return self
def reply(self, clear=True):
"""
Send a reply stanza.
Overrides StanzaBase.reply
Sets the 'type' to 'result' in addition to the default
StanzaBase.reply behavior.
Arguments:
clear -- Indicates if existing content should be
removed before replying. Defaults to True.
"""
self['type'] = 'result'
StanzaBase.reply(self, clear)
return self
def send(self, block=True, timeout=None, callback=None, now=False):
"""
Send an stanza over the XML stream.
The send call can optionally block until a response is received or
a timeout occurs. Be aware that using blocking in non-threaded event
handlers can drastically impact performance. Otherwise, a callback
handler can be provided that will be executed when the Iq stanza's
result reply is received. Be aware though that that the callback
handler will not be executed in its own thread.
Using both block and callback is not recommended, and only the
callback argument will be used in that case.
Overrides StanzaBase.send
Arguments:
block -- Specify if the send call will block until a response
is received, or a timeout occurs. Defaults to True.
timeout -- The length of time (in seconds) to wait for a response
before exiting the send call if blocking is used.
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
now -- Indicates if the send queue should be skipped and send
the stanza immediately. Used during stream
initialization. Defaults to False.
"""
if timeout is None:
timeout = self.stream.response_timeout
if callback is not None and self['type'] in ('get', 'set'):
handler_name = 'IqCallback_%s' % self['id']
handler = Callback(handler_name,
MatcherId(self['id']),
callback,
once=True)
self.stream.register_handler(handler)
StanzaBase.send(self, now=now)
return handler_name
elif block and self['type'] in ('get', 'set'):
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
self.stream.register_handler(waitfor)
StanzaBase.send(self, now=now)
return waitfor.wait(timeout)
else:
return StanzaBase.send(self, now=now)
def _set_stanza_values(self, values):
"""
Set multiple stanza interface values using a dictionary.
Stanza plugin values may be set usind nested dictionaries.
If the interface 'query' is given, then it will be set
last to avoid duplication of the element.
Overrides ElementBase._set_stanza_values.
Arguments:
values -- A dictionary mapping stanza interface with values.
Plugin interfaces may accept a nested dictionary that
will be used recursively.
"""
query = values.get('query', '')
if query:
del values['query']
StanzaBase._set_stanza_values(self, values)
self['query'] = query
else:
StanzaBase._set_stanza_values(self, values)
return self
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
Iq.setPayload = Iq.set_payload
Iq.getQuery = Iq.get_query
Iq.setQuery = Iq.set_query
Iq.delQuery = Iq.del_query
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/message.py 0000664 0000000 0000000 00000011312 11577753404 0022553 0 ustar 00root root 0000000 0000000 """
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.stanza import Error
from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import StanzaBase, ET
class Message(RootStanza):
"""
XMPP's stanzas are a "push" mechanism to send information
to other XMPP entities without requiring a response.
Chat clients will typically use stanzas that have a type
of either "chat" or "groupchat".
When handling a message event, be sure to check if the message is
an error response.
Example stanzas:
Hi!
Hi everyone!
Stanza Interface:
body -- The main contents of the message.
subject -- An optional description of the message's contents.
mucroom -- (Read-only) The name of the MUC room that sent the message.
mucnick -- (Read-only) The MUC nickname of message's sender.
Attributes:
types -- May be one of: normal, chat, headline, groupchat, or error.
Methods:
setup -- Overrides StanzaBase.setup.
chat -- Set the message type to 'chat'.
normal -- Set the message type to 'normal'.
reply -- Overrides StanzaBase.reply
get_type -- Overrides StanzaBase interface
get_mucroom -- Return the name of the MUC room of the message.
set_mucroom -- Dummy method to prevent assignment.
del_mucroom -- Dummy method to prevent deletion.
get_mucnick -- Return the MUC nickname of the message's sender.
set_mucnick -- Dummy method to prevent assignment.
del_mucnick -- Dummy method to prevent deletion.
"""
namespace = 'jabber:client'
name = 'message'
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject',
'mucroom', 'mucnick'))
sub_interfaces = set(('body', 'subject'))
plugin_attrib = name
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
def get_type(self):
"""
Return the message type.
Overrides default stanza interface behavior.
Returns 'normal' if no type attribute is present.
"""
return self._get_attr('type', 'normal')
def chat(self):
"""Set the message type to 'chat'."""
self['type'] = 'chat'
return self
def normal(self):
"""Set the message type to 'chat'."""
self['type'] = 'normal'
return self
def reply(self, body=None, clear=True):
"""
Create a message reply.
Overrides StanzaBase.reply.
Sets proper 'to' attribute if the message is from a MUC, and
adds a message body if one is given.
Arguments:
body -- Optional text content for the message.
clear -- Indicates if existing content should be removed
before replying. Defaults to True.
"""
StanzaBase.reply(self, clear)
if self['type'] == 'groupchat':
self['to'] = self['to'].bare
del self['id']
if body is not None:
self['body'] = body
return self
def get_mucroom(self):
"""
Return the name of the MUC room where the message originated.
Read-only stanza interface.
"""
if self['type'] == 'groupchat':
return self['from'].bare
else:
return ''
def get_mucnick(self):
"""
Return the nickname of the MUC user that sent the message.
Read-only stanza interface.
"""
if self['type'] == 'groupchat':
return self['from'].resource
else:
return ''
def set_mucroom(self, value):
"""Dummy method to prevent modification."""
pass
def del_mucroom(self):
"""Dummy method to prevent deletion."""
pass
def set_mucnick(self, value):
"""Dummy method to prevent modification."""
pass
def del_mucnick(self):
"""Dummy method to prevent deletion."""
pass
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
Message.getType = Message.get_type
Message.getMucroom = Message.get_mucroom
Message.setMucroom = Message.set_mucroom
Message.delMucroom = Message.del_mucroom
Message.getMucnick = Message.get_mucnick
Message.setMucnick = Message.set_mucnick
Message.delMucnick = Message.del_mucnick
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/nick.py 0000664 0000000 0000000 00000004553 11577753404 0022064 0 ustar 00root root 0000000 0000000 """
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.stanza import Message, Presence
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class Nick(ElementBase):
"""
XEP-0172: User Nickname allows the addition of a element
in several stanza types, including and stanzas.
The nickname contained in a should be the global, friendly or
informal name chosen by the owner of a bare JID. The element
may be included when establishing communications with new entities,
such as normal XMPP users or MUC services.
The nickname contained in a element will not necessarily be
the same as the nickname used in a MUC.
Example stanzas:
The User
...
The User
Stanza Interface:
nick -- A global, friendly or informal name chosen by a user.
Methods:
setup -- Overrides ElementBase.setup.
get_nick -- Return the nickname in the element.
set_nick -- Add a element with the given nickname.
del_nick -- Remove the element.
"""
namespace = 'http://jabber.org/protocol/nick'
name = 'nick'
plugin_attrib = name
interfaces = set(('nick',))
def set_nick(self, nick):
"""
Add a element with the given nickname.
Arguments:
nick -- A human readable, informal name.
"""
self.xml.text = nick
def get_nick(self):
"""Return the nickname in the element."""
return self.xml.text
def del_nick(self):
"""Remove the element."""
if self.parent is not None:
self.parent().xml.remove(self.xml)
register_stanza_plugin(Message, Nick)
register_stanza_plugin(Presence, Nick)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
Nick.setNick = Nick.set_nick
Nick.getNick = Nick.get_nick
Nick.delNick = Nick.del_nick
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/presence.py 0000664 0000000 0000000 00000013442 11577753404 0022741 0 ustar 00root root 0000000 0000000 """
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.stanza import Error
from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import StanzaBase, ET
class Presence(RootStanza):
"""
XMPP's stanza allows entities to know the status of other
clients and components. Since it is currently the only multi-cast
stanza in XMPP, many extensions add more information to
stanzas to broadcast to every entry in the roster, such as
capabilities, music choices, or locations (XEP-0115: Entity Capabilities
and XEP-0163: Personal Eventing Protocol).
Since stanzas are broadcast when an XMPP entity changes
its status, the bulk of the traffic in an XMPP network will be from
stanzas. Therefore, do not include more information than
necessary in a status message or within a stanza in order
to help keep the network running smoothly.
Example stanzas:
awayGetting lunch.5
Stanza Interface:
priority -- A value used by servers to determine message routing.
show -- The type of status, such as away or available for chat.
status -- Custom, human readable status message.
Attributes:
types -- One of: available, unavailable, error, probe,
subscribe, subscribed, unsubscribe,
and unsubscribed.
showtypes -- One of: away, chat, dnd, and xa.
Methods:
setup -- Overrides StanzaBase.setup
reply -- Overrides StanzaBase.reply
set_show -- Set the value of the element.
get_type -- Get the value of the type attribute or element.
set_type -- Set the value of the type attribute or element.
get_priority -- Get the value of the element.
set_priority -- Set the value of the element.
"""
namespace = 'jabber:client'
name = 'presence'
interfaces = set(('type', 'to', 'from', 'id', 'show',
'status', 'priority'))
sub_interfaces = set(('show', 'status', 'priority'))
plugin_attrib = name
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'chat', 'xa', 'away'))
def exception(self, e):
"""
Override exception passback for presence.
"""
pass
def set_show(self, show):
"""
Set the value of the element.
Arguments:
show -- Must be one of: away, chat, dnd, or xa.
"""
if show is None:
self._del_sub('show')
elif show in self.showtypes:
self._set_sub_text('show', text=show)
return self
def get_type(self):
"""
Return the value of the stanza's type attribute, or
the value of the element.
"""
out = self._get_attr('type')
if not out:
out = self['show']
if not out or out is None:
out = 'available'
return out
def set_type(self, value):
"""
Set the type attribute's value, and the element
if applicable.
Arguments:
value -- Must be in either self.types or self.showtypes.
"""
if value in self.types:
self['show'] = None
if value == 'available':
value = ''
self._set_attr('type', value)
elif value in self.showtypes:
self['show'] = value
return self
def del_type(self):
"""
Remove both the type attribute and the element.
"""
self._del_attr('type')
self._del_sub('show')
def set_priority(self, value):
"""
Set the entity's priority value. Some server use priority to
determine message routing behavior.
Bot clients should typically use a priority of 0 if the same
JID is used elsewhere by a human-interacting client.
Arguments:
value -- An integer value greater than or equal to 0.
"""
self._set_sub_text('priority', text=str(value))
def get_priority(self):
"""
Return the value of the element as an integer.
"""
p = self._get_sub_text('priority')
if not p:
p = 0
try:
return int(p)
except ValueError:
# The priority is not a number: we consider it 0 as a default
return 0
def reply(self, clear=True):
"""
Set the appropriate presence reply type.
Overrides StanzaBase.reply.
Arguments:
clear -- Indicates if the stanza contents should be removed
before replying. Defaults to True.
"""
if self['type'] == 'unsubscribe':
self['type'] = 'unsubscribed'
elif self['type'] == 'subscribe':
self['type'] = 'subscribed'
return StanzaBase.reply(self, clear)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
Presence.setShow = Presence.set_show
Presence.getType = Presence.get_type
Presence.setType = Presence.set_type
Presence.delType = Presence.get_type
Presence.getPriority = Presence.get_priority
Presence.setPriority = Presence.set_priority
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/rootstanza.py 0000664 0000000 0000000 00000004225 11577753404 0023340 0 ustar 00root root 0000000 0000000 """
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 traceback
import sys
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.stanza import Error
from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin
log = logging.getLogger(__name__)
class RootStanza(StanzaBase):
"""
A top-level XMPP stanza in an XMLStream.
The RootStanza class provides a more XMPP specific exception
handler than provided by the generic StanzaBase class.
Methods:
exception -- Overrides StanzaBase.exception
"""
def exception(self, e):
"""
Create and send an error reply.
Typically called when an event handler raises an exception.
The error's type and text content are based on the exception
object's type and content.
Overrides StanzaBase.exception.
Arguments:
e -- Exception object
"""
if isinstance(e, XMPPError):
self.reply(clear=e.clear)
# We raised this deliberately
self['error']['condition'] = e.condition
self['error']['text'] = e.text
if e.extension is not None:
# Extended error tag
extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension),
e.extension_args)
self['error'].append(extxml)
self['error']['type'] = e.etype
self.send()
else:
self.reply()
# We probably didn't raise this on purpose, so send an error stanza
self['error']['condition'] = 'undefined-condition'
self['error']['text'] = "SleekXMPP got into trouble."
self.send()
# log the error
log.exception('Error handling {%s}%s stanza' %
(self.namespace, self.name))
# Finally raise the exception, so it can be handled (or not)
# at a higher level by using sys.excepthook.
raise e
register_stanza_plugin(RootStanza, Error)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/roster.py 0000664 0000000 0000000 00000007511 11577753404 0022453 0 ustar 00root root 0000000 0000000 """
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.stanza import Iq
from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
class Roster(ElementBase):
"""
Example roster stanzas:
Friends
Stanza Inteface:
items -- A dictionary of roster entries contained
in the stanza.
Methods:
get_items -- Return a dictionary of roster entries.
set_items -- Add elements.
del_items -- Remove all elements.
"""
namespace = 'jabber:iq:roster'
name = 'query'
plugin_attrib = 'roster'
interfaces = set(('items',))
def set_items(self, items):
"""
Set the roster entries in the stanza.
Uses a dictionary using JIDs as keys, where each entry is itself
a dictionary that contains:
name -- An alias or nickname for the JID.
subscription -- The subscription type. Can be one of 'to',
'from', 'both', 'none', or 'remove'.
groups -- A list of group names to which the JID
has been assigned.
Arguments:
items -- A dictionary of roster entries.
"""
self.del_items()
for jid in items:
ijid = str(jid)
item = ET.Element('{jabber:iq:roster}item', {'jid': ijid})
if 'subscription' in items[jid]:
item.attrib['subscription'] = items[jid]['subscription']
if 'name' in items[jid]:
name = items[jid]['name']
if name is not None:
item.attrib['name'] = name
if 'groups' in items[jid]:
for group in items[jid]['groups']:
groupxml = ET.Element('{jabber:iq:roster}group')
groupxml.text = group
item.append(groupxml)
self.xml.append(item)
return self
def get_items(self):
"""
Return a dictionary of roster entries.
Each item is keyed using its JID, and contains:
name -- An assigned alias or nickname for the JID.
subscription -- The subscription type. Can be one of 'to',
'from', 'both', 'none', or 'remove'.
groups -- A list of group names to which the JID has
been assigned.
"""
items = {}
itemsxml = self.xml.findall('{jabber:iq:roster}item')
if itemsxml is not None:
for itemxml in itemsxml:
item = {}
item['name'] = itemxml.get('name', '')
item['subscription'] = itemxml.get('subscription', '')
item['groups'] = []
groupsxml = itemxml.findall('{jabber:iq:roster}group')
if groupsxml is not None:
for groupxml in groupsxml:
item['groups'].append(groupxml.text)
items[itemxml.get('jid')] = item
return items
def del_items(self):
"""
Remove all elements from the roster stanza.
"""
for child in self.xml.getchildren():
self.xml.remove(child)
register_stanza_plugin(Iq, Roster)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
Roster.setItems = Roster.set_items
Roster.getItems = Roster.get_items
Roster.delItems = Roster.del_items
fritzy-SleekXMPP-5c4ee57/sleekxmpp/stanza/stream_error.py 0000664 0000000 0000000 00000005174 11577753404 0023644 0 ustar 00root root 0000000 0000000 """
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.stanza.error import Error
from sleekxmpp.xmlstream import StanzaBase, ElementBase, ET
from sleekxmpp.xmlstream import register_stanza_plugin
class StreamError(Error, StanzaBase):
"""
XMPP stanzas of type 'error' should include an stanza that
describes the nature of the error and how it should be handled.
Use the 'XEP-0086: Error Condition Mappings' plugin to include error
codes used in older XMPP versions.
The stream:error stanza is used to provide more information for
error that occur with the underlying XML stream itself, and not
a particular stanza.
Note: The StreamError stanza is mostly the same as the normal
Error stanza, but with different namespaces and
condition names.
Example error stanza:
XML was not well-formed.
Stanza Interface:
condition -- The name of the condition element.
text -- Human readable description of the error.
Attributes:
conditions -- The set of allowable error condition elements.
condition_ns -- The namespace for the condition element.
Methods:
setup -- Overrides ElementBase.setup.
get_condition -- Retrieve the name of the condition element.
set_condition -- Add a condition element.
del_condition -- Remove the condition element.
get_text -- Retrieve the contents of the element.
set_text -- Set the contents of the element.
del_text -- Remove the element.
"""
namespace = 'http://etherx.jabber.org/streams'
interfaces = set(('condition', 'text'))
conditions = set((
'bad-format', 'bad-namespace-prefix', 'conflict',
'connection-timeout', 'host-gone', 'host-unknown',
'improper-addressing', 'internal-server-error', 'invalid-from',
'invalid-namespace', 'invalid-xml', 'not-authorized',
'not-well-formed', 'policy-violation', 'remote-connection-failed',
'reset', 'resource-constraint', 'restricted-xml', 'see-other-host',
'system-shutdown', 'undefined-condition', 'unsupported-encoding',
'unsupported-feature', 'unsupported-stanza-type',
'unsupported-version'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
fritzy-SleekXMPP-5c4ee57/sleekxmpp/test/ 0000775 0000000 0000000 00000000000 11577753404 0020236 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/test/__init__.py 0000664 0000000 0000000 00000000516 11577753404 0022351 0 ustar 00root root 0000000 0000000 """
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.test.mocksocket import TestSocket
from sleekxmpp.test.livesocket import TestLiveSocket
from sleekxmpp.test.sleektest import *
fritzy-SleekXMPP-5c4ee57/sleekxmpp/test/livesocket.py 0000664 0000000 0000000 00000011267 11577753404 0022767 0 ustar 00root root 0000000 0000000 """
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 socket
import threading
try:
import queue
except ImportError:
import Queue as queue
class TestLiveSocket(object):
"""
A live test socket that reads and writes to queues in
addition to an actual networking socket.
Methods:
next_sent -- Return the next sent stanza.
next_recv -- Return the next received stanza.
recv_data -- Dummy method to have same interface as TestSocket.
recv -- Read the next stanza from the socket.
send -- Write a stanza to the socket.
makefile -- Dummy call, returns self.
read -- Read the next stanza from the socket.
"""
def __init__(self, *args, **kwargs):
"""
Create a new, live test socket.
Arguments:
Same as arguments for socket.socket
"""
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.recv_buffer = []
self.recv_queue = queue.Queue()
self.send_queue = queue.Queue()
self.send_queue_lock = threading.Lock()
self.recv_queue_lock = threading.Lock()
self.is_live = True
def __getattr__(self, name):
"""
Return attribute values of internal, live socket.
Arguments:
name -- Name of the attribute requested.
"""
return getattr(self.socket, name)
# ------------------------------------------------------------------
# Testing Interface
def disconnect_errror(self):
"""
Used to simulate a socket disconnection error.
Not used by live sockets.
"""
try:
self.socket.shutdown()
self.socket.close()
except:
pass
def next_sent(self, timeout=None):
"""
Get the next stanza that has been sent.
Arguments:
timeout -- Optional timeout for waiting for a new value.
"""
args = {'block': False}
if timeout is not None:
args = {'block': True, 'timeout': timeout}
try:
return self.send_queue.get(**args)
except:
return None
def next_recv(self, timeout=None):
"""
Get the next stanza that has been received.
Arguments:
timeout -- Optional timeout for waiting for a new value.
"""
args = {'block': False}
if timeout is not None:
args = {'block': True, 'timeout': timeout}
try:
if self.recv_buffer:
return self.recv_buffer.pop(0)
else:
return self.recv_queue.get(**args)
except:
return None
def recv_data(self, data):
"""
Add data to a receive buffer for cases when more than a single stanza
was received.
"""
self.recv_buffer.append(data)
# ------------------------------------------------------------------
# Socket Interface
def recv(self, *args, **kwargs):
"""
Read data from the socket.
Store a copy in the receive queue.
Arguments:
Placeholders. Same as for socket.recv.
"""
data = self.socket.recv(*args, **kwargs)
with self.recv_queue_lock:
self.recv_queue.put(data)
return data
def send(self, data):
"""
Send data on the socket.
Store a copy in the send queue.
Arguments:
data -- String value to write.
"""
with self.send_queue_lock:
self.send_queue.put(data)
self.socket.send(data)
# ------------------------------------------------------------------
# File Socket
def makefile(self, *args, **kwargs):
"""
File socket version to use with ElementTree.
Arguments:
Placeholders, same as socket.makefile()
"""
return self
def read(self, *args, **kwargs):
"""
Implement the file socket read interface.
Arguments:
Placeholders, same as socket.recv()
"""
return self.recv(*args, **kwargs)
def clear(self):
"""
Empty the send queue, typically done once the session has started to
remove the feature negotiation and log in stanzas.
"""
with self.send_queue_lock:
for i in range(0, self.send_queue.qsize()):
self.send_queue.get(block=False)
with self.recv_queue_lock:
for i in range(0, self.recv_queue.qsize()):
self.recv_queue.get(block=False)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/test/mocksocket.py 0000664 0000000 0000000 00000010245 11577753404 0022754 0 ustar 00root root 0000000 0000000 """
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 socket
try:
import queue
except ImportError:
import Queue as queue
class TestSocket(object):
"""
A dummy socket that reads and writes to queues instead
of an actual networking socket.
Methods:
next_sent -- Return the next sent stanza.
recv_data -- Make a stanza available to read next.
recv -- Read the next stanza from the socket.
send -- Write a stanza to the socket.
makefile -- Dummy call, returns self.
read -- Read the next stanza from the socket.
"""
def __init__(self, *args, **kwargs):
"""
Create a new test socket.
Arguments:
Same as arguments for socket.socket
"""
self.socket = socket.socket(*args, **kwargs)
self.recv_queue = queue.Queue()
self.send_queue = queue.Queue()
self.is_live = False
self.disconnected = False
def __getattr__(self, name):
"""
Return attribute values of internal, dummy socket.
Some attributes and methods are disabled to prevent the
socket from connecting to the network.
Arguments:
name -- Name of the attribute requested.
"""
def dummy(*args):
"""Method to do nothing and prevent actual socket connections."""
return None
overrides = {'connect': dummy,
'close': dummy,
'shutdown': dummy}
return overrides.get(name, getattr(self.socket, name))
# ------------------------------------------------------------------
# Testing Interface
def next_sent(self, timeout=None):
"""
Get the next stanza that has been 'sent'.
Arguments:
timeout -- Optional timeout for waiting for a new value.
"""
args = {'block': False}
if timeout is not None:
args = {'block': True, 'timeout': timeout}
try:
return self.send_queue.get(**args)
except:
return None
def recv_data(self, data):
"""
Add data to the receiving queue.
Arguments:
data -- String data to 'write' to the socket to be received
by the XMPP client.
"""
self.recv_queue.put(data)
def disconnect_error(self):
"""
Simulate a disconnect error by raising a socket.error exception
for any current or further socket operations.
"""
self.disconnected = True
# ------------------------------------------------------------------
# Socket Interface
def recv(self, *args, **kwargs):
"""
Read a value from the received queue.
Arguments:
Placeholders. Same as for socket.Socket.recv.
"""
if self.disconnected:
raise socket.error
return self.read(block=True)
def send(self, data):
"""
Send data by placing it in the send queue.
Arguments:
data -- String value to write.
"""
if self.disconnected:
raise socket.error
self.send_queue.put(data)
# ------------------------------------------------------------------
# File Socket
def makefile(self, *args, **kwargs):
"""
File socket version to use with ElementTree.
Arguments:
Placeholders, same as socket.Socket.makefile()
"""
return self
def read(self, block=True, timeout=None, **kwargs):
"""
Implement the file socket interface.
Arguments:
block -- Indicate if the read should block until a
value is ready.
timeout -- Time in seconds a block should last before
returning None.
"""
if self.disconnected:
raise socket.error
if timeout is not None:
block = True
try:
return self.recv_queue.get(block, timeout)
except:
return None
fritzy-SleekXMPP-5c4ee57/sleekxmpp/test/sleektest.py 0000664 0000000 0000000 00000072022 11577753404 0022616 0 ustar 00root root 0000000 0000000 """
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 unittest
try:
import Queue as queue
except:
import queue
import sleekxmpp
from sleekxmpp import ClientXMPP, ComponentXMPP
from sleekxmpp.stanza import Message, Iq, Presence
from sleekxmpp.test import TestSocket, TestLiveSocket
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream import ElementBase, StanzaBase
from sleekxmpp.xmlstream.tostring import tostring
from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId
from sleekxmpp.xmlstream.matcher import MatchXMLMask, MatchXPath
class SleekTest(unittest.TestCase):
"""
A SleekXMPP specific TestCase class that provides
methods for comparing message, iq, and presence stanzas.
Methods:
Message -- Create a Message stanza object.
Iq -- Create an Iq stanza object.
Presence -- Create a Presence stanza object.
check_jid -- Check a JID and its component parts.
check -- Compare a stanza against an XML string.
stream_start -- Initialize a dummy XMPP client.
stream_close -- Disconnect the XMPP client.
make_header -- Create a stream header.
send_header -- Check that the given header has been sent.
send_feature -- Send a raw XML element.
send -- Check that the XMPP client sent the given
generic stanza.
recv -- Queue data for XMPP client to receive, or
verify the data that was received from a
live connection.
recv_header -- Check that a given stream header
was received.
recv_feature -- Check that a given, raw XML element
was recveived.
fix_namespaces -- Add top-level namespace to an XML object.
compare -- Compare XML objects against each other.
"""
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self.xmpp = None
def runTest(self):
pass
def parse_xml(self, xml_string):
try:
xml = ET.fromstring(xml_string)
return xml
except SyntaxError as e:
if 'unbound' in e.msg:
known_prefixes = {
'stream': 'http://etherx.jabber.org/streams'}
prefix = xml_string.split('<')[1].split(':')[0]
if prefix in known_prefixes:
xml_string = '%s' % (
prefix,
known_prefixes[prefix],
xml_string)
xml = self.parse_xml(xml_string)
xml = xml.getchildren()[0]
return xml
else:
self.fail("XML data was mal-formed:\n%s" % xml_string)
# ------------------------------------------------------------------
# Shortcut methods for creating stanza objects
def Message(self, *args, **kwargs):
"""
Create a Message stanza.
Uses same arguments as StanzaBase.__init__
Arguments:
xml -- An XML object to use for the Message's values.
"""
return Message(self.xmpp, *args, **kwargs)
def Iq(self, *args, **kwargs):
"""
Create an Iq stanza.
Uses same arguments as StanzaBase.__init__
Arguments:
xml -- An XML object to use for the Iq's values.
"""
return Iq(self.xmpp, *args, **kwargs)
def Presence(self, *args, **kwargs):
"""
Create a Presence stanza.
Uses same arguments as StanzaBase.__init__
Arguments:
xml -- An XML object to use for the Iq's values.
"""
return Presence(self.xmpp, *args, **kwargs)
def check_jid(self, jid, user=None, domain=None, resource=None,
bare=None, full=None, string=None):
"""
Verify the components of a JID.
Arguments:
jid -- The JID object to test.
user -- Optional. The user name portion of the JID.
domain -- Optional. The domain name portion of the JID.
resource -- Optional. The resource portion of the JID.
bare -- Optional. The bare JID.
full -- Optional. The full JID.
string -- Optional. The string version of the JID.
"""
if user is not None:
self.assertEqual(jid.user, user,
"User does not match: %s" % jid.user)
if domain is not None:
self.assertEqual(jid.domain, domain,
"Domain does not match: %s" % jid.domain)
if resource is not None:
self.assertEqual(jid.resource, resource,
"Resource does not match: %s" % jid.resource)
if bare is not None:
self.assertEqual(jid.bare, bare,
"Bare JID does not match: %s" % jid.bare)
if full is not None:
self.assertEqual(jid.full, full,
"Full JID does not match: %s" % jid.full)
if string is not None:
self.assertEqual(str(jid), string,
"String does not match: %s" % str(jid))
# ------------------------------------------------------------------
# Methods for comparing stanza objects to XML strings
def check(self, stanza, criteria, method='exact',
defaults=None, use_values=True):
"""
Create and compare several stanza objects to a correct XML string.
If use_values is False, tests using stanza.values will not be used.
Some stanzas provide default values for some interfaces, but
these defaults can be problematic for testing since they can easily
be forgotten when supplying the XML string. A list of interfaces that
use defaults may be provided and the generated stanzas will use the
default values for those interfaces if needed.
However, correcting the supplied XML is not possible for interfaces
that add or remove XML elements. Only interfaces that map to XML
attributes may be set using the defaults parameter. The supplied XML
must take into account any extra elements that are included by default.
Arguments:
stanza -- The stanza object to test.
criteria -- An expression the stanza must match against.
method -- The type of matching to use; one of:
'exact', 'mask', 'id', 'xpath', and 'stanzapath'.
Defaults to the value of self.match_method.
defaults -- A list of stanza interfaces that have default
values. These interfaces will be set to their
defaults for the given and generated stanzas to
prevent unexpected test failures.
use_values -- Indicates if testing using stanza.values should
be used. Defaults to True.
"""
if method is None and hasattr(self, 'match_method'):
method = getattr(self, 'match_method')
if method != 'exact':
matchers = {'stanzapath': StanzaPath,
'xpath': MatchXPath,
'mask': MatchXMLMask,
'id': MatcherId}
Matcher = matchers.get(method, None)
if Matcher is None:
raise ValueError("Unknown matching method.")
test = Matcher(criteria)
self.failUnless(test.match(stanza),
"Stanza did not match using %s method:\n" % method + \
"Criteria:\n%s\n" % str(criteria) + \
"Stanza:\n%s" % str(stanza))
else:
stanza_class = stanza.__class__
if not isinstance(criteria, ElementBase):
xml = self.parse_xml(criteria)
else:
xml = criteria.xml
# Ensure that top level namespaces are used, even if they
# were not provided.
self.fix_namespaces(stanza.xml, 'jabber:client')
self.fix_namespaces(xml, 'jabber:client')
stanza2 = stanza_class(xml=xml)
if use_values:
# Using stanza.values will add XML for any interface that
# has a default value. We need to set those defaults on
# the existing stanzas and XML so that they will compare
# correctly.
default_stanza = stanza_class()
if defaults is None:
known_defaults = {
Message: ['type'],
Presence: ['priority']
}
defaults = known_defaults.get(stanza_class, [])
for interface in defaults:
stanza[interface] = stanza[interface]
stanza2[interface] = stanza2[interface]
# Can really only automatically add defaults for top
# level attribute values. Anything else must be accounted
# for in the provided XML string.
if interface not in xml.attrib:
if interface in default_stanza.xml.attrib:
value = default_stanza.xml.attrib[interface]
xml.attrib[interface] = value
values = stanza2.values
stanza3 = stanza_class()
stanza3.values = values
debug = "Three methods for creating stanzas do not match.\n"
debug += "Given XML:\n%s\n" % tostring(xml)
debug += "Given stanza:\n%s\n" % tostring(stanza.xml)
debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml)
debug += "Second generated stanza:\n%s\n" % tostring(stanza3.xml)
result = self.compare(xml, stanza.xml, stanza2.xml, stanza3.xml)
else:
debug = "Two methods for creating stanzas do not match.\n"
debug += "Given XML:\n%s\n" % tostring(xml)
debug += "Given stanza:\n%s\n" % tostring(stanza.xml)
debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml)
result = self.compare(xml, stanza.xml, stanza2.xml)
self.failUnless(result, debug)
# ------------------------------------------------------------------
# Methods for simulating stanza streams.
def stream_disconnect(self):
"""
Simulate a stream disconnection.
"""
if self.xmpp:
self.xmpp.socket.disconnect_error()
def stream_start(self, mode='client', skip=True, header=None,
socket='mock', jid='tester@localhost',
password='test', server='localhost',
port=5222, plugins=None):
"""
Initialize an XMPP client or component using a dummy XML stream.
Arguments:
mode -- Either 'client' or 'component'. Defaults to 'client'.
skip -- Indicates if the first item in the sent queue (the
stream header) should be removed. Tests that wish
to test initializing the stream should set this to
False. Otherwise, the default of True should be used.
socket -- Either 'mock' or 'live' to indicate if the socket
should be a dummy, mock socket or a live, functioning
socket. Defaults to 'mock'.
jid -- The JID to use for the connection.
Defaults to 'tester@localhost'.
password -- The password to use for the connection.
Defaults to 'test'.
server -- The name of the XMPP server. Defaults to 'localhost'.
port -- The port to use when connecting to the server.
Defaults to 5222.
plugins -- List of plugins to register. By default, all plugins
are loaded.
"""
if mode == 'client':
self.xmpp = ClientXMPP(jid, password)
elif mode == 'component':
self.xmpp = ComponentXMPP(jid, password,
server, port)
else:
raise ValueError("Unknown XMPP connection mode.")
# We will use this to wait for the session_start event
# for live connections.
skip_queue = queue.Queue()
if socket == 'mock':
self.xmpp.set_socket(TestSocket())
# Simulate connecting for mock sockets.
self.xmpp.auto_reconnect = False
self.xmpp.is_client = True
self.xmpp.state._set_state('connected')
# Must have the stream header ready for xmpp.process() to work.
if not header:
header = self.xmpp.stream_header
self.xmpp.socket.recv_data(header)
elif socket == 'live':
self.xmpp.socket_class = TestLiveSocket
def wait_for_session(x):
self.xmpp.socket.clear()
skip_queue.put('started')
self.xmpp.add_event_handler('session_start', wait_for_session)
self.xmpp.connect()
else:
raise ValueError("Unknown socket type.")
if plugins is None:
self.xmpp.register_plugins()
else:
for plugin in plugins:
self.xmpp.register_plugin(plugin)
self.xmpp.process(threaded=True)
if skip:
if socket != 'live':
# Mark send queue as usable
self.xmpp.session_started_event.set()
# Clear startup stanzas
self.xmpp.socket.next_sent(timeout=1)
if mode == 'component':
self.xmpp.socket.next_sent(timeout=1)
else:
skip_queue.get(block=True, timeout=10)
def make_header(self, sto='',
sfrom='',
sid='',
stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client",
version="1.0",
xml_header=True):
"""
Create a stream header to be received by the test XMPP agent.
The header must be saved and passed to stream_start.
Arguments:
sto -- The recipient of the stream header.
sfrom -- The agent sending the stream header.
sid -- The stream's id.
stream_ns -- The namespace of the stream's root element.
default_ns -- The default stanza namespace.
version -- The stream version.
xml_header -- Indicates if the XML version header should be
appended before the stream header.
"""
header = ''
parts = []
if xml_header:
header = '' + header
if sto:
parts.append('to="%s"' % sto)
if sfrom:
parts.append('from="%s"' % sfrom)
if sid:
parts.append('id="%s"' % sid)
parts.append('version="%s"' % version)
parts.append('xmlns:stream="%s"' % stream_ns)
parts.append('xmlns="%s"' % default_ns)
return header % ' '.join(parts)
def recv(self, data, defaults=[], method='exact',
use_values=True, timeout=1):
"""
Pass data to the dummy XMPP client as if it came from an XMPP server.
If using a live connection, verify what the server has sent.
Arguments:
data -- If a dummy socket is being used, the XML that is to
be received next. Otherwise it is the criteria used
to match against live data that is received.
defaults -- A list of stanza interfaces with default values that
may interfere with comparisons.
method -- Select the type of comparison to use for
verifying the received stanza. Options are 'exact',
'id', 'stanzapath', 'xpath', and 'mask'.
Defaults to the value of self.match_method.
use_values -- Indicates if stanza comparisons should test using
stanza.values. Defaults to True.
timeout -- Time to wait in seconds for data to be received by
a live connection.
"""
if self.xmpp.socket.is_live:
# we are working with a live connection, so we should
# verify what has been received instead of simulating
# receiving data.
recv_data = self.xmpp.socket.next_recv(timeout)
if recv_data is None:
self.fail("No stanza was received.")
xml = self.parse_xml(recv_data)
self.fix_namespaces(xml, 'jabber:client')
stanza = self.xmpp._build_stanza(xml, 'jabber:client')
self.check(stanza, data,
method=method,
defaults=defaults,
use_values=use_values)
else:
# place the data in the dummy socket receiving queue.
data = str(data)
self.xmpp.socket.recv_data(data)
def recv_header(self, sto='',
sfrom='',
sid='',
stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client",
version="1.0",
xml_header=False,
timeout=1):
"""
Check that a given stream header was received.
Arguments:
sto -- The recipient of the stream header.
sfrom -- The agent sending the stream header.
sid -- The stream's id. Set to None to ignore.
stream_ns -- The namespace of the stream's root element.
default_ns -- The default stanza namespace.
version -- The stream version.
xml_header -- Indicates if the XML version header should be
appended before the stream header.
timeout -- Length of time to wait in seconds for a
response.
"""
header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns,
default_ns=default_ns,
version=version,
xml_header=xml_header)
recv_header = self.xmpp.socket.next_recv(timeout)
if recv_header is None:
raise ValueError("Socket did not return data.")
# Apply closing elements so that we can construct
# XML objects for comparison.
header2 = header + ''
recv_header2 = recv_header + ''
xml = self.parse_xml(header2)
recv_xml = self.parse_xml(recv_header2)
if sid is None:
# Ignore the id sent by the server since
# we can't know in advance what it will be.
if 'id' in recv_xml.attrib:
del recv_xml.attrib['id']
# Ignore the xml:lang attribute for now.
if 'xml:lang' in recv_xml.attrib:
del recv_xml.attrib['xml:lang']
xml_ns = 'http://www.w3.org/XML/1998/namespace'
if '{%s}lang' % xml_ns in recv_xml.attrib:
del recv_xml.attrib['{%s}lang' % xml_ns]
if recv_xml.getchildren:
# We received more than just the header
for xml in recv_xml.getchildren():
self.xmpp.socket.recv_data(tostring(xml))
attrib = recv_xml.attrib
recv_xml.clear()
recv_xml.attrib = attrib
self.failUnless(
self.compare(xml, recv_xml),
"Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
'%s %s' % (xml.tag, xml.attrib),
'%s %s' % (recv_xml.tag, recv_xml.attrib)))
def recv_feature(self, data, method='mask', use_values=True, timeout=1):
"""
"""
if method is None and hasattr(self, 'match_method'):
method = getattr(self, 'match_method')
if self.xmpp.socket.is_live:
# we are working with a live connection, so we should
# verify what has been received instead of simulating
# receiving data.
recv_data = self.xmpp.socket.next_recv(timeout)
xml = self.parse_xml(data)
recv_xml = self.parse_xml(recv_data)
if recv_data is None:
self.fail("No stanza was received.")
if method == 'exact':
self.failUnless(self.compare(xml, recv_xml),
"Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
tostring(xml), tostring(recv_xml)))
elif method == 'mask':
matcher = MatchXMLMask(xml)
self.failUnless(matcher.match(recv_xml),
"Stanza did not match using %s method:\n" % method + \
"Criteria:\n%s\n" % tostring(xml) + \
"Stanza:\n%s" % tostring(recv_xml))
else:
raise ValueError("Uknown matching method: %s" % method)
else:
# place the data in the dummy socket receiving queue.
data = str(data)
self.xmpp.socket.recv_data(data)
def send_header(self, sto='',
sfrom='',
sid='',
stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client",
version="1.0",
xml_header=False,
timeout=1):
"""
Check that a given stream header was sent.
Arguments:
sto -- The recipient of the stream header.
sfrom -- The agent sending the stream header.
sid -- The stream's id.
stream_ns -- The namespace of the stream's root element.
default_ns -- The default stanza namespace.
version -- The stream version.
xml_header -- Indicates if the XML version header should be
appended before the stream header.
timeout -- Length of time to wait in seconds for a
response.
"""
header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns,
default_ns=default_ns,
version=version,
xml_header=xml_header)
sent_header = self.xmpp.socket.next_sent(timeout)
if sent_header is None:
raise ValueError("Socket did not return data.")
# Apply closing elements so that we can construct
# XML objects for comparison.
header2 = header + ''
sent_header2 = sent_header + b''
xml = self.parse_xml(header2)
sent_xml = self.parse_xml(sent_header2)
self.failUnless(
self.compare(xml, sent_xml),
"Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
header, sent_header))
def send_feature(self, data, method='mask', use_values=True, timeout=1):
"""
"""
sent_data = self.xmpp.socket.next_sent(timeout)
xml = self.parse_xml(data)
sent_xml = self.parse_xml(sent_data)
if sent_data is None:
self.fail("No stanza was sent.")
if method == 'exact':
self.failUnless(self.compare(xml, sent_xml),
"Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
tostring(xml), tostring(sent_xml)))
elif method == 'mask':
matcher = MatchXMLMask(xml)
self.failUnless(matcher.match(sent_xml),
"Stanza did not match using %s method:\n" % method + \
"Criteria:\n%s\n" % tostring(xml) + \
"Stanza:\n%s" % tostring(sent_xml))
else:
raise ValueError("Uknown matching method: %s" % method)
def send(self, data, defaults=None, use_values=True,
timeout=.5, method='exact'):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using check.
Arguments:
stanza_class -- The class of the sent stanza object.
data -- The XML string of the expected Message stanza,
or an equivalent stanza object.
use_values -- Modifies the type of tests used by check_message.
defaults -- A list of stanza interfaces that have defaults
values which may interfere with comparisons.
timeout -- Time in seconds to wait for a stanza before
failing the check.
method -- Select the type of comparison to use for
verifying the sent stanza. Options are 'exact',
'id', 'stanzapath', 'xpath', and 'mask'.
Defaults to the value of self.match_method.
"""
sent = self.xmpp.socket.next_sent(timeout)
if data is None and sent is None:
return
if data is None and sent is not None:
self.fail("Stanza data was sent: %s" % sent)
if sent is None:
self.fail("No stanza was sent.")
xml = self.parse_xml(sent)
self.fix_namespaces(xml, 'jabber:client')
sent = self.xmpp._build_stanza(xml, 'jabber:client')
self.check(sent, data,
method=method,
defaults=defaults,
use_values=use_values)
def stream_close(self):
"""
Disconnect the dummy XMPP client.
Can be safely called even if stream_start has not been called.
Must be placed in the tearDown method of a test class to ensure
that the XMPP client is disconnected after an error.
"""
if hasattr(self, 'xmpp') and self.xmpp is not None:
self.xmpp.socket.recv_data(self.xmpp.stream_footer)
self.xmpp.disconnect()
# ------------------------------------------------------------------
# XML Comparison and Cleanup
def fix_namespaces(self, xml, ns):
"""
Assign a namespace to an element and any children that
don't have a namespace.
Arguments:
xml -- The XML object to fix.
ns -- The namespace to add to the XML object.
"""
if xml.tag.startswith('{'):
return
xml.tag = '{%s}%s' % (ns, xml.tag)
for child in xml.getchildren():
self.fix_namespaces(child, ns)
def compare(self, xml, *other):
"""
Compare XML objects.
Arguments:
xml -- The XML object to compare against.
*other -- The list of XML objects to compare.
"""
if not other:
return False
# Compare multiple objects
if len(other) > 1:
for xml2 in other:
if not self.compare(xml, xml2):
return False
return True
other = other[0]
# Step 1: Check tags
if xml.tag != other.tag:
return False
# Step 2: Check attributes
if xml.attrib != other.attrib:
return False
# Step 3: Check text
if xml.text is None:
xml.text = ""
if other.text is None:
other.text = ""
xml.text = xml.text.strip()
other.text = other.text.strip()
if xml.text != other.text:
return False
# Step 4: Check children count
if len(xml.getchildren()) != len(other.getchildren()):
return False
# Step 5: Recursively check children
for child in xml:
child2s = other.findall("%s" % child.tag)
if child2s is None:
return False
for child2 in child2s:
if self.compare(child, child2):
break
else:
return False
# Step 6: Recursively check children the other way.
for child in other:
child2s = xml.findall("%s" % child.tag)
if child2s is None:
return False
for child2 in child2s:
if self.compare(child, child2):
break
else:
return False
# Everything matches
return True
fritzy-SleekXMPP-5c4ee57/sleekxmpp/thirdparty/ 0000775 0000000 0000000 00000000000 11577753404 0021451 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/thirdparty/__init__.py 0000664 0000000 0000000 00000000162 11577753404 0023561 0 ustar 00root root 0000000 0000000 try:
from collections import OrderedDict
except:
from sleekxmpp.thirdparty.ordereddict import OrderedDict
fritzy-SleekXMPP-5c4ee57/sleekxmpp/thirdparty/ordereddict.py 0000664 0000000 0000000 00000010175 11577753404 0024317 0 ustar 00root root 0000000 0000000 # 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.
from UserDict import DictMixin
class OrderedDict(dict, DictMixin):
def __init__(self, *args, **kwds):
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__end
except AttributeError:
self.clear()
self.update(*args, **kwds)
def clear(self):
self.__end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.__map = {} # key --> [key, prev, next]
dict.clear(self)
def __setitem__(self, key, value):
if key not in self:
end = self.__end
curr = end[1]
curr[2] = end[1] = self.__map[key] = [key, curr, end]
dict.__setitem__(self, key, value)
def __delitem__(self, key):
dict.__delitem__(self, key)
key, prev, next = self.__map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.__end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.__end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def popitem(self, last=True):
if not self:
raise KeyError('dictionary is empty')
if last:
key = reversed(self).next()
else:
key = iter(self).next()
value = self.pop(key)
return key, value
def __reduce__(self):
items = [[k, self[k]] for k in self]
tmp = self.__map, self.__end
del self.__map, self.__end
inst_dict = vars(self).copy()
self.__map, self.__end = tmp
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
def keys(self):
return list(self)
setdefault = DictMixin.setdefault
update = DictMixin.update
pop = DictMixin.pop
values = DictMixin.values
items = DictMixin.items
iterkeys = DictMixin.iterkeys
itervalues = DictMixin.itervalues
iteritems = DictMixin.iteritems
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
def copy(self):
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
if isinstance(other, OrderedDict):
if len(self) != len(other):
return False
for p, q in zip(self.items(), other.items()):
if p != q:
return False
return True
return dict.__eq__(self, other)
def __ne__(self, other):
return not self == other
fritzy-SleekXMPP-5c4ee57/sleekxmpp/thirdparty/statemachine.py 0000664 0000000 0000000 00000027174 11577753404 0024503 0 ustar 00root root 0000000 0000000 """
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 threading
import time
import logging
log = logging.getLogger(__name__)
class StateMachine(object):
def __init__(self, states=[]):
self.lock = threading.Lock()
self.notifier = threading.Event()
self.__states = []
self.addStates(states)
self.__default_state = self.__states[0]
self.__current_state = self.__default_state
def addStates(self, states):
self.lock.acquire()
try:
for state in states:
if state in self.__states:
raise IndexError("The state '%s' is already in the StateMachine." % state)
self.__states.append(state)
finally: self.lock.release()
def transition(self, from_state, to_state, wait=0.0, func=None, args=[], kwargs={}):
'''
Transition from the given `from_state` to the given `to_state`.
This method will return `True` if the state machine is now in `to_state`. It
will return `False` if a timeout occurred the transition did not occur.
If `wait` is 0 (the default,) this method returns immediately if the state machine
is not in `from_state`.
If you want the thread to block and transition once the state machine to enters
`from_state`, set `wait` to a non-negative value. Note there is no 'block
indefinitely' flag since this leads to deadlock. If you want to wait indefinitely,
choose a reasonable value for `wait` (e.g. 20 seconds) and do so in a while loop like so:
::
while not thread_should_exit and not state_machine.transition('disconnected', 'connecting', wait=20 ):
pass # timeout will occur every 20s unless transition occurs
if thread_should_exit: return
# perform actions here after successful transition
This allows the thread to be responsive by setting `thread_should_exit=True`.
The optional `func` argument allows the user to pass a callable operation which occurs
within the context of the state transition (e.g. while the state machine is locked.)
If `func` returns a True value, the transition will occur. If `func` returns a non-
True value or if an exception is thrown, the transition will not occur. Any thrown
exception is not caught by the state machine and is the caller's responsibility to handle.
If `func` completes normally, this method will return the value returned by `func.` If
values for `args` and `kwargs` are provided, they are expanded and passed like so:
`func( *args, **kwargs )`.
'''
return self.transition_any((from_state,), to_state, wait=wait,
func=func, args=args, kwargs=kwargs)
def transition_any(self, from_states, to_state, wait=0.0, func=None, args=[], kwargs={}):
'''
Transition from any of the given `from_states` to the given `to_state`.
'''
if not (isinstance(from_states,tuple) or isinstance(from_states,list)):
raise ValueError("from_states should be a list or tuple")
for state in from_states:
if not state in self.__states:
raise ValueError("StateMachine does not contain from_state %s." % state)
if not to_state in self.__states:
raise ValueError("StateMachine does not contain to_state %s." % to_state)
start = time.time()
while not self.lock.acquire(False):
time.sleep(.001)
if (start + wait - time.time()) <= 0.0:
log.debug("Could not acquire lock")
return False
while not self.__current_state in from_states:
# detect timeout:
remainder = start + wait - time.time()
if remainder > 0:
self.notifier.wait(remainder)
else:
log.debug("State was not ready")
self.lock.release()
return False
try: # lock is acquired; all other threads will return false or wait until notify/timeout
if self.__current_state in from_states: # should always be True due to lock
# Note that func might throw an exception, but that's OK, it aborts the transition
return_val = func(*args,**kwargs) if func is not None else True
# some 'false' value returned from func,
# indicating that transition should not occur:
if not return_val: return return_val
log.debug(' ==== TRANSITION %s -> %s', self.__current_state, to_state)
self._set_state(to_state)
return return_val # some 'true' value returned by func or True if func was None
else:
log.error("StateMachine bug!! The lock should ensure this doesn't happen!")
return False
finally:
self.notifier.set() # notify any waiting threads that the state has changed.
self.notifier.clear()
self.lock.release()
def transition_ctx(self, from_state, to_state, wait=0.0):
'''
Use the state machine as a context manager. The transition occurs on /exit/ from
the `with` context, so long as no exception is thrown. For example:
::
with state_machine.transition_ctx('one','two', wait=5) as locked:
if locked:
# the state machine is currently locked in state 'one', and will
# transition to 'two' when the 'with' statement ends, so long as
# no exception is thrown.
print 'Currently locked in state one: %s' % state_machine['one']
else:
# The 'wait' timed out, and no lock has been acquired
print 'Timed out before entering state "one"'
print 'Since no exception was thrown, we are now in state "two": %s' % state_machine['two']
The other main difference between this method and `transition()` is that the
state machine is locked for the duration of the `with` statement. Normally,
after a `transition()` occurs, the state machine is immediately unlocked and
available to another thread to call `transition()` again.
'''
if not from_state in self.__states:
raise ValueError("StateMachine does not contain from_state %s." % from_state)
if not to_state in self.__states:
raise ValueError("StateMachine does not contain to_state %s." % to_state)
return _StateCtx(self, from_state, to_state, wait)
def ensure(self, state, wait=0.0, block_on_transition=False):
'''
Ensure the state machine is currently in `state`, or wait until it enters `state`.
'''
return self.ensure_any((state,), wait=wait, block_on_transition=block_on_transition)
def ensure_any(self, states, wait=0.0, block_on_transition=False):
'''
Ensure we are currently in one of the given `states` or wait until
we enter one of those states.
Note that due to the nature of the function, you cannot guarantee that
the entirety of some operation completes while you remain in a given
state. That would require acquiring and holding a lock, which
would mean no other threads could do the same. (You'd essentially
be serializing all of the threads that are 'ensuring' their tasks
occurred in some state.
'''
if not (isinstance(states,tuple) or isinstance(states,list)):
raise ValueError('states arg should be a tuple or list')
for state in states:
if not state in self.__states:
raise ValueError("StateMachine does not contain state '%s'" % state)
# if we're in the middle of a transition, determine whether we should
# 'fall back' to the 'current' state, or wait for the new state, in order to
# avoid an operation occurring in the wrong state.
# TODO another option would be an ensure_ctx that uses a semaphore to allow
# threads to indicate they want to remain in a particular state.
# will return immediately if no transition is in process.
if block_on_transition:
# we're not in the middle of a transition; don't hold the lock
if self.lock.acquire(False): self.lock.release()
# wait for the transition to complete
else: self.notifier.wait()
start = time.time()
while not self.__current_state in states:
# detect timeout:
remainder = start + wait - time.time()
if remainder > 0: self.notifier.wait(remainder)
else: return False
return True
def reset(self):
# TODO need to lock before calling this?
self.transition(self.__current_state, self.__default_state)
def _set_state(self, state): #unsynchronized, only call internally after lock is acquired
self.__current_state = state
return state
def current_state(self):
'''
Return the current state name.
'''
return self.__current_state
def __getitem__(self, state):
'''
Non-blocking, non-synchronized test to determine if we are in the given state.
Use `StateMachine.ensure(state)` to wait until the machine enters a certain state.
'''
return self.__current_state == state
def __str__(self):
return "".join(("StateMachine(", ','.join(self.__states), "): ", self.__current_state))
class _StateCtx:
def __init__(self, state_machine, from_state, to_state, wait):
self.state_machine = state_machine
self.from_state = from_state
self.to_state = to_state
self.wait = wait
self._locked = False
def __enter__(self):
start = time.time()
while not self.state_machine[self.from_state] or not self.state_machine.lock.acquire(False):
# detect timeout:
remainder = start + self.wait - time.time()
if remainder > 0: self.state_machine.notifier.wait(remainder)
else:
log.debug('StateMachine timeout while waiting for state: %s', self.from_state)
return False
self._locked = True # lock has been acquired at this point
self.state_machine.notifier.clear()
log.debug('StateMachine entered context in state: %s',
self.state_machine.current_state())
return True
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_val is not None:
log.exception("StateMachine exception in context, remaining in state: %s\n%s:%s",
self.state_machine.current_state(), exc_type.__name__, exc_val)
if self._locked:
if exc_val is None:
log.debug(' ==== TRANSITION %s -> %s',
self.state_machine.current_state(), self.to_state)
self.state_machine._set_state(self.to_state)
self.state_machine.notifier.set()
self.state_machine.lock.release()
return False # re-raise any exception
if __name__ == '__main__':
def callback(s, s2):
print((1, s.transition('on', 'off', wait=0.0, func=callback, args=[s,s2])))
print((2, s2.transition('off', 'on', func=callback, args=[s,s2])))
return True
s = StateMachine(('off', 'on'))
s2 = StateMachine(('off', 'on'))
print((3, s.transition('off', 'on', wait=0.0, func=callback, args=[s,s2]),))
print((s.current_state(), s2.current_state()))
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/ 0000775 0000000 0000000 00000000000 11577753404 0021273 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/__init__.py 0000664 0000000 0000000 00000001354 11577753404 0023407 0 ustar 00root root 0000000 0000000 """
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.jid import JID
from sleekxmpp.xmlstream.scheduler import Scheduler
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase, ET
from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
from sleekxmpp.xmlstream.tostring import tostring
from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT
from sleekxmpp.xmlstream.xmlstream import RestartStream
__all__ = ['JID', 'Scheduler', 'StanzaBase', 'ElementBase',
'ET', 'StateMachine', 'tostring', 'XMLStream',
'RESPONSE_TIMEOUT', 'RestartStream']
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/filesocket.py 0000664 0000000 0000000 00000002364 11577753404 0024002 0 ustar 00root root 0000000 0000000 """
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 socket import _fileobject
import socket
class FileSocket(_fileobject):
"""
Create a file object wrapper for a socket to work around
issues present in Python 2.6 when using sockets as file objects.
The parser for xml.etree.cElementTree requires a file, but we will
be reading from the XMPP connection socket instead.
"""
def read(self, size=4096):
"""Read data from the socket as if it were a file."""
if self._sock is None:
return None
data = self._sock.recv(size)
if data is not None:
return data
class Socket26(socket._socketobject):
"""
A custom socket implementation that uses our own FileSocket class
to work around issues in Python 2.6 when using sockets as files.
"""
def makefile(self, mode='r', bufsize=-1):
"""makefile([mode[, bufsize]]) -> file object
Return a regular file object corresponding to the socket. The mode
and bufsize arguments are as for the built-in open() function."""
return FileSocket(self._sock, mode, bufsize)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/handler/ 0000775 0000000 0000000 00000000000 11577753404 0022710 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/handler/__init__.py 0000664 0000000 0000000 00000000732 11577753404 0025023 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.handler.waiter import Waiter
from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback
from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter
__all__ = ['Callback', 'Waiter', 'XMLCallback', 'XMLWaiter']
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/handler/base.py 0000664 0000000 0000000 00000005445 11577753404 0024204 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
class BaseHandler(object):
"""
Base class for stream handlers. Stream handlers are matched with
incoming stanzas so that the stanza may be processed in some way.
Stanzas may be matched with multiple handlers.
Handler execution may take place in two phases. The first is during
the stream processing itself. The second is after stream processing
and during SleekXMPP's main event loop. The prerun method is used
for execution during stream processing, and the run method is used
during the main event loop.
Attributes:
name -- The name of the handler.
stream -- The stream this handler is assigned to.
Methods:
match -- Compare a stanza with the handler's matcher.
prerun -- Handler execution during stream processing.
run -- Handler execution during the main event loop.
check_delete -- Indicate if the handler may be removed from use.
"""
def __init__(self, name, matcher, stream=None):
"""
Create a new stream handler.
Arguments:
name -- The name of the handler.
matcher -- A matcher object from xmlstream.matcher that will be
used to determine if a stanza should be accepted by
this handler.
stream -- The XMLStream instance the handler should monitor.
"""
self.name = name
self.stream = stream
self._destroy = False
self._payload = None
self._matcher = matcher
if stream is not None:
stream.registerHandler(self)
def match(self, xml):
"""
Compare a stanza or XML object with the handler's matcher.
Arguments
xml -- An XML or stanza object.
"""
return self._matcher.match(xml)
def prerun(self, payload):
"""
Prepare the handler for execution while the XML stream is being
processed.
Arguments:
payload -- A stanza object.
"""
self._payload = payload
def run(self, payload):
"""
Execute the handler after XML stream processing and during the
main event loop.
Arguments:
payload -- A stanza object.
"""
self._payload = payload
def check_delete(self):
"""
Check if the handler should be removed from the list of stream
handlers.
"""
return self._destroy
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
BaseHandler.checkDelete = BaseHandler.check_delete
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/handler/callback.py 0000664 0000000 0000000 00000005477 11577753404 0025033 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.handler.base import BaseHandler
class Callback(BaseHandler):
"""
The Callback handler will execute a callback function with
matched stanzas.
The handler may execute the callback either during stream
processing or during the main event loop.
Callback functions are all executed in the same thread, so be
aware if you are executing functions that will block for extended
periods of time. Typically, you should signal your own events using the
SleekXMPP object's event() method to pass the stanza off to a threaded
event handler for further processing.
Methods:
prerun -- Overrides BaseHandler.prerun
run -- Overrides BaseHandler.run
"""
def __init__(self, name, matcher, pointer, thread=False,
once=False, instream=False, stream=None):
"""
Create a new callback handler.
Arguments:
name -- The name of the handler.
matcher -- A matcher object for matching stanza objects.
pointer -- The function to execute during callback.
thread -- DEPRECATED. Remains only for backwards compatibility.
once -- Indicates if the handler should be used only
once. Defaults to False.
instream -- Indicates if the callback should be executed
during stream processing instead of in the
main event loop.
stream -- The XMLStream instance this handler should monitor.
"""
BaseHandler.__init__(self, name, matcher, stream)
self._pointer = pointer
self._once = once
self._instream = instream
def prerun(self, payload):
"""
Execute the callback during stream processing, if
the callback was created with instream=True.
Overrides BaseHandler.prerun
Arguments:
payload -- The matched stanza object.
"""
if self._once:
self._destroy = True
if self._instream:
self.run(payload, True)
def run(self, payload, instream=False):
"""
Execute the callback function with the matched stanza payload.
Overrides BaseHandler.run
Arguments:
payload -- The matched stanza object.
instream -- Force the handler to execute during
stream processing. Used only by prerun.
Defaults to False.
"""
if not self._instream or instream:
self._pointer(payload)
if self._once:
self._destroy = True
del self._pointer
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/handler/waiter.py 0000664 0000000 0000000 00000005445 11577753404 0024565 0 ustar 00root root 0000000 0000000 """
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
try:
import queue
except ImportError:
import Queue as queue
from sleekxmpp.xmlstream import StanzaBase
from sleekxmpp.xmlstream.handler.base import BaseHandler
log = logging.getLogger(__name__)
class Waiter(BaseHandler):
"""
The Waiter handler allows an event handler to block
until a particular stanza has been received. The handler
will either be given the matched stanza, or False if the
waiter has timed out.
Methods:
check_delete -- Overrides BaseHandler.check_delete
prerun -- Overrides BaseHandler.prerun
run -- Overrides BaseHandler.run
wait -- Wait for a stanza to arrive and return it to
an event handler.
"""
def __init__(self, name, matcher, stream=None):
"""
Create a new Waiter.
Arguments:
name -- The name of the waiter.
matcher -- A matcher object to detect the desired stanza.
stream -- Optional XMLStream instance to monitor.
"""
BaseHandler.__init__(self, name, matcher, stream=stream)
self._payload = queue.Queue()
def prerun(self, payload):
"""
Store the matched stanza.
Overrides BaseHandler.prerun
Arguments:
payload -- The matched stanza object.
"""
self._payload.put(payload)
def run(self, payload):
"""
Do not process this handler during the main event loop.
Overrides BaseHandler.run
Arguments:
payload -- The matched stanza object.
"""
pass
def wait(self, timeout=None):
"""
Block an event handler while waiting for a stanza to arrive.
Be aware that this will impact performance if called from a
non-threaded event handler.
Will return either the received stanza, or False if the waiter
timed out.
Arguments:
timeout -- The number of seconds to wait for the stanza to
arrive. Defaults to the global default timeout
value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
"""
if timeout is None:
timeout = self.stream.response_timeout
try:
stanza = self._payload.get(True, timeout)
except queue.Empty:
stanza = False
log.warning("Timed out waiting for %s" % self.name)
self.stream.removeHandler(self.name)
return stanza
def check_delete(self):
"""
Always remove waiters after use.
Overrides BaseHandler.check_delete
"""
return True
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/handler/xmlcallback.py 0000664 0000000 0000000 00000001766 11577753404 0025551 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.handler import Callback
class XMLCallback(Callback):
"""
The XMLCallback class is identical to the normal Callback class,
except that XML contents of matched stanzas will be processed instead
of the stanza objects themselves.
Methods:
run -- Overrides Callback.run
"""
def run(self, payload, instream=False):
"""
Execute the callback function with the matched stanza's
XML contents, instead of the stanza itself.
Overrides BaseHandler.run
Arguments:
payload -- The matched stanza object.
instream -- Force the handler to execute during
stream processing. Used only by prerun.
Defaults to False.
"""
Callback.run(self, payload.xml, instream)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/handler/xmlwaiter.py 0000664 0000000 0000000 00000001420 11577753404 0025273 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.handler import Waiter
class XMLWaiter(Waiter):
"""
The XMLWaiter class is identical to the normal Waiter class
except that it returns the XML contents of the stanza instead
of the full stanza object itself.
Methods:
prerun -- Overrides Waiter.prerun
"""
def prerun(self, payload):
"""
Store the XML contents of the stanza to return to the
waiting event handler.
Overrides Waiter.prerun
Arguments:
payload -- The matched stanza object.
"""
Waiter.prerun(self, payload.xml)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/jid.py 0000664 0000000 0000000 00000010353 11577753404 0022415 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import unicode_literals
class JID(object):
"""
A representation of a Jabber ID, or JID.
Each JID may have three components: a user, a domain, and an optional
resource. For example: user@domain/resource
When a resource is not used, the JID is called a bare JID.
The JID is a full JID otherwise.
Attributes:
jid -- Alias for 'full'.
full -- The value of the full JID.
bare -- The value of the bare JID.
user -- The username portion of the JID.
domain -- The domain name portion of the JID.
server -- Alias for 'domain'.
resource -- The resource portion of the JID.
Methods:
reset -- Use a new JID value.
regenerate -- Recreate the JID from its components.
"""
def __init__(self, jid):
"""Initialize a new JID"""
self.reset(jid)
def reset(self, jid):
"""
Start fresh from a new JID string.
Arguments:
jid - The new JID value.
"""
if isinstance(jid, JID):
jid = jid.full
self._full = self._jid = jid
self._domain = None
self._resource = None
self._user = None
self._bare = None
def __getattr__(self, name):
"""
Handle getting the JID values, using cache if available.
Arguments:
name -- One of: user, server, domain, resource,
full, or bare.
"""
if name == 'resource':
if self._resource is None and '/' in self._jid:
self._resource = self._jid.split('/', 1)[-1]
return self._resource or ""
elif name == 'user':
if self._user is None:
if '@' in self._jid:
self._user = self._jid.split('@', 1)[0]
else:
self._user = self._user
return self._user or ""
elif name in ('server', 'domain', 'host'):
if self._domain is None:
self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0]
return self._domain or ""
elif name in ('full', 'jid'):
return self._jid or ""
elif name == 'bare':
if self._bare is None:
self._bare = self._jid.split('/', 1)[0]
return self._bare or ""
def __setattr__(self, name, value):
"""
Edit a JID by updating it's individual values, resetting the
generated JID in the end.
Arguments:
name -- The name of the JID part. One of: user, domain,
server, resource, full, jid, or bare.
value -- The new value for the JID part.
"""
if name in ('resource', 'user', 'domain'):
object.__setattr__(self, "_%s" % name, value)
self.regenerate()
elif name in ('server', 'domain', 'host'):
self.domain = value
elif name in ('full', 'jid'):
self.reset(value)
self.regenerate()
elif name == 'bare':
if '@' in value:
u, d = value.split('@', 1)
object.__setattr__(self, "_user", u)
object.__setattr__(self, "_domain", d)
else:
object.__setattr__(self, "_user", '')
object.__setattr__(self, "_domain", value)
self.regenerate()
else:
object.__setattr__(self, name, value)
def regenerate(self):
"""Generate a new JID based on current values, useful after editing."""
jid = ""
if self.user:
jid = "%s@" % self.user
jid += self.domain
if self.resource:
jid += "/%s" % self.resource
self.reset(jid)
def __str__(self):
"""Use the full JID as the string value."""
return self.full
def __repr__(self):
return self.full
def __eq__(self, other):
"""
Two JIDs are considered equal if they have the same full JID value.
"""
other = JID(other)
return self.full == other.full
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/ 0000775 0000000 0000000 00000000000 11577753404 0022716 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/__init__.py 0000664 0000000 0000000 00000001055 11577753404 0025030 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.matcher.id import MatcherId
from sleekxmpp.xmlstream.matcher.many import MatchMany
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
__all__ = ['MatcherId', 'MatchMany', 'StanzaPath',
'MatchXMLMask', 'MatchXPath']
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/base.py 0000664 0000000 0000000 00000001423 11577753404 0024202 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
class MatcherBase(object):
"""
Base class for stanza matchers. Stanza matchers are used to pick
stanzas out of the XML stream and pass them to the appropriate
stream handlers.
"""
def __init__(self, criteria):
"""
Create a new stanza matcher.
Arguments:
criteria -- Object to compare some aspect of a stanza
against.
"""
self._criteria = criteria
def match(self, xml):
"""
Check if a stanza matches the stored criteria.
Meant to be overridden.
"""
return False
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/id.py 0000664 0000000 0000000 00000001333 11577753404 0023664 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.matcher.base import MatcherBase
class MatcherId(MatcherBase):
"""
The ID matcher selects stanzas that have the same stanza 'id'
interface value as the desired ID.
Methods:
match -- Overrides MatcherBase.match.
"""
def match(self, xml):
"""
Compare the given stanza's 'id' attribute to the stored
id value.
Overrides MatcherBase.match.
Arguments:
xml -- The stanza to compare against.
"""
return xml['id'] == self._criteria
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/many.py 0000664 0000000 0000000 00000001747 11577753404 0024245 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.matcher.base import MatcherBase
class MatchMany(MatcherBase):
"""
The MatchMany matcher may compare a stanza against multiple
criteria. It is essentially an OR relation combining multiple
matchers.
Each of the criteria must implement a match() method.
Methods:
match -- Overrides MatcherBase.match.
"""
def match(self, xml):
"""
Match a stanza against multiple criteria. The match is successful
if one of the criteria matches.
Each of the criteria must implement a match() method.
Overrides MatcherBase.match.
Arguments:
xml -- The stanza object to compare against.
"""
for m in self._criteria:
if m.match(xml):
return True
return False
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/stanzapath.py 0000664 0000000 0000000 00000002252 11577753404 0025446 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.matcher.base import MatcherBase
class StanzaPath(MatcherBase):
"""
The StanzaPath matcher selects stanzas that match a given "stanza path",
which is similar to a normal XPath except that it uses the interfaces and
plugins of the stanza instead of the actual, underlying XML.
In most cases, the stanza path and XPath should be identical, but be
aware that differences may occur.
Methods:
match -- Overrides MatcherBase.match.
"""
def match(self, stanza):
"""
Compare a stanza against a "stanza path". A stanza path is similar to
an XPath expression, but uses the stanza's interfaces and plugins
instead of the underlying XML. For most cases, the stanza path and
XPath should be identical, but be aware that differences may occur.
Overrides MatcherBase.match.
Arguments:
stanza -- The stanza object to compare against.
"""
return stanza.match(self._criteria)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/xmlmask.py 0000664 0000000 0000000 00000012707 11577753404 0024753 0 ustar 00root root 0000000 0000000 """
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
from xml.parsers.expat import ExpatError
from sleekxmpp.xmlstream.stanzabase import ET
from sleekxmpp.xmlstream.matcher.base import MatcherBase
# Flag indicating if the builtin XPath matcher should be used, which
# uses namespaces, or a custom matcher that ignores namespaces.
# Changing this will affect ALL XMLMask matchers.
IGNORE_NS = False
log = logging.getLogger(__name__)
class MatchXMLMask(MatcherBase):
"""
The XMLMask matcher selects stanzas whose XML matches a given
XML pattern, or mask. For example, message stanzas with body elements
could be matched using the mask:
Use of XMLMask is discouraged, and XPath or StanzaPath should be used
instead.
The use of namespaces in the mask comparison is controlled by
IGNORE_NS. Setting IGNORE_NS to True will disable namespace based matching
for ALL XMLMask matchers.
Methods:
match -- Overrides MatcherBase.match.
setDefaultNS -- Set the default namespace for the mask.
"""
def __init__(self, criteria):
"""
Create a new XMLMask matcher.
Arguments:
criteria -- Either an XML object or XML string to use as a mask.
"""
MatcherBase.__init__(self, criteria)
if isinstance(criteria, str):
self._criteria = ET.fromstring(self._criteria)
self.default_ns = 'jabber:client'
def setDefaultNS(self, ns):
"""
Set the default namespace to use during comparisons.
Arguments:
ns -- The new namespace to use as the default.
"""
self.default_ns = ns
def match(self, xml):
"""
Compare a stanza object or XML object against the stored XML mask.
Overrides MatcherBase.match.
Arguments:
xml -- The stanza object or XML object to compare against.
"""
if hasattr(xml, 'xml'):
xml = xml.xml
return self._mask_cmp(xml, self._criteria, True)
def _mask_cmp(self, source, mask, use_ns=False, default_ns='__no_ns__'):
"""
Compare an XML object against an XML mask.
Arguments:
source -- The XML object to compare against the mask.
mask -- The XML object serving as the mask.
use_ns -- Indicates if namespaces should be respected during
the comparison.
default_ns -- The default namespace to apply to elements that
do not have a specified namespace.
Defaults to "__no_ns__".
"""
use_ns = not IGNORE_NS
if source is None:
# If the element was not found. May happend during recursive calls.
return False
# Convert the mask to an XML object if it is a string.
if not hasattr(mask, 'attrib'):
try:
mask = ET.fromstring(mask)
except ExpatError:
log.warning("Expat error: %s\nIn parsing: %s" % ('', mask))
if not use_ns:
# Compare the element without using namespaces.
source_tag = source.tag.split('}', 1)[-1]
mask_tag = mask.tag.split('}', 1)[-1]
if source_tag != mask_tag:
return False
else:
# Compare the element using namespaces
mask_ns_tag = "{%s}%s" % (self.default_ns, mask.tag)
if source.tag not in [mask.tag, mask_ns_tag]:
return False
# If the mask includes text, compare it.
if mask.text and source.text and \
source.text.strip() != mask.text.strip():
return False
# Compare attributes. The stanza must include the attributes
# defined by the mask, but may include others.
for name, value in mask.attrib.items():
if source.attrib.get(name, "__None__") != value:
return False
# Recursively check subelements.
matched_elements = {}
for subelement in mask:
if use_ns:
matched = False
for other in source.findall(subelement.tag):
matched_elements[other] = False
if self._mask_cmp(other, subelement, use_ns):
if not matched_elements.get(other, False):
matched_elements[other] = True
matched = True
if not matched:
return False
else:
if not self._mask_cmp(self._get_child(source, subelement.tag),
subelement, use_ns):
return False
# Everything matches.
return True
def _get_child(self, xml, tag):
"""
Return a child element given its tag, ignoring namespace values.
Returns None if the child was not found.
Arguments:
xml -- The XML object to search for the given child tag.
tag -- The name of the subelement to find.
"""
tag = tag.split('}')[-1]
try:
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
index = children.index(tag)
except ValueError:
return None
return xml.getchildren()[index]
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/matcher/xpath.py 0000664 0000000 0000000 00000005053 11577753404 0024417 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.stanzabase import ET
from sleekxmpp.xmlstream.matcher.base import MatcherBase
# Flag indicating if the builtin XPath matcher should be used, which
# uses namespaces, or a custom matcher that ignores namespaces.
# Changing this will affect ALL XPath matchers.
IGNORE_NS = False
class MatchXPath(MatcherBase):
"""
The XPath matcher selects stanzas whose XML contents matches a given
XPath expression.
Note that using this matcher may not produce expected behavior when using
attribute selectors. For Python 2.6 and 3.1, the ElementTree find method
does not support the use of attribute selectors. If you need to support
Python 2.6 or 3.1, it might be more useful to use a StanzaPath matcher.
If the value of IGNORE_NS is set to true, then XPath expressions will
be matched without using namespaces.
Methods:
match -- Overrides MatcherBase.match.
"""
def match(self, xml):
"""
Compare a stanza's XML contents to an XPath expression.
If the value of IGNORE_NS is set to true, then XPath expressions
will be matched without using namespaces.
Note that in Python 2.6 and 3.1 the ElementTree find method does
not support attribute selectors in the XPath expression.
Arguments:
xml -- The stanza object to compare against.
"""
if hasattr(xml, 'xml'):
xml = xml.xml
x = ET.Element('x')
x.append(xml)
if not IGNORE_NS:
# Use builtin, namespace respecting, XPath matcher.
if x.find(self._criteria) is not None:
return True
return False
else:
# Remove namespaces from the XPath expression.
criteria = []
for ns_block in self._criteria.split('{'):
criteria.extend(ns_block.split('}')[-1].split('/'))
# Walk the XPath expression.
xml = x
for tag in criteria:
if not tag:
# Skip empty tag name artifacts from the cleanup phase.
continue
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
try:
index = children.index(tag)
except ValueError:
return False
xml = xml.getchildren()[index]
return True
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/scheduler.py 0000664 0000000 0000000 00000016177 11577753404 0023637 0 ustar 00root root 0000000 0000000 """
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 time
import threading
import logging
try:
import queue
except ImportError:
import Queue as queue
log = logging.getLogger(__name__)
class Task(object):
"""
A scheduled task that will be executed by the scheduler
after a given time interval has passed.
Attributes:
name -- The name of the task.
seconds -- The number of seconds to wait before executing.
callback -- The function to execute.
args -- The arguments to pass to the callback.
kwargs -- The keyword arguments to pass to the callback.
repeat -- Indicates if the task should repeat.
Defaults to False.
qpointer -- A pointer to an event queue for queuing callback
execution instead of executing immediately.
Methods:
run -- Either queue or execute the callback.
reset -- Reset the task's timer.
"""
def __init__(self, name, seconds, callback, args=None,
kwargs=None, repeat=False, qpointer=None):
"""
Create a new task.
Arguments:
name -- The name of the task.
seconds -- The number of seconds to wait before executing.
callback -- The function to execute.
args -- The arguments to pass to the callback.
kwargs -- The keyword arguments to pass to the callback.
repeat -- Indicates if the task should repeat.
Defaults to False.
qpointer -- A pointer to an event queue for queuing callback
execution instead of executing immediately.
"""
self.name = name
self.seconds = seconds
self.callback = callback
self.args = args or tuple()
self.kwargs = kwargs or {}
self.repeat = repeat
self.next = time.time() + self.seconds
self.qpointer = qpointer
def run(self):
"""
Execute the task's callback.
If an event queue was supplied, place the callback in the queue;
otherwise, execute the callback immediately.
"""
if self.qpointer is not None:
self.qpointer.put(('schedule', self.callback, self.args))
else:
self.callback(*self.args, **self.kwargs)
self.reset()
return self.repeat
def reset(self):
"""
Reset the task's timer so that it will repeat.
"""
self.next = time.time() + self.seconds
class Scheduler(object):
"""
A threaded scheduler that allows for updates mid-execution unlike the
scheduler in the standard library.
http://docs.python.org/library/sched.html#module-sched
Attributes:
addq -- A queue storing added tasks.
schedule -- A list of tasks in order of execution times.
thread -- If threaded, the thread processing the schedule.
run -- Indicates if the scheduler is running.
parentqueue -- A parent event queue in control of this scheduler.
Methods:
add -- Add a new task to the schedule.
process -- Process and schedule tasks.
quit -- Stop the scheduler.
"""
def __init__(self, parentqueue=None, parentstop=None):
"""
Create a new scheduler.
Arguments:
parentqueue -- A separate event queue controlling this scheduler.
"""
self.addq = queue.Queue()
self.schedule = []
self.thread = None
self.run = False
self.parentqueue = parentqueue
self.parentstop = parentstop
def process(self, threaded=True):
"""
Begin accepting and processing scheduled tasks.
Arguments:
threaded -- Indicates if the scheduler should execute in its own
thread. Defaults to True.
"""
if threaded:
self.thread = threading.Thread(name='sheduler_process',
target=self._process)
self.thread.daemon = True
self.thread.start()
else:
self._process()
def _process(self):
"""Process scheduled tasks."""
self.run = True
try:
while self.run and (self.parentstop is None or \
not self.parentstop.isSet()):
wait = 1
updated = False
if self.schedule:
wait = self.schedule[0].next - time.time()
try:
if wait <= 0.0:
newtask = self.addq.get(False)
else:
if wait >= 3.0:
wait = 3.0
newtask = self.addq.get(True, wait)
except queue.Empty:
cleanup = []
for task in self.schedule:
if time.time() >= task.next:
updated = True
if not task.run():
cleanup.append(task)
else:
break
for task in cleanup:
x = self.schedule.pop(self.schedule.index(task))
else:
updated = True
self.schedule.append(newtask)
finally:
if updated:
self.schedule = sorted(self.schedule,
key=lambda task: task.next)
except KeyboardInterrupt:
self.run = False
if self.parentstop is not None:
log.debug("stopping parent")
self.parentstop.set()
except SystemExit:
self.run = False
if self.parentstop is not None:
self.parentstop.set()
log.debug("Quitting Scheduler thread")
if self.parentqueue is not None:
self.parentqueue.put(('quit', None, None))
def add(self, name, seconds, callback, args=None,
kwargs=None, repeat=False, qpointer=None):
"""
Schedule a new task.
Arguments:
name -- The name of the task.
seconds -- The number of seconds to wait before executing.
callback -- The function to execute.
args -- The arguments to pass to the callback.
kwargs -- The keyword arguments to pass to the callback.
repeat -- Indicates if the task should repeat.
Defaults to False.
qpointer -- A pointer to an event queue for queuing callback
execution instead of executing immediately.
"""
self.addq.put(Task(name, seconds, callback, args,
kwargs, repeat, qpointer))
def quit(self):
"""Shutdown the scheduler."""
self.run = False
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/stanzabase.py 0000664 0000000 0000000 00000136251 11577753404 0024010 0 ustar 00root root 0000000 0000000 """
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 copy
import logging
import sys
import weakref
from xml.etree import cElementTree as ET
from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream.tostring import tostring
from sleekxmpp.thirdparty import OrderedDict
log = logging.getLogger(__name__)
# Used to check if an argument is an XML object.
XML_TYPE = type(ET.Element('xml'))
def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
"""
Associate a stanza object as a plugin for another stanza.
Arguments:
stanza -- The class of the parent stanza.
plugin -- The class of the plugin stanza.
iterable -- Indicates if the plugin stanza should be
included in the parent stanza's iterable
'substanzas' interface results.
overrides -- Indicates if the plugin should be allowed
to override the interface handlers for
the parent stanza.
"""
tag = "{%s}%s" % (plugin.namespace, plugin.name)
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map[tag] = plugin
if iterable:
# Prevent weird memory reference gotchas.
stanza.plugin_iterables = stanza.plugin_iterables.copy()
stanza.plugin_iterables.add(plugin)
if overrides:
# Prevent weird memory reference gotchas.
stanza.plugin_overrides = stanza.plugin_overrides.copy()
for interface in plugin.overrides:
stanza.plugin_overrides[interface] = plugin.plugin_attrib
# To maintain backwards compatibility for now, preserve the camel case name.
registerStanzaPlugin = register_stanza_plugin
class ElementBase(object):
"""
The core of SleekXMPP's stanza XML manipulation and handling is provided
by ElementBase. ElementBase wraps XML cElementTree objects and enables
access to the XML contents through dictionary syntax, similar in style
to the Ruby XMPP library Blather's stanza implementation.
Stanzas are defined by their name, namespace, and interfaces. For
example, a simplistic Message stanza could be defined as:
>>> class Message(ElementBase):
... name = "message"
... namespace = "jabber:client"
... interfaces = set(('to', 'from', 'type', 'body'))
... sub_interfaces = set(('body',))
The resulting Message stanza's contents may be accessed as so:
>>> message['to'] = "user@example.com"
>>> message['body'] = "Hi!"
>>> message['body']
"Hi!"
>>> del message['body']
>>> message['body']
""
The interface values map to either custom access methods, stanza
XML attributes, or (if the interface is also in sub_interfaces) the
text contents of a stanza's subelement.
Custom access methods may be created by adding methods of the
form "getInterface", "setInterface", or "delInterface", where
"Interface" is the titlecase version of the interface name.
Stanzas may be extended through the use of plugins. A plugin
is simply a stanza that has a plugin_attrib value. For example:
>>> class MessagePlugin(ElementBase):
... name = "custom_plugin"
... namespace = "custom"
... interfaces = set(('useful_thing', 'custom'))
... plugin_attrib = "custom"
The plugin stanza class must be associated with its intended
container stanza by using register_stanza_plugin as so:
>>> register_stanza_plugin(Message, MessagePlugin)
The plugin may then be accessed as if it were built-in to the parent
stanza.
>>> message['custom']['useful_thing'] = 'foo'
If a plugin provides an interface that is the same as the plugin's
plugin_attrib value, then the plugin's interface may be assigned
directly from the parent stanza, as shown below, but retrieving
information will require all interfaces to be used, as so:
>>> message['custom'] = 'bar' # Same as using message['custom']['custom']
>>> message['custom']['custom'] # Must use all interfaces
'bar'
If the plugin sets the value is_extension = True, then both setting
and getting an interface value that is the same as the plugin's
plugin_attrib value will work, as so:
>>> message['custom'] = 'bar' # Using is_extension=True
>>> message['custom']
'bar'
Class Attributes:
name -- The name of the stanza's main element.
namespace -- The namespace of the stanza's main element.
interfaces -- A set of attribute and element names that may
be accessed using dictionary syntax.
sub_interfaces -- A subset of the set of interfaces which map
to subelements instead of attributes.
subitem -- A set of stanza classes which are allowed to
be added as substanzas. Deprecated version
of plugin_iterables.
overrides -- A list of interfaces prepended with 'get_',
'set_', or 'del_'. If the stanza is registered
as a plugin with overrides=True, then the
parent's interface handlers will be
overridden by the plugin's matching handler.
types -- A set of generic type attribute values.
tag -- The namespaced name of the stanza's root
element. Example: "{foo_ns}bar"
plugin_attrib -- The interface name that the stanza uses to be
accessed as a plugin from another stanza.
plugin_attrib_map -- A mapping of plugin attribute names with the
associated plugin stanza classes.
plugin_iterables -- A set of stanza classes which are allowed to
be added as substanzas.
plugin_overrides -- A mapping of interfaces prepended with 'get_',
'set_' or 'del_' to plugin attrib names. Allows
a plugin to override the behaviour of a parent
stanza's interface handlers.
plugin_tag_map -- A mapping of plugin stanza tag names with
the associated plugin stanza classes.
is_extension -- When True, allows the stanza to provide one
additional interface to the parent stanza,
extending the interfaces supported by the
parent. Defaults to False.
xml_ns -- The XML namespace,
http://www.w3.org/XML/1998/namespace,
for use with xml:lang values.
Instance Attributes:
xml -- The stanza's XML contents.
parent -- The parent stanza of this stanza.
plugins -- A map of enabled plugin names with the
initialized plugin stanza objects.
values -- A dictionary of the stanza's interfaces
and interface values, including plugins.
Class Methods
tag_name -- Return the namespaced version of the stanza's
root element's name.
Methods:
setup -- Initialize the stanza's XML contents.
enable -- Instantiate a stanza plugin.
Alias for init_plugin.
init_plugin -- Instantiate a stanza plugin.
_get_stanza_values -- Return a dictionary of stanza interfaces and
their values.
_set_stanza_values -- Set stanza interface values given a dictionary
of interfaces and values.
__getitem__ -- Return the value of a stanza interface.
__setitem__ -- Set the value of a stanza interface.
__delitem__ -- Remove the value of a stanza interface.
_set_attr -- Set an attribute value of the main
stanza element.
_del_attr -- Remove an attribute from the main
stanza element.
_get_attr -- Return an attribute's value from the main
stanza element.
_get_sub_text -- Return the text contents of a subelement.
_set_sub_text -- Set the text contents of a subelement.
_del_sub -- Remove a subelement.
match -- Compare the stanza against an XPath expression.
find -- Return subelement matching an XPath expression.
findall -- Return subelements matching an XPath expression.
get -- Return the value of a stanza interface, with an
optional default value.
keys -- Return the set of interface names accepted by
the stanza.
append -- Add XML content or a substanza to the stanza.
appendxml -- Add XML content to the stanza.
pop -- Remove a substanza.
next -- Return the next iterable substanza.
clear -- Reset the stanza's XML contents.
_fix_ns -- Apply the stanza's namespace to non-namespaced
elements in an XPath expression.
"""
name = 'stanza'
plugin_attrib = 'plugin'
namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple()
overrides = {}
plugin_attrib_map = {}
plugin_overrides = {}
plugin_iterables = set()
plugin_tag_map = {}
subitem = set()
is_extension = False
xml_ns = 'http://www.w3.org/XML/1998/namespace'
def __init__(self, xml=None, parent=None):
"""
Create a new stanza object.
Arguments:
xml -- Initialize the stanza with optional existing XML.
parent -- Optional stanza object that contains this stanza.
"""
self.xml = xml
self.plugins = OrderedDict()
self.iterables = []
self._index = 0
self.tag = self.tag_name()
if parent is None:
self.parent = None
else:
self.parent = weakref.ref(parent)
ElementBase.values = property(ElementBase._get_stanza_values,
ElementBase._set_stanza_values)
if self.subitem is not None:
for sub in self.subitem:
self.plugin_iterables.add(sub)
if self.setup(xml):
# If we generated our own XML, then everything is ready.
return
# Initialize values using provided XML
for child in self.xml.getchildren():
if child.tag in self.plugin_tag_map:
plugin = self.plugin_tag_map[child.tag]
self.plugins[plugin.plugin_attrib] = plugin(child, self)
for sub in self.plugin_iterables:
if child.tag == "{%s}%s" % (sub.namespace, sub.name):
self.iterables.append(sub(child, self))
break
def setup(self, xml=None):
"""
Initialize the stanza's XML contents.
Will return True if XML was generated according to the stanza's
definition.
Arguments:
xml -- Optional XML object to use for the stanza's content
instead of generating XML.
"""
if self.xml is None:
self.xml = xml
if self.xml is None:
# Generate XML from the stanza definition
for ename in self.name.split('/'):
new = ET.Element("{%s}%s" % (self.namespace, ename))
if self.xml is None:
self.xml = new
else:
last_xml.append(new)
last_xml = new
if self.parent is not None:
self.parent().xml.append(self.xml)
# We had to generate XML
return True
else:
# We did not generate XML
return False
def enable(self, attrib):
"""
Enable and initialize a stanza plugin.
Alias for init_plugin.
Arguments:
attrib -- The stanza interface for the plugin.
"""
return self.init_plugin(attrib)
def init_plugin(self, attrib):
"""
Enable and initialize a stanza plugin.
Arguments:
attrib -- The stanza interface for the plugin.
"""
if attrib not in self.plugins:
plugin_class = self.plugin_attrib_map[attrib]
self.plugins[attrib] = plugin_class(parent=self)
return self
def _get_stanza_values(self):
"""
Return a dictionary of the stanza's interface values.
Stanza plugin values are included as nested dictionaries.
"""
values = {}
for interface in self.interfaces:
values[interface] = self[interface]
for plugin, stanza in self.plugins.items():
values[plugin] = stanza.values
if self.iterables:
iterables = []
for stanza in self.iterables:
iterables.append(stanza.values)
iterables[-1]['__childtag__'] = stanza.tag
values['substanzas'] = iterables
return values
def _set_stanza_values(self, values):
"""
Set multiple stanza interface values using a dictionary.
Stanza plugin values may be set using nested dictionaries.
Arguments:
values -- A dictionary mapping stanza interface with values.
Plugin interfaces may accept a nested dictionary that
will be used recursively.
"""
iterable_interfaces = [p.plugin_attrib for \
p in self.plugin_iterables]
for interface, value in values.items():
if interface == 'substanzas':
# Remove existing substanzas
for stanza in self.iterables:
self.xml.remove(stanza.xml)
self.iterables = []
# Add new substanzas
for subdict in value:
if '__childtag__' in subdict:
for subclass in self.plugin_iterables:
child_tag = "{%s}%s" % (subclass.namespace,
subclass.name)
if subdict['__childtag__'] == child_tag:
sub = subclass(parent=self)
sub.values = subdict
self.iterables.append(sub)
break
elif interface in self.interfaces:
self[interface] = value
elif interface in self.plugin_attrib_map:
if interface not in iterable_interfaces:
if interface not in self.plugins:
self.init_plugin(interface)
self.plugins[interface].values = value
return self
def __getitem__(self, attrib):
"""
Return the value of a stanza interface using dictionary-like syntax.
Example:
>>> msg['body']
'Message contents'
Stanza interfaces are typically mapped directly to the underlying XML
object, but can be overridden by the presence of a get_attrib method
(or get_foo where the interface is named foo, etc).
The search order for interface value retrieval for an interface
named 'foo' is:
1. The list of substanzas.
2. The result of calling the get_foo override handler.
3. The result of calling get_foo.
4. The result of calling getFoo.
5. The contents of the foo subelement, if foo is a sub interface.
6. The value of the foo attribute of the XML object.
7. The plugin named 'foo'
8. An empty string.
Arguments:
attrib -- The name of the requested stanza interface.
"""
if attrib == 'substanzas':
return self.iterables
elif attrib in self.interfaces:
get_method = "get_%s" % attrib.lower()
get_method2 = "get%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(get_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], get_method, None)
if handler:
return handler()
if hasattr(self, get_method):
return getattr(self, get_method)()
elif hasattr(self, get_method2):
return getattr(self, get_method2)()
else:
if attrib in self.sub_interfaces:
return self._get_sub_text(attrib)
else:
return self._get_attr(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins:
self.init_plugin(attrib)
if self.plugins[attrib].is_extension:
return self.plugins[attrib][attrib]
return self.plugins[attrib]
else:
return ''
def __setitem__(self, attrib, value):
"""
Set the value of a stanza interface using dictionary-like syntax.
Example:
>>> msg['body'] = "Hi!"
>>> msg['body']
'Hi!'
Stanza interfaces are typically mapped directly to the underlying XML
object, but can be overridden by the presence of a set_attrib method
(or set_foo where the interface is named foo, etc).
The effect of interface value assignment for an interface
named 'foo' will be one of:
1. Delete the interface's contents if the value is None.
2. Call the set_foo override handler, if it exists.
3. Call set_foo, if it exists.
4. Call setFoo, if it exists.
5. Set the text of a foo element, if foo is in sub_interfaces.
6. Set the value of a top level XML attribute name foo.
7. Attempt to pass value to a plugin named foo using the plugin's
foo interface.
8. Do nothing.
Arguments:
attrib -- The name of the stanza interface to modify.
value -- The new value of the stanza interface.
"""
if attrib in self.interfaces:
if value is not None:
set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(set_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], set_method, None)
if handler:
return handler(value)
if hasattr(self, set_method):
getattr(self, set_method)(value,)
elif hasattr(self, set_method2):
getattr(self, set_method2)(value,)
else:
if attrib in self.sub_interfaces:
return self._set_sub_text(attrib, text=value)
else:
self._set_attr(attrib, value)
else:
self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins:
self.init_plugin(attrib)
self.plugins[attrib][attrib] = value
return self
def __delitem__(self, attrib):
"""
Delete the value of a stanza interface using dictionary-like syntax.
Example:
>>> msg['body'] = "Hi!"
>>> msg['body']
'Hi!'
>>> del msg['body']
>>> msg['body']
''
Stanza interfaces are typically mapped directly to the underlyig XML
object, but can be overridden by the presence of a del_attrib method
(or del_foo where the interface is named foo, etc).
The effect of deleting a stanza interface value named foo will be
one of:
1. Call del_foo override handler, if it exists.
2. Call del_foo, if it exists.
3. Call delFoo, if it exists.
4. Delete foo element, if foo is in sub_interfaces.
5. Delete top level XML attribute named foo.
6. Remove the foo plugin, if it was loaded.
7. Do nothing.
Arguments:
attrib -- The name of the affected stanza interface.
"""
if attrib in self.interfaces:
del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(del_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], del_method, None)
if handler:
return handler()
if hasattr(self, del_method):
getattr(self, del_method)()
elif hasattr(self, del_method2):
getattr(self, del_method2)()
else:
if attrib in self.sub_interfaces:
return self._del_sub(attrib)
else:
self._del_attr(attrib)
elif attrib in self.plugin_attrib_map:
if attrib in self.plugins:
xml = self.plugins[attrib].xml
if self.plugins[attrib].is_extension:
del self.plugins[attrib][attrib]
del self.plugins[attrib]
try:
self.xml.remove(xml)
except:
pass
return self
def _set_attr(self, name, value):
"""
Set the value of a top level attribute of the underlying XML object.
If the new value is None or an empty string, then the attribute will
be removed.
Arguments:
name -- The name of the attribute.
value -- The new value of the attribute, or None or '' to
remove it.
"""
if value is None or value == '':
self.__delitem__(name)
else:
self.xml.attrib[name] = value
def _del_attr(self, name):
"""
Remove a top level attribute of the underlying XML object.
Arguments:
name -- The name of the attribute.
"""
if name in self.xml.attrib:
del self.xml.attrib[name]
def _get_attr(self, name, default=''):
"""
Return the value of a top level attribute of the underlying
XML object.
In case the attribute has not been set, a default value can be
returned instead. An empty string is returned if no other default
is supplied.
Arguments:
name -- The name of the attribute.
default -- Optional value to return if the attribute has not
been set. An empty string is returned otherwise.
"""
return self.xml.attrib.get(name, default)
def _get_sub_text(self, name, default=''):
"""
Return the text contents of a sub element.
In case the element does not exist, or it has no textual content,
a default value can be returned instead. An empty string is returned
if no other default is supplied.
Arguments:
name -- The name or XPath expression of the element.
default -- Optional default to return if the element does
not exists. An empty string is returned otherwise.
"""
name = self._fix_ns(name)
stanza = self.xml.find(name)
if stanza is None or stanza.text is None:
return default
else:
return stanza.text
def _set_sub_text(self, name, text=None, keep=False):
"""
Set the text contents of a sub element.
In case the element does not exist, a element will be created,
and its text contents will be set.
If the text is set to an empty string, or None, then the
element will be removed, unless keep is set to True.
Arguments:
name -- The name or XPath expression of the element.
text -- The new textual content of the element. If the text
is an empty string or None, the element will be removed
unless the parameter keep is True.
keep -- Indicates if the element should be kept if its text is
removed. Defaults to False.
"""
path = self._fix_ns(name, split=True)
element = self.xml.find(name)
if not text and not keep:
return self._del_sub(name)
if element is None:
# We need to add the element. If the provided name was
# an XPath expression, some of the intermediate elements
# may already exist. If so, we want to use those instead
# of generating new elements.
last_xml = self.xml
walked = []
for ename in path:
walked.append(ename)
element = self.xml.find("/".join(walked))
if element is None:
element = ET.Element(ename)
last_xml.append(element)
last_xml = element
element = last_xml
element.text = text
return element
def _del_sub(self, name, all=False):
"""
Remove sub elements that match the given name or XPath.
If the element is in a path, then any parent elements that become
empty after deleting the element may also be deleted if requested
by setting all=True.
Arguments:
name -- The name or XPath expression for the element(s) to remove.
all -- If True, remove all empty elements in the path to the
deleted element. Defaults to False.
"""
path = self._fix_ns(name, split=True)
original_target = path[-1]
for level, _ in enumerate(path):
# Generate the paths to the target elements and their parent.
element_path = "/".join(path[:len(path) - level])
parent_path = "/".join(path[:len(path) - level - 1])
elements = self.xml.findall(element_path)
parent = self.xml.find(parent_path)
if elements:
if parent is None:
parent = self.xml
for element in elements:
if element.tag == original_target or \
not element.getchildren():
# Only delete the originally requested elements, and
# any parent elements that have become empty.
parent.remove(element)
if not all:
# If we don't want to delete elements up the tree, stop
# after deleting the first level of elements.
return
def match(self, xpath):
"""
Compare a stanza object with an XPath expression. If the XPath matches
the contents of the stanza object, the match is successful.
The XPath expression may include checks for stanza attributes.
For example:
presence@show=xa@priority=2/status
Would match a presence stanza whose show value is set to 'xa', has a
priority value of '2', and has a status element.
Arguments:
xpath -- The XPath expression to check against. It may be either a
string or a list of element names with attribute checks.
"""
if isinstance(xpath, str):
xpath = self._fix_ns(xpath, split=True, propagate_ns=False)
# Extract the tag name and attribute checks for the first XPath node.
components = xpath[0].split('@')
tag = components[0]
attributes = components[1:]
if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
tag not in self.plugins and tag not in self.plugin_attrib:
# The requested tag is not in this stanza, so no match.
return False
# Check the rest of the XPath against any substanzas.
matched_substanzas = False
for substanza in self.iterables:
if xpath[1:] == []:
break
matched_substanzas = substanza.match(xpath[1:])
if matched_substanzas:
break
# Check attribute values.
for attribute in attributes:
name, value = attribute.split('=')
if self[name] != value:
return False
# Check sub interfaces.
if len(xpath) > 1:
next_tag = xpath[1]
if next_tag in self.sub_interfaces and self[next_tag]:
return True
# Attempt to continue matching the XPath using the stanza's plugins.
if not matched_substanzas and len(xpath) > 1:
# Convert {namespace}tag@attribs to just tag
next_tag = xpath[1].split('@')[0].split('}')[-1]
if next_tag in self.plugins:
return self.plugins[next_tag].match(xpath[1:])
else:
return False
# Everything matched.
return True
def find(self, xpath):
"""
Find an XML object in this stanza given an XPath expression.
Exposes ElementTree interface for backwards compatibility.
Note that matching on attribute values is not supported in Python 2.6
or Python 3.1
Arguments:
xpath -- An XPath expression matching a single desired element.
"""
return self.xml.find(xpath)
def findall(self, xpath):
"""
Find multiple XML objects in this stanza given an XPath expression.
Exposes ElementTree interface for backwards compatibility.
Note that matching on attribute values is not supported in Python 2.6
or Python 3.1.
Arguments:
xpath -- An XPath expression matching multiple desired elements.
"""
return self.xml.findall(xpath)
def get(self, key, default=None):
"""
Return the value of a stanza interface. If the found value is None
or an empty string, return the supplied default value.
Allows stanza objects to be used like dictionaries.
Arguments:
key -- The name of the stanza interface to check.
default -- Value to return if the stanza interface has a value
of None or "". Will default to returning None.
"""
value = self[key]
if value is None or value == '':
return default
return value
def keys(self):
"""
Return the names of all stanza interfaces provided by the
stanza object.
Allows stanza objects to be used like dictionaries.
"""
out = []
out += [x for x in self.interfaces]
out += [x for x in self.plugins]
if self.iterables:
out.append('substanzas')
return out
def append(self, item):
"""
Append either an XML object or a substanza to this stanza object.
If a substanza object is appended, it will be added to the list
of iterable stanzas.
Allows stanza objects to be used like lists.
Arguments:
item -- Either an XML object or a stanza object to add to
this stanza's contents.
"""
if not isinstance(item, ElementBase):
if type(item) == XML_TYPE:
return self.appendxml(item)
else:
raise TypeError
self.xml.append(item.xml)
self.iterables.append(item)
return self
def appendxml(self, xml):
"""
Append an XML object to the stanza's XML.
The added XML will not be included in the list of
iterable substanzas.
Arguments:
xml -- The XML object to add to the stanza.
"""
self.xml.append(xml)
return self
def pop(self, index=0):
"""
Remove and return the last substanza in the list of
iterable substanzas.
Allows stanza objects to be used like lists.
Arguments:
index -- The index of the substanza to remove.
"""
substanza = self.iterables.pop(index)
self.xml.remove(substanza.xml)
return substanza
def next(self):
"""
Return the next iterable substanza.
"""
return self.__next__()
def clear(self):
"""
Remove all XML element contents and plugins.
Any attribute values will be preserved.
"""
for child in self.xml.getchildren():
self.xml.remove(child)
for plugin in list(self.plugins.keys()):
del self.plugins[plugin]
return self
@classmethod
def tag_name(cls):
"""
Return the namespaced name of the stanza's root element.
For example, for the stanza ,
stanza.tag would return "{bar}foo".
"""
return "{%s}%s" % (cls.namespace, cls.name)
@property
def attrib(self):
"""
DEPRECATED
For backwards compatibility, stanza.attrib returns the stanza itself.
Older implementations of stanza objects used XML objects directly,
requiring the use of .attrib to access attribute values.
Use of the dictionary syntax with the stanza object itself for
accessing stanza interfaces is preferred.
"""
return self
def _fix_ns(self, xpath, split=False, propagate_ns=True):
"""
Apply the stanza's namespace to elements in an XPath expression.
Arguments:
xpath -- The XPath expression to fix with namespaces.
split -- Indicates if the fixed XPath should be left as a
list of element names with namespaces. Defaults to
False, which returns a flat string path.
propagate_ns -- Overrides propagating parent element namespaces
to child elements. Useful if you wish to simply
split an XPath that has non-specified namespaces,
and child and parent namespaces are known not to
always match. Defaults to True.
"""
fixed = []
# Split the XPath into a series of blocks, where a block
# is started by an element with a namespace.
ns_blocks = xpath.split('{')
for ns_block in ns_blocks:
if '}' in ns_block:
# Apply the found namespace to following elements
# that do not have namespaces.
namespace = ns_block.split('}')[0]
elements = ns_block.split('}')[1].split('/')
else:
# Apply the stanza's namespace to the following
# elements since no namespace was provided.
namespace = self.namespace
elements = ns_block.split('/')
for element in elements:
if element:
# Skip empty entry artifacts from splitting.
if propagate_ns:
tag = '{%s}%s' % (namespace, element)
else:
tag = element
fixed.append(tag)
if split:
return fixed
return '/'.join(fixed)
def __eq__(self, other):
"""
Compare the stanza object with another to test for equality.
Stanzas are equal if their interfaces return the same values,
and if they are both instances of ElementBase.
Arguments:
other -- The stanza object to compare against.
"""
if not isinstance(other, ElementBase):
return False
# Check that this stanza is a superset of the other stanza.
values = self.values
for key in other.keys():
if key not in values or values[key] != other[key]:
return False
# Check that the other stanza is a superset of this stanza.
values = other.values
for key in self.keys():
if key not in values or values[key] != self[key]:
return False
# Both stanzas are supersets of each other, therefore they
# must be equal.
return True
def __ne__(self, other):
"""
Compare the stanza object with another to test for inequality.
Stanzas are not equal if their interfaces return different values,
or if they are not both instances of ElementBase.
Arguments:
other -- The stanza object to compare against.
"""
return not self.__eq__(other)
def __bool__(self):
"""
Stanza objects should be treated as True in boolean contexts.
Python 3.x version.
"""
return True
def __nonzero__(self):
"""
Stanza objects should be treated as True in boolean contexts.
Python 2.x version.
"""
return True
def __len__(self):
"""
Return the number of iterable substanzas contained in this stanza.
"""
return len(self.iterables)
def __iter__(self):
"""
Return an iterator object for iterating over the stanza's substanzas.
The iterator is the stanza object itself. Attempting to use two
iterators on the same stanza at the same time is discouraged.
"""
self._index = 0
return self
def __next__(self):
"""
Return the next iterable substanza.
"""
self._index += 1
if self._index > len(self.iterables):
self._index = 0
raise StopIteration
return self.iterables[self._index - 1]
def __copy__(self):
"""
Return a copy of the stanza object that does not share the same
underlying XML object.
"""
return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
def __str__(self, top_level_ns=True):
"""
Return a string serialization of the underlying XML object.
Arguments:
top_level_ns -- Display the top-most namespace.
Defaults to True.
"""
stanza_ns = '' if top_level_ns else self.namespace
return tostring(self.xml, xmlns='', stanza_ns=stanza_ns)
def __repr__(self):
"""
Use the stanza's serialized XML as its representation.
"""
return self.__str__()
class StanzaBase(ElementBase):
"""
StanzaBase provides the foundation for all other stanza objects used by
SleekXMPP, and defines a basic set of interfaces common to nearly
all stanzas. These interfaces are the 'id', 'type', 'to', and 'from'
attributes. An additional interface, 'payload', is available to access
the XML contents of the stanza. Most stanza objects will provided more
specific interfaces, however.
Stanza Interface:
from -- A JID object representing the sender's JID.
id -- An optional id value that can be used to associate stanzas
with their replies.
payload -- The XML contents of the stanza.
to -- A JID object representing the recipient's JID.
type -- The type of stanza, typically will be 'normal', 'error',
'get', or 'set', etc.
Attributes:
stream -- The XMLStream instance that will handle sending this stanza.
Methods:
set_type -- Set the type of the stanza.
get_to -- Return the stanza recipients JID.
set_to -- Set the stanza recipient's JID.
get_from -- Return the stanza sender's JID.
set_from -- Set the stanza sender's JID.
get_payload -- Return the stanza's XML contents.
set_payload -- Append to the stanza's XML contents.
del_payload -- Remove the stanza's XML contents.
reply -- Reset the stanza and modify the 'to' and 'from'
attributes to prepare for sending a reply.
error -- Set the stanza's type to 'error'.
unhandled -- Callback for when the stanza is not handled by a
stream handler.
exception -- Callback for if an exception is raised while
handling the stanza.
send -- Send the stanza using the stanza's stream.
"""
name = 'stanza'
namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple()
def __init__(self, stream=None, xml=None, stype=None,
sto=None, sfrom=None, sid=None):
"""
Create a new stanza.
Arguments:
stream -- Optional XMLStream responsible for sending this stanza.
xml -- Optional XML contents to initialize stanza values.
stype -- Optional stanza type value.
sto -- Optional string or JID object of the recipient's JID.
sfrom -- Optional string or JID object of the sender's JID.
sid -- Optional ID value for the stanza.
"""
self.stream = stream
if stream is not None:
self.namespace = stream.default_ns
ElementBase.__init__(self, xml)
if stype is not None:
self['type'] = stype
if sto is not None:
self['to'] = sto
if sfrom is not None:
self['from'] = sfrom
self.tag = "{%s}%s" % (self.namespace, self.name)
def set_type(self, value):
"""
Set the stanza's 'type' attribute.
Only type values contained in StanzaBase.types are accepted.
Arguments:
value -- One of the values contained in StanzaBase.types
"""
if value in self.types:
self.xml.attrib['type'] = value
return self
def get_to(self):
"""Return the value of the stanza's 'to' attribute."""
return JID(self._get_attr('to'))
def set_to(self, value):
"""
Set the 'to' attribute of the stanza.
Arguments:
value -- A string or JID object representing the recipient's JID.
"""
return self._set_attr('to', str(value))
def get_from(self):
"""Return the value of the stanza's 'from' attribute."""
return JID(self._get_attr('from'))
def set_from(self, value):
"""
Set the 'from' attribute of the stanza.
Arguments:
from -- A string or JID object representing the sender's JID.
"""
return self._set_attr('from', str(value))
def get_payload(self):
"""Return a list of XML objects contained in the stanza."""
return self.xml.getchildren()
def set_payload(self, value):
"""
Add XML content to the stanza.
Arguments:
value -- Either an XML or a stanza object, or a list
of XML or stanza objects.
"""
if not isinstance(value, list):
value = [value]
for val in value:
self.append(val)
return self
def del_payload(self):
"""Remove the XML contents of the stanza."""
self.clear()
return self
def reply(self, clear=True):
"""
Swap the 'from' and 'to' attributes to prepare the stanza for
sending a reply. If clear=True, then also remove the stanza's
contents to make room for the reply content.
For client streams, the 'from' attribute is removed.
Arguments:
clear -- Indicates if the stanza's contents should be
removed. Defaults to True
"""
# if it's a component, use from
if self.stream and hasattr(self.stream, "is_component") and \
self.stream.is_component:
self['from'], self['to'] = self['to'], self['from']
else:
self['to'] = self['from']
del self['from']
if clear:
self.clear()
return self
def error(self):
"""Set the stanza's type to 'error'."""
self['type'] = 'error'
return self
def unhandled(self):
"""
Called when no handlers have been registered to process this
stanza.
Meant to be overridden.
"""
pass
def exception(self, e):
"""
Handle exceptions raised during stanza processing.
Meant to be overridden.
"""
log.exception('Error handling {%s}%s stanza' % (self.namespace,
self.name))
def send(self, now=False):
"""
Queue the stanza to be sent on the XML stream.
Arguments:
now -- Indicates if the queue should be skipped and the
stanza sent immediately. Useful for stream
initialization. Defaults to False.
"""
self.stream.send_raw(self.__str__(), now=now)
def __copy__(self):
"""
Return a copy of the stanza object that does not share the
same underlying XML object, but does share the same XML stream.
"""
return self.__class__(xml=copy.deepcopy(self.xml),
stream=self.stream)
def __str__(self, top_level_ns=False):
"""
Serialize the stanza's XML to a string.
Arguments:
top_level_ns -- Display the top-most namespace.
Defaults to False.
"""
stanza_ns = '' if top_level_ns else self.namespace
return tostring(self.xml, xmlns='',
stanza_ns=stanza_ns,
stream=self.stream)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
ElementBase.initPlugin = ElementBase.init_plugin
ElementBase._getAttr = ElementBase._get_attr
ElementBase._setAttr = ElementBase._set_attr
ElementBase._delAttr = ElementBase._del_attr
ElementBase._getSubText = ElementBase._get_sub_text
ElementBase._setSubText = ElementBase._set_sub_text
ElementBase._delSub = ElementBase._del_sub
ElementBase.getStanzaValues = ElementBase._get_stanza_values
ElementBase.setStanzaValues = ElementBase._set_stanza_values
StanzaBase.setType = StanzaBase.set_type
StanzaBase.getTo = StanzaBase.get_to
StanzaBase.setTo = StanzaBase.set_to
StanzaBase.getFrom = StanzaBase.get_from
StanzaBase.setFrom = StanzaBase.set_from
StanzaBase.getPayload = StanzaBase.get_payload
StanzaBase.setPayload = StanzaBase.set_payload
StanzaBase.delPayload = StanzaBase.del_payload
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/test.py 0000664 0000000 0000000 00000001073 11577753404 0022625 0 ustar 00root root 0000000 0000000 import xmlstream
import time
import socket
from handler.callback import Callback
from matcher.xpath import MatchXPath
def server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', 5228))
s.listen(1)
servers = []
while True:
conn, addr = s.accept()
server = xmlstream.XMLStream(conn, 'localhost', 5228)
server.registerHandler(Callback('test', MatchXPath('test'), testHandler))
server.process()
servers.append(server)
def testHandler(xml):
print("weeeeeeeee!")
server()
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/test.xml 0000664 0000000 0000000 00000000023 11577753404 0022767 0 ustar 00root root 0000000 0000000
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/testclient.py 0000664 0000000 0000000 00000000356 11577753404 0024027 0 ustar 00root root 0000000 0000000 import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 5228))
s.send("")
#s.flush()
s.send("")
s.send("")
s.send("")
s.send("")
#s.flush()
s.close()
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/tostring/ 0000775 0000000 0000000 00000000000 11577753404 0023144 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/tostring/__init__.py 0000664 0000000 0000000 00000001031 11577753404 0025250 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import sys
# Import the correct tostring and xml_escape functions based on the Python
# version in order to properly handle Unicode.
if sys.version_info < (3, 0):
from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape
else:
from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape
__all__ = ['tostring', 'xml_escape']
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/tostring/tostring.py 0000664 0000000 0000000 00000007026 11577753404 0025374 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
"""
Serialize an XML object to a Unicode string.
If namespaces are provided using xmlns or stanza_ns, then elements
that use those namespaces will not include the xmlns attribute in
the output.
Arguments:
xml -- The XML object to serialize. If the value is None,
then the XML object contained in this stanza
object will be used.
xmlns -- Optional namespace of an element wrapping the XML
object.
stanza_ns -- The namespace of the stanza object that contains
the XML object.
stream -- The XML stream that generated the XML object.
outbuffer -- Optional buffer for storing serializations during
recursive calls.
"""
# Add previous results to the start of the output.
output = [outbuffer]
# Extract the element's tag name.
tag_name = xml.tag.split('}', 1)[-1]
# Extract the element's namespace if it is defined.
if '}' in xml.tag:
tag_xmlns = xml.tag.split('}', 1)[0][1:]
else:
tag_xmlns = ''
# Output the tag name and derived namespace of the element.
namespace = ''
if tag_xmlns not in ['', xmlns, stanza_ns]:
namespace = ' xmlns="%s"' % tag_xmlns
if stream and tag_xmlns in stream.namespace_map:
mapped_namespace = stream.namespace_map[tag_xmlns]
if mapped_namespace:
tag_name = "%s:%s" % (mapped_namespace, tag_name)
output.append("<%s" % tag_name)
output.append(namespace)
# Output escaped attribute values.
for attrib, value in xml.attrib.items():
value = xml_escape(value)
if '}' not in attrib:
output.append(' %s="%s"' % (attrib, value))
else:
attrib_ns = attrib.split('}')[0][1:]
attrib = attrib.split('}')[1]
if stream and attrib_ns in stream.namespace_map:
mapped_ns = stream.namespace_map[attrib_ns]
if mapped_ns:
output.append(' %s:%s="%s"' % (mapped_ns,
attrib,
value))
if len(xml) or xml.text:
# If there are additional child elements to serialize.
output.append(">")
if xml.text:
output.append(xml_escape(xml.text))
if len(xml):
for child in xml.getchildren():
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
output.append("%s>" % tag_name)
elif xml.text:
# If we only have text content.
output.append(">%s%s>" % (xml_escape(xml.text), tag_name))
else:
# Empty element.
output.append(" />")
if xml.tail:
# If there is additional text after the element.
output.append(xml_escape(xml.tail))
return ''.join(output)
def xml_escape(text):
"""
Convert special characters in XML to escape sequences.
Arguments:
text -- The XML text to convert.
"""
text = list(text)
escapes = {'&': '&',
'<': '<',
'>': '>',
"'": ''',
'"': '"'}
for i, c in enumerate(text):
text[i] = escapes.get(c, c)
return ''.join(text)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/tostring/tostring26.py 0000664 0000000 0000000 00000007315 11577753404 0025545 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import unicode_literals
import types
def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
"""
Serialize an XML object to a Unicode string.
If namespaces are provided using xmlns or stanza_ns, then elements
that use those namespaces will not include the xmlns attribute in
the output.
Arguments:
xml -- The XML object to serialize. If the value is None,
then the XML object contained in this stanza
object will be used.
xmlns -- Optional namespace of an element wrapping the XML
object.
stanza_ns -- The namespace of the stanza object that contains
the XML object.
stream -- The XML stream that generated the XML object.
outbuffer -- Optional buffer for storing serializations during
recursive calls.
"""
# Add previous results to the start of the output.
output = [outbuffer]
# Extract the element's tag name.
tag_name = xml.tag.split('}', 1)[-1]
# Extract the element's namespace if it is defined.
if '}' in xml.tag:
tag_xmlns = xml.tag.split('}', 1)[0][1:]
else:
tag_xmlns = u''
# Output the tag name and derived namespace of the element.
namespace = u''
if tag_xmlns not in ['', xmlns, stanza_ns]:
namespace = u' xmlns="%s"' % tag_xmlns
if stream and tag_xmlns in stream.namespace_map:
mapped_namespace = stream.namespace_map[tag_xmlns]
if mapped_namespace:
tag_name = u"%s:%s" % (mapped_namespace, tag_name)
output.append(u"<%s" % tag_name)
output.append(namespace)
# Output escaped attribute values.
for attrib, value in xml.attrib.items():
value = xml_escape(value)
if '}' not in attrib:
output.append(' %s="%s"' % (attrib, value))
else:
attrib_ns = attrib.split('}')[0][1:]
attrib = attrib.split('}')[1]
if stream and attrib_ns in stream.namespace_map:
mapped_ns = stream.namespace_map[attrib_ns]
if mapped_ns:
output.append(' %s:%s="%s"' % (mapped_ns,
attrib,
value))
if len(xml) or xml.text:
# If there are additional child elements to serialize.
output.append(u">")
if xml.text:
output.append(xml_escape(xml.text))
if len(xml):
for child in xml.getchildren():
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
output.append(u"%s>" % tag_name)
elif xml.text:
# If we only have text content.
output.append(u">%s%s>" % (xml_escape(xml.text), tag_name))
else:
# Empty element.
output.append(u" />")
if xml.tail:
# If there is additional text after the element.
output.append(xml_escape(xml.tail))
return u''.join(output)
def xml_escape(text):
"""
Convert special characters in XML to escape sequences.
Arguments:
text -- The XML text to convert.
"""
if type(text) != types.UnicodeType:
text = list(unicode(text, 'utf-8', 'ignore'))
else:
text = list(text)
escapes = {u'&': u'&',
u'<': u'<',
u'>': u'>',
u"'": u''',
u'"': u'"'}
for i, c in enumerate(text):
text[i] = escapes.get(c, c)
return u''.join(text)
fritzy-SleekXMPP-5c4ee57/sleekxmpp/xmlstream/xmlstream.py 0000664 0000000 0000000 00000123104 11577753404 0023662 0 ustar 00root root 0000000 0000000 """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement, unicode_literals
import copy
import logging
import signal
import socket as Socket
import ssl
import sys
import threading
import time
import types
import random
try:
import queue
except ImportError:
import Queue as queue
from sleekxmpp.thirdparty.statemachine import StateMachine
from sleekxmpp.xmlstream import Scheduler, tostring
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
from sleekxmpp.xmlstream.handler import Waiter, XMLCallback
from sleekxmpp.xmlstream.matcher import MatchXMLMask
# In Python 2.x, file socket objects are broken. A patched socket
# wrapper is provided for this case in filesocket.py.
if sys.version_info < (3, 0):
from sleekxmpp.xmlstream.filesocket import FileSocket, Socket26
# The time in seconds to wait before timing out waiting for response stanzas.
RESPONSE_TIMEOUT = 10
# The number of threads to use to handle XML stream events. This is not the
# same as the number of custom event handling threads. HANDLER_THREADS must
# be at least 1.
HANDLER_THREADS = 1
# Flag indicating if the SSL library is available for use.
SSL_SUPPORT = True
# Maximum time to delay between connection attempts is one hour.
RECONNECT_MAX_DELAY = 3600
log = logging.getLogger(__name__)
class RestartStream(Exception):
"""
Exception to restart stream processing, including
resending the stream header.
"""
class XMLStream(object):
"""
An XML stream connection manager and event dispatcher.
The XMLStream class abstracts away the issues of establishing a
connection with a server and sending and receiving XML "stanzas".
A stanza is a complete XML element that is a direct child of a root
document element. Two streams are used, one for each communication
direction, over the same socket. Once the connection is closed, both
streams should be complete and valid XML documents.
Three types of events are provided to manage the stream:
Stream -- Triggered based on received stanzas, similar in concept
to events in a SAX XML parser.
Custom -- Triggered manually.
Scheduled -- Triggered based on time delays.
Typically, stanzas are first processed by a stream event handler which
will then trigger custom events to continue further processing,
especially since custom event handlers may run in individual threads.
Attributes:
address -- The hostname and port of the server.
default_ns -- The default XML namespace that will be applied
to all non-namespaced stanzas.
event_queue -- A queue of stream, custom, and scheduled
events to be processed.
filesocket -- A filesocket created from the main connection socket.
Required for ElementTree.iterparse.
namespace_map -- Optional mapping of namespaces to namespace prefixes.
scheduler -- A scheduler object for triggering events
after a given period of time.
send_queue -- A queue of stanzas to be sent on the stream.
socket -- The connection to the server.
ssl_support -- Indicates if a SSL library is available for use.
ssl_version -- The version of the SSL protocol to use.
Defaults to ssl.PROTOCOL_TLSv1.
ca_certs -- File path to a CA certificate to verify the
server's identity.
state -- A state machine for managing the stream's
connection state.
stream_footer -- The start tag and any attributes for the stream's
root element.
stream_header -- The closing tag of the stream's root element.
use_ssl -- Flag indicating if SSL should be used.
use_tls -- Flag indicating if TLS should be used.
stop -- threading Event used to stop all threads.
auto_reconnect -- Flag to determine whether we auto reconnect.
reconnect_max_delay -- Maximum time to delay between connection
attempts. Defaults to RECONNECT_MAX_DELAY,
which is one hour.
Methods:
add_event_handler -- Add a handler for a custom event.
add_handler -- Shortcut method for registerHandler.
connect -- Connect to the given server.
del_event_handler -- Remove a handler for a custom event.
disconnect -- Disconnect from the server and terminate
processing.
event -- Trigger a custom event.
get_id -- Return the current stream ID.
incoming_filter -- Optionally filter stanzas before processing.
new_id -- Generate a new, unique ID value.
process -- Read XML stanzas from the stream and apply
matching stream handlers.
reconnect -- Reestablish a connection to the server.
register_handler -- Add a handler for a stream event.
register_stanza -- Add a new stanza object type that may appear
as a direct child of the stream's root.
remove_handler -- Remove a stream handler.
remove_stanza -- Remove a stanza object type.
schedule -- Schedule an event handler to execute after a
given delay.
send -- Send a stanza object on the stream.
send_raw -- Send a raw string on the stream.
send_xml -- Send an XML string on the stream.
set_socket -- Set the stream's socket and generate a new
filesocket.
start_stream_handler -- Perform any stream initialization such
as handshakes.
start_tls -- Establish a TLS connection and restart
the stream.
"""
def __init__(self, socket=None, host='', port=0):
"""
Establish a new XML stream.
Arguments:
socket -- Use an existing socket for the stream.
Defaults to None to generate a new socket.
host -- The name of the target server.
Defaults to the empty string.
port -- The port to use for the connection.
Defaults to 0.
"""
self.ssl_support = SSL_SUPPORT
self.ssl_version = ssl.PROTOCOL_TLSv1
self.ca_certs = None
self.response_timeout = RESPONSE_TIMEOUT
self.reconnect_delay = None
self.reconnect_max_delay = RECONNECT_MAX_DELAY
self.state = StateMachine(('disconnected', 'connected'))
self.state._set_state('disconnected')
self.address = (host, int(port))
self.filesocket = None
self.set_socket(socket)
if sys.version_info < (3, 0):
self.socket_class = Socket26
else:
self.socket_class = Socket.socket
self.use_ssl = False
self.use_tls = False
self.default_ns = ''
self.stream_header = ""
self.stream_footer = ""
self.stop = threading.Event()
self.stream_end_event = threading.Event()
self.stream_end_event.set()
self.session_started_event = threading.Event()
self.event_queue = queue.Queue()
self.send_queue = queue.Queue()
self.__failed_send_stanza = None
self.scheduler = Scheduler(self.event_queue, self.stop)
self.namespace_map = {StanzaBase.xml_ns: 'xml'}
self.__thread = {}
self.__root_stanza = []
self.__handlers = []
self.__event_handlers = {}
self.__event_handlers_lock = threading.Lock()
self._id = 0
self._id_lock = threading.Lock()
self.auto_reconnect = True
self.is_client = False
def use_signals(self, signals=None):
"""
Register signal handlers for SIGHUP and SIGTERM, if possible,
which will raise a "killed" event when the application is
terminated.
If a signal handler already existed, it will be executed first,
before the "killed" event is raised.
Arguments:
signals -- A list of signal names to be monitored.
Defaults to ['SIGHUP', 'SIGTERM'].
"""
if signals is None:
signals = ['SIGHUP', 'SIGTERM']
existing_handlers = {}
for sig_name in signals:
if hasattr(signal, sig_name):
sig = getattr(signal, sig_name)
handler = signal.getsignal(sig)
if handler:
existing_handlers[sig] = handler
def handle_kill(signum, frame):
"""
Capture kill event and disconnect cleanly after first
spawning the "killed" event.
"""
if signum in existing_handlers and \
existing_handlers[signum] != handle_kill:
existing_handlers[signum](signum, frame)
self.event("killed", direct=True)
self.disconnect()
try:
for sig_name in signals:
if hasattr(signal, sig_name):
sig = getattr(signal, sig_name)
signal.signal(sig, handle_kill)
self.__signals_installed = True
except:
log.debug("Can not set interrupt signal handlers. " + \
"SleekXMPP is not running from a main thread.")
def new_id(self):
"""
Generate and return a new stream ID in hexadecimal form.
Many stanzas, handlers, or matchers may require unique
ID values. Using this method ensures that all new ID values
are unique in this stream.
"""
with self._id_lock:
self._id += 1
return self.get_id()
def get_id(self):
"""
Return the current unique stream ID in hexadecimal form.
"""
return "%X" % self._id
def connect(self, host='', port=0, use_ssl=False,
use_tls=True, reattempt=True):
"""
Create a new socket and connect to the server.
Setting reattempt to True will cause connection attempts to be made
every second until a successful connection is established.
Arguments:
host -- The name of the desired server for the connection.
port -- Port to connect to on the server.
use_ssl -- Flag indicating if SSL should be used.
use_tls -- Flag indicating if TLS should be used.
reattempt -- Flag indicating if the socket should reconnect
after disconnections.
"""
if host and port:
self.address = (host, int(port))
self.is_client = True
# Respect previous SSL and TLS usage directives.
if use_ssl is not None:
self.use_ssl = use_ssl
if use_tls is not None:
self.use_tls = use_tls
# Repeatedly attempt to connect until a successful connection
# is established.
connected = self.state.transition('disconnected', 'connected',
func=self._connect)
while reattempt and not connected:
connected = self.state.transition('disconnected', 'connected',
func=self._connect)
return connected
def _connect(self):
self.stop.clear()
self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM)
self.socket.settimeout(None)
if self.reconnect_delay is None:
delay = 1.0
else:
delay = min(self.reconnect_delay * 2, self.reconnect_max_delay)
delay = random.normalvariate(delay, delay * 0.1)
log.debug('Waiting %s seconds before connecting.' % delay)
time.sleep(delay)
if self.use_ssl and self.ssl_support:
log.debug("Socket Wrapped for SSL")
if self.ca_certs is None:
cert_policy = ssl.CERT_NONE
else:
cert_policy = ssl.CERT_REQUIRED
ssl_socket = ssl.wrap_socket(self.socket,
ca_certs=self.ca_certs,
cert_reqs=cert_policy)
if hasattr(self.socket, 'socket'):
# We are using a testing socket, so preserve the top
# layer of wrapping.
self.socket.socket = ssl_socket
else:
self.socket = ssl_socket
try:
log.debug("Connecting to %s:%s" % self.address)
self.socket.connect(self.address)
self.set_socket(self.socket, ignore=True)
#this event is where you should set your application state
self.event("connected", direct=True)
self.reconnect_delay = 1.0
return True
except Socket.error as serr:
error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
self.event('socket_error', serr)
log.error(error_msg % (self.address[0], self.address[1],
serr.errno, serr.strerror))
self.reconnect_delay = delay
return False
def disconnect(self, reconnect=False):
"""
Terminate processing and close the XML streams.
Optionally, the connection may be reconnected and
resume processing afterwards.
Arguments:
reconnect -- Flag indicating if the connection
and processing should be restarted.
Defaults to False.
"""
self.state.transition('connected', 'disconnected', wait=0.0,
func=self._disconnect, args=(reconnect,))
def _disconnect(self, reconnect=False):
# Send the end of stream marker.
self.send_raw(self.stream_footer, now=True)
self.session_started_event.clear()
# Wait for confirmation that the stream was
# closed in the other direction.
self.auto_reconnect = reconnect
self.stream_end_event.wait(4)
if not self.auto_reconnect:
self.stop.set()
try:
self.socket.close()
self.filesocket.close()
self.socket.shutdown(Socket.SHUT_RDWR)
except Socket.error as serr:
self.event('socket_error', serr)
finally:
#clear your application state
self.event('session_end', direct=True)
self.event("disconnected", direct=True)
return True
def reconnect(self):
"""
Reset the stream's state and reconnect to the server.
"""
log.debug("reconnecting...")
self.state.transition('connected', 'disconnected', wait=2.0,
func=self._disconnect, args=(True,))
log.debug("connecting...")
return self.state.transition('disconnected', 'connected',
wait=2.0, func=self._connect)
def set_socket(self, socket, ignore=False):
"""
Set the socket to use for the stream.
The filesocket will be recreated as well.
Arguments:
socket -- The new socket to use.
ignore -- don't set the state
"""
self.socket = socket
if socket is not None:
# ElementTree.iterparse requires a file.
# 0 buffer files have to be binary.
# Use the correct fileobject type based on the Python
# version to work around a broken implementation in
# Python 2.x.
if sys.version_info < (3, 0):
self.filesocket = FileSocket(self.socket)
else:
self.filesocket = self.socket.makefile('rb', 0)
if not ignore:
self.state._set_state('connected')
def start_tls(self):
"""
Perform handshakes for TLS.
If the handshake is successful, the XML stream will need
to be restarted.
"""
if self.ssl_support:
log.info("Negotiating TLS")
log.info("Using SSL version: %s" % str(self.ssl_version))
if self.ca_certs is None:
cert_policy = ssl.CERT_NONE
else:
cert_policy = ssl.CERT_REQUIRED
ssl_socket = ssl.wrap_socket(self.socket,
ssl_version=self.ssl_version,
do_handshake_on_connect=False,
ca_certs=self.ca_certs,
cert_reqs=cert_policy)
if hasattr(self.socket, 'socket'):
# We are using a testing socket, so preserve the top
# layer of wrapping.
self.socket.socket = ssl_socket
else:
self.socket = ssl_socket
self.socket.do_handshake()
self.set_socket(self.socket)
return True
else:
log.warning("Tried to enable TLS, but ssl module not found.")
return False
def start_stream_handler(self, xml):
"""
Perform any initialization actions, such as handshakes, once the
stream header has been sent.
Meant to be overridden.
"""
pass
def register_stanza(self, stanza_class):
"""
Add a stanza object class as a known root stanza. A root stanza is
one that appears as a direct child of the stream's root element.
Stanzas that appear as substanzas of a root stanza do not need to
be registered here. That is done using register_stanza_plugin() from
sleekxmpp.xmlstream.stanzabase.
Stanzas that are not registered will not be converted into
stanza objects, but may still be processed using handlers and
matchers.
Arguments:
stanza_class -- The top-level stanza object's class.
"""
self.__root_stanza.append(stanza_class)
def remove_stanza(self, stanza_class):
"""
Remove a stanza from being a known root stanza. A root stanza is
one that appears as a direct child of the stream's root element.
Stanzas that are not registered will not be converted into
stanza objects, but may still be processed using handlers and
matchers.
"""
del self.__root_stanza[stanza_class]
def add_handler(self, mask, pointer, name=None, disposable=False,
threaded=False, filter=False, instream=False):
"""
A shortcut method for registering a handler using XML masks.
Arguments:
mask -- An XML snippet matching the structure of the
stanzas that will be passed to this handler.
pointer -- The handler function itself.
name -- A unique name for the handler. A name will
be generated if one is not provided.
disposable -- Indicates if the handler should be discarded
after one use.
threaded -- Deprecated. Remains for backwards compatibility.
filter -- Deprecated. Remains for backwards compatibility.
instream -- Indicates if the handler should execute during
stream processing and not during normal event
processing.
"""
# To prevent circular dependencies, we must load the matcher
# and handler classes here.
if name is None:
name = 'add_handler_%s' % self.getNewId()
self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer,
once=disposable, instream=instream))
def register_handler(self, handler, before=None, after=None):
"""
Add a stream event handler that will be executed when a matching
stanza is received.
Arguments:
handler -- The handler object to execute.
"""
if handler.stream is None:
self.__handlers.append(handler)
handler.stream = self
def remove_handler(self, name):
"""
Remove any stream event handlers with the given name.
Arguments:
name -- The name of the handler.
"""
idx = 0
for handler in self.__handlers:
if handler.name == name:
self.__handlers.pop(idx)
return True
idx += 1
return False
def add_event_handler(self, name, pointer,
threaded=False, disposable=False):
"""
Add a custom event handler that will be executed whenever
its event is manually triggered.
Arguments:
name -- The name of the event that will trigger
this handler.
pointer -- The function to execute.
threaded -- If set to True, the handler will execute
in its own thread. Defaults to False.
disposable -- If set to True, the handler will be
discarded after one use. Defaults to False.
"""
if not name in self.__event_handlers:
self.__event_handlers[name] = []
self.__event_handlers[name].append((pointer, threaded, disposable))
def del_event_handler(self, name, pointer):
"""
Remove a function as a handler for an event.
Arguments:
name -- The name of the event.
pointer -- The function to remove as a handler.
"""
if not name in self.__event_handlers:
return
# Need to keep handlers that do not use
# the given function pointer
def filter_pointers(handler):
return handler[0] != pointer
self.__event_handlers[name] = filter(filter_pointers,
self.__event_handlers[name])
def event_handled(self, name):
"""
Indicates if an event has any associated handlers.
Returns the number of registered handlers.
Arguments:
name -- The name of the event to check.
"""
return len(self.__event_handlers.get(name, []))
def event(self, name, data={}, direct=False):
"""
Manually trigger a custom event.
Arguments:
name -- The name of the event to trigger.
data -- Data that will be passed to each event handler.
Defaults to an empty dictionary.
direct -- Runs the event directly if True, skipping the
event queue. All event handlers will run in the
same thread.
"""
for handler in self.__event_handlers.get(name, []):
if direct:
try:
handler[0](copy.copy(data))
except Exception as e:
error_msg = 'Error processing event handler: %s'
log.exception(error_msg % str(handler[0]))
if hasattr(data, 'exception'):
data.exception(e)
else:
self.event_queue.put(('event', handler, copy.copy(data)))
if handler[2]:
# If the handler is disposable, we will go ahead and
# remove it now instead of waiting for it to be
# processed in the queue.
with self.__event_handlers_lock:
try:
h_index = self.__event_handlers[name].index(handler)
self.__event_handlers[name].pop(h_index)
except:
pass
def schedule(self, name, seconds, callback, args=None,
kwargs=None, repeat=False):
"""
Schedule a callback function to execute after a given delay.
Arguments:
name -- A unique name for the scheduled callback.
seconds -- The time in seconds to wait before executing.
callback -- A pointer to the function to execute.
args -- A tuple of arguments to pass to the function.
kwargs -- A dictionary of keyword arguments to pass to
the function.
repeat -- Flag indicating if the scheduled event should
be reset and repeat after executing.
"""
self.scheduler.add(name, seconds, callback, args, kwargs,
repeat, qpointer=self.event_queue)
def incoming_filter(self, xml):
"""
Filter incoming XML objects before they are processed.
Possible uses include remapping namespaces, or correcting elements
from sources with incorrect behavior.
Meant to be overridden.
"""
return xml
def send(self, data, mask=None, timeout=None, now=False):
"""
A wrapper for send_raw for sending stanza objects.
May optionally block until an expected response is received.
Arguments:
data -- The stanza object to send on the stream.
mask -- Deprecated. An XML snippet matching the structure
of the expected response. Execution will block
in this thread until the response is received
or a timeout occurs.
timeout -- Time in seconds to wait for a response before
continuing. Defaults to RESPONSE_TIMEOUT.
now -- Indicates if the send queue should be skipped,
sending the stanza immediately. Useful mainly
for stream initialization stanzas.
Defaults to False.
"""
if timeout is None:
timeout = self.response_timeout
if hasattr(mask, 'xml'):
mask = mask.xml
data = str(data)
if mask is not None:
log.warning("Use of send mask waiters is deprecated.")
wait_for = Waiter("SendWait_%s" % self.new_id(),
MatchXMLMask(mask))
self.register_handler(wait_for)
self.send_raw(data, now)
if mask is not None:
return wait_for.wait(timeout)
def send_xml(self, data, mask=None, timeout=None, now=False):
"""
Send an XML object on the stream, and optionally wait
for a response.
Arguments:
data -- The XML object to send on the stream.
mask -- Deprecated. An XML snippet matching the structure
of the expected response. Execution will block
in this thread until the response is received
or a timeout occurs.
timeout -- Time in seconds to wait for a response before
continuing. Defaults to RESPONSE_TIMEOUT.
now -- Indicates if the send queue should be skipped,
sending the stanza immediately. Useful mainly
for stream initialization stanzas.
Defaults to False.
"""
if timeout is None:
timeout = self.response_timeout
return self.send(tostring(data), mask, timeout, now)
def send_raw(self, data, now=False, reconnect=None):
"""
Send raw data across the stream.
Arguments:
data -- Any string value.
reconnect -- Indicates if the stream should be
restarted if there is an error sending
the stanza. Used mainly for testing.
Defaults to self.auto_reconnect.
"""
if now:
log.debug("SEND (IMMED): %s" % data)
try:
self.socket.send(data.encode('utf-8'))
except Socket.error as serr:
self.event('socket_error', serr)
log.warning("Failed to send %s" % data)
if reconnect is None:
reconnect = self.auto_reconnect
self.disconnect(reconnect)
else:
self.send_queue.put(data)
return True
def process(self, threaded=True):
"""
Initialize the XML streams and begin processing events.
The number of threads used for processing stream events is determined
by HANDLER_THREADS.
Arguments:
threaded -- If threaded=True then event dispatcher will run
in a separate thread, allowing for the stream to be
used in the background for another application.
Defaults to True.
Event handlers and the send queue will be threaded
regardless of this parameter's value.
"""
self._thread_excepthook()
self.scheduler.process(threaded=True)
def start_thread(name, target):
self.__thread[name] = threading.Thread(name=name, target=target)
self.__thread[name].daemon = True
self.__thread[name].start()
for t in range(0, HANDLER_THREADS):
log.debug("Starting HANDLER THREAD")
start_thread('stream_event_handler_%s' % t, self._event_runner)
start_thread('send_thread', self._send_thread)
if threaded:
# Run the XML stream in the background for another application.
start_thread('process', self._process)
else:
self._process()
def _process(self):
"""
Start processing the XML streams.
Processing will continue after any recoverable errors
if reconnections are allowed.
"""
firstrun = True
# The body of this loop will only execute once per connection.
# Additional passes will be made only if an error occurs and
# reconnecting is permitted.
while firstrun or (self.auto_reconnect and not self.stop.isSet()):
firstrun = False
try:
if self.is_client:
self.send_raw(self.stream_header, now=True)
# The call to self.__read_xml will block and prevent
# the body of the loop from running until a disconnect
# occurs. After any reconnection, the stream header will
# be resent and processing will resume.
while not self.stop.isSet() and self.__read_xml():
# Ensure the stream header is sent for any
# new connections.
if self.is_client:
self.send_raw(self.stream_header, now=True)
except KeyboardInterrupt:
log.debug("Keyboard Escape Detected in _process")
self.stop.set()
except SystemExit:
log.debug("SystemExit in _process")
self.stop.set()
except Socket.error as serr:
self.event('socket_error', serr)
log.exception('Socket Error')
except:
if not self.stop.isSet():
log.exception('Connection error.')
if not self.stop.isSet() and self.auto_reconnect:
self.reconnect()
else:
self.event('killed', direct=True)
self.disconnect()
self.event_queue.put(('quit', None, None))
self.scheduler.run = False
def __read_xml(self):
"""
Parse the incoming XML stream, raising stream events for
each received stanza.
"""
depth = 0
root = None
try:
for (event, xml) in ET.iterparse(self.filesocket,
(b'end', b'start')):
if event == b'start':
if depth == 0:
# We have received the start of the root element.
root = xml
# Perform any stream initialization actions, such
# as handshakes.
self.stream_end_event.clear()
self.start_stream_handler(root)
depth += 1
if event == b'end':
depth -= 1
if depth == 0:
# The stream's root element has closed,
# terminating the stream.
log.debug("End of stream recieved")
self.stream_end_event.set()
return False
elif depth == 1:
# We only raise events for stanzas that are direct
# children of the root element.
try:
self.__spawn_event(xml)
except RestartStream:
return True
if root:
# Keep the root element empty of children to
# save on memory use.
root.clear()
except SyntaxError:
log.error("Error reading from XML stream.")
log.debug("Ending read XML loop")
def _build_stanza(self, xml, default_ns=None):
"""
Create a stanza object from a given XML object.
If a specialized stanza type is not found for the XML, then
a generic StanzaBase stanza will be returned.
Arguments:
xml -- The XML object to convert into a stanza object.
default_ns -- Optional default namespace to use instead of the
stream's current default namespace.
"""
if default_ns is None:
default_ns = self.default_ns
stanza_type = StanzaBase
for stanza_class in self.__root_stanza:
if xml.tag == "{%s}%s" % (default_ns, stanza_class.name) or \
xml.tag == stanza_class.tag_name():
stanza_type = stanza_class
break
stanza = stanza_type(self, xml)
return stanza
def __spawn_event(self, xml):
"""
Analyze incoming XML stanzas and convert them into stanza
objects if applicable and queue stream events to be processed
by matching handlers.
Arguments:
xml -- The XML stanza to analyze.
"""
log.debug("RECV: %s" % tostring(xml,
xmlns=self.default_ns,
stream=self))
# Apply any preprocessing filters.
xml = self.incoming_filter(xml)
# Convert the raw XML object into a stanza object. If no registered
# stanza type applies, a generic StanzaBase stanza will be used.
stanza = self._build_stanza(xml)
# Match the stanza against registered handlers. Handlers marked
# to run "in stream" will be executed immediately; the rest will
# be queued.
unhandled = True
for handler in self.__handlers:
if handler.match(stanza):
stanza_copy = copy.copy(stanza)
handler.prerun(stanza_copy)
self.event_queue.put(('stanza', handler, stanza_copy))
try:
if handler.check_delete():
self.__handlers.remove(handler)
except:
pass # not thread safe
unhandled = False
# Some stanzas require responses, such as Iq queries. A default
# handler will be executed immediately for this case.
if unhandled:
stanza.unhandled()
def _threaded_event_wrapper(self, func, args):
"""
Capture exceptions for event handlers that run
in individual threads.
Arguments:
func -- The event handler to execute.
args -- Arguments to the event handler.
"""
orig = copy.copy(args[0])
try:
func(*args)
except Exception as e:
error_msg = 'Error processing event handler: %s'
log.exception(error_msg % str(func))
if hasattr(orig, 'exception'):
orig.exception(e)
def _event_runner(self):
"""
Process the event queue and execute handlers.
The number of event runner threads is controlled by HANDLER_THREADS.
Stream event handlers will all execute in this thread. Custom event
handlers may be spawned in individual threads.
"""
log.debug("Loading event runner")
try:
while not self.stop.isSet():
try:
event = self.event_queue.get(True, timeout=5)
except queue.Empty:
event = None
if event is None:
continue
etype, handler = event[0:2]
args = event[2:]
orig = copy.copy(args[0])
if etype == 'stanza':
try:
handler.run(args[0])
except Exception as e:
error_msg = 'Error processing stream handler: %s'
log.exception(error_msg % handler.name)
orig.exception(e)
elif etype == 'schedule':
try:
log.debug('Scheduled event: %s' % args)
handler(*args[0])
except:
log.exception('Error processing scheduled task')
elif etype == 'event':
func, threaded, disposable = handler
orig = copy.copy(args[0])
try:
if threaded:
x = threading.Thread(
name="Event_%s" % str(func),
target=self._threaded_event_wrapper,
args=(func, args))
x.start()
else:
func(*args)
except Exception as e:
error_msg = 'Error processing event handler: %s'
log.exception(error_msg % str(func))
if hasattr(orig, 'exception'):
orig.exception(e)
elif etype == 'quit':
log.debug("Quitting event runner thread")
return False
except KeyboardInterrupt:
log.debug("Keyboard Escape Detected in _event_runner")
self.event('killed', direct=True)
self.disconnect()
return
except SystemExit:
self.disconnect()
self.event_queue.put(('quit', None, None))
return
def _send_thread(self):
"""
Extract stanzas from the send queue and send them on the stream.
"""
try:
while not self.stop.isSet():
self.session_started_event.wait()
if self.__failed_send_stanza is not None:
data = self.__failed_send_stanza
self.__failed_send_stanza = None
else:
try:
data = self.send_queue.get(True, 1)
except queue.Empty:
continue
log.debug("SEND: %s" % data)
try:
self.socket.send(data.encode('utf-8'))
except Socket.error as serr:
self.event('socket_error', serr)
log.warning("Failed to send %s" % data)
self.__failed_send_stanza = data
self.disconnect(self.auto_reconnect)
except KeyboardInterrupt:
log.debug("Keyboard Escape Detected in _send_thread")
self.event('killed', direct=True)
self.disconnect()
return
except SystemExit:
self.disconnect()
self.event_queue.put(('quit', None, None))
return
def _thread_excepthook(self):
"""
If a threaded event handler raises an exception, there is no way to
catch it except with an excepthook. Currently, each thread has its own
excepthook, but ideally we could use the main sys.excepthook.
Modifies threading.Thread to use sys.excepthook when an exception
is not caught.
"""
init_old = threading.Thread.__init__
def init(self, *args, **kwargs):
init_old(self, *args, **kwargs)
run_old = self.run
def run_with_except_hook(*args, **kw):
try:
run_old(*args, **kw)
except (KeyboardInterrupt, SystemExit):
raise
except:
sys.excepthook(*sys.exc_info())
self.run = run_with_except_hook
threading.Thread.__init__ = init
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
XMLStream.startTLS = XMLStream.start_tls
XMLStream.registerStanza = XMLStream.register_stanza
XMLStream.removeStanza = XMLStream.remove_stanza
XMLStream.registerHandler = XMLStream.register_handler
XMLStream.removeHandler = XMLStream.remove_handler
XMLStream.setSocket = XMLStream.set_socket
XMLStream.sendRaw = XMLStream.send_raw
XMLStream.getId = XMLStream.get_id
XMLStream.getNewId = XMLStream.new_id
XMLStream.sendXML = XMLStream.send_xml
fritzy-SleekXMPP-5c4ee57/testall.py 0000664 0000000 0000000 00000005640 11577753404 0017276 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
import unittest
import logging
import sys
import os
class testoverall(unittest.TestCase):
def testModules(self):
"""Testing all modules by compiling them"""
import compileall
import re
if sys.version_info < (3,0):
self.failUnless(compileall.compile_dir('.' + os.sep + 'sleekxmpp', rx=re.compile('/[.]svn'), quiet=True))
else:
self.failUnless(compileall.compile_dir('.' + os.sep + 'sleekxmpp', rx=re.compile('/[.]svn|.*26.*'), quiet=True))
def testTabNanny(self):
"""Invoking the tabnanny"""
import tabnanny
self.failIf(tabnanny.check("." + os.sep + 'sleekxmpp'))
#raise "Help!"
def disabled_testMethodLength(self):
"""Testing for excessive method lengths"""
import re
dirs = os.walk(sys.path[0] + os.sep + 'sleekxmpp')
offenders = []
for d in dirs:
if not '.svn' in d[0]:
for filename in d[2]:
if filename.endswith('.py') and d[0].find("template%stemplates" % os.sep) == -1:
with open("%s%s%s" % (d[0],os.sep,filename), "r") as fp:
cur = None
methodline = lineno = methodlen = methodindent = 0
for line in fp:
indentlevel = re.compile("^[\t ]*").search(line).end()
line = line.expandtabs()
lineno += 1
if line.strip().startswith("def ") or line.strip().startswith("except") or (line.strip() and methodindent > indentlevel) or (line.strip() and methodindent == indentlevel): #new method found or old one ended
if cur: #existing method needs final evaluation
if methodlen > 50 and not cur.strip().startswith("def setupUi"):
offenders.append("Method '%s' on line %s of %s/%s is longer than 50 lines (%s)" % (cur.strip(),methodline,d[0][len(rootp):],filename,methodlen))
methodlen = 0
cur = line
methodindent = indentlevel
methodline = lineno
if line and cur and not line.strip().startswith("#") and not (cur.strip().startswith("try:") and methodindent == 0): #if we weren't all whitespace and weren't a comment
methodlen += 1
self.failIf(offenders,"\n".join(offenders))
if __name__ == '__main__':
logging.basicConfig(level=100)
logging.disable(100)
#this doesn't need to be very clean
alltests = [unittest.TestLoader().loadTestsFromTestCase(testoverall)]
rootp = sys.path[0] + os.sep + 'tests'
dirs = os.walk(rootp)
for d in dirs:
if not '.svn' in d[0]:
for filename in d[2]:
if filename.startswith('test_') and filename.endswith('.py'):
modname = ('tests' + "." + filename)[:-3].replace(os.sep,'.')
__import__(modname)
#sys.modules[modname].config = moduleconfig
alltests.append(sys.modules[modname].suite)
alltests_suite = unittest.TestSuite(alltests)
result = unittest.TextTestRunner(verbosity=2).run(alltests_suite)
print("""""" % (result.testsRun, len(result.errors), len(result.failures), result.wasSuccessful()))
fritzy-SleekXMPP-5c4ee57/tests/ 0000775 0000000 0000000 00000000000 11577753404 0016411 5 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/tests/__init__.py 0000664 0000000 0000000 00000000000 11577753404 0020510 0 ustar 00root root 0000000 0000000 fritzy-SleekXMPP-5c4ee57/tests/live_multiple_streams.py 0000664 0000000 0000000 00000003436 11577753404 0023401 0 ustar 00root root 0000000 0000000 import logging
from sleekxmpp.test import *
class TestMultipleStreams(SleekTest):
"""
Test that we can test a live stanza stream.
"""
def setUp(self):
self.client1 = SleekTest()
self.client2 = SleekTest()
def tearDown(self):
self.client1.stream_close()
self.client2.stream_close()
def testMultipleStreams(self):
"""Test that we can interact with multiple live ClientXMPP instance."""
client1 = self.client1
client2 = self.client2
client1.stream_start(mode='client',
socket='live',
skip=True,
jid='user@localhost/test1',
password='user')
client2.stream_start(mode='client',
socket='live',
skip=True,
jid='user@localhost/test2',
password='user')
client1.xmpp.send_message(mto='user@localhost/test2',
mbody='test')
client1.send('message@body=test', method='stanzapath')
client2.recv('message@body=test', method='stanzapath')
suite = unittest.TestLoader().loadTestsFromTestCase(TestMultipleStreams)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG,
format='%(levelname)-8s %(message)s')
tests = unittest.TestSuite([suite])
result = unittest.TextTestRunner(verbosity=2).run(tests)
test_ns = 'http://andyet.net/protocol/tests'
print("" % (
test_ns,
'ran="%s"' % result.testsRun,
'errors="%s"' % len(result.errors),
'fails="%s"' % len(result.failures),
'success="%s"' % result.wasSuccessful()))
fritzy-SleekXMPP-5c4ee57/tests/live_test.py 0000664 0000000 0000000 00000006536 11577753404 0020773 0 ustar 00root root 0000000 0000000 import logging
from sleekxmpp.test import *
class TestLiveStream(SleekTest):
"""
Test that we can test a live stanza stream.
"""
def tearDown(self):
self.stream_close()
def testClientConnection(self):
"""Test that we can interact with a live ClientXMPP instance."""
self.stream_start(mode='client',
socket='live',
skip=False,
jid='user@localhost/test',
password='user')
# Use sid=None to ignore any id sent by the server since
# we can't know it in advance.
self.recv_header(sfrom='localhost', sid=None)
self.send_header(sto='localhost')
self.recv_feature("""
DIGEST-MD5PLAIN
""")
self.send_feature("""
""")
self.recv_feature("""
""")
self.send_header(sto='localhost')
self.recv_header(sfrom='localhost', sid=None)
self.recv_feature("""
DIGEST-MD5PLAIN
""")
self.send_feature("""
AHVzZXIAdXNlcg==
""")
self.recv_feature("""
""")
self.send_header(sto='localhost')
self.recv_header(sfrom='localhost', sid=None)
self.recv_feature("""
""")
# Should really use send, but our Iq stanza objects
# can't handle bind element payloads yet.
self.send_feature("""
test
""")
self.recv_feature("""
user@localhost/test
""")
self.stream_close()
suite = unittest.TestLoader().loadTestsFromTestCase(TestLiveStream)
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG,
format='%(levelname)-8s %(message)s')
tests = unittest.TestSuite([suite])
result = unittest.TextTestRunner(verbosity=2).run(tests)
test_ns = 'http://andyet.net/protocol/tests'
print("" % (
test_ns,
'ran="%s"' % result.testsRun,
'errors="%s"' % len(result.errors),
'fails="%s"' % len(result.failures),
'success="%s"' % result.wasSuccessful()))
fritzy-SleekXMPP-5c4ee57/tests/test_events.py 0000664 0000000 0000000 00000004051 11577753404 0021326 0 ustar 00root root 0000000 0000000 import time
from sleekxmpp.test import *
class TestEvents(SleekTest):
def setUp(self):
self.stream_start()
def tearDown(self):
self.stream_close()
def testEventHappening(self):
"""Test handler working"""
happened = []
def handletestevent(event):
happened.append(True)
self.xmpp.add_event_handler("test_event", handletestevent)
self.xmpp.event("test_event")
self.xmpp.event("test_event")
# Give the event queue time to process.
time.sleep(0.1)
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True, True], msg)
def testDelEvent(self):
"""Test handler working, then deleted and not triggered"""
happened = []
def handletestevent(event):
happened.append(True)
self.xmpp.add_event_handler("test_event", handletestevent)
self.xmpp.event("test_event", {})
self.xmpp.del_event_handler("test_event", handletestevent)
# Should not trigger because it was deleted
self.xmpp.event("test_event", {})
# Give the event queue time to process.
time.sleep(0.1)
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True], msg % happened)
def testDisposableEvent(self):
"""Test disposable handler working, then not being triggered again."""
happened = []
def handletestevent(event):
happened.append(True)
self.xmpp.add_event_handler("test_event", handletestevent,
disposable=True)
self.xmpp.event("test_event", {})
# Should not trigger because it was deleted
self.xmpp.event("test_event", {})
# Give the event queue time to process.
time.sleep(0.1)
msg = "Event was not triggered the correct number of times: %s"
self.failUnless(happened == [True], msg % happened)
suite = unittest.TestLoader().loadTestsFromTestCase(TestEvents)
fritzy-SleekXMPP-5c4ee57/tests/test_jid.py 0000664 0000000 0000000 00000011006 11577753404 0020566 0 ustar 00root root 0000000 0000000 from sleekxmpp.test import *
from sleekxmpp.xmlstream.jid import JID
class TestJIDClass(SleekTest):
"""Verify that the JID class can parse and manipulate JIDs."""
def testJIDFromFull(self):
"""Test using JID of the form 'user@server/resource/with/slashes'."""
self.check_jid(JID('user@someserver/some/resource'),
'user',
'someserver',
'some/resource',
'user@someserver',
'user@someserver/some/resource',
'user@someserver/some/resource')
def testJIDchange(self):
"""Test changing JID of the form 'user@server/resource/with/slashes'"""
j = JID('user1@someserver1/some1/resource1')
j.user = 'user'
j.domain = 'someserver'
j.resource = 'some/resource'
self.check_jid(j,
'user',
'someserver',
'some/resource',
'user@someserver',
'user@someserver/some/resource',
'user@someserver/some/resource')
def testJIDaliases(self):
"""Test changing JID using aliases for domain."""
j = JID('user@someserver/resource')
j.server = 'anotherserver'
self.check_jid(j, domain='anotherserver')
j.host = 'yetanother'
self.check_jid(j, domain='yetanother')
def testJIDSetFullWithUser(self):
"""Test setting the full JID with a user portion."""
j = JID('user@domain/resource')
j.full = 'otheruser@otherdomain/otherresource'
self.check_jid(j,
'otheruser',
'otherdomain',
'otherresource',
'otheruser@otherdomain',
'otheruser@otherdomain/otherresource',
'otheruser@otherdomain/otherresource')
def testJIDFullNoUserWithResource(self):
"""
Test setting the full JID without a user
portion and with a resource.
"""
j = JID('user@domain/resource')
j.full = 'otherdomain/otherresource'
self.check_jid(j,
'',
'otherdomain',
'otherresource',
'otherdomain',
'otherdomain/otherresource',
'otherdomain/otherresource')
def testJIDFullNoUserNoResource(self):
"""
Test setting the full JID without a user
portion and without a resource.
"""
j = JID('user@domain/resource')
j.full = 'otherdomain'
self.check_jid(j,
'',
'otherdomain',
'',
'otherdomain',
'otherdomain',
'otherdomain')
def testJIDBareUser(self):
"""Test setting the bare JID with a user."""
j = JID('user@domain/resource')
j.bare = 'otheruser@otherdomain'
self.check_jid(j,
'otheruser',
'otherdomain',
'resource',
'otheruser@otherdomain',
'otheruser@otherdomain/resource',
'otheruser@otherdomain/resource')
def testJIDBareNoUser(self):
"""Test setting the bare JID without a user."""
j = JID('user@domain/resource')
j.bare = 'otherdomain'
self.check_jid(j,
'',
'otherdomain',
'resource',
'otherdomain',
'otherdomain/resource',
'otherdomain/resource')
def testJIDNoResource(self):
"""Test using JID of the form 'user@domain'."""
self.check_jid(JID('user@someserver'),
'user',
'someserver',
'',
'user@someserver',
'user@someserver',
'user@someserver')
def testJIDNoUser(self):
"""Test JID of the form 'component.domain.tld'."""
self.check_jid(JID('component.someserver'),
'',
'component.someserver',
'',
'component.someserver',
'component.someserver',
'component.someserver')
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)
fritzy-SleekXMPP-5c4ee57/tests/test_stanza_base.py 0000664 0000000 0000000 00000005565 11577753404 0022327 0 ustar 00root root 0000000 0000000 from sleekxmpp.test import *
from sleekxmpp.xmlstream.stanzabase import ET, StanzaBase
class TestStanzaBase(SleekTest):
def testTo(self):
"""Test the 'to' interface of StanzaBase."""
stanza = StanzaBase()
stanza['to'] = 'user@example.com'
self.failUnless(str(stanza['to']) == 'user@example.com',
"Setting and retrieving stanza 'to' attribute did not work.")
def testFrom(self):
"""Test the 'from' interface of StanzaBase."""
stanza = StanzaBase()
stanza['from'] = 'user@example.com'
self.failUnless(str(stanza['from']) == 'user@example.com',
"Setting and retrieving stanza 'from' attribute did not work.")
def testPayload(self):
"""Test the 'payload' interface of StanzaBase."""
stanza = StanzaBase()
self.failUnless(stanza['payload'] == [],
"Empty stanza does not have an empty payload.")
stanza['payload'] = ET.Element("{foo}foo")
self.failUnless(len(stanza['payload']) == 1,
"Stanza contents and payload do not match.")
stanza['payload'] = ET.Element('{bar}bar')
self.failUnless(len(stanza['payload']) == 2,
"Stanza payload was not appended.")
del stanza['payload']
self.failUnless(stanza['payload'] == [],
"Stanza payload not cleared after deletion.")
stanza['payload'] = [ET.Element('{foo}foo'),
ET.Element('{bar}bar')]
self.failUnless(len(stanza['payload']) == 2,
"Adding multiple elements to stanza's payload did not work.")
def testClear(self):
"""Test clearing a stanza."""
stanza = StanzaBase()
stanza['to'] = 'user@example.com'
stanza['payload'] = ET.Element("{foo}foo")
stanza.clear()
self.failUnless(stanza['payload'] == [],
"Stanza payload was not cleared after calling .clear()")
self.failUnless(str(stanza['to']) == "user@example.com",
"Stanza attributes were not preserved after calling .clear()")
def testReply(self):
"""Test creating a reply stanza."""
stanza = StanzaBase()
stanza['to'] = "recipient@example.com"
stanza['from'] = "sender@example.com"
stanza['payload'] = ET.Element("{foo}foo")
stanza.reply()
self.failUnless(str(stanza['to'] == "sender@example.com"),
"Stanza reply did not change 'to' attribute.")
self.failUnless(stanza['payload'] == [],
"Stanza reply did not empty stanza payload.")
def testError(self):
"""Test marking a stanza as an error."""
stanza = StanzaBase()
stanza['type'] = 'get'
stanza.error()
self.failUnless(stanza['type'] == 'error',
"Stanza type is not 'error' after calling error()")
suite = unittest.TestLoader().loadTestsFromTestCase(TestStanzaBase)
fritzy-SleekXMPP-5c4ee57/tests/test_stanza_element.py 0000664 0000000 0000000 00000053005 11577753404 0023036 0 ustar 00root root 0000000 0000000 from sleekxmpp.test import *
from sleekxmpp.xmlstream.stanzabase import ElementBase
class TestElementBase(SleekTest):
def testFixNs(self):
"""Test fixing namespaces in an XPath expression."""
e = ElementBase()
ns = "http://jabber.org/protocol/disco#items"
result = e._fix_ns("{%s}foo/bar/{abc}baz/{%s}more" % (ns, ns))
expected = "/".join(["{%s}foo" % ns,
"{%s}bar" % ns,
"{abc}baz",
"{%s}more" % ns])
self.failUnless(expected == result,
"Incorrect namespace fixing result: %s" % str(result))
def testExtendedName(self):
"""Test element names of the form tag1/tag2/tag3."""
class TestStanza(ElementBase):
name = "foo/bar/baz"
namespace = "test"
stanza = TestStanza()
self.check(stanza, """
""")
def testGetStanzaValues(self):
"""Test getStanzaValues using plugins and substanzas."""
class TestStanzaPlugin(ElementBase):
name = "foo2"
namespace = "foo"
interfaces = set(('bar', 'baz'))
plugin_attrib = "foo2"
class TestSubStanza(ElementBase):
name = "subfoo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
register_stanza_plugin(TestStanza, TestStanzaPlugin, iterable=True)
stanza = TestStanza()
stanza['bar'] = 'a'
stanza['foo2']['baz'] = 'b'
substanza = TestSubStanza()
substanza['bar'] = 'c'
stanza.append(substanza)
values = stanza.getStanzaValues()
expected = {'bar': 'a',
'baz': '',
'foo2': {'bar': '',
'baz': 'b'},
'substanzas': [{'__childtag__': '{foo}subfoo',
'bar': 'c',
'baz': ''}]}
self.failUnless(values == expected,
"Unexpected stanza values:\n%s\n%s" % (str(expected), str(values)))
def testSetStanzaValues(self):
"""Test using setStanzaValues with substanzas and plugins."""
class TestStanzaPlugin(ElementBase):
name = "pluginfoo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
plugin_attrib = "plugin_foo"
class TestStanzaPlugin2(ElementBase):
name = "pluginfoo2"
namespace = "foo"
interfaces = set(('bar', 'baz'))
plugin_attrib = "plugin_foo2"
class TestSubStanza(ElementBase):
name = "subfoo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin)
register_stanza_plugin(TestStanza, TestStanzaPlugin2)
stanza = TestStanza()
values = {'bar': 'a',
'baz': '',
'plugin_foo': {'bar': '',
'baz': 'b'},
'plugin_foo2': {'bar': 'd',
'baz': 'e'},
'substanzas': [{'__childtag__': '{foo}subfoo',
'bar': 'c',
'baz': ''}]}
stanza.values = values
self.check(stanza, """
""")
def testGetItem(self):
"""Test accessing stanza interfaces."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('baz',))
def getQux(self):
return 'qux'
class TestStanzaPlugin(ElementBase):
name = "foobar"
namespace = "foo"
plugin_attrib = "foobar"
interfaces = set(('fizz',))
register_stanza_plugin(TestStanza, TestStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
substanza = TestStanza()
stanza.append(substanza)
stanza.setStanzaValues({'bar': 'a',
'baz': 'b',
'qux': 42,
'foobar': {'fizz': 'c'}})
# Test non-plugin interfaces
expected = {'substanzas': [substanza],
'bar': 'a',
'baz': 'b',
'qux': 'qux',
'meh': ''}
for interface, value in expected.items():
result = stanza[interface]
self.failUnless(result == value,
"Incorrect stanza interface access result: %s" % result)
# Test plugin interfaces
self.failUnless(isinstance(stanza['foobar'], TestStanzaPlugin),
"Incorrect plugin object result.")
self.failUnless(stanza['foobar']['fizz'] == 'c',
"Incorrect plugin subvalue result.")
def testSetItem(self):
"""Test assigning to stanza interfaces."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('baz',))
def setQux(self, value):
pass
class TestStanzaPlugin(ElementBase):
name = "foobar"
namespace = "foo"
plugin_attrib = "foobar"
interfaces = set(('foobar',))
register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
stanza['bar'] = 'attribute!'
stanza['baz'] = 'element!'
stanza['qux'] = 'overridden'
stanza['foobar'] = 'plugin'
self.check(stanza, """
element!
""")
def testDelItem(self):
"""Test deleting stanza interface values."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('bar',))
def delQux(self):
pass
class TestStanzaPlugin(ElementBase):
name = "foobar"
namespace = "foo"
plugin_attrib = "foobar"
interfaces = set(('foobar',))
register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
stanza['bar'] = 'a'
stanza['baz'] = 'b'
stanza['qux'] = 'c'
stanza['foobar']['foobar'] = 'd'
self.check(stanza, """
a
""")
del stanza['bar']
del stanza['baz']
del stanza['qux']
del stanza['foobar']
self.check(stanza, """
""")
def testModifyingAttributes(self):
"""Test modifying top level attributes of a stanza's XML object."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
stanza = TestStanza()
self.check(stanza, """
""")
self.failUnless(stanza._get_attr('bar') == '',
"Incorrect value returned for an unset XML attribute.")
stanza._set_attr('bar', 'a')
stanza._set_attr('baz', 'b')
self.check(stanza, """
""")
self.failUnless(stanza._get_attr('bar') == 'a',
"Retrieved XML attribute value is incorrect.")
stanza._set_attr('bar', None)
stanza._del_attr('baz')
self.check(stanza, """
""")
self.failUnless(stanza._get_attr('bar', 'c') == 'c',
"Incorrect default value returned for an unset XML attribute.")
def testGetSubText(self):
"""Test retrieving the contents of a sub element."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar',))
def setBar(self, value):
wrapper = ET.Element("{foo}wrapper")
bar = ET.Element("{foo}bar")
bar.text = value
wrapper.append(bar)
self.xml.append(wrapper)
def getBar(self):
return self._get_sub_text("wrapper/bar", default="not found")
stanza = TestStanza()
self.failUnless(stanza['bar'] == 'not found',
"Default _get_sub_text value incorrect.")
stanza['bar'] = 'found'
self.check(stanza, """
found
""")
self.failUnless(stanza['bar'] == 'found',
"_get_sub_text value incorrect: %s." % stanza['bar'])
def testSubElement(self):
"""Test setting the contents of a sub element."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
def setBaz(self, value):
self._set_sub_text("wrapper/baz", text=value)
def getBaz(self):
return self._get_sub_text("wrapper/baz")
def setBar(self, value):
self._set_sub_text("wrapper/bar", text=value)
def getBar(self):
return self._get_sub_text("wrapper/bar")
stanza = TestStanza()
stanza['bar'] = 'a'
stanza['baz'] = 'b'
self.check(stanza, """
ab
""")
stanza._set_sub_text('wrapper/bar', text='', keep=True)
self.check(stanza, """
b
""", use_values=False)
stanza['bar'] = 'a'
stanza._set_sub_text('wrapper/bar', text='')
self.check(stanza, """
b
""")
def testDelSub(self):
"""Test removing sub elements."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
def setBar(self, value):
self._set_sub_text("path/to/only/bar", value);
def getBar(self):
return self._get_sub_text("path/to/only/bar")
def delBar(self):
self._del_sub("path/to/only/bar")
def setBaz(self, value):
self._set_sub_text("path/to/just/baz", value);
def getBaz(self):
return self._get_sub_text("path/to/just/baz")
def delBaz(self):
self._del_sub("path/to/just/baz")
stanza = TestStanza()
stanza['bar'] = 'a'
stanza['baz'] = 'b'
self.check(stanza, """
ab
""")
del stanza['bar']
del stanza['baz']
self.check(stanza, """
""", use_values=False)
stanza['bar'] = 'a'
stanza['baz'] = 'b'
stanza._del_sub('path/to/only/bar', all=True)
self.check(stanza, """
b
""")
def testMatch(self):
"""Test matching a stanza against an XPath expression."""
class TestSubStanza(ElementBase):
name = "sub"
namespace = "baz"
interfaces = set(('attrib',))
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar','baz', 'qux'))
sub_interfaces = set(('qux',))
def setQux(self, value):
self._set_sub_text('qux', text=value)
def getQux(self):
return self._get_sub_text('qux')
class TestStanzaPlugin(ElementBase):
name = "plugin"
namespace = "http://test/slash/bar"
interfaces = set(('attrib',))
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
self.failUnless(stanza.match("foo"),
"Stanza did not match its own tag name.")
self.failUnless(stanza.match("{foo}foo"),
"Stanza did not match its own namespaced name.")
stanza['bar'] = 'a'
self.failUnless(stanza.match("foo@bar=a"),
"Stanza did not match its own name with attribute value check.")
stanza['baz'] = 'b'
self.failUnless(stanza.match("foo@bar=a@baz=b"),
"Stanza did not match its own name with multiple attributes.")
stanza['qux'] = 'c'
self.failUnless(stanza.match("foo/qux"),
"Stanza did not match with subelements.")
stanza['qux'] = ''
self.failUnless(stanza.match("foo/qux") == False,
"Stanza matched missing subinterface element.")
self.failUnless(stanza.match("foo/bar") == False,
"Stanza matched nonexistent element.")
stanza['plugin']['attrib'] = 'c'
self.failUnless(stanza.match("foo/plugin@attrib=c"),
"Stanza did not match with plugin and attribute.")
self.failUnless(stanza.match("foo/{http://test/slash/bar}plugin"),
"Stanza did not match with namespaced plugin.")
substanza = TestSubStanza()
substanza['attrib'] = 'd'
stanza.append(substanza)
self.failUnless(stanza.match("foo/sub@attrib=d"),
"Stanza did not match with substanzas and attribute.")
self.failUnless(stanza.match("foo/{baz}sub"),
"Stanza did not match with namespaced substanza.")
def testComparisons(self):
"""Test comparing ElementBase objects."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
stanza1 = TestStanza()
stanza1['bar'] = 'a'
self.failUnless(stanza1,
"Stanza object does not evaluate to True")
stanza2 = TestStanza()
stanza2['baz'] = 'b'
self.failUnless(stanza1 != stanza2,
"Different stanza objects incorrectly compared equal.")
stanza1['baz'] = 'b'
stanza2['bar'] = 'a'
self.failUnless(stanza1 == stanza2,
"Equal stanzas incorrectly compared inequal.")
def testKeys(self):
"""Test extracting interface names from a stanza object."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
plugin_attrib = 'qux'
register_stanza_plugin(TestStanza, TestStanza)
stanza = TestStanza()
self.failUnless(set(stanza.keys()) == set(('bar', 'baz')),
"Returned set of interface keys does not match expected.")
stanza.enable('qux')
self.failUnless(set(stanza.keys()) == set(('bar', 'baz', 'qux')),
"Incorrect set of interface and plugin keys.")
def testGet(self):
"""Test accessing stanza interfaces using get()."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
stanza = TestStanza()
stanza['bar'] = 'a'
self.failUnless(stanza.get('bar') == 'a',
"Incorrect value returned by stanza.get")
self.failUnless(stanza.get('baz', 'b') == 'b',
"Incorrect default value returned by stanza.get")
def testSubStanzas(self):
"""Test manipulating substanzas of a stanza object."""
class TestSubStanza(ElementBase):
name = "foobar"
namespace = "foo"
interfaces = set(('qux',))
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
stanza = TestStanza()
substanza1 = TestSubStanza()
substanza2 = TestSubStanza()
substanza1['qux'] = 'a'
substanza2['qux'] = 'b'
# Test appending substanzas
self.failUnless(len(stanza) == 0,
"Incorrect empty stanza size.")
stanza.append(substanza1)
self.check(stanza, """
""", use_values=False)
self.failUnless(len(stanza) == 1,
"Incorrect stanza size with 1 substanza.")
stanza.append(substanza2)
self.check(stanza, """
""", use_values=False)
self.failUnless(len(stanza) == 2,
"Incorrect stanza size with 2 substanzas.")
# Test popping substanzas
stanza.pop(0)
self.check(stanza, """
""", use_values=False)
# Test iterating over substanzas
stanza.append(substanza1)
results = []
for substanza in stanza:
results.append(substanza['qux'])
self.failUnless(results == ['b', 'a'],
"Iteration over substanzas failed: %s." % str(results))
def testCopy(self):
"""Test copying stanza objects."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
stanza1 = TestStanza()
stanza1['bar'] = 'a'
stanza2 = stanza1.__copy__()
self.failUnless(stanza1 == stanza2,
"Copied stanzas are not equal to each other.")
stanza1['baz'] = 'b'
self.failUnless(stanza1 != stanza2,
"Divergent stanza copies incorrectly compared equal.")
def testExtension(self):
"""Testing using is_extension."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestExtension(ElementBase):
name = 'extended'
namespace = 'foo'
plugin_attrib = name
interfaces = set((name,))
is_extension = True
def set_extended(self, value):
self.xml.text = value
def get_extended(self):
return self.xml.text
def del_extended(self):
self.parent().xml.remove(self.xml)
register_stanza_plugin(TestStanza, TestExtension)
stanza = TestStanza()
stanza['extended'] = 'testing'
self.check(stanza, """
testing
""")
self.failUnless(stanza['extended'] == 'testing',
"Could not retrieve stanza extension value.")
del stanza['extended']
self.check(stanza, """
""")
def testOverrides(self):
"""Test using interface overrides."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestOverride(ElementBase):
name = 'overrider'
namespace = 'foo'
plugin_attrib = name
interfaces = set(('bar',))
overrides = ['set_bar']
def setup(self, xml):
# Don't create XML for the plugin
self.xml = ET.Element('')
def set_bar(self, value):
if not value.startswith('override-'):
self.parent()._set_attr('bar', 'override-%s' % value)
else:
self.parent()._set_attr('bar', value)
stanza = TestStanza()
stanza['bar'] = 'foo'
self.check(stanza, """
""")
register_stanza_plugin(TestStanza, TestOverride, overrides=True)
stanza = TestStanza()
stanza['bar'] = 'foo'
self.check(stanza, """
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)
fritzy-SleekXMPP-5c4ee57/tests/test_stanza_error.py 0000664 0000000 0000000 00000004700 11577753404 0022534 0 ustar 00root root 0000000 0000000 from sleekxmpp.test import *
class TestErrorStanzas(SleekTest):
def setUp(self):
# Ensure that the XEP-0086 plugin has been loaded.
self.stream_start()
self.stream_close()
def testSetup(self):
"""Test setting initial values in error stanza."""
msg = self.Message()
msg.enable('error')
self.check(msg, """
""")
def testCondition(self):
"""Test modifying the error condition."""
msg = self.Message()
msg['error']['condition'] = 'item-not-found'
self.check(msg, """
""")
self.failUnless(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
msg['error']['condition'] = 'resource-constraint'
self.check(msg, """
""")
def testDelCondition(self):
"""Test that deleting error conditions doesn't remove extra elements."""
msg = self.Message()
msg['error']['text'] = 'Error!'
msg['error']['condition'] = 'internal-server-error'
del msg['error']['condition']
self.check(msg, """
Error!
""", use_values=False)
def testDelText(self):
"""Test deleting the text of an error."""
msg = self.Message()
msg['error']['test'] = 'Error!'
msg['error']['condition'] = 'internal-server-error'
del msg['error']['text']
self.check(msg, """
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestErrorStanzas)
fritzy-SleekXMPP-5c4ee57/tests/test_stanza_gmail.py 0000664 0000000 0000000 00000010150 11577753404 0022470 0 ustar 00root root 0000000 0000000 from sleekxmpp.test import *
import sleekxmpp.plugins.gmail_notify as gmail
class TestGmail(SleekTest):
def setUp(self):
register_stanza_plugin(Iq, gmail.GmailQuery)
register_stanza_plugin(Iq, gmail.MailBox)
register_stanza_plugin(Iq, gmail.NewMail)
def testCreateQuery(self):
"""Testing querying Gmail for emails."""
iq = self.Iq()
iq['type'] = 'get'
iq['gmail']['search'] = 'is:starred'
iq['gmail']['newer-than-time'] = '1140638252542'
iq['gmail']['newer-than-tid'] = '11134623426430234'
self.check(iq, """
""", use_values=False)
def testMailBox(self):
"""Testing reading from Gmail mailbox result"""
# Use the example from Google's documentation at
# http://code.google.com/apis/talk/jep_extensions/gmail.html#notifications
xml = ET.fromstring("""
act1scene3Put thy rapier up.Ay, ay, a scratch, a scratch; marry, 'tis enough.
""")
iq = self.Iq(xml=xml)
mailbox = iq['mailbox']
self.failUnless(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
self.failUnless(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
self.failUnless(mailbox['matched'] == '95', "total-matched incorrect")
self.failUnless(mailbox['estimate'] == False, "total-estimate incorrect")
self.failUnless(len(mailbox['threads']) == 1, "could not extract message threads")
thread = mailbox['threads'][0]
self.failUnless(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
self.failUnless(thread['participation'] == '1', "thread participation incorrect")
self.failUnless(thread['messages'] == '28', "thread message count incorrect")
self.failUnless(thread['date'] == '1118012394209', "thread date doesn't match")
self.failUnless(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
self.failUnless(thread['labels'] == 'act1scene3', "thread labels incorrect")
self.failUnless(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
self.failUnless(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
self.failUnless(len(thread['senders']) == 3, "could not extract senders")
sender1 = thread['senders'][0]
self.failUnless(sender1['name'] == 'Me', "sender name doesn't match")
self.failUnless(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
self.failUnless(sender1['originator'] == True, "sender originator incorrect")
self.failUnless(sender1['unread'] == False, "sender unread incorrectly True")
sender2 = thread['senders'][2]
self.failUnless(sender2['unread'] == True, "sender unread incorrectly False")
suite = unittest.TestLoader().loadTestsFromTestCase(TestGmail)
fritzy-SleekXMPP-5c4ee57/tests/test_stanza_iq.py 0000664 0000000 0000000 00000004506 11577753404 0022020 0 ustar 00root root 0000000 0000000 from sleekxmpp.test import *
from sleekxmpp.xmlstream.stanzabase import ET
class TestIqStanzas(SleekTest):
def tearDown(self):
"""Shutdown the XML stream after testing."""
self.stream_close()
def testSetup(self):
"""Test initializing default Iq values."""
iq = self.Iq()
self.check(iq, """
""")
def testPayload(self):
"""Test setting Iq stanza payload."""
iq = self.Iq()
iq.setPayload(ET.Element('{test}tester'))
self.check(iq, """
""", use_values=False)
def testUnhandled(self):
"""Test behavior for Iq.unhandled."""
self.stream_start()
self.recv("""
""")
iq = self.Iq()
iq['id'] = 'test'
iq['error']['condition'] = 'feature-not-implemented'
iq['error']['text'] = 'No handlers registered for this request.'
self.send(iq, """
No handlers registered for this request.
""")
def testQuery(self):
"""Test modifying query element of Iq stanzas."""
iq = self.Iq()
iq['query'] = 'query_ns'
self.check(iq, """
""")
iq['query'] = 'query_ns2'
self.check(iq, """
""")
self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match")
del iq['query']
self.check(iq, """
""")
def testReply(self):
"""Test setting proper result type in Iq replies."""
iq = self.Iq()
iq['to'] = 'user@localhost'
iq['type'] = 'get'
iq.reply()
self.check(iq, """
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestIqStanzas)
fritzy-SleekXMPP-5c4ee57/tests/test_stanza_message.py 0000664 0000000 0000000 00000003716 11577753404 0023035 0 ustar 00root root 0000000 0000000 from sleekxmpp.test import *
from sleekxmpp.stanza.message import Message
from sleekxmpp.stanza.htmlim import HTMLIM
class TestMessageStanzas(SleekTest):
def setUp(self):
register_stanza_plugin(Message, HTMLIM)
def testGroupchatReplyRegression(self):
"Regression groupchat reply should be to barejid"
msg = self.Message()
msg['to'] = 'me@myserver.tld'
msg['from'] = 'room@someservice.someserver.tld/somenick'
msg['type'] = 'groupchat'
msg['body'] = "this is a message"
msg.reply()
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
def testAttribProperty(self):
"Test attrib property returning self"
msg = self.Message()
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
self.failUnless(str(msg['to']) == 'usr@server.tld')
def testHTMLPlugin(self):
"Test message/html/body stanza"
msg = self.Message()
msg['to'] = "fritzy@netflint.net/sleekxmpp"
msg['body'] = "this is the plaintext message"
msg['type'] = 'chat'
p = ET.Element('{http://www.w3.org/1999/xhtml}p')
p.text = "This is the htmlim message"
msg['html']['body'] = p
self.check(msg, """
this is the plaintext message