Compare commits
	
		
			248 Commits
		
	
	
		
			sleek-0.9-
			...
			1.0-Beta2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 5bdcd9ef9d | ||
|   | 2eff35cc7a | ||
|   | ac330b5c6c | ||
|   | 46ffa8e9fe | ||
|   | 03847497cc | ||
|   | 185d7cf28e | ||
|   | 8aa3d0c047 | ||
|   | 9e3d506651 | ||
|   | 2f3ff37a24 | ||
|   | 1f09d60a52 | ||
|   | d528884723 | ||
|   | d9aff3d36f | ||
|   | 04cc48775d | ||
|   | 27ebb6e8f6 | ||
|   | 8f55704928 | ||
|   | d88999691c | ||
|   | c4699b92e6 | ||
|   | ce69213a1e | ||
|   | 77eab6544f | ||
|   | 11264fe0a8 | ||
|   | 11a6e6d2e0 | ||
|   | 6e34b2cfdd | ||
|   | e18354ae0e | ||
|   | 4375ac7d8b | ||
|   | faec86b3be | ||
|   | 505a63da3a | ||
|   | 93fbcad277 | ||
|   | 3625573c7d | ||
|   | d9e7f555e6 | ||
|   | 2755d732a4 | ||
|   | 2d18d905a5 | ||
|   | 4eb4d729ee | ||
|   | 8b5c1010de | ||
|   | 95ad8a1878 | ||
|   | aeb7999e6a | ||
|   | 8468332381 | ||
|   | dc001bb201 | ||
|   | 0d0b963fe5 | ||
|   | a41a4369c6 | ||
|   | 7ad7a29a8f | ||
|   | b0e036d03c | ||
|   | a8b948cd33 | ||
|   | e02ffe8547 | ||
|   | 42bfca1c87 | ||
|   | 0fffbb8200 | ||
|   | 21c32c6e1c | ||
|   | 75a051556f | ||
|   | 78141fe5f3 | ||
|   | 88d21d210c | ||
|   | 799645f13f | ||
|   | f234dc02cf | ||
|   | c294c1a85c | ||
|   | cbe76c8a70 | ||
|   | 77b8f0f4bb | ||
|   | 259f91d2bd | ||
|   | ed366b338d | ||
|   | 9e2cada19e | ||
|   | d0ccbf6b7a | ||
|   | e1866ab328 | ||
|   | 3ffa09ba7c | ||
|   | a7410f2146 | ||
|   | 178608f4c0 | ||
|   | 19ee6979a5 | ||
|   | 9f0baec7b2 | ||
|   | 433c147627 | ||
|   | 9a34c9a9a1 | ||
|   | 2662131124 | ||
|   | bb219595a7 | ||
|   | fcdd57ce54 | ||
|   | 5522443e0e | ||
|   | 55cfe69fef | ||
|   | 6de87a1cbf | ||
|   | 7c10ff16fb | ||
|   | c258d2f19d | ||
|   | d576e32f7a | ||
|   | 4a2e7c5393 | ||
|   | 0b4320a196 | ||
|   | 9bef4b4d4d | ||
|   | 5c3066ba30 | ||
|   | 576eefb097 | ||
|   | aebd115ba2 | ||
|   | 6dfea828be | ||
|   | 3749c1b88c | ||
|   | 998741b87e | ||
|   | 9c62bce206 | ||
|   | f5ae27da4f | ||
|   | 89fb15e896 | ||
|   | 906aa0bd68 | ||
|   | bb6f4af8e2 | ||
|   | 6677df39f2 | ||
|   | a2c515bc97 | ||
|   | ca6ce26b0d | ||
|   | 37ff17b0cb | ||
|   | 00d7952001 | ||
|   | 56766508b3 | ||
|   | 5c59f5baca | ||
|   | e16b37d2be | ||
|   | d68bc2ba07 | ||
|   | 10298a6eab | ||
|   | a3580dcef9 | ||
|   | 1eaa9cb28c | ||
|   | 5d458bf6c2 | ||
|   | 2fa58a74ab | ||
|   | c8f406d1b3 | ||
|   | 203986dd7c | ||
|   | f4ecf0bac4 | ||
|   | 345656926e | ||
|   | c05ddcb7f5 | ||
|   | eb9e72fe3e | ||
|   | 8a0616b3e0 | ||
|   | b71cfe0492 | ||
|   | fac3bca1f6 | ||
|   | d150b35464 | ||
|   | 21b7109c06 | ||
|   | e4240dd593 | ||
|   | 2f6f4fc16d | ||
|   | fe49b8c377 | ||
|   | b580a3138d | ||
|   | c20fab0f6c | ||
|   | c721fb4126 | ||
|   | 415520200e | ||
|   | 747001d33c | ||
|   | b0fb205c16 | ||
|   | 4b52007e8c | ||
|   | 5da7bd1866 | ||
|   | 22134c302b | ||
|   | b40a489796 | ||
|   | 7a5ef28492 | ||
|   | c09e9c702c | ||
|   | 48ba7292bc | ||
|   | 4d1f071f83 | ||
|   | 0d0c044a68 | ||
|   | 3c0dfb56e6 | ||
|   | e077204a16 | ||
|   | 58f77d898f | ||
|   | c54466596f | ||
|   | aa1dbe97e0 | ||
|   | fec69be731 | ||
|   | 956fdf6970 | ||
|   | 183a3f1b87 | ||
|   | 18683d2b75 | ||
|   | 41ab2b8460 | ||
|   | 939ae298c2 | ||
|   | 851e90c572 | ||
|   | ecde696468 | ||
|   | 1cedea2804 | ||
|   | cbed8029ba | ||
|   | 1da3e5b35e | ||
|   | a96a046e27 | ||
|   | 60a183b011 | ||
|   | a49f511a2f | ||
|   | 25f43bd219 | ||
|   | d148f633f3 | ||
|   | e8e934fa95 | ||
|   | bd92ef6acf | ||
|   | aa02ecd154 | ||
|   | aad185fe29 | ||
|   | 2b6454786a | ||
|   | a349a2a317 | ||
|   | 2cb82afc2c | ||
|   | c8989c04f3 | ||
|   | 241aba8c76 | ||
|   | ec860bf9e2 | ||
|   | 73a3d07ad9 | ||
|   | 07208a3eaf | ||
|   | d0a5c539d8 | ||
|   | d70a6e6f32 | ||
|   | 66e92c6c9f | ||
|   | ca2c421e6c | ||
|   | 9fcd2e93a3 | ||
|   | 75afefb5c6 | ||
|   | b67b930596 | ||
|   | 5c9b47afbd | ||
|   | 7ad0143687 | ||
|   | de24e9ed45 | ||
|   | 9724efa123 | ||
|   | 690eaf8d3c | ||
|   | f505e229d6 | ||
|   | 9ca4bba2de | ||
|   | bb927c7e6a | ||
|   | 14f1c3ba51 | ||
|   | 278a8bb443 | ||
|   | 85ee30539d | ||
|   | f74baf1c23 | ||
|   | b5a14a0190 | ||
|   | fec8578cf6 | ||
|   | f80b3285d4 | ||
|   | 130a148d34 | ||
|   | 16104b6e56 | ||
|   | d5e42ac0e7 | ||
|   | e6bec8681e | ||
|   | 797e92a6a3 | ||
|   | 1ef112966b | ||
|   | 078c71ed3f | ||
|   | bae082f437 | ||
|   | 35212c7991 | ||
|   | 48f0843ace | ||
|   | b1c997be1d | ||
|   | d0cb400c54 | ||
|   | 7f8179d91e | ||
|   | 37ada49802 | ||
|   | 5c76d969f7 | ||
|   | 059cc9ccc4 | ||
|   | 309c9e74eb | ||
|   | 6041cd1952 | ||
|   | acb53ba371 | ||
|   | 646a609c0b | ||
|   | 8bb0f5e34c | ||
|   | 3c939313d2 | ||
|   | 9962f1a664 | ||
|   | 253de8518c | ||
|   | a38735cb2a | ||
|   | e700a54d11 | ||
|   | 6469cdb4ca | ||
|   | 18e27d65ce | ||
|   | 0c39567f20 | ||
|   | f5491c901f | ||
|   | f5cae85af5 | ||
|   | 01e8040a07 | ||
|   | aa916c9ac8 | ||
|   | 332eea3b3b | ||
|   | 109af1b1b6 | ||
|   | 629f6e76a9 | ||
|   | 82a3918aa4 | ||
|   | cff3079a04 | ||
|   | 4f864a07f5 | ||
|   | 938066bd50 | ||
|   | 9fee87c258 | ||
|   | fd573880eb | ||
|   | 2f1ba368e2 | ||
|   | bde1818400 | ||
|   | 3a28f9e5d2 | ||
|   | 0bda5fd3f2 | ||
|   | 1e3a6e1b5f | ||
|   | fa92bc866b | ||
|   | f4bc9d9722 | ||
|   | 9cfe19c1e1 | ||
|   | f18c790824 | ||
|   | f165b4b52b | ||
|   | 7ebc006516 | ||
|   | 5ca4ede5ac | ||
|   | 35f4ef3452 | ||
|   | 828cba875f | ||
|   | 3920ee3941 | ||
|   | feaa7539af | ||
|   | c004f042f9 | ||
|   | ae41c08fec | ||
|   | 223507f36f | 
							
								
								
									
										10
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,8 +1,12 @@ | ||||
| Pre-requisites: | ||||
| Python 3.1 or 2.6 | ||||
| - Python 3.1 or 2.6 | ||||
|  | ||||
| Install: | ||||
| python3 setup.py install | ||||
| > python3 setup.py install | ||||
|  | ||||
| Root install: | ||||
| sudo python3 setup.py install | ||||
| > sudo python3 setup.py install | ||||
|  | ||||
| To test: | ||||
| > cd examples | ||||
| > python echo_client.py -v -j [USER@example.com] -p [PASSWORD] | ||||
|   | ||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| Copyright (c) 2010 ICRL | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										39
									
								
								MANIFEST
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								MANIFEST
									
									
									
									
									
								
							| @@ -1,39 +0,0 @@ | ||||
| setup.py | ||||
| sleekxmpp/__init__.py | ||||
| sleekxmpp/basexmpp.py | ||||
| sleekxmpp/clientxmpp.py | ||||
| sleekxmpp/example.py | ||||
| sleekxmpp/plugins/__init__.py | ||||
| sleekxmpp/plugins/base.py | ||||
| sleekxmpp/plugins/gmail_notify.py | ||||
| sleekxmpp/plugins/xep_0004.py | ||||
| sleekxmpp/plugins/xep_0009.py | ||||
| sleekxmpp/plugins/xep_0030.py | ||||
| sleekxmpp/plugins/xep_0045.py | ||||
| sleekxmpp/plugins/xep_0050.py | ||||
| sleekxmpp/plugins/xep_0060.py | ||||
| sleekxmpp/plugins/xep_0078.py | ||||
| sleekxmpp/plugins/xep_0086.py | ||||
| sleekxmpp/plugins/xep_0092.py | ||||
| sleekxmpp/plugins/xep_0199.py | ||||
| sleekxmpp/stanza/__init__.py | ||||
| sleekxmpp/stanza/iq.py | ||||
| sleekxmpp/stanza/message.py | ||||
| sleekxmpp/stanza/presence.py | ||||
| sleekxmpp/xmlstream/__init__.py | ||||
| sleekxmpp/xmlstream/stanzabase.py | ||||
| sleekxmpp/xmlstream/statemachine.py | ||||
| sleekxmpp/xmlstream/test.py | ||||
| sleekxmpp/xmlstream/testclient.py | ||||
| sleekxmpp/xmlstream/xmlstream.py | ||||
| sleekxmpp/xmlstream/handler/__init__.py | ||||
| sleekxmpp/xmlstream/handler/base.py | ||||
| sleekxmpp/xmlstream/handler/callback.py | ||||
| sleekxmpp/xmlstream/handler/waiter.py | ||||
| sleekxmpp/xmlstream/handler/xmlcallback.py | ||||
| sleekxmpp/xmlstream/handler/xmlwaiter.py | ||||
| sleekxmpp/xmlstream/matcher/__init__.py | ||||
| sleekxmpp/xmlstream/matcher/base.py | ||||
| sleekxmpp/xmlstream/matcher/many.py | ||||
| sleekxmpp/xmlstream/matcher/xmlmask.py | ||||
| sleekxmpp/xmlstream/matcher/xpath.py | ||||
							
								
								
									
										14
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README
									
									
									
									
									
								
							| @@ -1,5 +1,13 @@ | ||||
| SleekXMPP is an XMPP library written for Python 3.x (with 2.6 compatibility). | ||||
| 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. | ||||
| @@ -31,7 +39,9 @@ Since 0.2, here's the Changelog: | ||||
| Credits | ||||
| ---------------- | ||||
| Main Author: Nathan Fritz fritz@netflint.net | ||||
| XEP-0045 original implementation: Kevin Smith | ||||
| 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. | ||||
|   | ||||
							
								
								
									
										171
									
								
								conn_tests/test_pubsubjobs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								conn_tests/test_pubsubjobs.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | ||||
| 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() | ||||
| @@ -5,7 +5,6 @@ from xml.etree import cElementTree as ET | ||||
| import os | ||||
| import time | ||||
| import sys | ||||
| import thread | ||||
| import unittest | ||||
| import sleekxmpp.plugins.xep_0004 | ||||
| from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath | ||||
| @@ -43,6 +42,10 @@ class TestPubsubServer(unittest.TestCase): | ||||
|  | ||||
| 	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)) | ||||
| @@ -130,6 +133,39 @@ class TestPubsubServer(unittest.TestCase): | ||||
| 		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.") | ||||
|   | ||||
| @@ -1,19 +1,9 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
| 
 | ||||
|     SleekXMPP is free software; you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|     the Free Software Foundation; either version 2 of the License, or | ||||
|     (at your option) any later version. | ||||
| 
 | ||||
|     SleekXMPP is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
| 
 | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with SleekXMPP; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|      | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| 
 | ||||
| import logging | ||||
| @@ -34,9 +24,9 @@ class testps(sleekxmpp.ClientXMPP): | ||||
| 		self.registerPlugin('xep_0030') | ||||
| 		self.registerPlugin('xep_0060') | ||||
| 		self.registerPlugin('xep_0092') | ||||
| 		self.add_handler("<message xmlns='jabber:client'><event xmlns='http://jabber.org/protocol/pubsub#event' /></message>", self.pubsubEventHandler, threaded=True) | ||||
| 		self.add_handler("<message xmlns='jabber:client'><event xmlns='http://jabber.org/protocol/pubsub#event' /></message>", self.pubsubEventHandler, name='Pubsub Event', threaded=True) | ||||
| 		self.add_event_handler("session_start", self.start, threaded=True) | ||||
| 		self.add_handler("<iq type='error' />", self.handleError) | ||||
| 		self.add_handler("<iq type='error' />", self.handleError, name='Iq Error') | ||||
| 		self.events = Queue.Queue() | ||||
| 		self.default_config = None | ||||
| 		self.ps = self.plugin['xep_0060'] | ||||
							
								
								
									
										48
									
								
								example.py
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								example.py
									
									
									
									
									
								
							| @@ -1,48 +0,0 @@ | ||||
| # coding=utf8 | ||||
|  | ||||
| import sleekxmpp | ||||
| import logging | ||||
| from optparse import OptionParser | ||||
| import time | ||||
|  | ||||
| import sys | ||||
|  | ||||
| if sys.version_info < (3,0): | ||||
| 	reload(sys) | ||||
| 	sys.setdefaultencoding('utf8') | ||||
|  | ||||
|  | ||||
| class Example(sleekxmpp.ClientXMPP): | ||||
| 	 | ||||
| 	def __init__(self, jid, password): | ||||
| 		sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
| 		self.add_event_handler("session_start", self.start) | ||||
| 		self.add_event_handler("message", self.message) | ||||
| 	 | ||||
| 	def start(self, event): | ||||
| 		self.getRoster() | ||||
| 		self.sendPresence() | ||||
|  | ||||
| 	def message(self, msg): | ||||
| 		msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| 	#parse command line arguements | ||||
| 	optp = OptionParser() | ||||
| 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||
| 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||
| 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||
| 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||
| 	opts,args = optp.parse_args() | ||||
| 	 | ||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||
| 	xmpp = Example('user@gmail.com/sleekxmpp', 'password') | ||||
| 	xmpp.registerPlugin('xep_0004') | ||||
| 	xmpp.registerPlugin('xep_0030') | ||||
| 	xmpp.registerPlugin('xep_0060') | ||||
| 	xmpp.registerPlugin('xep_0199') | ||||
| 	if xmpp.connect(('talk.google.com', 5222)): | ||||
| 		xmpp.process(threaded=False) | ||||
| 		print("done") | ||||
| 	else: | ||||
| 		print("Unable to connect.") | ||||
							
								
								
									
										10
									
								
								examples/config.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								examples/config.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <config xmlns="sleekxmpp:config"> | ||||
|   <jid>component.localhost</jid> | ||||
|   <secret>ssshh</secret> | ||||
|   <server>localhost</server> | ||||
|   <port>8888</port> | ||||
|  | ||||
|   <query xmlns="jabber:iq:roster"> | ||||
|     <item jid="user@example.com" subscription="both" /> | ||||
|   </query> | ||||
| </config> | ||||
							
								
								
									
										190
									
								
								examples/config_component.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										190
									
								
								examples/config_component.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import time | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.componentxmpp import ComponentXMPP | ||||
| from sleekxmpp.stanza.roster import Roster | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
| from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| # by default. To ensure that Unicode is handled properly | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|  | ||||
|  | ||||
| class Config(ElementBase): | ||||
|  | ||||
|     """ | ||||
|     In order to make loading and manipulating an XML config | ||||
|     file easier, we will create a custom stanza object for | ||||
|     our config XML file contents. See the documentation | ||||
|     on stanza objects for more information on how to create | ||||
|     and use stanza objects and stanza plugins. | ||||
|  | ||||
|     We will reuse the IQ roster query stanza to store roster | ||||
|     information since it already exists. | ||||
|  | ||||
|     Example config XML: | ||||
|       <config xmlns="sleekxmpp:config"> | ||||
|         <jid>component.localhost</jid> | ||||
|         <secret>ssshh</secret> | ||||
|         <server>localhost</server> | ||||
|         <port>8888</port> | ||||
|  | ||||
|         <query xmlns="jabber:iq:roster"> | ||||
|           <item jid="user@example.com" subscription="both" /> | ||||
|         </query> | ||||
|       </config> | ||||
|     """ | ||||
|  | ||||
|     name = "config" | ||||
|     namespace = "sleekxmpp:config" | ||||
|     interfaces = set(('jid', 'secret', 'server', 'port')) | ||||
|     sub_interfaces = interfaces | ||||
|  | ||||
|  | ||||
| registerStanzaPlugin(Config, Roster) | ||||
|  | ||||
|  | ||||
| class ConfigComponent(ComponentXMPP): | ||||
|  | ||||
|     """ | ||||
|     A simple SleekXMPP component that uses an external XML | ||||
|     file to store its configuration data. To make testing | ||||
|     that the component works, it will also echo messages sent | ||||
|     to it. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, config): | ||||
|         """ | ||||
|         Create a ConfigComponent. | ||||
|  | ||||
|         Arguments: | ||||
|             config      -- The XML contents of the config file. | ||||
|             config_file -- The XML config file object itself. | ||||
|         """ | ||||
|         ComponentXMPP.__init__(self, config['jid'], | ||||
|                                      config['secret'], | ||||
|                                      config['server'], | ||||
|                                      config['port']) | ||||
|  | ||||
|         # Store the roster information. | ||||
|         self.roster = config['roster']['items'] | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the component establishes its connection with the | ||||
|         # server and the XML streams are ready for use. We | ||||
|         # want to listen for this event so that we we can | ||||
|         # broadcast any needed initial presence stanzas. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|         # The message event is triggered whenever a message | ||||
|         # stanza is received. Be aware that that includes | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         The typical action for the session_start event in a component | ||||
|         is to broadcast presence stanzas to all subscribers to the | ||||
|         component. Note that the component does not have a roster | ||||
|         provided by the XMPP server. In this case, we have possibly | ||||
|         saved a roster in the component's configuration file. | ||||
|  | ||||
|         Since the component may use any number of JIDs, you should | ||||
|         also include the JID that is sending the presence. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         for jid in self.roster: | ||||
|             if self.roster[jid]['subscription'] != 'none': | ||||
|                 self.sendPresence(pfrom=self.jid, pto=jid) | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|         Process incoming message stanzas. Be aware that this also | ||||
|         includes MUC messages and error messages. It is usually | ||||
|         a good idea to check the messages's type before processing | ||||
|         or sending replies. | ||||
|  | ||||
|         Since a component may send messages from any number of JIDs, | ||||
|         it is best to always include a from JID. | ||||
|  | ||||
|         Arguments: | ||||
|             msg -- The received message stanza. See the documentation | ||||
|                    for stanza objects and the Message stanza to see | ||||
|                    how it may be used. | ||||
|         """ | ||||
|         # The reply method will use the messages 'to' JID as the | ||||
|         # outgoing reply's 'from' JID. | ||||
|         msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.ERROR, default=logging.INFO) | ||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.DEBUG, default=logging.INFO) | ||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=5, default=logging.INFO) | ||||
|  | ||||
|     # Component name and secret options. | ||||
|     optp.add_option("-c", "--config", help="path to config file", | ||||
|                     dest="config", default="config.xml") | ||||
|  | ||||
|     opts, args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     # Load configuration data. | ||||
|     config_file = open(opts.config, 'r+') | ||||
|     config_data = "\n".join([line for line in config_file]) | ||||
|     config = Config(xml=ET.fromstring(config_data)) | ||||
|     config_file.close() | ||||
|  | ||||
|     # Setup the ConfigComponent and register plugins. Note that while plugins | ||||
|     # may have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = ConfigComponent(config) | ||||
|     xmpp.registerPlugin('xep_0030') # Service Discovery | ||||
|     xmpp.registerPlugin('xep_0004') # Data Forms | ||||
|     xmpp.registerPlugin('xep_0060') # PubSub | ||||
|     xmpp.registerPlugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         xmpp.process(threaded=False) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										129
									
								
								examples/echo_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										129
									
								
								examples/echo_client.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| #!/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 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.getRoster() | ||||
|         self.sendPresence() | ||||
|  | ||||
|     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') | ||||
|  | ||||
|     # 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.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(): | ||||
|         # 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.") | ||||
							
								
								
									
										28
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								setup.py
									
									
									
									
									
								
							| @@ -16,16 +16,16 @@ import sys | ||||
| #     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          = '0.2.3.1' | ||||
| VERSION          = '1.0.0.0' | ||||
| 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). | ||||
| @@ -37,17 +37,20 @@ CLASSIFIERS      = [ 'Intended Audience :: Developers', | ||||
|                      'Topic :: Software Development :: Libraries :: Python Modules', | ||||
|                    ] | ||||
|  | ||||
| packages     = [ 'sleekxmpp',  | ||||
| 				 'sleekxmpp/plugins', | ||||
| 				 'sleekxmpp/stanza', | ||||
| 				 'sleekxmpp/xmlstream', | ||||
| 				 'sleekxmpp/xmlstream/matcher', | ||||
| 				 'sleekxmpp/xmlstream/handler' ] | ||||
| packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins', | ||||
|                  'sleekxmpp/stanza', | ||||
|                  'sleekxmpp/test', | ||||
|                  'sleekxmpp/xmlstream', | ||||
|                  'sleekxmpp/xmlstream/matcher', | ||||
|                  'sleekxmpp/xmlstream/handler', | ||||
|                  'sleekxmpp/thirdparty', | ||||
|                  ] | ||||
|  | ||||
| if sys.version_info < (3, 0): | ||||
| 	packages.append('sleekxmpp/xmlstream/tostring26') | ||||
|     py_modules = ['sleekxmpp.xmlstream.tostring.tostring26'] | ||||
| else: | ||||
| 	packages.append('sleekxmpp/xmlstream/tostring') | ||||
|     py_modules = ['sleekxmpp.xmlstream.tostring.tostring'] | ||||
|  | ||||
| setup( | ||||
|     name             = "sleekxmpp", | ||||
| @@ -59,7 +62,8 @@ setup( | ||||
|     url          = 'http://code.google.com/p/sleekxmpp', | ||||
|     license      = 'MIT', | ||||
|     platforms    = [ 'any' ], | ||||
| 	packages	 = packages, | ||||
|     packages     = packages, | ||||
|     py_modules   = py_modules, | ||||
|     requires     = [ 'tlslite', 'pythondns' ], | ||||
|     ) | ||||
|  | ||||
|   | ||||
| @@ -1,246 +1,16 @@ | ||||
| #!/usr/bin/python2.5 | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from __future__ import absolute_import, unicode_literals | ||||
| from . basexmpp import basexmpp | ||||
| from xml.etree import cElementTree as ET | ||||
| from . xmlstream.xmlstream import XMLStream | ||||
| from . xmlstream.xmlstream import RestartStream | ||||
| from . xmlstream.matcher.xmlmask import MatchXMLMask | ||||
| from . xmlstream.matcher.xpath import MatchXPath | ||||
| from . xmlstream.matcher.many import MatchMany | ||||
| from . xmlstream.handler.callback import Callback | ||||
| from . xmlstream.stanzabase import StanzaBase | ||||
| from . xmlstream import xmlstream as xmlstreammod | ||||
| from . stanza.message import Message | ||||
| from . stanza.iq import Iq | ||||
| import time | ||||
| import logging | ||||
| import base64 | ||||
| import sys | ||||
| import random | ||||
| import copy | ||||
| from . import plugins | ||||
| #from . import stanza | ||||
| srvsupport = True | ||||
| try: | ||||
| 	import dns.resolver | ||||
| except ImportError: | ||||
| 	srvsupport = False | ||||
|  | ||||
|  | ||||
|  | ||||
| #class PresenceStanzaType(object): | ||||
| #	 | ||||
| #	def fromXML(self, xml): | ||||
| #		self.ptype = xml.get('type') | ||||
|  | ||||
|  | ||||
| class ClientXMPP(basexmpp, XMLStream): | ||||
| 	"""SleekXMPP's client class.  Use only for good, not evil.""" | ||||
|  | ||||
| 	def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True): | ||||
| 		global srvsupport | ||||
| 		XMLStream.__init__(self) | ||||
| 		self.default_ns = 'jabber:client' | ||||
| 		basexmpp.__init__(self) | ||||
| 		self.plugin_config = plugin_config | ||||
| 		self.escape_quotes = escape_quotes | ||||
| 		self.set_jid(jid) | ||||
| 		self.plugin_whitelist = plugin_whitelist | ||||
| 		self.auto_reconnect = True | ||||
| 		self.srvsupport = srvsupport | ||||
| 		self.password = password | ||||
| 		self.registered_features = [] | ||||
| 		self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.server,self.default_ns) | ||||
| 		self.stream_footer = "</stream:stream>" | ||||
| 		#self.map_namespace('http://etherx.jabber.org/streams', 'stream') | ||||
| 		#self.map_namespace('jabber:client', '') | ||||
| 		self.features = [] | ||||
| 		#TODO: Use stream state here | ||||
| 		self.authenticated = False | ||||
| 		self.sessionstarted = False | ||||
| 		self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) | ||||
| 		self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) | ||||
| 		#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True)) | ||||
| 		self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True) | ||||
| 		self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True) | ||||
| 		self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource) | ||||
| 		self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session) | ||||
| 		 | ||||
| 		#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType) | ||||
| 		#self.register_plugins() | ||||
| 	 | ||||
| 	def __getitem__(self, key): | ||||
| 		if key in self.plugin: | ||||
| 			return self.plugin[key] | ||||
| 		else: | ||||
| 			logging.warning("""Plugin "%s" is not loaded.""" % key) | ||||
| 			return False | ||||
| 	 | ||||
| 	def get(self, key, default): | ||||
| 		return self.plugin.get(key, default) | ||||
|  | ||||
| 	def connect(self, address=tuple()): | ||||
| 		"""Connect to the Jabber Server.  Attempts SRV lookup, and if it fails, uses | ||||
| 		the JID server.""" | ||||
| 		if not address or len(address) < 2: | ||||
| 			if not self.srvsupport: | ||||
| 				logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org).  Continuing to attempt connection, using server hostname from JID.") | ||||
| 			else: | ||||
| 				logging.debug("Since no address is supplied, attempting SRV lookup.") | ||||
| 				try: | ||||
| 					answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, "SRV") | ||||
| 				except dns.resolver.NXDOMAIN: | ||||
| 					logging.debug("No appropriate SRV record found.  Using JID server name.") | ||||
| 				else: | ||||
| 					# pick a random answer, weighted by priority | ||||
| 					# there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway  | ||||
| 					# suggestions are welcome | ||||
| 					addresses = {} | ||||
| 					intmax = 0 | ||||
| 					priorities = [] | ||||
| 					for answer in answers: | ||||
| 						intmax += answer.priority | ||||
| 						addresses[intmax] = (answer.target.to_text()[:-1], answer.port) | ||||
| 						priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort() | ||||
| 					picked = random.randint(0, intmax) | ||||
| 					for priority in priorities: | ||||
| 						if picked <= priority: | ||||
| 							address = addresses[priority] | ||||
| 							break | ||||
| 		if not address: | ||||
| 			# if all else fails take server from JID. | ||||
| 			address = (self.server, 5222) | ||||
| 		result = XMLStream.connect(self, address[0], address[1], use_tls=True) | ||||
| 		if result: | ||||
| 			self.event("connected") | ||||
| 		else: | ||||
| 			logging.warning("Failed to connect") | ||||
| 			self.event("disconnected") | ||||
| 		return result | ||||
| 	 | ||||
| 	# overriding reconnect and disconnect so that we can get some events | ||||
| 	# should events be part of or required by xmlstream?  Maybe that would be cleaner | ||||
| 	def reconnect(self): | ||||
| 		logging.info("Reconnecting") | ||||
| 		self.event("disconnected") | ||||
| 		XMLStream.reconnect(self) | ||||
| 	 | ||||
| 	def disconnect(self, init=True, close=False, reconnect=False): | ||||
| 		self.event("disconnected") | ||||
| 		XMLStream.disconnect(self, reconnect) | ||||
| 	 | ||||
| 	def registerFeature(self, mask, pointer, breaker = False): | ||||
| 		"""Register a stream feature.""" | ||||
| 		self.registered_features.append((MatchXMLMask(mask), pointer, breaker)) | ||||
|  | ||||
| 	def updateRoster(self, jid, name=None, subscription=None, groups=[]): | ||||
| 		"""Add or change a roster item.""" | ||||
| 		iq = self.Iq().setValues({'type': 'set'}) | ||||
| 		iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} | ||||
| 		#self.send(iq, self.Iq().setValues({'id': iq['id']})) | ||||
| 		r = iq.send() | ||||
| 		return r['type'] == 'result' | ||||
| 	 | ||||
| 	def getRoster(self): | ||||
| 		"""Request the roster be sent.""" | ||||
| 		iq = self.Iq().setValues({'type': 'get'}).enable('roster').send() | ||||
| 		self._handleRoster(iq, request=True) | ||||
| 	 | ||||
| 	def _handleStreamFeatures(self, features): | ||||
| 		self.features = [] | ||||
| 		for sub in features.xml: | ||||
| 			self.features.append(sub.tag) | ||||
| 		for subelement in features.xml: | ||||
| 			for feature in self.registered_features: | ||||
| 				if feature[0].match(subelement): | ||||
| 				#if self.maskcmp(subelement, feature[0], True): | ||||
| 					if feature[1](subelement) and feature[2]: #if breaker, don't continue | ||||
| 						return True | ||||
| 	 | ||||
| 	def handler_starttls(self, xml): | ||||
| 		if not self.authenticated and self.ssl_support: | ||||
| 			self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, instream=True) | ||||
| 			self.sendXML(xml) | ||||
| 			return True | ||||
| 		else: | ||||
| 			logging.warning("The module tlslite is required in to some servers, and has not been found.") | ||||
| 			return False | ||||
|  | ||||
| 	def handler_tls_start(self, xml): | ||||
| 		logging.debug("Starting TLS") | ||||
| 		if self.startTLS(): | ||||
| 			raise RestartStream() | ||||
| 	 | ||||
| 	def handler_sasl_auth(self, xml): | ||||
| 		if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: | ||||
| 			return False | ||||
| 		logging.debug("Starting SASL Auth") | ||||
| 		self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True) | ||||
| 		self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, instream=True) | ||||
| 		sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') | ||||
| 		if len(sasl_mechs): | ||||
| 			for sasl_mech in sasl_mechs: | ||||
| 				self.features.append("sasl:%s" % sasl_mech.text) | ||||
| 			if 'sasl:PLAIN' in self.features: | ||||
| 				if sys.version_info < (3,0): | ||||
| 					self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) | ||||
| 				else: | ||||
| 					self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) | ||||
| 			else: | ||||
| 				logging.error("No appropriate login method.") | ||||
| 				self.disconnect() | ||||
| 				#if 'sasl:DIGEST-MD5' in self.features: | ||||
| 				#	self._auth_digestmd5() | ||||
| 		return True | ||||
| 	 | ||||
| 	def handler_auth_success(self, xml): | ||||
| 		self.authenticated = True | ||||
| 		self.features = [] | ||||
| 		raise RestartStream() | ||||
|  | ||||
| 	def handler_auth_fail(self, xml): | ||||
| 		logging.info("Authentication failed.") | ||||
| 		self.disconnect() | ||||
| 		self.event("failed_auth") | ||||
| 	 | ||||
| 	def handler_bind_resource(self, xml): | ||||
| 		logging.debug("Requesting resource: %s" % self.resource) | ||||
| 		iq = self.Iq(stype='set') | ||||
| 		res = ET.Element('resource') | ||||
| 		res.text = self.resource | ||||
| 		xml.append(res) | ||||
| 		iq.append(xml) | ||||
| 		response = iq.send() | ||||
| 		#response = self.send(iq, self.Iq(sid=iq['id'])) | ||||
| 		self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text) | ||||
| 		logging.info("Node set to: %s" % self.fulljid) | ||||
| 		if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features: | ||||
| 			logging.debug("Established Session") | ||||
| 			self.sessionstarted = True | ||||
| 			self.event("session_start") | ||||
| 	 | ||||
| 	def handler_start_session(self, xml): | ||||
| 		if self.authenticated: | ||||
| 			iq = self.makeIqSet(xml) | ||||
| 			response = iq.send() | ||||
| 			logging.debug("Established Session") | ||||
| 			self.sessionstarted = True | ||||
| 			self.event("session_start") | ||||
| 	 | ||||
| 	def _handleRoster(self, iq, request=False): | ||||
| 		if iq['type'] == 'set' or (iq['type'] == 'result' and request): | ||||
| 			for jid in iq['roster']['items']: | ||||
| 				if not jid in self.roster: | ||||
| 					self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} | ||||
| 				self.roster[jid].update(iq['roster']['items'][jid]) | ||||
| 			if iq['type'] == 'set': | ||||
| 				self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) | ||||
| 		self.event("roster_update", iq) | ||||
| 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 | ||||
|   | ||||
| @@ -3,291 +3,638 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from __future__ import with_statement, unicode_literals | ||||
|  | ||||
|  | ||||
| from xml.etree import cElementTree as ET | ||||
| from . xmlstream.xmlstream import XMLStream | ||||
| from . xmlstream.matcher.xmlmask import MatchXMLMask | ||||
| from . xmlstream.matcher.many import MatchMany | ||||
| from . xmlstream.handler.xmlcallback import XMLCallback | ||||
| from . xmlstream.handler.xmlwaiter import XMLWaiter | ||||
| from . xmlstream.handler.waiter import Waiter | ||||
| from . xmlstream.handler.callback import Callback | ||||
| from . import plugins | ||||
| from . stanza.message import Message | ||||
| from . stanza.iq import Iq | ||||
| from . stanza.presence import Presence | ||||
| from . stanza.roster import Roster | ||||
| from . stanza.nick import Nick | ||||
| from . stanza.htmlim import HTMLIM | ||||
| from . stanza.error import Error | ||||
|  | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| import sys | ||||
| import copy | ||||
| import logging | ||||
|  | ||||
| if sys.version_info < (3,0): | ||||
| 	reload(sys) | ||||
| 	sys.setdefaultencoding('utf8') | ||||
| import sleekxmpp | ||||
| from sleekxmpp import plugins | ||||
|  | ||||
| from sleekxmpp.stanza import Message, Presence, Iq, Error | ||||
| 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 * | ||||
|  | ||||
|  | ||||
| def stanzaPlugin(stanza, plugin): | ||||
| 	stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin | ||||
| 	stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin | ||||
| # Flag indicating if DNS SRV records are available for use. | ||||
| SRV_SUPPORT = True | ||||
| try: | ||||
|     import dns.resolver | ||||
| except: | ||||
|     SRV_SUPPORT = False | ||||
|  | ||||
|  | ||||
| class basexmpp(object): | ||||
| 	def __init__(self): | ||||
| 		self.id = 0 | ||||
| 		self.id_lock = threading.Lock() | ||||
| 		self.sentpresence = False | ||||
| 		self.fulljid = '' | ||||
| 		self.resource = '' | ||||
| 		self.jid = '' | ||||
| 		self.username = '' | ||||
| 		self.server = '' | ||||
| 		self.plugin = {} | ||||
| 		self.auto_authorize = True | ||||
| 		self.auto_subscribe = True | ||||
| 		self.event_handlers = {} | ||||
| 		self.roster = {} | ||||
| 		self.registerHandler(Callback('IM', MatchXMLMask("<message xmlns='%s'><body /></message>" % self.default_ns), self._handleMessage)) | ||||
| 		self.registerHandler(Callback('Presence', MatchXMLMask("<presence xmlns='%s' />" % self.default_ns), self._handlePresence)) | ||||
| 		self.add_event_handler('presence_subscribe', self._handlePresenceSubscribe) | ||||
| 		self.registerStanza(Message) | ||||
| 		self.registerStanza(Iq) | ||||
| 		self.registerStanza(Presence) | ||||
| 		self.stanzaPlugin(Iq, Roster) | ||||
| 		self.stanzaPlugin(Message, Nick) | ||||
| 		self.stanzaPlugin(Message, HTMLIM) | ||||
| # 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') | ||||
|  | ||||
| 	def stanzaPlugin(self, stanza, plugin): | ||||
| 		stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin | ||||
| 		stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin | ||||
| 	 | ||||
| 	def Message(self, *args, **kwargs): | ||||
| 		return Message(self, *args, **kwargs) | ||||
|  | ||||
| 	def Iq(self, *args, **kwargs): | ||||
| 		return Iq(self, *args, **kwargs) | ||||
| class BaseXMPP(XMLStream): | ||||
|  | ||||
| 	def Presence(self, *args, **kwargs): | ||||
| 		return Presence(self, *args, **kwargs) | ||||
| 	 | ||||
| 	def set_jid(self, jid): | ||||
| 		"""Rip a JID apart and claim it as our own.""" | ||||
| 		self.fulljid = jid | ||||
| 		self.resource = self.getjidresource(jid) | ||||
| 		self.jid = self.getjidbare(jid) | ||||
| 		self.username = jid.split('@', 1)[0] | ||||
| 		self.server = jid.split('@',1)[-1].split('/', 1)[0] | ||||
| 		 | ||||
| 	def registerPlugin(self, plugin, pconfig = {}): | ||||
| 		"""Register a plugin not in plugins.__init__.__all__ but in the plugins | ||||
| 		directory.""" | ||||
| 		# discover relative "path" to the plugins module from the main app, and import it. | ||||
| 		# TODO: | ||||
| 		# gross, this probably isn't necessary anymore, especially for an installed module | ||||
| 		__import__("%s.%s" % (globals()['plugins'].__name__, plugin)) | ||||
| 		# init the plugin class | ||||
| 		self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek | ||||
| 		# all of this for a nice debug? sure. | ||||
| 		xep = '' | ||||
| 		if hasattr(self.plugin[plugin], 'xep'): | ||||
| 			xep = "(XEP-%s) " % self.plugin[plugin].xep | ||||
| 		logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) | ||||
| 	 | ||||
| 	def register_plugins(self): | ||||
| 		"""Initiates all plugins in the plugins/__init__.__all__""" | ||||
| 		if self.plugin_whitelist: | ||||
| 			plugin_list = self.plugin_whitelist | ||||
| 		else: | ||||
| 			plugin_list = plugins.__all__ | ||||
| 		for plugin in plugin_list: | ||||
| 			if plugin in plugins.__all__: | ||||
| 				self.registerPlugin(plugin, self.plugin_config.get(plugin, {})) | ||||
| 			else: | ||||
| 				raise NameError("No plugin by the name of %s listed in plugins.__all__." % plugin) | ||||
| 		# run post_init() for cross-plugin interaction | ||||
| 		for plugin in self.plugin: | ||||
| 			self.plugin[plugin].post_init() | ||||
| 	 | ||||
| 	def getNewId(self): | ||||
| 		with self.id_lock: | ||||
| 			self.id += 1 | ||||
| 			return self.getId() | ||||
| 	 | ||||
| 	def add_handler(self, mask, pointer, disposable=False, threaded=False, filter=False, instream=False): | ||||
| 		#logging.warning("Deprecated add_handler used for %s: %s." % (mask, pointer)) | ||||
| 		self.registerHandler(XMLCallback('add_handler_%s' % self.getNewId(), MatchXMLMask(mask), pointer, threaded, disposable, instream)) | ||||
| 	 | ||||
| 	def getId(self): | ||||
| 		return "%x".upper() % self.id | ||||
|     """ | ||||
|     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. | ||||
|  | ||||
| 	def sendXML(self, data, mask=None, timeout=10): | ||||
| 		return self.send(self.tostring(data), mask, timeout) | ||||
| 	 | ||||
| 	def send(self, data, mask=None, timeout=10): | ||||
| 		#logging.warning("Deprecated send used for \"%s\"" % (data,)) | ||||
| 		#if not type(data) == type(''): | ||||
| 		#	data = self.tostring(data) | ||||
| 		if hasattr(mask, 'xml'): | ||||
| 			mask = mask.xml | ||||
| 		data = str(data) | ||||
| 		if mask is not None: | ||||
| 			logging.warning("Use of send mask waiters is deprecated") | ||||
| 			waitfor = Waiter('SendWait_%s' % self.getNewId(), MatchXMLMask(mask)) | ||||
| 			self.registerHandler(waitfor) | ||||
| 		self.sendRaw(data) | ||||
| 		if mask is not None: | ||||
| 			return waitfor.wait(timeout) | ||||
| 	 | ||||
| 	def makeIq(self, id=0, ifrom=None): | ||||
| 		return self.Iq().setValues({'id': id, 'from': ifrom}) | ||||
| 	 | ||||
| 	def makeIqGet(self, queryxmlns = None): | ||||
| 		iq = self.Iq().setValues({'type': 'get'}) | ||||
| 		if queryxmlns: | ||||
| 			iq.append(ET.Element("{%s}query" % queryxmlns)) | ||||
| 		return iq | ||||
| 	 | ||||
| 	def makeIqResult(self, id): | ||||
| 		return self.Iq().setValues({'id': id, 'type': 'result'}) | ||||
| 	 | ||||
| 	def makeIqSet(self, sub=None): | ||||
| 		iq = self.Iq().setValues({'type': 'set'}) | ||||
| 		if sub != None: | ||||
| 			iq.append(sub) | ||||
| 		return iq | ||||
|     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. | ||||
|  | ||||
| 	def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None): | ||||
| 		iq = self.Iq().setValues({'id': id}) | ||||
| 		iq['error'].setValues({'type': type, 'condition': condition, 'text': text}) | ||||
| 		return iq | ||||
|     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 makeIqQuery(self, iq, xmlns): | ||||
| 		query = ET.Element("{%s}query" % xmlns) | ||||
| 		iq.append(query) | ||||
| 		return iq | ||||
| 	 | ||||
| 	def makeQueryRoster(self, iq=None): | ||||
| 		query = ET.Element("{jabber:iq:roster}query") | ||||
| 		if iq: | ||||
| 			iq.append(query) | ||||
| 		return query | ||||
| 	 | ||||
| 	def add_event_handler(self, name, pointer, threaded=False, disposable=False): | ||||
| 		if not name in self.event_handlers: | ||||
| 			self.event_handlers[name] = [] | ||||
| 		self.event_handlers[name].append((pointer, threaded, disposable)) | ||||
|     def __init__(self, default_ns='jabber:client'): | ||||
|         """ | ||||
|         Adapt an XML stream for use with XMPP. | ||||
|  | ||||
| 	def event(self, name, eventdata = {}): # called on an event | ||||
| 		for handler in self.event_handlers.get(name, []): | ||||
| 			if handler[1]: #if threaded | ||||
| 				#thread.start_new(handler[0], (eventdata,)) | ||||
| 				x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(eventdata,)) | ||||
| 				x.start() | ||||
| 			else: | ||||
| 				handler[0](eventdata) | ||||
| 			if handler[2]: #disposable | ||||
| 				with self.lock: | ||||
| 					self.event_handlers[name].pop(self.event_handlers[name].index(handler)) | ||||
| 	 | ||||
| 	def makeMessage(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): | ||||
| 		message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) | ||||
| 		message['body'] = mbody | ||||
| 		message['subject'] = msubject | ||||
| 		if mnick is not None: message['nick'] = mnick | ||||
| 		if mhtml is not None: message['html']['html'] = mhtml | ||||
| 		return message | ||||
| 	 | ||||
| 	def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None): | ||||
| 		presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) | ||||
| 		if pshow is not None: presence['type'] = pshow | ||||
| 		if pfrom is None: #maybe this should be done in stanzabase | ||||
| 			presence['from'] = self.fulljid | ||||
| 		presence['priority'] = ppriority | ||||
| 		presence['status'] = pstatus | ||||
| 		return presence | ||||
| 	 | ||||
| 	def sendMessage(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): | ||||
| 		self.send(self.makeMessage(mto,mbody,msubject,mtype,mhtml,mfrom,mnick)) | ||||
| 	 | ||||
| 	def sendPresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None, ptype=None): | ||||
| 		self.send(self.makePresence(pshow,pstatus,ppriority,pto, ptype=ptype, pfrom=pfrom)) | ||||
| 		if not self.sentpresence: | ||||
| 			self.event('sent_presence') | ||||
| 			self.sentpresence = True | ||||
|         Arguments: | ||||
|             default_ns -- Ensure that the correct default XML namespace | ||||
|                           is used during initialization. | ||||
|         """ | ||||
|         XMLStream.__init__(self) | ||||
|  | ||||
| 	def sendPresenceSubscription(self, pto, pfrom=None, ptype='subscribe', pnick=None) : | ||||
| 		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) | ||||
| 		self.send(presence) | ||||
| 	 | ||||
| 	def getjidresource(self, fulljid): | ||||
| 		if '/' in fulljid: | ||||
| 			return fulljid.split('/', 1)[-1] | ||||
| 		else: | ||||
| 			return '' | ||||
| 	 | ||||
| 	def getjidbare(self, fulljid): | ||||
| 		return fulljid.split('/', 1)[0] | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.registerPlugin = self.register_plugin | ||||
|         self.makeIq = self.make_iq | ||||
|         self.makeIqGet = self.make_iq_get | ||||
|         self.makeIqResult = self.make_iq_result | ||||
|         self.makeIqSet = self.make_iq_set | ||||
|         self.makeIqError = self.make_iq_error | ||||
|         self.makeIqQuery = self.make_iq_query | ||||
|         self.makeQueryRoster = self.make_query_roster | ||||
|         self.makeMessage = self.make_message | ||||
|         self.makePresence = self.make_presence | ||||
|         self.sendMessage = self.send_message | ||||
|         self.sendPresence = self.send_presence | ||||
|         self.sendPresenceSubscription = self.send_presence_subscription | ||||
|  | ||||
| 	def _handleMessage(self, msg): | ||||
| 		self.event('message', msg) | ||||
| 	 | ||||
| 	def _handlePresence(self, presence): | ||||
| 		"""Update roster items based on presence""" | ||||
| 		self.event("presence_%s" % presence['type'], presence) | ||||
| 		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 | ||||
| 		jid = presence['from'].bare | ||||
| 		resource = presence['from'].resource | ||||
| 		show = presence['type'] | ||||
| 		status = presence['status'] | ||||
| 		priority = presence['priority'] | ||||
| 		wasoffline = False | ||||
| 		oldroster = self.roster.get(jid, {}).get(resource, {}) | ||||
| 		if not presence['from'].bare in self.roster: | ||||
| 			self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False} | ||||
| 		if not resource in self.roster[jid]['presence']: | ||||
| 			if (show == 'available' or show in presence.showtypes): | ||||
| 				self.event("got_online", presence) | ||||
| 			wasoffline = True | ||||
| 			self.roster[jid]['presence'][resource] = {} | ||||
| 		if self.roster[jid]['presence'][resource].get('show', 'unavailable') == 'unavailable': | ||||
| 			wasoffline = True | ||||
| 		self.roster[jid]['presence'][resource] = {'show': show, 'status': status, 'priority': priority} | ||||
| 		name = self.roster[jid].get('name', '') | ||||
| 		if show == 'unavailable': | ||||
| 			logging.debug("%s %s got offline" % (jid, resource)) | ||||
| 			if len(self.roster[jid]['presence']): | ||||
| 				del self.roster[jid]['presence'][resource] | ||||
| 			else: | ||||
| 				del self.roster[jid] | ||||
| 			if not wasoffline: | ||||
| 				self.event("got_offline", presence) | ||||
| 		self.event("changed_status", presence) | ||||
| 		name = '' | ||||
| 		if name: | ||||
| 			name = "(%s) " % name | ||||
| 		logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show,status)) | ||||
| 	 | ||||
| 	def _handlePresenceSubscribe(self, presence): | ||||
| 		"""Handling subscriptions automatically.""" | ||||
| 		if self.auto_authorize == True: | ||||
| 			self.send(self.makePresence(ptype='subscribed', pto=presence['from'].bare)) | ||||
| 			if self.auto_subscribe: | ||||
| 				self.send(self.makePresence(ptype='subscribe', pto=presence['from'].bare)) | ||||
| 		elif self.auto_authorize == False: | ||||
| 			self.send(self.makePresence(ptype='unsubscribed', pto=presence['from'].bare)) | ||||
|         self.default_ns = default_ns | ||||
|         self.stream_ns = 'http://etherx.jabber.org/streams' | ||||
|  | ||||
|         self.boundjid = JID("") | ||||
|  | ||||
|         self.plugin = {} | ||||
|         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.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.registerStanza(Message) | ||||
|         self.registerStanza(Iq) | ||||
|         self.registerStanza(Presence) | ||||
|  | ||||
|         # 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) | ||||
|             logging.debug("Loaded Plugin %s%s" % desc) | ||||
|         except: | ||||
|             logging.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: | ||||
|             logging.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): | ||||
|         """ | ||||
|         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. | ||||
|         """ | ||||
|         return self.Iq()._set_stanza_values({'id': str(id), | ||||
|                                              'from': ifrom}) | ||||
|  | ||||
|     def make_iq_get(self, queryxmlns=None): | ||||
|         """ | ||||
|         Create an Iq stanza of type 'get'. | ||||
|  | ||||
|         Optionally, a query element may be added. | ||||
|  | ||||
|         Arguments: | ||||
|             queryxmlns -- The namespace of the query to use. | ||||
|         """ | ||||
|         return self.Iq()._set_stanza_values({'type': 'get', | ||||
|                                              'query': queryxmlns}) | ||||
|  | ||||
|     def make_iq_result(self, id): | ||||
|         """ | ||||
|         Create an Iq stanza of type 'result' with the given ID value. | ||||
|  | ||||
|         Arguments: | ||||
|             id -- An ideally unique ID value. May use self.new_id(). | ||||
|         """ | ||||
|         return self.Iq()._set_stanza_values({'id': id, | ||||
|                                              'type': 'result'}) | ||||
|  | ||||
|     def make_iq_set(self, sub=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. | ||||
|         """ | ||||
|         iq = self.Iq()._set_stanza_values({'type': 'set'}) | ||||
|         if sub != None: | ||||
|             iq.append(sub) | ||||
|         return iq | ||||
|  | ||||
|     def make_iq_error(self, id, type='cancel', | ||||
|                       condition='feature-not-implemented', text=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. | ||||
|         """ | ||||
|         iq = self.Iq()._set_stanza_values({'id': id}) | ||||
|         iq['error']._set_stanza_values({'type': type, | ||||
|                                         'condition': condition, | ||||
|                                         'text': text}) | ||||
|         return iq | ||||
|  | ||||
|     def make_iq_query(self, iq=None, xmlns=''): | ||||
|         """ | ||||
|         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. | ||||
|         """ | ||||
|         if not iq: | ||||
|             iq = self.Iq() | ||||
|         iq['query'] = xmlns | ||||
|         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 | ||||
|         """ | ||||
|         logging.warning("jid property deprecated. Use boundjid.bare") | ||||
|         return self.boundjid.bare | ||||
|  | ||||
|     @jid.setter | ||||
|     def jid(self, value): | ||||
|         logging.warning("jid property deprecated. Use boundjid.bare") | ||||
|         self.boundjid.bare = value | ||||
|  | ||||
|     @property | ||||
|     def fulljid(self): | ||||
|         """ | ||||
|         Attribute accessor for full jid | ||||
|         """ | ||||
|         logging.warning("fulljid property deprecated. Use boundjid.full") | ||||
|         return self.boundjid.full | ||||
|  | ||||
|     @fulljid.setter | ||||
|     def fulljid(self, value): | ||||
|         logging.warning("fulljid property deprecated. Use boundjid.full") | ||||
|         self.boundjid.full = value | ||||
|  | ||||
|     @property | ||||
|     def resource(self): | ||||
|         """ | ||||
|         Attribute accessor for jid resource | ||||
|         """ | ||||
|         logging.warning("resource property deprecated. Use boundjid.resource") | ||||
|         return self.boundjid.resource | ||||
|  | ||||
|     @resource.setter | ||||
|     def resource(self, value): | ||||
|         logging.warning("fulljid property deprecated. Use boundjid.full") | ||||
|         self.boundjid.resource = value | ||||
|  | ||||
|     @property | ||||
|     def username(self): | ||||
|         """ | ||||
|         Attribute accessor for jid usernode | ||||
|         """ | ||||
|         logging.warning("username property deprecated. Use boundjid.user") | ||||
|         return self.boundjid.user | ||||
|  | ||||
|     @username.setter | ||||
|     def username(self, value): | ||||
|         logging.warning("username property deprecated. Use boundjid.user") | ||||
|         self.boundjid.user = value | ||||
|  | ||||
|     @property | ||||
|     def server(self): | ||||
|         """ | ||||
|         Attribute accessor for jid host | ||||
|         """ | ||||
|         logging.warning("server property deprecated. Use boundjid.host") | ||||
|         return self.boundjid.server | ||||
|  | ||||
|     @server.setter | ||||
|     def server(self, value): | ||||
|         logging.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.""" | ||||
|         logging.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_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]['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': | ||||
|             logging.debug("%s %s got offline" % (jid, resource)) | ||||
|             del connections[resource] | ||||
|  | ||||
|             if not connections and not self.roster[jid]['in_roster']: | ||||
|                 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) | ||||
|         logging.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 | ||||
|   | ||||
							
								
								
									
										433
									
								
								sleekxmpp/clientxmpp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										433
									
								
								sleekxmpp/clientxmpp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,433 @@ | ||||
| """ | ||||
|     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 | ||||
|  | ||||
|  | ||||
| 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') | ||||
|  | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.updateRoster = self.update_roster | ||||
|         self.delRosterItem = self.del_roster_item | ||||
|         self.getRoster = self.get_roster | ||||
|         self.registerFeature = self.register_feature | ||||
|  | ||||
|         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.session_started_event = threading.Event() | ||||
|         self.session_started_event.clear() | ||||
|  | ||||
|         self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( | ||||
|                 self.boundjid.host, | ||||
|                 "xmlns:stream='%s'" % self.stream_ns, | ||||
|                 "xmlns='%s'" % self.default_ns) | ||||
|         self.stream_footer = "</stream:stream>" | ||||
|  | ||||
|         self.features = [] | ||||
|         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( | ||||
|             "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", | ||||
|             self._handle_starttls, True) | ||||
|         self.register_feature( | ||||
|             "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", | ||||
|             self._handle_sasl_auth, True) | ||||
|         self.register_feature( | ||||
|             "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", | ||||
|             self._handle_bind_resource) | ||||
|         self.register_feature( | ||||
|             "<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", | ||||
|             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(): | ||||
|             logging.debug("Session start has taken more than 15 seconds") | ||||
|             self.disconnect(reconnect=self.auto_reconnect) | ||||
|  | ||||
|     def connect(self, address=tuple()): | ||||
|         """ | ||||
|         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. | ||||
|         """ | ||||
|         self.session_started_event.clear() | ||||
|         if not address or len(address) < 2: | ||||
|             if not self.srv_support: | ||||
|                 logging.debug("Did not supply (address, port) to connect" + \ | ||||
|                               " to and no SRV support is installed" + \ | ||||
|                               " (http://www.dnspython.org)." + \ | ||||
|                               " Continuing to attempt connection, using" + \ | ||||
|                               " server hostname from JID.") | ||||
|             else: | ||||
|                 logging.debug("Since no address is supplied," + \ | ||||
|                               "attempting SRV lookup.") | ||||
|                 try: | ||||
|                     xmpp_srv = "_xmpp-client._tcp.%s" % self.server | ||||
|                     answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) | ||||
|                 except dns.resolver.NXDOMAIN: | ||||
|                     logging.debug("No appropriate SRV record found." + \ | ||||
|                                   " Using JID server name.") | ||||
|                 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=True) | ||||
|  | ||||
|     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=[]): | ||||
|         """ | ||||
|         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. | ||||
|         """ | ||||
|         iq = self.Iq()._set_stanza_values({'type': 'set'}) | ||||
|         iq['roster']['items'] = {jid: {'name': name, | ||||
|                                        'subscription': subscription, | ||||
|                                        'groups': groups}} | ||||
|         response = iq.send() | ||||
|         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): | ||||
|         """Request the roster from the server.""" | ||||
|         iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster') | ||||
|         response = iq.send() | ||||
|         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.authenticated and self.ssl_support: | ||||
|             tls_ns = 'urn:ietf:params:xml:ns:xmpp-tls' | ||||
|             self.add_handler("<proceed xmlns='%s' />" % tls_ns, | ||||
|                              self._handle_tls_start, | ||||
|                              name='TLS Proceed', | ||||
|                              instream=True) | ||||
|             self.send_xml(xml) | ||||
|             return True | ||||
|         else: | ||||
|             logging.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. | ||||
|         """ | ||||
|         logging.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 '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: | ||||
|             return False | ||||
|  | ||||
|         logging.debug("Starting SASL Auth") | ||||
|         sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|         self.add_handler("<success xmlns='%s' />" % sasl_ns, | ||||
|                          self._handle_auth_success, | ||||
|                          name='SASL Sucess', | ||||
|                          instream=True) | ||||
|         self.add_handler("<failure xmlns='%s' />" % 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("<auth xmlns='%s' mechanism='PLAIN'>%s</auth>" % ( | ||||
|                     sasl_ns, | ||||
|                     auth)) | ||||
|             elif 'sasl:ANONYMOUS' in self.features and not self.boundjid.user: | ||||
|                 self.send("<auth xmlns='%s' mechanism='%s' />" % ( | ||||
|                     sasl_ns, | ||||
|                     'ANONYMOUS')) | ||||
|             else: | ||||
|                 logging.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. | ||||
|         """ | ||||
|         logging.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. | ||||
|         """ | ||||
|         logging.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() | ||||
|  | ||||
|         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 | ||||
|         logging.info("Node set to: %s" % self.boundjid.fulljid) | ||||
|         session_ns = 'urn:ietf:params:xml:ns:xmpp-session' | ||||
|         if "{%s}session" % session_ns not in self.features or self.bindfail: | ||||
|             logging.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() | ||||
|             logging.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_update", iq) | ||||
|         if iq['type'] == 'set': | ||||
|             iq.reply() | ||||
|             iq.enable('roster') | ||||
|             iq.send() | ||||
| @@ -1,41 +0,0 @@ | ||||
| import sleekxmpp.componentxmpp | ||||
| import logging | ||||
| from optparse import OptionParser | ||||
| import time | ||||
|  | ||||
| class Example(sleekxmpp.componentxmpp.ComponentXMPP): | ||||
| 	 | ||||
| 	def __init__(self, jid, password): | ||||
| 		sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'vm1', 5230) | ||||
| 		self.add_event_handler("session_start", self.start) | ||||
| 		self.add_event_handler("message", self.message) | ||||
| 	 | ||||
| 	def start(self, event): | ||||
| 		#self.getRoster() | ||||
| 		#self.sendPresence(pto='admin@tigase.netflint.net/sarkozy') | ||||
| 		#self.sendPresence(pto='tigase.netflint.net') | ||||
| 		pass | ||||
|  | ||||
| 	def message(self, event): | ||||
| 		self.sendMessage("%s/%s" % (event['jid'], event['resource']), "Thanks for sending me, \"%s\"." % event['message'], mtype=event['type']) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| 	#parse command line arguements | ||||
| 	optp = OptionParser() | ||||
| 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||
| 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||
| 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||
| 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||
| 	opts,args = optp.parse_args() | ||||
| 	 | ||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||
| 	xmpp = Example('component.vm1', 'secreteating') | ||||
| 	xmpp.registerPlugin('xep_0004') | ||||
| 	xmpp.registerPlugin('xep_0030') | ||||
| 	xmpp.registerPlugin('xep_0060') | ||||
| 	xmpp.registerPlugin('xep_0199') | ||||
| 	if xmpp.connect(): | ||||
| 		xmpp.process(threaded=False) | ||||
| 		print("done") | ||||
| 	else: | ||||
| 		print("Unable to connect.") | ||||
							
								
								
									
										186
									
								
								sleekxmpp/componentxmpp.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										186
									
								
								sleekxmpp/componentxmpp.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @@ -1,78 +1,138 @@ | ||||
| #!/usr/bin/python2.5 | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from __future__ import absolute_import | ||||
| from . basexmpp import basexmpp | ||||
| from xml.etree import cElementTree as ET | ||||
|  | ||||
| from . xmlstream.xmlstream import XMLStream | ||||
| from . xmlstream.xmlstream import RestartStream | ||||
| from . xmlstream.matcher.xmlmask import MatchXMLMask | ||||
| from . xmlstream.matcher.xpath import MatchXPath | ||||
| from . xmlstream.matcher.many import MatchMany | ||||
| from . xmlstream.handler.callback import Callback | ||||
| from . xmlstream.stanzabase import StanzaBase | ||||
| from . xmlstream import xmlstream as xmlstreammod | ||||
| import time | ||||
| from __future__ import absolute_import | ||||
|  | ||||
| import logging | ||||
| import base64 | ||||
| import sys | ||||
| import random | ||||
| import copy | ||||
| from . import plugins | ||||
| from . import stanza | ||||
| import hashlib | ||||
| srvsupport = True | ||||
| try: | ||||
| 	import dns.resolver | ||||
| except ImportError: | ||||
| 	srvsupport = False | ||||
|  | ||||
| from sleekxmpp import plugins | ||||
| from sleekxmpp import stanza | ||||
| from sleekxmpp.basexmpp import BaseXMPP, SRV_SUPPORT | ||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
|  | ||||
|  | ||||
| class ComponentXMPP(basexmpp, XMLStream): | ||||
| 	"""SleekXMPP's client class.  Use only for good, not evil.""" | ||||
| class ComponentXMPP(BaseXMPP): | ||||
|  | ||||
| 	def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False): | ||||
| 		XMLStream.__init__(self) | ||||
| 		if use_jc_ns: | ||||
| 			self.default_ns = 'jabber:client' | ||||
| 		else: | ||||
| 			self.default_ns = 'jabber:component:accept' | ||||
| 		basexmpp.__init__(self) | ||||
| 		self.auto_authorize = None | ||||
| 		self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid | ||||
| 		self.stream_footer = "</stream:stream>" | ||||
| 		self.server_host = host | ||||
| 		self.server_port = port | ||||
| 		self.set_jid(jid) | ||||
| 		self.secret = secret | ||||
| 		self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) | ||||
| 	 | ||||
| 	def incoming_filter(self, xmlobj): | ||||
| 		if xmlobj.tag.startswith('{jabber:client}'): | ||||
| 			xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns) | ||||
| 		for sub in xmlobj: | ||||
| 			self.incoming_filter(sub) | ||||
| 		return xmlobj | ||||
|     """ | ||||
|     SleekXMPP's basic XMPP server component. | ||||
|  | ||||
| 	def start_stream_handler(self, xml): | ||||
| 		sid = xml.get('id', '') | ||||
| 		handshake = ET.Element('{jabber:component:accept}handshake') | ||||
| 		if sys.version_info < (3,0): | ||||
| 			handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower() | ||||
| 		else: | ||||
| 			handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower() | ||||
| 		self.sendXML(handshake) | ||||
| 	 | ||||
| 	def _handleHandshake(self, xml): | ||||
| 		self.event("session_start") | ||||
| 	 | ||||
| 	def connect(self): | ||||
| 		logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port)) | ||||
| 		return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port) | ||||
|     Use only for good, not for evil. | ||||
|  | ||||
|     Methods: | ||||
|         connect              -- Overrides XMLStream.connect. | ||||
|         incoming_filter      -- Overrides XMLStream.incoming_filter. | ||||
|         start_stream_handler -- Overrides XMLStream.start_stream_handler. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, secret, host, port, | ||||
|                  plugin_config={}, plugin_whitelist=[], use_jc_ns=False): | ||||
|         """ | ||||
|         Arguments: | ||||
|             jid              -- The JID of the component. | ||||
|             secret           -- The secret or password for the component. | ||||
|             host             -- The server accepting the component. | ||||
|             port             -- The port used to connect to the server. | ||||
|             plugin_config    -- A dictionary of plugin configurations. | ||||
|             plugin_whitelist -- A list of desired plugins to load | ||||
|                                 when using register_plugins. | ||||
|             use_js_ns        -- Indicates if the 'jabber:client' namespace | ||||
|                                 should be used instead of the standard | ||||
|                                 'jabber:component:accept' namespace. | ||||
|                                 Defaults to False. | ||||
|         """ | ||||
|         if use_jc_ns: | ||||
|             default_ns = 'jabber:client' | ||||
|         else: | ||||
|             default_ns = 'jabber:component:accept' | ||||
|         BaseXMPP.__init__(self, default_ns) | ||||
|  | ||||
|         self.auto_authorize = None | ||||
|         self.stream_header = "<stream:stream %s %s to='%s'>" % ( | ||||
|                 'xmlns="jabber:component:accept"', | ||||
|                 'xmlns:stream="%s"' % self.stream_ns, | ||||
|                 jid) | ||||
|         self.stream_footer = "</stream:stream>" | ||||
|         self.server_host = host | ||||
|         self.server_port = port | ||||
|         self.set_jid(jid) | ||||
|         self.secret = secret | ||||
|         self.plugin_config = plugin_config | ||||
|         self.plugin_whitelist = plugin_whitelist | ||||
|         self.is_component = True | ||||
|  | ||||
|         self.register_handler( | ||||
|                 Callback('Handshake', | ||||
|                          MatchXPath('{jabber:component:accept}handshake'), | ||||
|                          self._handle_handshake)) | ||||
|  | ||||
|     def connect(self): | ||||
|         """ | ||||
|         Connect to the server. | ||||
|  | ||||
|         Overrides XMLStream.connect. | ||||
|         """ | ||||
|         logging.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) | ||||
|  | ||||
|     def _handle_handshake(self, xml): | ||||
|         """ | ||||
|         The handshake has been accepted. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The reply handshake stanza. | ||||
|         """ | ||||
|         self.event("session_start") | ||||
|   | ||||
| @@ -3,14 +3,47 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
| See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
|  | ||||
| class XMPPError(Exception): | ||||
| 	def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None): | ||||
| 		self.condition = condition | ||||
| 		self.text = text | ||||
| 		self.etype = etype | ||||
| 		self.extension = extension | ||||
| 		self.extension_ns = extension_ns | ||||
| 		self.extension_args = extension_args | ||||
|  | ||||
|     """ | ||||
|     A generic exception that may be raised while processing an XMPP stanza | ||||
|     to indicate that an error response stanza should be sent. | ||||
|  | ||||
|     The exception method for stanza objects extending RootStanza will create | ||||
|     an error stanza and initialize any additional substanzas using the | ||||
|     extension information included in the exception. | ||||
|  | ||||
|     Meant for use in SleekXMPP plugins and applications using SleekXMPP. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, condition='undefined-condition', text=None, etype=None, | ||||
|                  extension=None, extension_ns=None, extension_args=None): | ||||
|         """ | ||||
|         Create a new XMPPError exception. | ||||
|  | ||||
|         Extension information can be included to add additional XML elements | ||||
|         to the generated error stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             condition      -- The XMPP defined error condition. | ||||
|             text           -- Human readable text describing the error. | ||||
|             etype          -- The XMPP error type, such as cancel or modify. | ||||
|             extension      -- Tag name of the extension's XML content. | ||||
|             extension_ns   -- XML namespace of the extensions' XML content. | ||||
|             extension_args -- Content and attributes for the extension | ||||
|                               element. Same as the additional arguments to | ||||
|                               the ET.Element constructor. | ||||
|         """ | ||||
|         if extension_args is None: | ||||
|             extension_args = {} | ||||
|  | ||||
|         self.condition = condition | ||||
|         self.text = text | ||||
|         self.etype = etype | ||||
|         self.extension = extension | ||||
|         self.extension_ns = extension_ns | ||||
|         self.extension_args = extension_args | ||||
|   | ||||
| @@ -1,20 +1,9 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2007  Nathanael C. Fritz | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     SleekXMPP is free software; you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|     the Free Software Foundation; either version 2 of the License, or | ||||
|     (at your option) any later version. | ||||
|  | ||||
|     SleekXMPP is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with SleekXMPP; if not, write to the Free Software | ||||
|     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| __all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] | ||||
| __all__ = ['xep_0004', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', | ||||
| 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] | ||||
|   | ||||
| @@ -1,35 +1,26 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	This file is part of SleekXMPP. | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
|  | ||||
| class base_plugin(object): | ||||
| 	 | ||||
| 	def __init__(self, xmpp, config): | ||||
| 		self.xep = 'base' | ||||
| 		self.description = 'Base Plugin' | ||||
| 		self.xmpp = xmpp | ||||
| 		self.config = config | ||||
| 		self.enable = config.get('enable', True) | ||||
| 		if self.enable: | ||||
| 			self.plugin_init() | ||||
| 	 | ||||
| 	def plugin_init(self): | ||||
| 		pass | ||||
| 	 | ||||
| 	def post_init(self): | ||||
| 		pass | ||||
|  | ||||
|     def __init__(self, xmpp, config): | ||||
|         self.xep = 'base' | ||||
|         self.description = 'Base Plugin' | ||||
|         self.xmpp = xmpp | ||||
|         self.config = config | ||||
|         self.post_inited = False | ||||
|         self.enable = config.get('enable', True) | ||||
|         if self.enable: | ||||
|             self.plugin_init() | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         pass | ||||
|  | ||||
|     def post_init(self): | ||||
|         self.post_inited = True | ||||
|   | ||||
| @@ -1,57 +1,146 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	This file is part of SleekXMPP. | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from __future__ import with_statement | ||||
| from . import base | ||||
|  | ||||
| import logging | ||||
| from xml.etree import cElementTree as ET | ||||
| import traceback | ||||
| import time | ||||
| from . import base | ||||
| from .. xmlstream.handler.callback import Callback | ||||
| from .. xmlstream.matcher.xpath import MatchXPath | ||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID | ||||
| from .. stanza.iq import Iq | ||||
|  | ||||
|  | ||||
| class GmailQuery(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'query' | ||||
|     plugin_attrib = 'gmail' | ||||
|     interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) | ||||
|  | ||||
|     def getSearch(self): | ||||
|         return self['q'] | ||||
|  | ||||
|     def setSearch(self, search): | ||||
|         self['q'] = search | ||||
|  | ||||
|     def delSearch(self): | ||||
|         del self['q'] | ||||
|  | ||||
|  | ||||
| class MailBox(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'mailbox' | ||||
|     plugin_attrib = 'mailbox' | ||||
|     interfaces = set(('result-time', 'total-matched', 'total-estimate',  | ||||
|                       'url', 'threads', 'matched', 'estimate')) | ||||
|  | ||||
|     def getThreads(self): | ||||
|         threads = [] | ||||
|         for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,  | ||||
|                                                       MailThread.name)): | ||||
|             threads.append(MailThread(xml=threadXML, parent=None)) | ||||
|         return threads | ||||
|  | ||||
|     def getMatched(self): | ||||
|         return self['total-matched'] | ||||
|  | ||||
|     def getEstimate(self): | ||||
|         return self['total-estimate'] == '1' | ||||
|  | ||||
|  | ||||
| class MailThread(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'mail-thread-info' | ||||
|     plugin_attrib = 'thread' | ||||
|     interfaces = set(('tid', 'participation', 'messages', 'date',  | ||||
|                       'senders', 'url', 'labels', 'subject', 'snippet')) | ||||
|     sub_interfaces = set(('labels', 'subject', 'snippet')) | ||||
|          | ||||
|     def getSenders(self): | ||||
|         senders = [] | ||||
|         sendersXML = self.xml.find('{%s}senders' % self.namespace) | ||||
|         if sendersXML is not None: | ||||
|             for senderXML in sendersXML.findall('{%s}sender' % self.namespace): | ||||
|                 senders.append(MailSender(xml=senderXML, parent=None)) | ||||
|         return senders | ||||
|  | ||||
|  | ||||
| class MailSender(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'sender' | ||||
|     plugin_attrib = 'sender' | ||||
|     interfaces = set(('address', 'name', 'originator', 'unread')) | ||||
|  | ||||
|     def getOriginator(self): | ||||
|         return self.xml.attrib.get('originator', '0') == '1' | ||||
|  | ||||
|     def getUnread(self): | ||||
|         return self.xml.attrib.get('unread', '0') == '1' | ||||
|  | ||||
|  | ||||
| class NewMail(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'new-mail' | ||||
|     plugin_attrib = 'new-mail' | ||||
|  | ||||
|  | ||||
| class gmail_notify(base.base_plugin): | ||||
| 	 | ||||
| 	def plugin_init(self): | ||||
| 		self.description = 'Google Talk Gmail Notification' | ||||
| 		self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True) | ||||
| 		self.emails = [] | ||||
| 	 | ||||
| 	def handler_gmailcheck(self, payload): | ||||
| 		#TODO XEP 30 should cache results and have getFeature | ||||
| 		result = self.xmpp['xep_0030'].getInfo(self.xmpp.server) | ||||
| 		features = [] | ||||
| 		for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'): | ||||
| 			features.append(feature.get('var')) | ||||
| 		if 'google:mail:notify' in features: | ||||
| 			logging.debug("Server supports Gmail Notify") | ||||
| 			self.xmpp.add_handler("<iq type='set' xmlns='%s'><new-mail xmlns='google:mail:notify' /></iq>" % self.xmpp.default_ns, self.handler_notify) | ||||
| 			self.getEmail() | ||||
| 	 | ||||
| 	def handler_notify(self, xml): | ||||
| 		logging.info("New Gmail recieved!") | ||||
| 		self.xmpp.event('gmail_notify') | ||||
| 		 | ||||
| 	def getEmail(self): | ||||
| 		iq = self.xmpp.makeIqGet() | ||||
| 		iq.attrib['from'] = self.xmpp.fulljid | ||||
| 		iq.attrib['to'] = self.xmpp.jid | ||||
| 		self.xmpp.makeIqQuery(iq, 'google:mail:notify') | ||||
| 		emails = iq.send() | ||||
| 		mailbox = emails.find('{google:mail:notify}mailbox') | ||||
| 		total = int(mailbox.get('total-matched', 0)) | ||||
| 		logging.info("%s New Gmail Messages" % total) | ||||
|     """ | ||||
|     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 '' | ||||
|         logging.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) | ||||
|         self.last_result_time = mailbox['result-time'] | ||||
|         self.xmpp.event('gmail_messages', iq) | ||||
|  | ||||
|     def handle_new_mail(self, iq): | ||||
|         logging.info("Gmail: New emails received!") | ||||
|         self.xmpp.event('gmail_notify') | ||||
|         self.checkEmail() | ||||
|  | ||||
|     def getEmail(self, query=None): | ||||
|         return self.search(query) | ||||
|  | ||||
|     def checkEmail(self): | ||||
|         return self.search(newer=self.last_result_time) | ||||
|  | ||||
|     def search(self, query=None, newer=None): | ||||
|         if query is None: | ||||
|             logging.info("Gmail: Checking for new emails") | ||||
|         else: | ||||
|             logging.info('Gmail: Searching for emails matching: "%s"' % query) | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['to'] = self.xmpp.jid | ||||
|         iq['gmail']['q'] = query | ||||
|         iq['gmail']['newer-than-time'] = newer | ||||
|         return iq.send() | ||||
|   | ||||
							
								
								
									
										46
									
								
								sleekxmpp/plugins/jobs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								sleekxmpp/plugins/jobs.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| from . import base | ||||
| import logging | ||||
| from xml.etree import cElementTree as ET | ||||
| import types | ||||
|  | ||||
| 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) == types.BooleanType or result['type'] != 'result': | ||||
| 			logging.error("Unable to change %s:%s to %s" % (node, jobid, state)) | ||||
| 			return False | ||||
| 		return True | ||||
|  | ||||
							
								
								
									
										417
									
								
								sleekxmpp/plugins/old_0004.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								sleekxmpp/plugins/old_0004.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,417 @@ | ||||
| """ | ||||
|     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 | ||||
|  | ||||
| class old_0004(base.base_plugin): | ||||
| 	 | ||||
| 	def plugin_init(self): | ||||
| 		self.xep = '0004' | ||||
| 		self.description = '*Deprecated Data Forms' | ||||
| 		self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form') | ||||
| 	 | ||||
| 	def post_init(self): | ||||
| 		base.base_plugin.post_init(self) | ||||
| 		self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') | ||||
| 		logging.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 | ||||
| 		 | ||||
| @@ -1,4 +1,4 @@ | ||||
| from .. xmlstream.stanzabase import ElementBase, ET, JID | ||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID | ||||
| from .. stanza.iq import Iq | ||||
| from .. stanza.message import Message | ||||
| from .. basexmpp import basexmpp | ||||
| @@ -6,9 +6,39 @@ from .. xmlstream.xmlstream import XMLStream | ||||
| import logging | ||||
| from . import xep_0004 | ||||
|  | ||||
| def stanzaPlugin(stanza, plugin):                                                                        | ||||
| 	stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin                                              | ||||
| 	stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin  | ||||
|  | ||||
| class PubsubState(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/psstate' | ||||
| 	name = 'state' | ||||
| 	plugin_attrib = 'psstate' | ||||
| 	interfaces = set(('node', 'item', 'payload')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def setPayload(self, value): | ||||
| 		self.xml.append(value) | ||||
| 	 | ||||
| 	def getPayload(self): | ||||
| 		childs = self.xml.getchildren() | ||||
| 		if len(childs) > 0: | ||||
| 			return childs[0] | ||||
| 	 | ||||
| 	def delPayload(self): | ||||
| 		for child in self.xml.getchildren(): | ||||
| 			self.xml.remove(child) | ||||
|  | ||||
| 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' | ||||
| @@ -18,7 +48,7 @@ class Pubsub(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(Iq, Pubsub) | ||||
| registerStanzaPlugin(Iq, Pubsub) | ||||
|  | ||||
| class PubsubOwner(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -28,7 +58,7 @@ class PubsubOwner(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(Iq, PubsubOwner) | ||||
| registerStanzaPlugin(Iq, PubsubOwner) | ||||
|  | ||||
| class Affiliation(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -53,7 +83,7 @@ class Affiliations(ElementBase): | ||||
| 		self.xml.append(affiliation.xml) | ||||
| 		return self.iterables.append(affiliation) | ||||
|  | ||||
| stanzaPlugin(Pubsub, Affiliations) | ||||
| registerStanzaPlugin(Pubsub, Affiliations) | ||||
|  | ||||
|  | ||||
| class Subscription(ElementBase): | ||||
| @@ -70,7 +100,7 @@ class Subscription(ElementBase): | ||||
| 	def getjid(self): | ||||
| 		return jid(self._getattr('jid')) | ||||
|  | ||||
| stanzaPlugin(Pubsub, Subscription) | ||||
| registerStanzaPlugin(Pubsub, Subscription) | ||||
|  | ||||
| class Subscriptions(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -81,7 +111,7 @@ class Subscriptions(ElementBase): | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (Subscription,) | ||||
|  | ||||
| stanzaPlugin(Pubsub, Subscriptions) | ||||
| registerStanzaPlugin(Pubsub, Subscriptions) | ||||
|  | ||||
| class OptionalSetting(object): | ||||
| 	interfaces = set(('required',)) | ||||
| @@ -114,7 +144,7 @@ class SubscribeOptions(ElementBase, OptionalSetting): | ||||
| 	plugin_tag_map = {} | ||||
| 	interfaces = set(('required',)) | ||||
|  | ||||
| stanzaPlugin(Subscription, SubscribeOptions) | ||||
| registerStanzaPlugin(Subscription, SubscribeOptions) | ||||
|  | ||||
| class Item(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -140,12 +170,12 @@ class Items(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'items' | ||||
| 	plugin_attrib = 'items' | ||||
| 	interfaces = set(tuple()) | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (Item,) | ||||
|  | ||||
| stanzaPlugin(Pubsub, Items) | ||||
| registerStanzaPlugin(Pubsub, Items) | ||||
|  | ||||
| class Create(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -155,7 +185,7 @@ class Create(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(Pubsub, Create) | ||||
| registerStanzaPlugin(Pubsub, Create) | ||||
|  | ||||
| #class Default(ElementBase): | ||||
| #	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -170,7 +200,7 @@ stanzaPlugin(Pubsub, Create) | ||||
| #		if not t: t == 'leaf' | ||||
| #		return t | ||||
| # | ||||
| #stanzaPlugin(Pubsub, Default) | ||||
| #registerStanzaPlugin(Pubsub, Default) | ||||
|  | ||||
| class Publish(Items): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -181,7 +211,7 @@ class Publish(Items): | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (Item,) | ||||
|  | ||||
| stanzaPlugin(Pubsub, Publish) | ||||
| registerStanzaPlugin(Pubsub, Publish) | ||||
|  | ||||
| class Retract(Items): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -191,7 +221,7 @@ class Retract(Items): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(Pubsub, Retract) | ||||
| registerStanzaPlugin(Pubsub, Retract) | ||||
|  | ||||
| class Unsubscribe(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -221,13 +251,13 @@ class Subscribe(ElementBase): | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| stanzaPlugin(Pubsub, Subscribe) | ||||
| registerStanzaPlugin(Pubsub, Subscribe) | ||||
|  | ||||
| class Configure(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'configure' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'type', 'config')) | ||||
| 	interfaces = set(('node', 'type')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| @@ -236,22 +266,8 @@ class Configure(ElementBase): | ||||
| 		if not t: t == 'leaf' | ||||
| 		return t | ||||
| 	 | ||||
| 	def getConfig(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		form = xep_0004.Form() | ||||
| 		if config is not None: | ||||
| 			form.fromXML(config) | ||||
| 		return form | ||||
| 	 | ||||
| 	def setConfig(self, value): | ||||
| 		self.xml.append(value.getXML()) | ||||
| 		return self | ||||
| 	 | ||||
| 	def delConfig(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		self.xml.remove(config) | ||||
| 	 | ||||
| stanzaPlugin(Pubsub, Configure) | ||||
| registerStanzaPlugin(Pubsub, Configure) | ||||
| registerStanzaPlugin(Configure, xep_0004.Form) | ||||
|  | ||||
| class DefaultConfig(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -263,28 +279,21 @@ class DefaultConfig(ElementBase): | ||||
| 	 | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		ElementBase.__init__(self, *args, **kwargs) | ||||
| 		 | ||||
| 	def getConfig(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		form = xep_0004.Form() | ||||
| 		if config is not None: | ||||
| 			form.fromXML(config) | ||||
| 		return form | ||||
| 	 | ||||
| 	def setConfig(self, value): | ||||
| 		self.xml.append(value.getXML()) | ||||
| 		return self | ||||
| 	 | ||||
| 	def delConfig(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		self.xml.remove(config) | ||||
|  | ||||
| 	def getType(self): | ||||
| 		t = self._getAttr('type') | ||||
| 		if not t: t == 'leaf' | ||||
| 		if not t: t = 'leaf' | ||||
| 		return t | ||||
| 	 | ||||
| 	def getConfig(self): | ||||
| 		return self['form'] | ||||
| 	 | ||||
| 	def setConfig(self, value): | ||||
| 		self['form'].setStanzaValues(value.getStanzaValues()) | ||||
| 		return self | ||||
|  | ||||
| stanzaPlugin(PubsubOwner, DefaultConfig) | ||||
| registerStanzaPlugin(PubsubOwner, DefaultConfig) | ||||
| registerStanzaPlugin(DefaultConfig, xep_0004.Form) | ||||
|  | ||||
| class Options(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| @@ -318,21 +327,9 @@ class Options(ElementBase): | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| stanzaPlugin(Pubsub, Options) | ||||
| stanzaPlugin(Subscribe, Options) | ||||
| registerStanzaPlugin(Pubsub, Options) | ||||
| registerStanzaPlugin(Subscribe, Options) | ||||
|  | ||||
| #iq = Iq() | ||||
| #iq['pubsub']['defaultconfig'] | ||||
| #print(iq) | ||||
|  | ||||
| #from xml.etree import cElementTree as ET | ||||
| #iq = Iq() | ||||
| #item = Item() | ||||
| #item['payload'] = ET.Element("{http://netflint.net/p/crap}stupidshit") | ||||
| #item['id'] = 'aa11bbcc' | ||||
| #iq['pubsub']['items'].append(item) | ||||
| #print(iq) | ||||
| 	 | ||||
| class OwnerAffiliations(Affiliations): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	interfaces = set(('node')) | ||||
| @@ -345,7 +342,7 @@ class OwnerAffiliations(Affiliations): | ||||
| 		self.xml.append(affiliation.xml) | ||||
| 		return self.affiliations.append(affiliation) | ||||
|  | ||||
| stanzaPlugin(PubsubOwner, OwnerAffiliations) | ||||
| registerStanzaPlugin(PubsubOwner, OwnerAffiliations) | ||||
|  | ||||
| class OwnerAffiliation(Affiliation): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -359,15 +356,23 @@ class OwnerConfigure(Configure): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(PubsubOwner, OwnerConfigure) | ||||
| registerStanzaPlugin(PubsubOwner, OwnerConfigure) | ||||
|  | ||||
| class OwnerDefault(OwnerConfigure): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	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 | ||||
|  | ||||
| stanzaPlugin(PubsubOwner, OwnerDefault) | ||||
| registerStanzaPlugin(PubsubOwner, OwnerDefault) | ||||
| registerStanzaPlugin(OwnerDefault, xep_0004.Form) | ||||
|  | ||||
| class OwnerDelete(ElementBase, OptionalSetting): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -377,7 +382,7 @@ class OwnerDelete(ElementBase, OptionalSetting): | ||||
| 	plugin_tag_map = {} | ||||
| 	interfaces = set(('node',)) | ||||
|  | ||||
| stanzaPlugin(PubsubOwner, OwnerDelete) | ||||
| registerStanzaPlugin(PubsubOwner, OwnerDelete) | ||||
|  | ||||
| class OwnerPurge(ElementBase, OptionalSetting): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -386,7 +391,7 @@ class OwnerPurge(ElementBase, OptionalSetting): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(PubsubOwner, OwnerPurge) | ||||
| registerStanzaPlugin(PubsubOwner, OwnerPurge) | ||||
|  | ||||
| class OwnerRedirect(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -402,7 +407,7 @@ class OwnerRedirect(ElementBase): | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| stanzaPlugin(OwnerDelete, OwnerRedirect) | ||||
| registerStanzaPlugin(OwnerDelete, OwnerRedirect) | ||||
|  | ||||
| class OwnerSubscriptions(Subscriptions): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -416,7 +421,7 @@ class OwnerSubscriptions(Subscriptions): | ||||
| 		self.xml.append(subscription.xml) | ||||
| 		return self.subscriptions.append(subscription) | ||||
|  | ||||
| stanzaPlugin(PubsubOwner, OwnerSubscriptions) | ||||
| registerStanzaPlugin(PubsubOwner, OwnerSubscriptions) | ||||
|  | ||||
| class OwnerSubscription(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| @@ -440,7 +445,7 @@ class Event(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(Message, Event) | ||||
| registerStanzaPlugin(Message, Event) | ||||
|  | ||||
| class EventItem(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| @@ -480,7 +485,7 @@ class EventItems(ElementBase): | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (EventItem, EventRetract) | ||||
|  | ||||
| stanzaPlugin(Event, EventItems) | ||||
| registerStanzaPlugin(Event, EventItems) | ||||
|  | ||||
| class EventCollection(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| @@ -490,7 +495,7 @@ class EventCollection(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(Event, EventCollection) | ||||
| registerStanzaPlugin(Event, EventCollection) | ||||
|  | ||||
| class EventAssociate(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| @@ -500,7 +505,7 @@ class EventAssociate(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(EventCollection, EventAssociate) | ||||
| registerStanzaPlugin(EventCollection, EventAssociate) | ||||
|  | ||||
| class EventDisassociate(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| @@ -510,7 +515,7 @@ class EventDisassociate(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(EventCollection, EventDisassociate) | ||||
| registerStanzaPlugin(EventCollection, EventDisassociate) | ||||
|  | ||||
| class EventConfiguration(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| @@ -520,22 +525,8 @@ class EventConfiguration(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def getConfig(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		form = xep_0004.Form() | ||||
| 		if config is not None: | ||||
| 			form.fromXML(config) | ||||
| 		return form | ||||
| 	 | ||||
| 	def setConfig(self, value): | ||||
| 		self.xml.append(value.getXML()) | ||||
| 		return self | ||||
| 	 | ||||
| 	def delConfig(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		self.xml.remove(config) | ||||
| 	 | ||||
| stanzaPlugin(Event, EventConfiguration) | ||||
| registerStanzaPlugin(Event, EventConfiguration) | ||||
| registerStanzaPlugin(EventConfiguration, xep_0004.Form) | ||||
|  | ||||
| class EventPurge(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| @@ -545,7 +536,7 @@ class EventPurge(ElementBase): | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| stanzaPlugin(Event, EventPurge) | ||||
| registerStanzaPlugin(Event, EventPurge) | ||||
|  | ||||
| class EventSubscription(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| @@ -561,4 +552,4 @@ class EventSubscription(ElementBase): | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| stanzaPlugin(Event, EventSubscription) | ||||
| registerStanzaPlugin(Event, EventSubscription) | ||||
|   | ||||
| @@ -1,427 +1,392 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
| 	This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| 	See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
|  | ||||
| import logging | ||||
| from xml.etree import cElementTree as ET | ||||
| import copy | ||||
| #TODO support item groups and results | ||||
| from . import base | ||||
| from .. xmlstream.handler.callback import Callback | ||||
| from .. xmlstream.matcher.xpath import MatchXPath | ||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID | ||||
| from .. stanza.message import Message | ||||
| import types | ||||
|  | ||||
|  | ||||
| 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'): | ||||
| 		logging.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") | ||||
| 		return self.xml | ||||
| 	 | ||||
| 	def fromXML(self, xml): | ||||
| 		logging.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") | ||||
| 		n = Form(xml=xml) | ||||
| 		return n | ||||
|  | ||||
| 	def addItem(self, values): | ||||
| 		itemXML = ET.Element('{%s}item' % self.namespace) | ||||
| 		self.xml.append(itemXML) | ||||
| 		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) == types.DictType: | ||||
| 			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.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform) | ||||
|  | ||||
| 		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): | ||||
| 		self.xmpp['xep_0030'].add_feature('jabber:x:data') | ||||
| 	 | ||||
| 	def handler_message_xform(self, xml): | ||||
| 		object = self.handle_form(xml) | ||||
| 		self.xmpp.event("message_form", object) | ||||
| 	 | ||||
| 	def handler_presence_xform(self, xml): | ||||
| 		object = self.handle_form(xml) | ||||
| 		self.xmpp.event("presence_form", object) | ||||
| 	 | ||||
| 	def handle_form(self, xml): | ||||
| 		xmlform = xml.find('{jabber:x:data}x') | ||||
| 		object = self.buildForm(xmlform) | ||||
| 		self.xmpp.event("message_xform", object) | ||||
| 		return object | ||||
| 	 | ||||
| 		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): | ||||
| 		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): | ||||
| 		logging.debug("creating form as %s" % ftype) | ||||
| 		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 | ||||
| 		 | ||||
| 		return Form(xml=xml) | ||||
|   | ||||
| @@ -178,15 +178,19 @@ class xep_0009(base.base_plugin): | ||||
| 	def plugin_init(self): | ||||
| 		self.xep = '0009' | ||||
| 		self.description = 'Jabber-RPC' | ||||
| 		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", self._callMethod) | ||||
| 		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", self._callResult) | ||||
| 		self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", self._callError) | ||||
| 		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",  | ||||
|                                       self._callMethod, name='Jabber RPC Call') | ||||
| 		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",  | ||||
|                                       self._callResult, name='Jabber RPC Result') | ||||
| 		self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",  | ||||
|                                       self._callError, name='Jabber RPC Error') | ||||
| 		self.entries = {} | ||||
| 		self.activeCalls = [] | ||||
|  | ||||
| 	def post_init(self): | ||||
| 		self.xmpp['xep_0030'].add_feature('jabber:iq:rpc') | ||||
| 		self.xmpp['xep_0030'].add_identity('automatition','rpc') | ||||
| 		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. | ||||
|   | ||||
| @@ -1,25 +1,187 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	This file is part of SleekXMPP. | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
|  | ||||
| import logging | ||||
| from xml.etree import cElementTree as ET | ||||
| 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 | ||||
|  | ||||
| class DiscoInfo(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/disco#info' | ||||
| 	name = 'query' | ||||
| 	plugin_attrib = 'disco_info' | ||||
| 	interfaces = set(('node', 'features', 'identities')) | ||||
|  | ||||
| 	def getFeatures(self): | ||||
| 		features = [] | ||||
| 		featuresXML = self.xml.findall('{%s}feature' % self.namespace) | ||||
| 		for feature in featuresXML: | ||||
| 			features.append(feature.attrib['var']) | ||||
| 		return features | ||||
|  | ||||
| 	def setFeatures(self, features): | ||||
| 		self.delFeatures() | ||||
| 		for name in features: | ||||
| 			self.addFeature(name) | ||||
|  | ||||
| 	def delFeatures(self): | ||||
| 		featuresXML = self.xml.findall('{%s}feature' % self.namespace) | ||||
| 		for feature in featuresXML: | ||||
| 			self.xml.remove(feature) | ||||
|  | ||||
| 	def addFeature(self, feature): | ||||
| 		featureXML = ET.Element('{%s}feature' % self.namespace,  | ||||
| 					{'var': feature}) | ||||
| 		self.xml.append(featureXML) | ||||
|  | ||||
| 	def delFeature(self, feature): | ||||
| 		featuresXML = self.xml.findall('{%s}feature' % self.namespace) | ||||
| 		for featureXML in featuresXML: | ||||
| 			if featureXML.attrib['var'] == feature: | ||||
| 				self.xml.remove(featureXML) | ||||
|  | ||||
| 	def getIdentities(self): | ||||
| 		ids = [] | ||||
| 		idsXML = self.xml.findall('{%s}identity' % self.namespace) | ||||
| 		for idXML in idsXML: | ||||
| 			idData = (idXML.attrib['category'], | ||||
| 				  idXML.attrib['type'], | ||||
| 				  idXML.attrib.get('name', '')) | ||||
| 			ids.append(idData) | ||||
| 		return ids | ||||
|  | ||||
| 	def setIdentities(self, ids): | ||||
| 		self.delIdentities() | ||||
| 		for idData in ids: | ||||
| 			self.addIdentity(*idData) | ||||
|  | ||||
| 	def delIdentities(self): | ||||
| 		idsXML = self.xml.findall('{%s}identity' % self.namespace) | ||||
| 		for idXML in idsXML: | ||||
| 			self.xml.remove(idXML) | ||||
|  | ||||
| 	def addIdentity(self, category, id_type, name=''): | ||||
| 		idXML = ET.Element('{%s}identity' % self.namespace,  | ||||
| 				   {'category': category, | ||||
| 				    'type': id_type, | ||||
| 				    'name': name}) | ||||
| 		self.xml.append(idXML) | ||||
|  | ||||
| 	def delIdentity(self, category, id_type, name=''): | ||||
| 		idsXML = self.xml.findall('{%s}identity' % self.namespace) | ||||
| 		for idXML in idsXML: | ||||
| 			idData = (idXML.attrib['category'],  | ||||
| 				  idXML.attrib['type']) | ||||
| 			delId = (category, id_type) | ||||
| 			if idData == delId: | ||||
| 				self.xml.remove(idXML) | ||||
|  | ||||
|  | ||||
| class DiscoItems(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/disco#items' | ||||
| 	name = 'query' | ||||
| 	plugin_attrib = 'disco_items' | ||||
| 	interfaces = set(('node', 'items')) | ||||
|  | ||||
| 	def getItems(self): | ||||
| 		items = [] | ||||
| 		itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||
| 		for item in itemsXML: | ||||
| 			itemData = (item.attrib['jid'], | ||||
| 				    item.attrib.get('node'), | ||||
| 				    item.attrib.get('name')) | ||||
| 			items.append(itemData) | ||||
| 		return items | ||||
|  | ||||
| 	def setItems(self, items): | ||||
| 		self.delItems() | ||||
| 		for item in items: | ||||
| 			self.addItem(*item) | ||||
|  | ||||
| 	def delItems(self): | ||||
| 		itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||
| 		for item in itemsXML: | ||||
| 			self.xml.remove(item) | ||||
|  | ||||
| 	def addItem(self, jid, node='', name=''): | ||||
| 		itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid}) | ||||
| 		if name: | ||||
| 			itemXML.attrib['name'] = name | ||||
| 		if node: | ||||
| 			itemXML.attrib['node'] = node | ||||
| 		self.xml.append(itemXML) | ||||
|  | ||||
| 	def delItem(self, jid, node=''): | ||||
| 		itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||
| 		for itemXML in itemsXML: | ||||
| 			itemData = (itemXML.attrib['jid'], | ||||
| 				    itemXML.attrib.get('node', '')) | ||||
| 			itemDel = (jid, node) | ||||
| 			if itemData == itemDel: | ||||
| 				self.xml.remove(itemXML) | ||||
| 	 | ||||
|  | ||||
| class DiscoNode(object): | ||||
| 	""" | ||||
| 	Collection object for grouping info and item information | ||||
| 	into nodes. | ||||
| 	""" | ||||
| 	def __init__(self, name): | ||||
| 		self.name = name | ||||
| 		self.info = DiscoInfo() | ||||
| 		self.items = DiscoItems() | ||||
|  | ||||
| 		self.info['node'] = name | ||||
| 		self.items['node'] = name | ||||
|  | ||||
| 		# This is a bit like poor man's inheritance, but | ||||
| 		# to simplify adding information to the node we  | ||||
| 		# map node functions to either the info or items | ||||
| 		# stanza objects. | ||||
| 		# | ||||
| 		# We don't want to make DiscoNode inherit from  | ||||
| 		# DiscoInfo and DiscoItems because DiscoNode is | ||||
| 		# not an actual stanza, and doing so would create | ||||
| 		# confusion and potential bugs. | ||||
|  | ||||
| 		self._map(self.items, 'items', ['get', 'set', 'del']) | ||||
| 		self._map(self.items, 'item', ['add', 'del']) | ||||
| 		self._map(self.info, 'identities', ['get', 'set', 'del']) | ||||
| 		self._map(self.info, 'identity', ['add', 'del']) | ||||
| 		self._map(self.info, 'features', ['get', 'set', 'del']) | ||||
| 		self._map(self.info, 'feature', ['add', 'del']) | ||||
|  | ||||
| 	def isEmpty(self): | ||||
| 		""" | ||||
| 		Test if the node contains any information. Useful for | ||||
| 		determining if a node can be deleted. | ||||
| 		""" | ||||
| 		ids = self.getIdentities() | ||||
| 		features = self.getFeatures() | ||||
| 		items = self.getItems() | ||||
|  | ||||
| 		if not ids and not features and not items: | ||||
| 			return True | ||||
| 		return False | ||||
|  | ||||
| 	def _map(self, obj, interface, access): | ||||
| 		""" | ||||
| 		Map functions of the form obj.accessInterface | ||||
| 		to self.accessInterface for each given access type. | ||||
| 		""" | ||||
| 		interface = interface.title() | ||||
| 		for access_type in access: | ||||
| 			method = access_type + interface | ||||
| 			if hasattr(obj, method): | ||||
| 				setattr(self, method, getattr(obj, method)) | ||||
|  | ||||
|  | ||||
| class xep_0030(base.base_plugin): | ||||
| 	""" | ||||
| @@ -29,85 +191,137 @@ class xep_0030(base.base_plugin): | ||||
| 	def plugin_init(self): | ||||
| 		self.xep = '0030' | ||||
| 		self.description = 'Service Discovery' | ||||
| 		self.features = {'main': ['http://jabber.org/protocol/disco#info', 'http://jabber.org/protocol/disco#items']} | ||||
| 		self.identities = {'main': [{'category': 'client', 'type': 'pc', 'name': 'SleekXMPP'}]} | ||||
| 		self.items = {'main': []} | ||||
| 		self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>" % self.xmpp.default_ns, self.info_handler) | ||||
| 		self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='http://jabber.org/protocol/disco#items' /></iq>" % self.xmpp.default_ns, self.item_handler) | ||||
|  | ||||
| 		self.xmpp.registerHandler( | ||||
| 			Callback('Disco Items', | ||||
| 				 MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,  | ||||
| 								  DiscoItems.namespace)), | ||||
| 				 self.handle_item_query)) | ||||
|  | ||||
| 		self.xmpp.registerHandler( | ||||
| 			Callback('Disco Info', | ||||
| 				 MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,  | ||||
| 								  DiscoInfo.namespace)), | ||||
| 				 self.handle_info_query)) | ||||
|  | ||||
| 		registerStanzaPlugin(Iq, DiscoInfo) | ||||
| 		registerStanzaPlugin(Iq, DiscoItems) | ||||
|  | ||||
| 		self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) | ||||
| 		self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) | ||||
|  | ||||
| 		self.nodes = {'main': DiscoNode('main')} | ||||
|  | ||||
| 	def add_node(self, node): | ||||
| 		if node not in self.nodes: | ||||
| 			self.nodes[node] = DiscoNode(node) | ||||
|  | ||||
| 	def del_node(self, node): | ||||
| 		if node in self.nodes: | ||||
| 			del self.nodes[node] | ||||
|  | ||||
| 	def handle_item_query(self, iq): | ||||
| 		if iq['type'] == 'get': | ||||
| 			logging.debug("Items requested by %s" % iq['from']) | ||||
| 			self.xmpp.event('disco_items_request', iq) | ||||
| 		elif iq['type'] == 'result': | ||||
| 			logging.debug("Items result from %s" % iq['from']) | ||||
| 			self.xmpp.event('disco_items', iq) | ||||
|  | ||||
| 	def handle_info_query(self, iq): | ||||
| 		if iq['type'] == 'get': | ||||
| 			logging.debug("Info requested by %s" % iq['from']) | ||||
| 			self.xmpp.event('disco_info_request', iq) | ||||
| 		elif iq['type'] == 'result': | ||||
| 			logging.debug("Info result from %s" % iq['from']) | ||||
| 			self.xmpp.event('disco_info', iq) | ||||
|  | ||||
| 	def handle_disco_info(self, iq, forwarded=False): | ||||
| 		""" | ||||
| 		A default handler for disco#info requests. If another | ||||
| 		handler is registered, this one will defer and not run. | ||||
| 		""" | ||||
| 		handlers = self.xmpp.event_handlers['disco_info_request'] | ||||
| 		if not forwarded and len(handlers) > 1: | ||||
| 			return | ||||
|  | ||||
| 		node_name = iq['disco_info']['node'] | ||||
| 		if not node_name: | ||||
| 			node_name = 'main' | ||||
|  | ||||
| 		logging.debug("Using default handler for disco#info on node '%s'." % node_name) | ||||
|  | ||||
| 		if node_name in self.nodes: | ||||
| 			node = self.nodes[node_name] | ||||
| 			iq.reply().setPayload(node.info.xml).send() | ||||
| 		else: | ||||
| 			logging.debug("Node %s requested, but does not exist." % node_name) | ||||
| 			iq.reply().error().setPayload(iq['disco_info'].xml) | ||||
| 			iq['error']['code'] = '404' | ||||
| 			iq['error']['type'] = 'cancel' | ||||
| 			iq['error']['condition'] = 'item-not-found' | ||||
| 			iq.send() | ||||
| 			 | ||||
| 	def handle_disco_items(self, iq, forwarded=False): | ||||
| 		""" | ||||
| 		A default handler for disco#items requests. If another | ||||
| 		handler is registered, this one will defer and not run. | ||||
|  | ||||
| 		If this handler is called by your own custom handler with | ||||
| 		forwarded set to True, then it will run as normal. | ||||
| 		""" | ||||
| 		handlers = self.xmpp.event_handlers['disco_items_request'] | ||||
| 		if not forwarded and len(handlers) > 1: | ||||
| 			return | ||||
|  | ||||
| 		node_name = iq['disco_items']['node'] | ||||
| 		if not node_name: | ||||
| 			node_name = 'main' | ||||
|  | ||||
| 		logging.debug("Using default handler for disco#items on node '%s'." % node_name) | ||||
|  | ||||
| 		if node_name in self.nodes: | ||||
| 			node = self.nodes[node_name] | ||||
| 			iq.reply().setPayload(node.items.xml).send() | ||||
| 		else:	 | ||||
| 			logging.debug("Node %s requested, but does not exist." % node_name) | ||||
| 			iq.reply().error().setPayload(iq['disco_items'].xml) | ||||
| 			iq['error']['code'] = '404' | ||||
| 			iq['error']['type'] = 'cancel' | ||||
| 			iq['error']['condition'] = 'item-not-found' | ||||
| 			iq.send() | ||||
|  | ||||
| 	# Older interface methods for backwards compatibility | ||||
|  | ||||
| 	def getInfo(self, jid, node='', dfrom=None): | ||||
| 		iq = self.xmpp.Iq() | ||||
| 		iq['type'] = 'get' | ||||
| 		iq['to'] = jid | ||||
| 		iq['from'] = dfrom | ||||
| 		iq['disco_info']['node'] = node | ||||
| 		return iq.send() | ||||
|  | ||||
| 	def getItems(self, jid, node='', dfrom=None): | ||||
| 		iq = self.xmpp.Iq() | ||||
| 		iq['type'] = 'get' | ||||
| 		iq['to'] = jid | ||||
| 		iq['from'] = dfrom | ||||
| 		iq['disco_items']['node'] = node | ||||
| 		return iq.send() | ||||
| 	 | ||||
| 	def add_feature(self, feature, node='main'): | ||||
| 		if not node in self.features: | ||||
| 			self.features[node] = [] | ||||
| 		self.features[node].append(feature) | ||||
| 		self.add_node(node) | ||||
| 		self.nodes[node].addFeature(feature) | ||||
| 	 | ||||
| 	def add_identity(self, category=None, itype=None, name=None, node='main'): | ||||
| 		if not node in self.identities: | ||||
| 			self.identities[node] = [] | ||||
| 		self.identities[node].append({'category': category, 'type': itype, 'name': name}) | ||||
| 	def add_identity(self, category='', itype='', name='', node='main'): | ||||
| 		self.add_node(node) | ||||
| 		self.nodes[node].addIdentity(category=category, | ||||
| 					     id_type=itype, | ||||
| 					     name=name) | ||||
| 	 | ||||
| 	def add_item(self, jid=None, name=None, node='main', subnode=''): | ||||
| 		if not node in self.items: | ||||
| 			self.items[node] = [] | ||||
| 		self.items[node].append({'jid': jid, 'name': name, 'node': subnode}) | ||||
|  | ||||
| 	def info_handler(self, xml): | ||||
| 		logging.debug("Info request from %s" % xml.get('from', '')) | ||||
| 		iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId())) | ||||
| 		iq.attrib['from'] = xml.get('to') | ||||
| 		iq.attrib['to'] = xml.get('from', self.xmpp.server) | ||||
| 		query = xml.find('{http://jabber.org/protocol/disco#info}query') | ||||
| 		node = query.get('node', 'main') | ||||
| 		for identity in self.identities.get(node, []): | ||||
| 			idxml = ET.Element('identity') | ||||
| 			for attrib in identity: | ||||
| 				if identity[attrib]: | ||||
| 					idxml.attrib[attrib] = identity[attrib] | ||||
| 			query.append(idxml) | ||||
| 		for feature in self.features.get(node, []): | ||||
| 			featxml = ET.Element('feature') | ||||
| 			featxml.attrib['var'] = feature | ||||
| 			query.append(featxml) | ||||
| 		iq.append(query) | ||||
| 		#print ET.tostring(iq) | ||||
| 		self.xmpp.send(iq) | ||||
|  | ||||
| 	def item_handler(self, xml): | ||||
| 		logging.debug("Item request from %s" % xml.get('from', '')) | ||||
| 		iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId())) | ||||
| 		iq.attrib['from'] = xml.get('to') | ||||
| 		iq.attrib['to'] = xml.get('from', self.xmpp.server) | ||||
| 		query = self.xmpp.makeIqQuery(iq, 'http://jabber.org/protocol/disco#items').find('{http://jabber.org/protocol/disco#items}query') | ||||
| 		node = xml.find('{http://jabber.org/protocol/disco#items}query').get('node', 'main') | ||||
| 		for item in self.items.get(node, []): | ||||
| 			itemxml = ET.Element('item') | ||||
| 			itemxml.attrib = item | ||||
| 			if itemxml.attrib['jid'] is None: | ||||
| 				itemxml.attrib['jid'] = xml.get('to') | ||||
| 			query.append(itemxml) | ||||
| 		self.xmpp.send(iq) | ||||
| 	 | ||||
| 	def getItems(self, jid, node=None): | ||||
| 		iq = self.xmpp.makeIqGet() | ||||
| 		iq.attrib['from'] = self.xmpp.fulljid | ||||
| 		iq.attrib['to'] = jid | ||||
| 		self.xmpp.makeIqQuery(iq, 'http://jabber.org/protocol/disco#items') | ||||
| 		if node: | ||||
| 			iq.find('{http://jabber.org/protocol/disco#items}query').attrib['node'] = node | ||||
| 		return iq.send() | ||||
| 	 | ||||
| 	def getInfo(self, jid, node=None): | ||||
| 		iq = self.xmpp.makeIqGet() | ||||
| 		iq.attrib['from'] = self.xmpp.fulljid | ||||
| 		iq.attrib['to'] = jid | ||||
| 		self.xmpp.makeIqQuery(iq, 'http://jabber.org/protocol/disco#info') | ||||
| 		if node: | ||||
| 			iq.find('{http://jabber.org/protocol/disco#info}query').attrib['node'] = node | ||||
| 		return iq.send() | ||||
|  | ||||
| 	def parseInfo(self, xml): | ||||
| 		result = {'identity': {}, 'feature': []} | ||||
| 		for identity in xml.findall('{http://jabber.org/protocol/disco#info}query/{{http://jabber.org/protocol/disco#info}identity'): | ||||
| 			result['identity'][identity['name']] = identity.attrib | ||||
| 		for feature in xml.findall('{http://jabber.org/protocol/disco#info}query/{{http://jabber.org/protocol/disco#info}feature'): | ||||
| 			result['feature'].append(feature.get('var', '__unknown__')) | ||||
| 		return result | ||||
| 	def add_item(self, jid=None, name='', node='main', subnode=''): | ||||
| 		self.add_node(node) | ||||
| 		self.add_node(subnode) | ||||
| 		if jid is None: | ||||
| 			jid = self.xmpp.fulljid | ||||
| 		self.nodes[node].addItem(jid=jid, name=name, node=subnode) | ||||
|   | ||||
							
								
								
									
										161
									
								
								sleekxmpp/plugins/xep_0033.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								sleekxmpp/plugins/xep_0033.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,161 @@ | ||||
| """ | ||||
|     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) | ||||
| @@ -1,27 +1,15 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|      | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from __future__ import with_statement | ||||
| from . import base | ||||
| import logging | ||||
| from xml.etree import cElementTree as ET | ||||
| from .. xmlstream.stanzabase import ElementBase, JID | ||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID | ||||
| from .. stanza.presence import Presence | ||||
| from .. xmlstream.handler.callback import Callback | ||||
| from .. xmlstream.matcher.xpath import MatchXPath | ||||
| @@ -93,10 +81,10 @@ class MUCPresence(ElementBase): | ||||
| 		return self | ||||
| 	 | ||||
| 	def getNick(self): | ||||
| 		return self.parent['from'].resource | ||||
| 		return self.parent()['from'].resource | ||||
| 	 | ||||
| 	def getRoom(self): | ||||
| 		return self.parent['from'].bare | ||||
| 		return self.parent()['from'].bare | ||||
| 	 | ||||
| 	def setNick(self, value): | ||||
| 		logging.warning("Cannot set nick through mucpresence plugin.") | ||||
| @@ -125,27 +113,41 @@ class xep_0045(base.base_plugin): | ||||
| 		self.xep = '0045' | ||||
| 		self.description = 'Multi User Chat' | ||||
| 		# load MUC support in presence stanzas | ||||
| 		self.xmpp.stanzaPlugin(Presence, MUCPresence) | ||||
| 		registerStanzaPlugin(Presence, MUCPresence) | ||||
| 		self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) | ||||
| 		self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) | ||||
| 	 | ||||
| 	def handle_groupchat_presence(self, pr): | ||||
| 		""" Handle a presence in a muc. | ||||
| 		""" | ||||
| 		got_offline = False | ||||
| 		got_online = False | ||||
| 		if pr['muc']['room'] not in self.rooms.keys(): | ||||
| 			return | ||||
| 		entry = pr['muc'].getValues() | ||||
| 		entry = pr['muc'].getStanzaValues() | ||||
| 		entry['show'] = pr['show'] | ||||
| 		entry['status'] = pr['status'] | ||||
| 		if pr['type'] == 'unavailable': | ||||
| 			del self.rooms[entry['room']][entry['nick']] | ||||
| 			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 | ||||
| 		logging.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 jidInRoom(self, room, jid): | ||||
| 		for nick in self.rooms[room]: | ||||
| @@ -153,6 +155,12 @@ class xep_0045(base.base_plugin): | ||||
| 			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() | ||||
| @@ -166,13 +174,13 @@ class xep_0045(base.base_plugin): | ||||
| 			return False | ||||
| 		xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') | ||||
| 		if xform is None: return False | ||||
| 		form = self.xmpp.plugin['xep_0004'].buildForm(xform) | ||||
| 		form = self.xmpp.plugin['old_0004'].buildForm(xform) | ||||
| 		return form | ||||
| 	 | ||||
| 	def configureRoom(self, room, form=None, ifrom=None): | ||||
| 		if form is None: | ||||
| 			form = self.getRoomForm(room, ifrom=ifrom) | ||||
| 			#form = self.xmpp.plugin['xep_0004'].makeForm(ftype='submit') | ||||
| 			#form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') | ||||
| 			#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')	 | ||||
| 		iq = self.xmpp.makeIqSet() | ||||
| 		iq['to'] = room | ||||
| @@ -196,9 +204,13 @@ class xep_0045(base.base_plugin): | ||||
| 			passelement = ET.Element('password') | ||||
| 			passelement.text = password | ||||
| 			x.append(passelement) | ||||
| 		history = ET.Element('history') | ||||
| 		history.attrib['maxstanzas'] = maxhistory | ||||
| 		x.append(history) | ||||
| 		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) | ||||
| @@ -259,15 +271,19 @@ class xep_0045(base.base_plugin): | ||||
| 		msg.append(x) | ||||
| 		self.xmpp.send(msg) | ||||
|  | ||||
| 	def leaveMUC(self, room, nick): | ||||
| 	def leaveMUC(self, room, nick, msg=''): | ||||
| 		""" Leave the specified room. | ||||
| 		""" | ||||
| 		self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick)) | ||||
| 		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): | ||||
| 		iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') | ||||
| 		iq['to'] = room | ||||
| 		iq['from'] = self.xmpp.jid | ||||
| 		result = iq.send() | ||||
| 		if result is None or result['type'] != 'result': | ||||
| 			raise ValueError | ||||
| @@ -289,6 +305,7 @@ class xep_0045(base.base_plugin): | ||||
| 		query.append(x) | ||||
| 		iq = self.xmpp.makeIqSet(query) | ||||
| 		iq['to'] = room | ||||
| 		iq['from'] = self.xmpp.jid | ||||
| 		iq.send() | ||||
| 		 | ||||
| 	def getJoinedRooms(self): | ||||
|   | ||||
| @@ -1,27 +1,14 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|      | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from __future__ import with_statement | ||||
| from . import base | ||||
| import logging | ||||
| from xml.etree import cElementTree as ET | ||||
| import traceback | ||||
| import time | ||||
|  | ||||
| class xep_0050(base.base_plugin): | ||||
| @@ -32,16 +19,17 @@ class xep_0050(base.base_plugin): | ||||
| 	def plugin_init(self): | ||||
| 		self.xep = '0050' | ||||
| 		self.description = 'Ad-Hoc Commands' | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command) | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command) | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, threaded=True) | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel) | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete) | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None') | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute') | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True) | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel') | ||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete') | ||||
| 		self.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): | ||||
| @@ -82,7 +70,7 @@ class xep_0050(base.base_plugin): | ||||
| 		in_command = xml.find('{http://jabber.org/protocol/commands}command') | ||||
| 		sessionid = in_command.get('sessionid', None) | ||||
| 		pointer = self.sessions[sessionid]['next'] | ||||
| 		results = self.xmpp.plugin['xep_0004'].makeForm('result') | ||||
| 		results = self.xmpp.plugin['old_0004'].makeForm('result') | ||||
| 		results.fromXML(in_command.find('{jabber:x:data}x')) | ||||
| 		pointer(results,sessionid) | ||||
| 		self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[])) | ||||
| @@ -93,7 +81,7 @@ class xep_0050(base.base_plugin): | ||||
| 		in_command = xml.find('{http://jabber.org/protocol/commands}command') | ||||
| 		sessionid = in_command.get('sessionid', None) | ||||
| 		pointer = self.sessions[sessionid]['next'] | ||||
| 		results = self.xmpp.plugin['xep_0004'].makeForm('result') | ||||
| 		results = self.xmpp.plugin['old_0004'].makeForm('result') | ||||
| 		results.fromXML(in_command.find('{jabber:x:data}x')) | ||||
| 		form, npointer, next = pointer(results,sessionid) | ||||
| 		self.sessions[sessionid]['next'] = npointer | ||||
|   | ||||
| @@ -2,8 +2,9 @@ from __future__ import with_statement | ||||
| from . import base | ||||
| import logging | ||||
| #from xml.etree import cElementTree as ET | ||||
| from .. xmlstream.stanzabase import ElementBase, ET | ||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET | ||||
| from . import stanza_pubsub | ||||
| from . xep_0004 import Form | ||||
|  | ||||
| class xep_0060(base.base_plugin): | ||||
| 	""" | ||||
| @@ -14,12 +15,14 @@ class xep_0060(base.base_plugin): | ||||
| 		self.xep = '0060' | ||||
| 		self.description = 'Publish-Subscribe' | ||||
| 	 | ||||
| 	def create_node(self, jid, node, config=None, collection=False): | ||||
| 	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: | ||||
| @@ -29,17 +32,18 @@ class xep_0060(base.base_plugin): | ||||
| 				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 collection: | ||||
| 			if ntype: | ||||
| 				if 'pubsub#node_type' in submitform.field: | ||||
| 					submitform.field['pubsub#node_type'].setValue('collection') | ||||
| 					submitform.field['pubsub#node_type'].setValue(ntype) | ||||
| 				else: | ||||
| 					submitform.addField('pubsub#node_type', value='collection') | ||||
| 					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') | ||||
| 			configure.append(submitform.getXML('submit')) | ||||
| 			submitform['type'] = 'submit' | ||||
| 			configure.append(submitform.xml) | ||||
| 		pubsub.append(configure) | ||||
| 		iq = self.xmpp.makeIqSet(pubsub) | ||||
| 		iq.attrib['to'] = jid | ||||
| @@ -115,7 +119,7 @@ class xep_0060(base.base_plugin): | ||||
| 		if not form or form is None: | ||||
| 			logging.error("No form found.") | ||||
| 			return False | ||||
| 		return self.xmpp.plugin['xep_0004'].buildForm(form) | ||||
| 		return Form(xml=form) | ||||
| 	 | ||||
| 	def getNodeSubscriptions(self, jid, node): | ||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') | ||||
|   | ||||
| @@ -1,21 +1,9 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|      | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from __future__ import with_statement | ||||
| from xml.etree import cElementTree as ET | ||||
|   | ||||
							
								
								
									
										101
									
								
								sleekxmpp/plugins/xep_0085.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								sleekxmpp/plugins/xep_0085.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| """ | ||||
|     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 permissio | ||||
| """ | ||||
|  | ||||
| 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 ChatState(ElementBase): | ||||
|     namespace = 'http://jabber.org/protocol/chatstates' | ||||
|     plugin_attrib = 'chat_state' | ||||
|     interface = set(('state',)) | ||||
|     states = set(('active', 'composing', 'gone', 'inactive', 'paused')) | ||||
|      | ||||
|     def active(self): | ||||
|         self.setState('active') | ||||
|          | ||||
|     def composing(self): | ||||
|         self.setState('composing') | ||||
|  | ||||
|     def gone(self): | ||||
|         self.setState('gone') | ||||
|  | ||||
|     def inactive(self): | ||||
|         self.setState('inactive') | ||||
|  | ||||
|     def paused(self): | ||||
|         self.setState('paused') | ||||
|  | ||||
|     def setState(self, state): | ||||
|         if state in self.states: | ||||
|             self.name = state | ||||
|             self.xml.tag = '{%s}%s' % (self.namespace, state) | ||||
|         else: | ||||
|             raise ValueError('Invalid chat state') | ||||
|  | ||||
|     def getState(self): | ||||
|         return self.name | ||||
|  | ||||
| # In order to match the various chat state elements, | ||||
| # we need one stanza object per state, even though | ||||
| # they are all the same except for the initial name | ||||
| # value. Do not depend on the type of the chat state | ||||
| # stanza object for the actual state. | ||||
|  | ||||
| class Active(ChatState): | ||||
|     name = 'active' | ||||
| class Composing(ChatState): | ||||
|     name = 'composing' | ||||
| class Gone(ChatState): | ||||
|     name = 'gone' | ||||
| class Inactive(ChatState): | ||||
|     name = 'inactive' | ||||
| class Paused(ChatState): | ||||
|     name = 'paused' | ||||
|  | ||||
|  | ||||
| class xep_0085(base.base_plugin): | ||||
|     """ | ||||
|     XEP-0085 Chat State Notifications | ||||
|     """ | ||||
|      | ||||
|     def plugin_init(self): | ||||
|         self.xep = '0085' | ||||
|         self.description = 'Chat State Notifications' | ||||
|          | ||||
|         handlers = [('Active Chat State', 'active'), | ||||
|                     ('Composing Chat State', 'composing'), | ||||
|                     ('Gone Chat State', 'gone'), | ||||
|                     ('Inactive Chat State', 'inactive'), | ||||
|                     ('Paused Chat State', 'paused')] | ||||
|         for handler in handlers: | ||||
|             self.xmpp.registerHandler( | ||||
|                 Callback(handler[0],  | ||||
|                          MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns,  | ||||
|                                                             ChatState.namespace, | ||||
|                                                             handler[1])),  | ||||
|                          self._handleChatState)) | ||||
|  | ||||
|         registerStanzaPlugin(Message, Active) | ||||
|         registerStanzaPlugin(Message, Composing) | ||||
|         registerStanzaPlugin(Message, Gone) | ||||
|         registerStanzaPlugin(Message, Inactive) | ||||
|         registerStanzaPlugin(Message, Paused) | ||||
|          | ||||
|     def post_init(self): | ||||
|         base.base_plugin.post_init(self) | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates') | ||||
|          | ||||
|     def _handleChatState(self, msg): | ||||
|         state = msg['chat_state'].name | ||||
|         logging.debug("Chat State: %s, %s" % (state, msg['from'].jid)) | ||||
|         self.xmpp.event('chatstate_%s' % state, msg) | ||||
| @@ -1,21 +1,9 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2007  Nathanael C. Fritz | ||||
| 	This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|      | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from xml.etree import cElementTree as ET | ||||
| from . import base | ||||
| @@ -30,10 +18,11 @@ class xep_0092(base.base_plugin): | ||||
| 		self.xep = "0092" | ||||
| 		self.name = self.config.get('name', 'SleekXMPP') | ||||
| 		self.version = self.config.get('version', '0.1-dev') | ||||
| 		self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version) | ||||
| 		self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version, name='Sofware Version') | ||||
| 	 | ||||
| 	def post_init(self): | ||||
| 		self.xmpp['xep_0030'].add_feature('jabber:iq:version') | ||||
| 		base.base_plugin.post_init(self) | ||||
| 		self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') | ||||
| 	 | ||||
| 	def report_version(self, xml): | ||||
| 		iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) | ||||
|   | ||||
							
								
								
									
										51
									
								
								sleekxmpp/plugins/xep_0128.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								sleekxmpp/plugins/xep_0128.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE 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 | ||||
| from . xep_0030 import DiscoInfo, DiscoItems | ||||
| from . xep_0004 import Form | ||||
|  | ||||
|  | ||||
| class xep_0128(base.base_plugin): | ||||
|     """ | ||||
|     XEP-0128 Service Discovery Extensions | ||||
|     """ | ||||
| 	 | ||||
|     def plugin_init(self): | ||||
|         self.xep = '0128' | ||||
|         self.description = 'Service Discovery Extensions' | ||||
|  | ||||
|         registerStanzaPlugin(DiscoInfo, Form) | ||||
|         registerStanzaPlugin(DiscoItems, Form) | ||||
|  | ||||
|     def extend_info(self, node, data=None): | ||||
|         if data is None: | ||||
|             data = {} | ||||
|         node = self.xmpp['xep_0030'].nodes.get(node, None) | ||||
|         if node is None: | ||||
|             self.xmpp['xep_0030'].add_node(node) | ||||
|          | ||||
|         info = node.info | ||||
|         info['form']['type'] = 'result' | ||||
|         info['form'].setFields(data, default=None) | ||||
|  | ||||
|     def extend_items(self, node, data=None): | ||||
|         if data is None: | ||||
|             data = {} | ||||
|         node = self.xmpp['xep_0030'].nodes.get(node, None) | ||||
|         if node is None: | ||||
|             self.xmpp['xep_0030'].add_node(node) | ||||
|          | ||||
|         items = node.items | ||||
|         items['form']['type'] = 'result' | ||||
|         items['form'].setFields(data, default=None) | ||||
| @@ -1,22 +1,9 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	XEP-0199 (Ping) support | ||||
| 	Copyright (C) 2007  Kevin Smith | ||||
| 	This file is part of SleekXMPP. | ||||
|  | ||||
| 	SleekXMPP is free software; you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation; either version 2 of the License, or | ||||
| 	(at your option) any later version. | ||||
|  | ||||
| 	SleekXMPP is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
|  | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with SleekXMPP; if not, write to the Free Software | ||||
| 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|      | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from xml.etree import cElementTree as ET | ||||
| from . import base | ||||
| @@ -29,13 +16,14 @@ class xep_0199(base.base_plugin): | ||||
| 	def plugin_init(self): | ||||
| 		self.description = "XMPP Ping" | ||||
| 		self.xep = "0199" | ||||
| 		self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping) | ||||
| 		self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') | ||||
| 		self.running = False | ||||
| 		#if self.config.get('keepalive', True): | ||||
| 			#self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) | ||||
| 	 | ||||
| 	def post_init(self): | ||||
| 		self.xmpp['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns') | ||||
| 		base.base_plugin.post_init(self) | ||||
| 		self.xmpp.plugin['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns') | ||||
| 	 | ||||
| 	def handler_pingserver(self, xml): | ||||
| 		if not self.running: | ||||
|   | ||||
| @@ -3,6 +3,11 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| __all__ = ['presence'] | ||||
|  | ||||
|  | ||||
| from sleekxmpp.stanza.error import Error | ||||
| from sleekxmpp.stanza.iq import Iq | ||||
| from sleekxmpp.stanza.message import Message | ||||
| from sleekxmpp.stanza.presence import Presence | ||||
|   | ||||
							
								
								
									
										26
									
								
								sleekxmpp/stanza/atom.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								sleekxmpp/stanza/atom.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| """ | ||||
|     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')) | ||||
| @@ -3,60 +3,139 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import ElementBase, ET | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Error(ElementBase): | ||||
| 	namespace = 'jabber:client' | ||||
| 	name = 'error' | ||||
| 	plugin_attrib = 'error' | ||||
| 	conditions = set(('bad-request', 'conflict', 'feature-not-implemented', 'forbidden', 'gone', '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', 'service-unavailable', 'subscription-required', 'undefined-condition', 'unexpected-request')) | ||||
| 	interfaces = set(('condition', 'text', 'type')) | ||||
| 	types = set(('cancel', 'continue', 'modify', 'auth', 'wait')) | ||||
| 	sub_interfaces = set(('text',)) | ||||
| 	condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas' | ||||
| 	 | ||||
| 	def setup(self, xml=None): | ||||
| 		if ElementBase.setup(self, xml): #if we had to generate xml | ||||
| 			self['type'] = 'cancel' | ||||
| 			self['condition'] = 'feature-not-implemented' | ||||
| 		if self.parent is not None: | ||||
| 			self.parent['type'] = 'error' | ||||
| 	 | ||||
| 	def getCondition(self): | ||||
| 		for child in self.xml.getchildren(): | ||||
| 			if "{%s}" % self.condition_ns in child.tag: | ||||
| 				return child.tag.split('}', 1)[-1] | ||||
| 		return '' | ||||
| 	 | ||||
| 	def setCondition(self, value): | ||||
| 		if value in self.conditions: | ||||
| 			for child in self.xml.getchildren(): | ||||
| 				if "{%s}" % self.condition_ns in child.tag: | ||||
| 					self.xml.remove(child) | ||||
| 			condition = ET.Element("{%s}%s" % (self.condition_ns, value)) | ||||
| 			self.xml.append(condition) | ||||
| 		return self | ||||
| 	 | ||||
| 	def delCondition(self): | ||||
| 		return self | ||||
| 	 | ||||
| 	def getText(self): | ||||
| 		text = '' | ||||
| 		textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") | ||||
| 		if textxml is not None: | ||||
| 			text = textxml.text | ||||
| 		return text | ||||
| 	 | ||||
| 	def setText(self, value): | ||||
| 		self.delText() | ||||
| 		textxml = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}text') | ||||
| 		textxml.text = value | ||||
| 		self.xml.append(textxml) | ||||
| 		return self | ||||
| 	 | ||||
| 	def delText(self): | ||||
| 		textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") | ||||
| 		if textxml is not None: | ||||
| 			self.xml.remove(textxml) | ||||
|  | ||||
|     """ | ||||
|     XMPP stanzas of type 'error' should include an <error> 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: | ||||
|         <error type="cancel" code="404"> | ||||
|           <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|           <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> | ||||
|             The item was not found. | ||||
|           </text> | ||||
|         </error> | ||||
|  | ||||
|     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 <text> element. | ||||
|         set_text      -- Set the contents of the <text> element. | ||||
|         del_text      -- Remove the <text> 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. | ||||
|         """ | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.getCondition = self.get_condition | ||||
|         self.setCondition = self.set_condition | ||||
|         self.delCondition = self.del_condition | ||||
|         self.getText = self.get_text | ||||
|         self.setText = self.set_text | ||||
|         self.delText = self.del_text | ||||
|  | ||||
|         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 <text> element.""" | ||||
|         return self._get_sub_text('{%s}text' % self.condition_ns) | ||||
|  | ||||
|     def set_text(self, value): | ||||
|         """ | ||||
|         Set the contents of the <text> element. | ||||
|  | ||||
|         Arguments: | ||||
|             value -- The new contents for the <text> element. | ||||
|         """ | ||||
|         self._set_sub_text('{%s}text' % self.condition_ns, text=value) | ||||
|         return self | ||||
|  | ||||
|     def del_text(self): | ||||
|         """Remove the <text> element.""" | ||||
|         self._del_sub('{%s}text' % self.condition_ns) | ||||
|         return self | ||||
|   | ||||
| @@ -3,32 +3,95 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import ElementBase, ET | ||||
|  | ||||
| from sleekxmpp.stanza import Message | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class HTMLIM(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/xhtml-im' | ||||
| 	name = 'html' | ||||
| 	plugin_attrib = 'html' | ||||
| 	interfaces = set(('html',)) | ||||
| 	plugin_attrib_map = set() | ||||
| 	plugin_xml_map = set() | ||||
|  | ||||
| 	def setHtml(self, html): | ||||
| 		if isinstance(html, str): | ||||
| 			html = ET.XML(html) | ||||
| 		if html.tag != '{http://www.w3.org/1999/xhtml}body': | ||||
| 			body = ET.Element('{http://www.w3.org/1999/xhtml}body') | ||||
| 			body.append(html) | ||||
| 			self.xml.append(body) | ||||
| 		else: | ||||
| 			self.xml.append(html) | ||||
| 	 | ||||
| 	def getHtml(self): | ||||
| 		html = self.xml.find('{http://www.w3.org/1999/xhtml}body') | ||||
| 		if html is None: return '' | ||||
| 		return html | ||||
| 	 | ||||
| 	def delHtml(self): | ||||
| 		return self.__del__() | ||||
|     """ | ||||
|     XEP-0071: XHTML-IM defines a method for embedding XHTML content | ||||
|     within a <message> 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: | ||||
|         <message to="user@example.com"> | ||||
|           <body>Non-html message content.</body> | ||||
|           <html xmlns="http://jabber.org/protocol/xhtml-im"> | ||||
|             <body xmlns="http://www.w3.org/1999/xhtml"> | ||||
|               <p><b>HTML!</b></p> | ||||
|             </body> | ||||
|           </html> | ||||
|         </message> | ||||
|  | ||||
|     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 setup(self, xml=None): | ||||
|         """ | ||||
|         Populate the stanza object using an optional XML object. | ||||
|  | ||||
|         Overrides StanzaBase.setup. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- Use an existing XML object for the stanza's values. | ||||
|         """ | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.setBody = self.set_body | ||||
|         self.getBody = self.get_body | ||||
|         self.delBody = self.del_body | ||||
|  | ||||
|         return ElementBase.setup(self, xml) | ||||
|  | ||||
|     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 <body> 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) | ||||
|   | ||||
| @@ -3,74 +3,181 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import StanzaBase | ||||
| from xml.etree import cElementTree as ET | ||||
| from . error import Error | ||||
| from .. xmlstream.handler.waiter import Waiter | ||||
| from .. xmlstream.matcher.id import MatcherId | ||||
| from . rootstanza import RootStanza | ||||
|  | ||||
| from sleekxmpp.stanza import Error | ||||
| from sleekxmpp.stanza.rootstanza import RootStanza | ||||
| from sleekxmpp.xmlstream import RESPONSE_TIMEOUT, StanzaBase, ET | ||||
| from sleekxmpp.xmlstream.handler import Waiter | ||||
| from sleekxmpp.xmlstream.matcher import MatcherId | ||||
|  | ||||
|  | ||||
| class Iq(RootStanza): | ||||
| 	interfaces = set(('type', 'to', 'from', 'id','query')) | ||||
| 	types = set(('get', 'result', 'set', 'error')) | ||||
| 	name = 'iq' | ||||
| 	plugin_attrib = name | ||||
| 	namespace = 'jabber:client' | ||||
|  | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		StanzaBase.__init__(self, *args, **kwargs) | ||||
| 		if self['id'] == '': | ||||
| 			if self.stream is not None: | ||||
| 				self['id'] = self.stream.getNewId() | ||||
| 			else: | ||||
| 				self['id'] = '0' | ||||
| 	 | ||||
| 	def unhandled(self): | ||||
| 		if self['type'] in ('get', 'set'): | ||||
| 			self.reply() | ||||
| 			self['error']['condition'] = 'feature-not-implemented' | ||||
| 			self['error']['text'] = 'No handlers registered for this request.' | ||||
| 			self.send() | ||||
| 	 | ||||
| 	def setPayload(self, value): | ||||
| 		self.clear() | ||||
| 		StanzaBase.setPayload(self, value) | ||||
| 	 | ||||
| 	def setQuery(self, value): | ||||
| 		query = self.xml.find("{%s}query" % value) | ||||
| 		if query is None and value: | ||||
| 			self.clear() | ||||
| 			query = ET.Element("{%s}query" % value) | ||||
| 			self.xml.append(query) | ||||
| 		return self | ||||
| 	 | ||||
| 	def getQuery(self): | ||||
| 		for child in self.xml.getchildren(): | ||||
| 			if child.tag.endswith('query'): | ||||
| 				ns =child.tag.split('}')[0] | ||||
| 				if '{' in ns: | ||||
| 					ns = ns[1:] | ||||
| 				return ns | ||||
| 		return '' | ||||
| 	 | ||||
| 	def reply(self): | ||||
| 		self['type'] = 'result' | ||||
| 		StanzaBase.reply(self) | ||||
| 		return self | ||||
| 	 | ||||
| 	def delQuery(self): | ||||
| 		for child in self.getchildren(): | ||||
| 			if child.tag.endswith('query'): | ||||
| 				self.xml.remove(child) | ||||
| 		return self | ||||
| 	 | ||||
| 	def send(self, block=True, timeout=10): | ||||
| 		if block and self['type'] in ('get', 'set'): | ||||
| 			waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) | ||||
| 			self.stream.registerHandler(waitfor) | ||||
| 			StanzaBase.send(self) | ||||
| 			return waitfor.wait(timeout) | ||||
| 		else: | ||||
| 			return StanzaBase.send(self) | ||||
|     """ | ||||
|     XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of | ||||
|     requesting and modifying information, similar to HTTP's GET and | ||||
|     POST methods. | ||||
|  | ||||
|     Each <iq> stanza must have an 'id' value which associates the | ||||
|     stanza with the response stanza. XMPP entities must always | ||||
|     be given a response <iq> stanza with a type of 'result' after | ||||
|     sending a stanza of type 'get' or 'set'. | ||||
|  | ||||
|     Most uses cases for <iq> stanzas will involve adding a <query> | ||||
|     element whose namespace indicates the type of information | ||||
|     desired. However, some custom XMPP applications use <iq> stanzas | ||||
|     as a carrier stanza for an application-specific protocol instead. | ||||
|  | ||||
|     Example <iq> Stanzas: | ||||
|         <iq to="user@example.com" type="get" id="314"> | ||||
|           <query xmlns="http://jabber.org/protocol/disco#items" /> | ||||
|         </iq> | ||||
|  | ||||
|         <iq to="user@localhost" type="result" id="17"> | ||||
|           <query xmlns='jabber:iq:roster'> | ||||
|             <item jid='otheruser@example.net' | ||||
|                   name='John Doe' | ||||
|                   subscription='both'> | ||||
|               <group>Friends</group> | ||||
|             </item> | ||||
|           </query> | ||||
|         </iq> | ||||
|  | ||||
|     Stanza Interface: | ||||
|         query -- The namespace of the <query> 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 <query> element. | ||||
|         get_query   -- Return the namespace of the <query> element. | ||||
|         del_query   -- Remove the <query> 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 <iq> stanza with an 'id' value. | ||||
|  | ||||
|         Overrides StanzaBase.__init__. | ||||
|         """ | ||||
|         StanzaBase.__init__(self, *args, **kwargs) | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.setPayload = self.set_payload | ||||
|         self.getQuery = self.get_query | ||||
|         self.setQuery = self.set_query | ||||
|         self.delQuery = self.del_query | ||||
|  | ||||
|         if self['id'] == '': | ||||
|             if self.stream is not None: | ||||
|                 self['id'] = self.stream.getNewId() | ||||
|             else: | ||||
|                 self['id'] = '0' | ||||
|  | ||||
|     def unhandled(self): | ||||
|         """ | ||||
|         Send a feature-not-implemented error if the stanza is not handled. | ||||
|  | ||||
|         Overrides StanzaBase.unhandled. | ||||
|         """ | ||||
|         if self['type'] in ('get', 'set'): | ||||
|             self.reply() | ||||
|             self['error']['condition'] = 'feature-not-implemented' | ||||
|             self['error']['text'] = 'No handlers registered for this request.' | ||||
|             self.send() | ||||
|  | ||||
|     def set_payload(self, value): | ||||
|         """ | ||||
|         Set the XML contents of the <iq> stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             value -- An XML object to use as the <iq> stanza's contents | ||||
|         """ | ||||
|         self.clear() | ||||
|         StanzaBase.set_payload(self, value) | ||||
|         return self | ||||
|  | ||||
|     def set_query(self, value): | ||||
|         """ | ||||
|         Add or modify a <query> element. | ||||
|  | ||||
|         Query elements are differentiated by their namespace. | ||||
|  | ||||
|         Arguments: | ||||
|             value -- The namespace of the <query> 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 <query> 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 <query> element.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|             if child.tag.endswith('query'): | ||||
|                 self.xml.remove(child) | ||||
|         return self | ||||
|  | ||||
|     def reply(self): | ||||
|         """ | ||||
|         Send a reply <iq> stanza. | ||||
|  | ||||
|         Overrides StanzaBase.reply | ||||
|  | ||||
|         Sets the 'type' to 'result' in addition to the default | ||||
|         StanzaBase.reply behavior. | ||||
|         """ | ||||
|         self['type'] = 'result' | ||||
|         StanzaBase.reply(self) | ||||
|         return self | ||||
|  | ||||
|     def send(self, block=True, timeout=RESPONSE_TIMEOUT): | ||||
|         """ | ||||
|         Send an <iq> stanza over the XML stream. | ||||
|  | ||||
|         The send call can optionally block until a response is received or | ||||
|         a timeout occurs. Be aware that using blocking in non-threaded event | ||||
|         handlers can drastically impact performance. | ||||
|  | ||||
|         Overrides StanzaBase.send | ||||
|  | ||||
|         Arguments: | ||||
|             block   -- Specify if the send call will block until a response | ||||
|                        is received, or a timeout occurs. Defaults to True. | ||||
|             timeout -- The length of time (in seconds) to wait for a response | ||||
|                        before exiting the send call if blocking is used. | ||||
|                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT | ||||
|         """ | ||||
|         if block and self['type'] in ('get', 'set'): | ||||
|             waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) | ||||
|             self.stream.registerHandler(waitfor) | ||||
|             StanzaBase.send(self) | ||||
|             return waitfor.wait(timeout) | ||||
|         else: | ||||
|             return StanzaBase.send(self) | ||||
|   | ||||
| @@ -3,61 +3,163 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import StanzaBase | ||||
| from xml.etree import cElementTree as ET | ||||
| from . error import Error | ||||
| from . rootstanza import RootStanza | ||||
|  | ||||
| from sleekxmpp.stanza import Error | ||||
| from sleekxmpp.stanza.rootstanza import RootStanza | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET | ||||
|  | ||||
|  | ||||
| class Message(RootStanza): | ||||
| 	interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject', 'mucroom', 'mucnick')) | ||||
| 	types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) | ||||
| 	sub_interfaces = set(('body', 'subject')) | ||||
| 	name = 'message' | ||||
| 	plugin_attrib = name | ||||
| 	namespace = 'jabber:client' | ||||
|  | ||||
| 	def getType(self): | ||||
| 		return self.xml.attrib.get('type', 'normal') | ||||
| 	 | ||||
| 	def chat(self): | ||||
| 		self['type'] = 'chat' | ||||
| 		return self | ||||
| 	 | ||||
| 	def normal(self): | ||||
| 		self['type'] = 'normal' | ||||
| 		return self | ||||
| 	 | ||||
| 	def reply(self, body=None): | ||||
| 		StanzaBase.reply(self) | ||||
| 		if self['type'] == 'groupchat': | ||||
| 			self['to'] = self['to'].bare | ||||
| 		del self['id'] | ||||
| 		if body is not None: | ||||
| 			self['body'] = body | ||||
| 		return self | ||||
| 	 | ||||
| 	def getMucroom(self): | ||||
| 		if self['type'] == 'groupchat': | ||||
| 			return self['from'].bare | ||||
| 		else: | ||||
| 			return '' | ||||
| 	 | ||||
| 	def setMucroom(self, value): | ||||
| 		pass | ||||
| 	 | ||||
| 	def delMucroom(self): | ||||
| 		pass | ||||
| 	 | ||||
| 	def getMucnick(self): | ||||
| 		if self['type'] == 'groupchat': | ||||
| 			return self['from'].resource | ||||
| 		else: | ||||
| 			return '' | ||||
| 	 | ||||
| 	def setMucnick(self, value): | ||||
| 		pass | ||||
| 	 | ||||
| 	def delMucnick(self): | ||||
| 		pass | ||||
|     """ | ||||
|     XMPP's <message> stanzas are a "push" mechanism to send information | ||||
|     to other XMPP entities without requiring a response. | ||||
|  | ||||
|     Chat clients will typically use <message> 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 <message> stanzas: | ||||
|         <message to="user1@example.com" from="user2@example.com"> | ||||
|           <body>Hi!</body> | ||||
|         </message> | ||||
|  | ||||
|         <message type="groupchat" to="room@conference.example.com"> | ||||
|           <body>Hi everyone!</body> | ||||
|         </message> | ||||
|  | ||||
|     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 setup(self, xml=None): | ||||
|         """ | ||||
|         Populate the stanza object using an optional XML object. | ||||
|  | ||||
|         Overrides StanzaBase.setup. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- Use an existing XML object for the stanza's values. | ||||
|         """ | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.getType = self.get_type | ||||
|         self.getMucroom = self.get_mucroom | ||||
|         self.setMucroom = self.set_mucroom | ||||
|         self.delMucroom = self.del_mucroom | ||||
|         self.getMucnick = self.get_mucnick | ||||
|         self.setMucnick = self.set_mucnick | ||||
|         self.delMucnick = self.del_mucnick | ||||
|  | ||||
|         return StanzaBase.setup(self, xml) | ||||
|  | ||||
|     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): | ||||
|         """ | ||||
|         Create a message reply. | ||||
|  | ||||
|         Overrides StanzaBase.reply. | ||||
|  | ||||
|         Sets proper 'to' attribute if the message is from a MUC, and | ||||
|         adds a message body if one is given. | ||||
|  | ||||
|         Arguments: | ||||
|             body -- Optional text content for the message. | ||||
|         """ | ||||
|         StanzaBase.reply(self) | ||||
|         if self['type'] == 'groupchat': | ||||
|             self['to'] = self['to'].bare | ||||
|  | ||||
|         del self['id'] | ||||
|  | ||||
|         if body is not None: | ||||
|             self['body'] = body | ||||
|         return self | ||||
|  | ||||
|     def 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 | ||||
|   | ||||
| @@ -3,23 +3,87 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import ElementBase, ET | ||||
|  | ||||
| from sleekxmpp.stanza import Message, Presence | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Nick(ElementBase): | ||||
| 	namespace = 'http://jabber.org/nick/nick' | ||||
| 	name = 'nick' | ||||
| 	plugin_attrib = 'nick' | ||||
| 	interfaces = set(('nick')) | ||||
| 	plugin_attrib_map = set() | ||||
| 	plugin_xml_map = set() | ||||
|  | ||||
| 	def setNick(self, nick): | ||||
| 		self.xml.text = nick | ||||
| 	 | ||||
| 	def getNick(self): | ||||
| 		return self.xml.text | ||||
| 	 | ||||
| 	def delNick(self): | ||||
| 		return self.__del__() | ||||
|     """ | ||||
|     XEP-0172: User Nickname allows the addition of a <nick> element | ||||
|     in several stanza types, including <message> and <presence> stanzas. | ||||
|  | ||||
|     The nickname contained in a <nick> should be the global, friendly or | ||||
|     informal name chosen by the owner of a bare JID. The <nick> element | ||||
|     may be included when establishing communications with new entities, | ||||
|     such as normal XMPP users or MUC services. | ||||
|  | ||||
|     The nickname contained in a <nick> element will not necessarily be | ||||
|     the same as the nickname used in a MUC. | ||||
|  | ||||
|     Example stanzas: | ||||
|         <message to="user@example.com"> | ||||
|           <nick xmlns="http://jabber.org/nick/nick">The User</nick> | ||||
|           <body>...</body> | ||||
|         </message> | ||||
|  | ||||
|         <presence to="otheruser@example.com" type="subscribe"> | ||||
|           <nick xmlns="http://jabber.org/nick/nick">The User</nick> | ||||
|         </presence> | ||||
|  | ||||
|     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 <nick> element. | ||||
|         set_nick -- Add a <nick> element with the given nickname. | ||||
|         del_nick -- Remove the <nick> element. | ||||
|     """ | ||||
|  | ||||
|     namespace = 'http://jabber.org/nick/nick' | ||||
|     name = 'nick' | ||||
|     plugin_attrib = name | ||||
|     interfaces = set(('nick',)) | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         """ | ||||
|         Populate the stanza object using an optional XML object. | ||||
|  | ||||
|         Overrides StanzaBase.setup. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- Use an existing XML object for the stanza's values. | ||||
|         """ | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.setNick = self.set_nick | ||||
|         self.getNick = self.get_nick | ||||
|         self.delNick = self.del_nick | ||||
|  | ||||
|         return ElementBase.setup(self, xml) | ||||
|  | ||||
|     def set_nick(self, nick): | ||||
|         """ | ||||
|         Add a <nick> 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 <nick> element.""" | ||||
|         return self.xml.text | ||||
|  | ||||
|     def del_nick(self): | ||||
|         """Remove the <nick> element.""" | ||||
|         if self.parent is not None: | ||||
|             self.parent().xml.remove(self.xml) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Message, Nick) | ||||
| register_stanza_plugin(Presence, Nick) | ||||
|   | ||||
| @@ -3,61 +3,184 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import StanzaBase | ||||
| from xml.etree import cElementTree as ET | ||||
| from . error import Error | ||||
| from . rootstanza import RootStanza | ||||
|  | ||||
| from sleekxmpp.stanza import Error | ||||
| from sleekxmpp.stanza.rootstanza import RootStanza | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET | ||||
|  | ||||
|  | ||||
| class Presence(RootStanza): | ||||
| 	interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority')) | ||||
| 	types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed')) | ||||
| 	showtypes = set(('dnd', 'chat', 'xa', 'away')) | ||||
| 	sub_interfaces = set(('status', 'priority')) | ||||
| 	name = 'presence' | ||||
| 	plugin_attrib = name | ||||
| 	namespace = 'jabber:client' | ||||
|  | ||||
| 	def getShowElement(self): | ||||
| 		return self.xml.find("{%s}show" % self.namespace) | ||||
|     """ | ||||
|     XMPP's <presence> 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 <presence> | ||||
|     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). | ||||
|  | ||||
| 	def setType(self, value): | ||||
| 		show = self.getShowElement() | ||||
| 		if value in self.types: | ||||
| 			if show is not None: | ||||
| 				self.xml.remove(show) | ||||
| 			if value == 'available': | ||||
| 				value = '' | ||||
| 			self._setAttr('type', value) | ||||
| 		elif value in self.showtypes: | ||||
| 			if show is None: | ||||
| 				show = ET.Element("{%s}show" % self.namespace) | ||||
| 				self.xml.append(show) | ||||
| 			show.text = value | ||||
| 		return self | ||||
|     Since <presence> stanzas are broadcast when an XMPP entity changes | ||||
|     its status, the bulk of the traffic in an XMPP network will be from | ||||
|     <presence> stanzas. Therefore, do not include more information than | ||||
|     necessary in a status message or within a <presence> stanza in order | ||||
|     to help keep the network running smoothly. | ||||
|  | ||||
| 	def setPriority(self, value): | ||||
| 		self._setSubText('priority', text = str(value)) | ||||
| 	 | ||||
| 	def getPriority(self): | ||||
| 		p = self._getSubText('priority') | ||||
| 		if not p: p = 0 | ||||
| 		return int(p) | ||||
| 	 | ||||
| 	def getType(self): | ||||
| 		out = self._getAttr('type') | ||||
| 		if not out: | ||||
| 			show = self.getShowElement() | ||||
| 			if show is not None: | ||||
| 				out = show.text | ||||
| 		if not out or out is None: | ||||
| 			out = 'available' | ||||
| 		return out | ||||
| 	 | ||||
| 	def reply(self): | ||||
| 		if self['type'] == 'unsubscribe': | ||||
| 			self['type'] = 'unsubscribed' | ||||
| 		elif self['type'] == 'subscribe': | ||||
| 			self['type'] = 'subscribed' | ||||
| 		return StanzaBase.reply(self) | ||||
|     Example <presence> stanzas: | ||||
|         <presence /> | ||||
|  | ||||
|         <presence from="user@example.com"> | ||||
|           <show>away</show> | ||||
|           <status>Getting lunch.</status> | ||||
|           <priority>5</priority> | ||||
|         </presence> | ||||
|  | ||||
|         <presence type="unavailable" /> | ||||
|  | ||||
|         <presence to="user@otherhost.com" type="subscribe" /> | ||||
|  | ||||
|     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 <show> element. | ||||
|         get_type     -- Get the value of the type attribute or <show> element. | ||||
|         set_type     -- Set the value of the type attribute or <show> element. | ||||
|         get_priority -- Get the value of the <priority> element. | ||||
|         set_priority -- Set the value of the <priority> 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 setup(self, xml=None): | ||||
|         """ | ||||
|         Populate the stanza object using an optional XML object. | ||||
|  | ||||
|         Overrides ElementBase.setup. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- Use an existing XML object for the stanza's values. | ||||
|         """ | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.setShow = self.set_show | ||||
|         self.getType = self.get_type | ||||
|         self.setType = self.set_type | ||||
|         self.delType = self.get_type | ||||
|         self.getPriority = self.get_priority | ||||
|         self.setPriority = self.set_priority | ||||
|  | ||||
|         return StanzaBase.setup(self, xml) | ||||
|  | ||||
|     def exception(self, e): | ||||
|         """ | ||||
|         Override exception passback for presence. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def set_show(self, show): | ||||
|         """ | ||||
|         Set the value of the <show> 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 <presence> stanza's type attribute, or | ||||
|         the value of the <show> 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 <show> 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 <show> 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 <presence> 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): | ||||
|         """ | ||||
|         Set the appropriate presence reply type. | ||||
|  | ||||
|         Overrides StanzaBase.reply. | ||||
|         """ | ||||
|         if self['type'] == 'unsubscribe': | ||||
|             self['type'] = 'unsubscribed' | ||||
|         elif self['type'] == 'subscribe': | ||||
|             self['type'] = 'subscribed' | ||||
|         return StanzaBase.reply(self) | ||||
|   | ||||
| @@ -3,34 +3,64 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import StanzaBase | ||||
| from xml.etree import cElementTree as ET | ||||
| from . error import Error | ||||
| from .. exceptions import XMPPError | ||||
|  | ||||
| import logging | ||||
| import traceback | ||||
| import sys | ||||
|  | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.stanza import Error | ||||
| from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class RootStanza(StanzaBase): | ||||
|  | ||||
| 	def exception(self, e): #called when a handler raises an exception | ||||
| 		self.reply() | ||||
| 		if isinstance(e, XMPPError): # we raised this deliberately | ||||
| 			self['error']['condition'] = e.condition | ||||
| 			self['error']['text'] = e.text | ||||
| 			if e.extension is not None: # extended error tag | ||||
| 				extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args) | ||||
| 				self['error'].xml.append(extxml) | ||||
| 				self['error']['type'] = e.etype | ||||
| 		else: # we probably didn't raise this on purpose, so send back a traceback | ||||
| 			self['error']['condition'] = 'undefined-condition' | ||||
| 			if sys.version_info < (3,0): | ||||
| 				self['error']['text'] = "SleekXMPP got into trouble." | ||||
| 			else: | ||||
| 				self['error']['text'] = traceback.format_tb(e.__traceback__) | ||||
| 		self.send() | ||||
|     """ | ||||
|     A top-level XMPP stanza in an XMLStream. | ||||
|  | ||||
| # all jabber:client root stanzas should have the error plugin | ||||
| RootStanza.plugin_attrib_map['error'] = Error | ||||
| RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error | ||||
|     The RootStanza class provides a more XMPP specific exception | ||||
|     handler than provided by the generic StanzaBase class. | ||||
|  | ||||
|     Methods: | ||||
|         exception -- Overrides StanzaBase.exception | ||||
|     """ | ||||
|  | ||||
|     def exception(self, e): | ||||
|         """ | ||||
|         Create and send an error reply. | ||||
|  | ||||
|         Typically called when an event handler raises an exception. | ||||
|         The error's type and text content are based on the exception | ||||
|         object's type and content. | ||||
|  | ||||
|         Overrides StanzaBase.exception. | ||||
|  | ||||
|         Arguments: | ||||
|             e -- Exception object | ||||
|         """ | ||||
|         self.reply() | ||||
|         if isinstance(e, XMPPError): | ||||
|             # We raised this deliberately | ||||
|             self['error']['condition'] = e.condition | ||||
|             self['error']['text'] = e.text | ||||
|             if e.extension is not None: | ||||
|                 # Extended error tag | ||||
|                 extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), | ||||
|                                     e.extension_args) | ||||
|                 self['error'].append(extxml) | ||||
|                 self['error']['type'] = e.etype | ||||
|         else: | ||||
|             # We probably didn't raise this on purpose, so send a traceback | ||||
|             self['error']['condition'] = 'undefined-condition' | ||||
|             if sys.version_info < (3, 0): | ||||
|                 self['error']['text'] = "SleekXMPP got into trouble." | ||||
|             else: | ||||
|                 self['error']['text'] = traceback.format_tb(e.__traceback__) | ||||
|                 logging.exception('Error handling {%s}%s stanza' % | ||||
|                                   (self.namespace, self.name)) | ||||
|         self.send() | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(RootStanza, Error) | ||||
|   | ||||
| @@ -3,51 +3,123 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from .. xmlstream.stanzabase import ElementBase, ET, JID | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.stanza import Iq | ||||
| from sleekxmpp.xmlstream import JID | ||||
| from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Roster(ElementBase): | ||||
| 	namespace = 'jabber:iq:roster' | ||||
| 	name = 'query' | ||||
| 	plugin_attrib = 'roster' | ||||
| 	interfaces = set(('items',)) | ||||
| 	sub_interfaces = set() | ||||
|  | ||||
| 	def setItems(self, items): | ||||
| 		self.delItems() | ||||
| 		for jid in items: | ||||
| 			ijid = str(jid) | ||||
| 			item = ET.Element('{jabber:iq:roster}item', {'jid': ijid}) | ||||
| 			if 'subscription' in items[jid]: | ||||
| 				item.attrib['subscription'] = items[jid]['subscription'] | ||||
| 			if 'name' in items[jid]: | ||||
| 				item.attrib['name'] = items[jid]['name'] | ||||
| 			if 'groups' in items[jid]: | ||||
| 				for group in items[jid]['groups']: | ||||
| 					groupxml = ET.Element('{jabber:iq:roster}group') | ||||
| 					groupxml.text = group | ||||
| 					item.append(groupxml) | ||||
| 			self.xml.append(item) | ||||
| 		return self | ||||
| 	 | ||||
| 	def getItems(self): | ||||
| 		items = {} | ||||
| 		itemsxml = self.xml.findall('{jabber:iq:roster}item') | ||||
| 		if itemsxml is not None: | ||||
| 			for itemxml in itemsxml: | ||||
| 				item = {} | ||||
| 				item['name'] = itemxml.get('name', '') | ||||
| 				item['subscription'] = itemxml.get('subscription', '') | ||||
| 				item['groups'] = [] | ||||
| 				groupsxml = itemxml.findall('{jabber:iq:roster}group') | ||||
| 				if groupsxml is not None: | ||||
| 					for groupxml in groupsxml: | ||||
| 						item['groups'].append(groupxml.text) | ||||
| 				items[itemxml.get('jid')] = item | ||||
| 		return items | ||||
| 	 | ||||
| 	def delItems(self): | ||||
| 		for child in self.xml.getchildren(): | ||||
| 			self.xml.remove(child) | ||||
|     """ | ||||
|     Example roster stanzas: | ||||
|         <iq type="set"> | ||||
|           <query xmlns="jabber:iq:roster"> | ||||
|             <item jid="user@example.com" subscription="both" name="User"> | ||||
|               <group>Friends</group> | ||||
|             </item> | ||||
|           </query> | ||||
|         </iq> | ||||
|  | ||||
|     Stanza Inteface: | ||||
|         items -- A dictionary of roster entries contained | ||||
|                  in the stanza. | ||||
|  | ||||
|     Methods: | ||||
|         get_items -- Return a dictionary of roster entries. | ||||
|         set_items -- Add <item> elements. | ||||
|         del_items -- Remove all <item> elements. | ||||
|     """ | ||||
|  | ||||
|     namespace = 'jabber:iq:roster' | ||||
|     name = 'query' | ||||
|     plugin_attrib = 'roster' | ||||
|     interfaces = set(('items',)) | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         """ | ||||
|         Populate the stanza object using an optional XML object. | ||||
|  | ||||
|         Overrides StanzaBase.setup. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- Use an existing XML object for the stanza's values. | ||||
|         """ | ||||
|         # To comply with PEP8, method names now use underscores. | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.setItems = self.set_items | ||||
|         self.getItems = self.get_items | ||||
|         self.delItems = self.del_items | ||||
|  | ||||
|         return ElementBase.setup(self, xml) | ||||
|  | ||||
|     def set_items(self, items): | ||||
|         """ | ||||
|         Set the roster entries in the <roster> 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 <item> elements from the roster stanza. | ||||
|         """ | ||||
|         for child in self.xml.getchildren(): | ||||
|             self.xml.remove(child) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Iq, Roster) | ||||
|   | ||||
							
								
								
									
										11
									
								
								sleekxmpp/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								sleekxmpp/test/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| """ | ||||
|     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 * | ||||
							
								
								
									
										145
									
								
								sleekxmpp/test/livesocket.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								sleekxmpp/test/livesocket.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| """ | ||||
|     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 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.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 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) | ||||
|         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. | ||||
|         """ | ||||
|         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) | ||||
							
								
								
									
										140
									
								
								sleekxmpp/test/mocksocket.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								sleekxmpp/test/mocksocket.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| """ | ||||
|     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 | ||||
|  | ||||
|     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) | ||||
|  | ||||
|     # ------------------------------------------------------------------ | ||||
|     # Socket Interface | ||||
|  | ||||
|     def recv(self, *args, **kwargs): | ||||
|         """ | ||||
|         Read a value from the received queue. | ||||
|  | ||||
|         Arguments: | ||||
|             Placeholders. Same as for socket.Socket.recv. | ||||
|         """ | ||||
|         return self.read(block=True) | ||||
|  | ||||
|     def send(self, data): | ||||
|         """ | ||||
|         Send data by placing it in the send queue. | ||||
|  | ||||
|         Arguments: | ||||
|             data -- String value to write. | ||||
|         """ | ||||
|         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 timeout is not None: | ||||
|             block = True | ||||
|         try: | ||||
|             return self.recv_queue.get(block, timeout) | ||||
|         except: | ||||
|             return None | ||||
							
								
								
									
										763
									
								
								sleekxmpp/test/sleektest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										763
									
								
								sleekxmpp/test/sleektest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,763 @@ | ||||
| """ | ||||
|  | ||||
|     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 | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp import ClientXMPP, ComponentXMPP | ||||
| from sleekxmpp.stanza import Message, Iq, Presence | ||||
| from sleekxmpp.test import TestSocket, TestLiveSocket | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.tostring import tostring | ||||
|  | ||||
|  | ||||
| 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_stanza         -- Compare a generic stanza against an XML string. | ||||
|         check_message        -- Compare a Message stanza against an XML string. | ||||
|         check_iq             -- Compare an Iq stanza against an XML string. | ||||
|         check_presence       -- Compare a Presence stanza against an XML string. | ||||
|         stream_start         -- Initialize a dummy XMPP client. | ||||
|         stream_recv          -- Queue data for XMPP client to receive. | ||||
|         stream_make_header   -- Create a stream header. | ||||
|         stream_send_header   -- Check that the given header has been sent. | ||||
|         stream_send_message  -- Check that the XMPP client sent the given | ||||
|                                 Message stanza. | ||||
|         stream_send_iq       -- Check that the XMPP client sent the given | ||||
|                                 Iq stanza. | ||||
|         stream_send_presence -- Check thatt the XMPP client sent the given | ||||
|                                 Presence stanza. | ||||
|         stream_send_stanza   -- Check that the XMPP client sent the given | ||||
|                                 generic stanza. | ||||
|         stream_close         -- Disconnect the XMPP client. | ||||
|         fix_namespaces       -- Add top-level namespace to an XML object. | ||||
|         compare              -- Compare XML objects against each other. | ||||
|     """ | ||||
|  | ||||
|     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 = '<fixns xmlns:%s="%s">%s</fixns>' % ( | ||||
|                             prefix, | ||||
|                             known_prefixes[prefix], | ||||
|                             xml_string) | ||||
|                 xml = self.parse_xml(xml_string) | ||||
|                 xml = xml.getchildren()[0] | ||||
|                 return xml | ||||
|  | ||||
|     # ------------------------------------------------------------------ | ||||
|     # 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(None, *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(None, *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(None, *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_stanza(self, stanza_class, stanza, xml_string, | ||||
|                      defaults=None, use_values=True): | ||||
|         """ | ||||
|         Create and compare several stanza objects to a correct XML string. | ||||
|  | ||||
|         If use_values is False, test using getStanzaValues() and | ||||
|         setStanzaValues() 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_class -- The class of the stanza being tested. | ||||
|             stanza       -- The stanza object to test. | ||||
|             xml_string   -- A string version of the correct XML expected. | ||||
|             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 getStanzaValues() and | ||||
|                             setStanzaValues() should be used. Defaults to | ||||
|                             True. | ||||
|         """ | ||||
|         xml = self.parse_xml(xml_string) | ||||
|  | ||||
|         # 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 getStanzaValues() and setStanzaValues() 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: | ||||
|                 defaults = [] | ||||
|             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.getStanzaValues() | ||||
|             stanza3 = stanza_class() | ||||
|             stanza3.setStanzaValues(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) | ||||
|  | ||||
|     def check_message(self, msg, xml_string, use_values=True): | ||||
|         """ | ||||
|         Create and compare several message stanza objects to a | ||||
|         correct XML string. | ||||
|  | ||||
|         If use_values is False, the test using getStanzaValues() and | ||||
|         setStanzaValues() will not be used. | ||||
|  | ||||
|         Arguments: | ||||
|             msg        -- The Message stanza object to check. | ||||
|             xml_string -- The XML contents to compare against. | ||||
|             use_values -- Indicates if the test using getStanzaValues | ||||
|                           and setStanzaValues should be used. Defaults | ||||
|                           to True. | ||||
|         """ | ||||
|  | ||||
|         return self.check_stanza(Message, msg, xml_string, | ||||
|                                  defaults=['type'], | ||||
|                                  use_values=use_values) | ||||
|  | ||||
|     def check_iq(self, iq, xml_string, use_values=True): | ||||
|         """ | ||||
|         Create and compare several iq stanza objects to a | ||||
|         correct XML string. | ||||
|  | ||||
|         If use_values is False, the test using getStanzaValues() and | ||||
|         setStanzaValues() will not be used. | ||||
|  | ||||
|         Arguments: | ||||
|             iq         -- The Iq stanza object to check. | ||||
|             xml_string -- The XML contents to compare against. | ||||
|             use_values -- Indicates if the test using getStanzaValues | ||||
|                           and setStanzaValues should be used. Defaults | ||||
|                           to True. | ||||
|         """ | ||||
|         return self.check_stanza(Iq, iq, xml_string, use_values=use_values) | ||||
|  | ||||
|     def check_presence(self, pres, xml_string, use_values=True): | ||||
|         """ | ||||
|         Create and compare several presence stanza objects to a | ||||
|         correct XML string. | ||||
|  | ||||
|         If use_values is False, the test using getStanzaValues() and | ||||
|         setStanzaValues() will not be used. | ||||
|  | ||||
|         Arguments: | ||||
|             iq         -- The Iq stanza object to check. | ||||
|             xml_string -- The XML contents to compare against. | ||||
|             use_values -- Indicates if the test using getStanzaValues | ||||
|                           and setStanzaValues should be used. Defaults | ||||
|                           to True. | ||||
|         """ | ||||
|         return self.check_stanza(Presence, pres, xml_string, | ||||
|                                  defaults=['priority'], | ||||
|                                  use_values=use_values) | ||||
|  | ||||
|     # ------------------------------------------------------------------ | ||||
|     # Methods for simulating stanza streams. | ||||
|  | ||||
|     def stream_start(self, mode='client', skip=True, header=None, | ||||
|                            socket='mock', jid='tester@localhost', | ||||
|                            password='test', server='localhost', | ||||
|                            port=5222): | ||||
|         """ | ||||
|         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. | ||||
|         """ | ||||
|  | ||||
|         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.") | ||||
|  | ||||
|         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 | ||||
|             self.xmpp.connect() | ||||
|         else: | ||||
|             raise ValueError("Unknown socket type.") | ||||
|  | ||||
|         self.xmpp.register_plugins() | ||||
|         self.xmpp.process(threaded=True) | ||||
|         if skip: | ||||
|             # Clear startup stanzas | ||||
|             self.xmpp.socket.next_sent(timeout=1) | ||||
|             if mode == 'component': | ||||
|                 self.xmpp.socket.next_sent(timeout=1) | ||||
|  | ||||
|     def stream_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 = '<stream:stream %s>' | ||||
|         parts = [] | ||||
|         if xml_header: | ||||
|             header = '<?xml version="1.0"?>' + 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 stream_recv(self, data, stanza_class=StanzaBase, defaults=[], | ||||
|                     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         -- String stanza XML to be received and processed by | ||||
|                             the XMPP client or component. | ||||
|             stanza_class -- The stanza object class for verifying data received | ||||
|                             by a live connection. Defaults to StanzaBase. | ||||
|             defaults     -- A list of stanza interfaces with default values that | ||||
|                             may interfere with comparisons. | ||||
|             use_values   -- Indicates if stanza comparisons should test using | ||||
|                             getStanzaValues() and setStanzaValues(). | ||||
|                             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: | ||||
|                 return False | ||||
|             stanza = stanza_class(xml=self.parse_xml(recv_data)) | ||||
|             return self.check_stanza(stanza_class, stanza, data, | ||||
|                                      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 stream_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.stream_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 + '</stream:stream>' | ||||
|         recv_header2 = recv_header + '</stream:stream>' | ||||
|  | ||||
|         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))) | ||||
|                 #tostring(xml), tostring(recv_xml)))#recv_header)) | ||||
|  | ||||
|     def stream_recv_feature(self, data, use_values=True, timeout=1): | ||||
|         """ | ||||
|         """ | ||||
|         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: | ||||
|                 return False | ||||
|             xml = self.parse_xml(data) | ||||
|             recv_xml = self.parse_xml(recv_data) | ||||
|             self.failUnless(self.compare(xml, recv_xml), | ||||
|                 "Features do not match.\nDesired:\n%s\nReceived:\n%s" % ( | ||||
|                     tostring(xml), tostring(recv_xml))) | ||||
|         else: | ||||
|             # place the data in the dummy socket receiving queue. | ||||
|             data = str(data) | ||||
|             self.xmpp.socket.recv_data(data) | ||||
|  | ||||
|  | ||||
|  | ||||
|     def stream_recv_message(self, data, use_values=True, timeout=1): | ||||
|         """ | ||||
|         """ | ||||
|         return self.stream_recv(data, stanza_class=Message, | ||||
|                                       defaults=['type'], | ||||
|                                       use_values=use_values, | ||||
|                                       timeout=timeout) | ||||
|  | ||||
|     def stream_recv_iq(self, data, use_values=True, timeout=1): | ||||
|         """ | ||||
|         """ | ||||
|         return self.stream_recv(data, stanza_class=Iq, | ||||
|                                       use_values=use_values, | ||||
|                                       timeout=timeout) | ||||
|  | ||||
|     def stream_recv_presence(self, data, use_values=True, timeout=1): | ||||
|         """ | ||||
|         """ | ||||
|         return self.stream_recv(data, stanza_class=Presence, | ||||
|                                       defaults=['priority'], | ||||
|                                       use_values=use_values, | ||||
|                                       timeout=timeout) | ||||
|  | ||||
|     def stream_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.stream_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 + '</stream:stream>' | ||||
|         sent_header2 = sent_header + b'</stream:stream>' | ||||
|  | ||||
|         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 stream_send_feature(self, data, use_values=True, timeout=1): | ||||
|         """ | ||||
|         """ | ||||
|         sent_data = self.xmpp.socket.next_sent(timeout) | ||||
|         if sent_data is None: | ||||
|             return False | ||||
|         xml = self.parse_xml(data) | ||||
|         sent_xml = self.parse_xml(sent_data) | ||||
|         self.failUnless(self.compare(xml, sent_xml), | ||||
|             "Features do not match.\nDesired:\n%s\nSent:\n%s" % ( | ||||
|                 tostring(xml), tostring(sent_xml))) | ||||
|  | ||||
|     def stream_send_stanza(self, stanza_class, data, defaults=None, | ||||
|                            use_values=True, timeout=.1): | ||||
|         """ | ||||
|         Check that the XMPP client sent the given stanza XML. | ||||
|  | ||||
|         Extracts the next sent stanza and compares it with the given | ||||
|         XML using check_stanza. | ||||
|  | ||||
|         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. | ||||
|         """ | ||||
|         if isintance(data, str): | ||||
|             data = stanza_class(xml=self.parse_xml(data)) | ||||
|         sent = self.xmpp.socket.next_sent(timeout) | ||||
|         self.check_stanza(stanza_class, data, sent, | ||||
|                           defaults=defaults, | ||||
|                           use_values=use_values) | ||||
|  | ||||
|     def stream_send_message(self, data, use_values=True, timeout=.1): | ||||
|         """ | ||||
|         Check that the XMPP client sent the given stanza XML. | ||||
|  | ||||
|         Extracts the next sent stanza and compares it with the given | ||||
|         XML using check_message. | ||||
|  | ||||
|         Arguments: | ||||
|             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. | ||||
|             timeout    -- Time in seconds to wait for a stanza before | ||||
|                           failing the check. | ||||
|         """ | ||||
|         if isinstance(data, str): | ||||
|             data = self.Message(xml=self.parse_xml(data)) | ||||
|         sent = self.xmpp.socket.next_sent(timeout) | ||||
|         self.check_message(data, sent, use_values) | ||||
|  | ||||
|     def stream_send_iq(self, data, use_values=True, timeout=.1): | ||||
|         """ | ||||
|         Check that the XMPP client sent the given stanza XML. | ||||
|  | ||||
|         Extracts the next sent stanza and compares it with the given | ||||
|         XML using check_iq. | ||||
|  | ||||
|         Arguments: | ||||
|             data       -- The XML string of the expected Iq stanza, | ||||
|                           or an equivalent stanza object. | ||||
|             use_values -- Modifies the type of tests used by check_iq. | ||||
|             timeout    -- Time in seconds to wait for a stanza before | ||||
|                           failing the check. | ||||
|         """ | ||||
|         if isinstance(data, str): | ||||
|             data = self.Iq(xml=self.parse_xml(data)) | ||||
|         sent = self.xmpp.socket.next_sent(timeout) | ||||
|         self.check_iq(data, sent, use_values) | ||||
|  | ||||
|     def stream_send_presence(self, data, use_values=True, timeout=.1): | ||||
|         """ | ||||
|         Check that the XMPP client sent the given stanza XML. | ||||
|  | ||||
|         Extracts the next sent stanza and compares it with the given | ||||
|         XML using check_presence. | ||||
|  | ||||
|         Arguments: | ||||
|             data       -- The XML string of the expected Presence stanza, | ||||
|                           or an equivalent stanza object. | ||||
|             use_values -- Modifies the type of tests used by check_presence. | ||||
|             timeout    -- Time in seconds to wait for a stanza before | ||||
|                           failing the check. | ||||
|         """ | ||||
|         if isinstance(data, str): | ||||
|             data = self.Presence(xml=self.parse_xml(data)) | ||||
|         sent = self.xmpp.socket.next_sent(timeout) | ||||
|         self.check_presence(data, sent, 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 | ||||
							
								
								
									
										0
									
								
								sleekxmpp/thirdparty/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								sleekxmpp/thirdparty/__init__.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										287
									
								
								sleekxmpp/thirdparty/statemachine.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								sleekxmpp/thirdparty/statemachine.py
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | ||||
| """ | ||||
|     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: | ||||
|                 logging.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:  | ||||
|                 logging.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())) | ||||
| @@ -0,0 +1,19 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| 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'] | ||||
|   | ||||
| @@ -3,23 +3,39 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from socket import _fileobject | ||||
| import socket | ||||
|  | ||||
| class filesocket(_fileobject): | ||||
|  | ||||
| 	def read(self, size=4096): | ||||
| 		data = self._sock.recv(size) | ||||
| 		if data is not None: | ||||
| 			return data | ||||
| 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.""" | ||||
|         data = self._sock.recv(size) | ||||
|         if data is not None: | ||||
|             return data | ||||
|  | ||||
|  | ||||
| class Socket26(socket._socketobject): | ||||
|  | ||||
| 	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) | ||||
|     """ | ||||
|     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) | ||||
|   | ||||
| @@ -0,0 +1,14 @@ | ||||
| """ | ||||
|     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'] | ||||
|   | ||||
| @@ -3,26 +3,87 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
|  | ||||
| class BaseHandler(object): | ||||
|  | ||||
|     """ | ||||
|     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. | ||||
|  | ||||
| 	def __init__(self, name, matcher): | ||||
| 		self.name = name | ||||
| 		self._destroy = False | ||||
| 		self._payload = None | ||||
| 		self._matcher = matcher | ||||
| 	 | ||||
| 	def match(self, xml): | ||||
| 		return self._matcher.match(xml) | ||||
| 	 | ||||
| 	def prerun(self, payload): | ||||
| 		self._payload = payload | ||||
|     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. | ||||
|  | ||||
| 	def run(self, payload): | ||||
| 		self._payload = payload | ||||
| 	 | ||||
| 	def checkDelete(self): | ||||
| 		return self._destroy | ||||
|     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.checkDelete = self.check_delete | ||||
|  | ||||
|         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 | ||||
|   | ||||
| @@ -3,32 +3,82 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
| import logging | ||||
|  | ||||
| class Callback(base.BaseHandler): | ||||
| 	 | ||||
| 	def __init__(self, name, matcher, pointer, thread=False, once=False, instream=False): | ||||
| 		base.BaseHandler.__init__(self, name, matcher) | ||||
| 		self._pointer = pointer | ||||
| 		self._thread = thread | ||||
| 		self._once = once | ||||
| 		self._instream = instream | ||||
| from sleekxmpp.xmlstream.handler.base import BaseHandler | ||||
|  | ||||
| 	def prerun(self, payload): | ||||
| 		base.BaseHandler.prerun(self, payload) | ||||
| 		if self._instream: | ||||
| 			self.run(payload, True) | ||||
| 	 | ||||
| 	def run(self, payload, instream=False): | ||||
| 		if not self._instream or instream: | ||||
| 			base.BaseHandler.run(self, payload) | ||||
| 			#if self._thread: | ||||
| 			#	x = threading.Thread(name="Callback_%s" % self.name, target=self._pointer, args=(payload,)) | ||||
| 			#	x.start() | ||||
| 			#else: | ||||
| 			self._pointer(payload) | ||||
| 			if self._once: | ||||
| 				self._destroy = True | ||||
|  | ||||
| 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. | ||||
|         """ | ||||
|         BaseHandler.prerun(self, payload) | ||||
|         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: | ||||
|             BaseHandler.run(self, payload) | ||||
|             self._pointer(payload) | ||||
|             if self._once: | ||||
|                 self._destroy = True | ||||
|   | ||||
| @@ -3,34 +3,96 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
| try: | ||||
| 	import queue | ||||
| except ImportError: | ||||
| 	import Queue as queue | ||||
|  | ||||
| import logging | ||||
| from .. stanzabase import StanzaBase | ||||
| try: | ||||
|     import queue | ||||
| except ImportError: | ||||
|     import Queue as queue | ||||
|  | ||||
| class Waiter(base.BaseHandler): | ||||
| 	 | ||||
| 	def __init__(self, name, matcher): | ||||
| 		base.BaseHandler.__init__(self, name, matcher) | ||||
| 		self._payload = queue.Queue() | ||||
| 	 | ||||
| 	def prerun(self, payload): | ||||
| 		self._payload.put(payload) | ||||
| 	 | ||||
| 	def run(self, payload): | ||||
| 		pass | ||||
| from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT | ||||
| from sleekxmpp.xmlstream.handler.base import BaseHandler | ||||
|  | ||||
| 	def wait(self, timeout=60): | ||||
| 		try: | ||||
| 			return self._payload.get(True, timeout) | ||||
| 		except queue.Empty: | ||||
| 			logging.warning("Timed out waiting for %s" % self.name) | ||||
| 			return False | ||||
| 	 | ||||
| 	def checkDelete(self): | ||||
| 		return True | ||||
|  | ||||
| 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=RESPONSE_TIMEOUT): | ||||
|         """ | ||||
|         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. | ||||
|         """ | ||||
|         try: | ||||
|             stanza = self._payload.get(True, timeout) | ||||
|         except queue.Empty: | ||||
|             stanza = False | ||||
|             logging.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 | ||||
|   | ||||
| @@ -3,12 +3,34 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| import threading | ||||
| from . callback import Callback | ||||
|  | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
|  | ||||
|  | ||||
| class XMLCallback(Callback): | ||||
| 	 | ||||
| 	def run(self, payload, instream=False): | ||||
| 		Callback.run(self, payload.xml, instream) | ||||
|  | ||||
|     """ | ||||
|     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) | ||||
|   | ||||
| @@ -3,11 +3,31 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . waiter import Waiter | ||||
|  | ||||
| from sleekxmpp.xmlstream.handler import Waiter | ||||
|  | ||||
|  | ||||
| class XMLWaiter(Waiter): | ||||
| 	 | ||||
| 	def prerun(self, payload): | ||||
| 		Waiter.prerun(self, payload.xml) | ||||
|  | ||||
|     """ | ||||
|     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) | ||||
|   | ||||
							
								
								
									
										123
									
								
								sleekxmpp/xmlstream/jid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								sleekxmpp/xmlstream/jid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
|  | ||||
| class JID(object): | ||||
|     """ | ||||
|     A representation of a Jabber ID, or JID. | ||||
|  | ||||
|     Each JID may have three components: a user, a domain, and an optional | ||||
|     resource. For example: user@domain/resource | ||||
|  | ||||
|     When a resource is not used, the JID is called a bare JID. | ||||
|     The JID is a full JID otherwise. | ||||
|  | ||||
|     Attributes: | ||||
|         jid      -- Alias for 'full'. | ||||
|         full     -- The value of the full JID. | ||||
|         bare     -- The value of the bare JID. | ||||
|         user     -- The username portion of the JID. | ||||
|         domain   -- The domain name portion of the JID. | ||||
|         server   -- Alias for 'domain'. | ||||
|         resource -- The resource portion of the JID. | ||||
|  | ||||
|     Methods: | ||||
|         reset      -- Use a new JID value. | ||||
|         regenerate -- Recreate the JID from its components. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid): | ||||
|         """Initialize a new JID""" | ||||
|         self.reset(jid) | ||||
|  | ||||
|     def reset(self, jid): | ||||
|         """ | ||||
|         Start fresh from a new JID string. | ||||
|  | ||||
|         Arguments: | ||||
|             jid - The new JID value. | ||||
|         """ | ||||
|         self._full = self._jid = str(jid) | ||||
|         self._domain = None | ||||
|         self._resource = None | ||||
|         self._user = None | ||||
|         self._bare = None | ||||
|  | ||||
|     def __getattr__(self, name): | ||||
|         """ | ||||
|         Handle getting the JID values, using cache if available. | ||||
|  | ||||
|         Arguments: | ||||
|             name -- One of: user, server, domain, resource, | ||||
|                     full, or bare. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             if self._resource is None 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 == 'full': | ||||
|             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 | ||||
| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     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'] | ||||
|   | ||||
| @@ -3,12 +3,32 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
|  | ||||
| class MatcherBase(object): | ||||
|  | ||||
| 	def __init__(self, criteria): | ||||
| 		self._criteria = criteria | ||||
| 	 | ||||
| 	def match(self, xml): | ||||
| 		return False | ||||
|     """ | ||||
|     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 | ||||
|   | ||||
| @@ -3,11 +3,30 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
|  | ||||
| class MatcherId(base.MatcherBase): | ||||
| 	 | ||||
| 	def match(self, xml): | ||||
| 		return xml['id'] == self._criteria | ||||
| 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 | ||||
|   | ||||
| @@ -3,15 +3,38 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
| from xml.etree import cElementTree | ||||
|  | ||||
| class MatchMany(base.MatcherBase): | ||||
| from sleekxmpp.xmlstream.matcher.base import MatcherBase | ||||
|  | ||||
| 	def match(self, xml): | ||||
| 		for m in self._criteria: | ||||
| 			if m.match(xml): | ||||
| 				return True | ||||
| 		return False | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -3,12 +3,36 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
| from xml.etree import cElementTree | ||||
|  | ||||
| class StanzaPath(base.MatcherBase): | ||||
| from sleekxmpp.xmlstream.matcher.base import MatcherBase | ||||
|  | ||||
| 	def match(self, stanza): | ||||
| 		return stanza.match(self._criteria) | ||||
|  | ||||
| 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) | ||||
|   | ||||
| @@ -3,65 +3,153 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
| from xml.etree import cElementTree | ||||
|  | ||||
| from xml.parsers.expat import ExpatError | ||||
|  | ||||
| ignore_ns = False | ||||
| from sleekxmpp.xmlstream.stanzabase import ET | ||||
| from sleekxmpp.xmlstream.matcher.base import MatcherBase | ||||
|  | ||||
| class MatchXMLMask(base.MatcherBase): | ||||
|  | ||||
| 	def __init__(self, criteria): | ||||
| 		base.MatcherBase.__init__(self, criteria) | ||||
| 		if type(criteria) == type(''): | ||||
| 			self._criteria = cElementTree.fromstring(self._criteria) | ||||
| 		self.default_ns = 'jabber:client' | ||||
| 	 | ||||
| 	def setDefaultNS(self, ns): | ||||
| 		self.default_ns = ns | ||||
| # 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 | ||||
|  | ||||
| 	def match(self, xml): | ||||
| 		if hasattr(xml, 'xml'): | ||||
| 			xml = xml.xml | ||||
| 		return self.maskcmp(xml, self._criteria, True) | ||||
| 	 | ||||
| 	def maskcmp(self, source, maskobj, use_ns=False, default_ns='__no_ns__'): | ||||
| 		"""maskcmp(xmlobj, maskobj): | ||||
| 		Compare etree xml object to etree xml object mask""" | ||||
| 		use_ns = not ignore_ns | ||||
| 		#TODO require namespaces | ||||
| 		if source == None: #if element not found (happens during recursive check below) | ||||
| 			return False | ||||
| 		if not hasattr(maskobj, 'attrib'): #if the mask is a string, make it an xml obj | ||||
| 			try: | ||||
| 				maskobj = cElementTree.fromstring(maskobj) | ||||
| 			except ExpatError: | ||||
| 				logging.log(logging.WARNING, "Expat error: %s\nIn parsing: %s" % ('', maskobj)) | ||||
| 		if not use_ns and source.tag.split('}', 1)[-1] != maskobj.tag.split('}', 1)[-1]: # strip off ns and compare | ||||
| 			return False | ||||
| 		if use_ns and (source.tag != maskobj.tag and "{%s}%s" % (self.default_ns, maskobj.tag) != source.tag ): | ||||
| 			return False | ||||
| 		if maskobj.text and source.text != maskobj.text: | ||||
| 			return False | ||||
| 		for attr_name in maskobj.attrib: #compare attributes | ||||
| 			if source.attrib.get(attr_name, "__None__") != maskobj.attrib[attr_name]: | ||||
| 				return False | ||||
| 		#for subelement in maskobj.getiterator()[1:]: #recursively compare subelements | ||||
| 		for subelement in maskobj: #recursively compare subelements | ||||
| 			if use_ns: | ||||
| 				if not self.maskcmp(source.find(subelement.tag), subelement, use_ns): | ||||
| 					return False | ||||
| 			else: | ||||
| 				if not self.maskcmp(self.getChildIgnoreNS(source, subelement.tag), subelement, use_ns): | ||||
| 					return False | ||||
| 		return True | ||||
| 	 | ||||
| 	def getChildIgnoreNS(self, xml, tag): | ||||
| 		tag = tag.split('}')[-1] | ||||
| 		try: | ||||
| 			idx = [c.tag.split('}')[-1] for c in xml.getchildren()].index(tag) | ||||
| 		except ValueError: | ||||
| 			return None | ||||
| 		return xml.getchildren()[idx] | ||||
|  | ||||
| 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: | ||||
|  | ||||
|         <message xmlns="jabber:client"><body /></message> | ||||
|  | ||||
|     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: | ||||
|                 logging.log(logging.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 != mask.text: | ||||
|             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. | ||||
|         for subelement in mask: | ||||
|             if use_ns: | ||||
|                 if not self._mask_cmp(source.find(subelement.tag), | ||||
|                                       subelement, use_ns): | ||||
|                     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] | ||||
|   | ||||
| @@ -3,32 +3,77 @@ | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| from . import base | ||||
| from xml.etree import cElementTree | ||||
|  | ||||
| ignore_ns = False | ||||
| from sleekxmpp.xmlstream.stanzabase import ET | ||||
| from sleekxmpp.xmlstream.matcher.base import MatcherBase | ||||
|  | ||||
| class MatchXPath(base.MatcherBase): | ||||
|  | ||||
| 	def match(self, xml): | ||||
| 		if hasattr(xml, 'xml'): | ||||
| 			xml = xml.xml | ||||
| 		x = cElementTree.Element('x') | ||||
| 		x.append(xml) | ||||
| 		if not ignore_ns: | ||||
| 			if x.find(self._criteria) is not None: | ||||
| 				return True | ||||
| 			return False | ||||
| 		else: | ||||
| 			criteria = [c.split('}')[-1] for c in self._criteria.split('/')] | ||||
| 			xml = x | ||||
| 			for tag in criteria: | ||||
| 				children = [c.tag.split('}')[-1] for c in xml.getchildren()] | ||||
| 				try: | ||||
| 					idx = children.index(tag) | ||||
| 				except ValueError: | ||||
| 					return False | ||||
| 				xml = xml.getchildren()[idx] | ||||
| 			return True | ||||
| # 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 | ||||
|   | ||||
							
								
								
									
										202
									
								
								sleekxmpp/xmlstream/scheduler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								sleekxmpp/xmlstream/scheduler.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| """ | ||||
|     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 | ||||
|  | ||||
|  | ||||
| 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.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: | ||||
|                             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: | ||||
|                 logging.debug("stopping parent") | ||||
|                 self.parentstop.set() | ||||
|         except SystemExit: | ||||
|             self.run = False | ||||
|             if self.parentstop is not None: | ||||
|                 self.parentstop.set() | ||||
|         logging.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 | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,59 +0,0 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file license.txt for copying permission. | ||||
| """ | ||||
| from __future__ import with_statement | ||||
| import threading | ||||
|  | ||||
| class StateMachine(object): | ||||
|  | ||||
| 	def __init__(self, states=[], groups=[]): | ||||
| 		self.lock = threading.Lock() | ||||
| 		self.__state = {} | ||||
| 		self.__default_state = {} | ||||
| 		self.__group = {} | ||||
| 		self.addStates(states) | ||||
| 		self.addGroups(groups) | ||||
| 	 | ||||
| 	def addStates(self, states): | ||||
| 		with self.lock: | ||||
| 			for state in states: | ||||
| 				if state in self.__state or state in self.__group: | ||||
| 					raise IndexError("The state or group '%s' is already in the StateMachine." % state) | ||||
| 				self.__state[state] = states[state] | ||||
| 				self.__default_state[state] = states[state] | ||||
| 	 | ||||
| 	def addGroups(self, groups): | ||||
| 		with self.lock: | ||||
| 			for gstate in groups: | ||||
| 				if gstate in self.__state or gstate in self.__group: | ||||
| 					raise IndexError("The key or group '%s' is already in the StateMachine." % gstate) | ||||
| 				for state in groups[gstate]: | ||||
| 					if state in self.__state: | ||||
| 						raise IndexError("The group %s contains a key %s which is not set in the StateMachine." % (gstate, state)) | ||||
| 				self.__group[gstate] = groups[gstate] | ||||
| 	 | ||||
| 	def set(self, state, status): | ||||
| 		with self.lock: | ||||
| 			if state in self.__state: | ||||
| 				self.__state[state] = bool(status) | ||||
| 			else: | ||||
| 				raise KeyError("StateMachine does not contain state %s." % state) | ||||
| 	 | ||||
| 	def __getitem__(self, key): | ||||
| 		if key in self.__group: | ||||
| 			for state in self.__group[key]: | ||||
| 				if not self.__state[state]: | ||||
| 					return False | ||||
| 			return True | ||||
| 		return self.__state[key] | ||||
| 	 | ||||
| 	def __getattr__(self, attr): | ||||
| 		return self.__getitem__(attr) | ||||
| 	 | ||||
| 	def reset(self): | ||||
| 		self.__state = self.__default_state | ||||
|  | ||||
| @@ -1,60 +1,19 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
| class ToString(object): | ||||
| 	def __str__(self, xml=None, xmlns='', stringbuffer=''): | ||||
| 		if xml is None: | ||||
| 			xml = self.xml | ||||
| 		newoutput = [stringbuffer] | ||||
| 		#TODO respect ET mapped namespaces | ||||
| 		itag = xml.tag.split('}', 1)[-1] | ||||
| 		if '}' in xml.tag: | ||||
| 			ixmlns = xml.tag.split('}', 1)[0][1:] | ||||
| 		else: | ||||
| 			ixmlns = '' | ||||
| 		nsbuffer = '' | ||||
| 		if xmlns != ixmlns and ixmlns != '' and ixmlns != self.namespace: | ||||
| 			if self.stream is not None and ixmlns in self.stream.namespace_map: | ||||
| 				if self.stream.namespace_map[ixmlns] != '': | ||||
| 					itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag) | ||||
| 			else: | ||||
| 				nsbuffer = """ xmlns="%s\"""" % ixmlns | ||||
| 		if ixmlns not in ('', xmlns, self.namespace): | ||||
| 			nsbuffer = """ xmlns="%s\"""" % ixmlns | ||||
| 		newoutput.append("<%s" % itag) | ||||
| 		newoutput.append(nsbuffer) | ||||
| 		for attrib in xml.attrib: | ||||
| 			if '{' not in attrib: | ||||
| 				newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) | ||||
| 		if len(xml) or xml.text or xml.tail: | ||||
| 			newoutput.append(">") | ||||
| 			if xml.text: | ||||
| 				newoutput.append(self.xmlesc(xml.text)) | ||||
| 			if len(xml): | ||||
| 				for child in xml.getchildren(): | ||||
| 					newoutput.append(self.__str__(child, ixmlns)) | ||||
| 			newoutput.append("</%s>" % (itag, )) | ||||
| 			if xml.tail: | ||||
| 				newoutput.append(self.xmlesc(xml.tail)) | ||||
| 		elif xml.text: | ||||
| 			newoutput.append(">%s</%s>" % (self.xmlesc(xml.text), itag)) | ||||
| 		else: | ||||
| 			newoutput.append(" />") | ||||
| 		return ''.join(newoutput) | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| 	def xmlesc(self, text): | ||||
| 		text = list(text) | ||||
| 		cc = 0 | ||||
| 		matches = ('&', '<', '"', '>', "'") | ||||
| 		for c in text: | ||||
| 			if c in matches: | ||||
| 				if c == '&': | ||||
| 					text[cc] = '&' | ||||
| 				elif c == '<': | ||||
| 					text[cc] = '<' | ||||
| 				elif c == '>': | ||||
| 					text[cc] = '>' | ||||
| 				elif c == "'": | ||||
| 					text[cc] = ''' | ||||
| 				else: | ||||
| 					text[cc] = '"' | ||||
| 			cc += 1 | ||||
| 		return ''.join(text) | ||||
| import sys | ||||
|  | ||||
| # Import the correct tostring 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'] | ||||
|   | ||||
							
								
								
									
										95
									
								
								sleekxmpp/xmlstream/tostring/tostring.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								sleekxmpp/xmlstream/tostring/tostring.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| """ | ||||
|     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(): | ||||
|         if '{' not in attrib: | ||||
|             value = xml_escape(value) | ||||
|             output.append(' %s="%s"' % (attrib, value)) | ||||
|  | ||||
|     if len(xml) or xml.text: | ||||
|         # If there are additional child elements to serialize. | ||||
|         output.append(">") | ||||
|         if xml.text: | ||||
|             output.append(xml_escape(xml.text)) | ||||
|         if len(xml): | ||||
|             for child in xml.getchildren(): | ||||
|                 output.append(tostring(child, tag_xmlns, stanza_ns, stream)) | ||||
|         output.append("</%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) | ||||
							
								
								
									
										101
									
								
								sleekxmpp/xmlstream/tostring/tostring26.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								sleekxmpp/xmlstream/tostring/tostring26.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| """ | ||||
|     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(): | ||||
|         if '{' not in attrib: | ||||
|             value = xml_escape(value) | ||||
|             output.append(u' %s="%s"' % (attrib, value)) | ||||
|  | ||||
|     if len(xml) or xml.text: | ||||
|         # If there are additional child elements to serialize. | ||||
|         output.append(u">") | ||||
|         if xml.text: | ||||
|             output.append(xml_escape(xml.text)) | ||||
|         if len(xml): | ||||
|             for child in xml.getchildren(): | ||||
|                 output.append(tostring(child, tag_xmlns, stanza_ns, stream)) | ||||
|         output.append(u"</%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) | ||||
| @@ -1,65 +0,0 @@ | ||||
| import types | ||||
|  | ||||
| class ToString(object): | ||||
| 	def __str__(self, xml=None, xmlns='', stringbuffer=''): | ||||
| 		if xml is None: | ||||
| 			xml = self.xml | ||||
| 		newoutput = [stringbuffer] | ||||
| 		#TODO respect ET mapped namespaces | ||||
| 		itag = xml.tag.split('}', 1)[-1] | ||||
| 		if '}' in xml.tag: | ||||
| 			ixmlns = xml.tag.split('}', 1)[0][1:] | ||||
| 		else: | ||||
| 			ixmlns = '' | ||||
| 		nsbuffer = '' | ||||
| 		if xmlns != ixmlns and ixmlns != u'' and ixmlns != self.namespace: | ||||
| 			if self.stream is not None and ixmlns in self.stream.namespace_map: | ||||
| 				if self.stream.namespace_map[ixmlns] != u'': | ||||
| 					itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag) | ||||
| 			else: | ||||
| 				nsbuffer = """ xmlns="%s\"""" % ixmlns | ||||
| 		if ixmlns not in ('', xmlns, self.namespace): | ||||
| 			nsbuffer = """ xmlns="%s\"""" % ixmlns | ||||
| 		newoutput.append("<%s" % itag) | ||||
| 		newoutput.append(nsbuffer) | ||||
| 		for attrib in xml.attrib: | ||||
| 			if '{' not in attrib: | ||||
| 				newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) | ||||
| 		if len(xml) or xml.text or xml.tail: | ||||
| 			newoutput.append(u">") | ||||
| 			if xml.text: | ||||
| 				newoutput.append(self.xmlesc(xml.text)) | ||||
| 			if len(xml): | ||||
| 				for child in xml.getchildren(): | ||||
| 					newoutput.append(self.__str__(child, ixmlns)) | ||||
| 			newoutput.append(u"</%s>" % (itag, )) | ||||
| 			if xml.tail: | ||||
| 				newoutput.append(self.xmlesc(xml.tail)) | ||||
| 		elif xml.text: | ||||
| 			newoutput.append(">%s</%s>" % (self.xmlesc(xml.text), itag)) | ||||
| 		else: | ||||
| 			newoutput.append(" />") | ||||
| 		return u''.join(newoutput) | ||||
|  | ||||
| 	def xmlesc(self, text): | ||||
| 		if type(text) != types.UnicodeType: | ||||
| 			text = list(unicode(text, 'utf-8', 'ignore')) | ||||
| 		else: | ||||
| 			text = list(text) | ||||
|  | ||||
| 		cc = 0 | ||||
| 		matches = (u'&', u'<', u'"', u'>', u"'") | ||||
| 		for c in text: | ||||
| 			if c in matches: | ||||
| 				if c == u'&': | ||||
| 					text[cc] = u'&' | ||||
| 				elif c == u'<': | ||||
| 					text[cc] = u'<' | ||||
| 				elif c == u'>': | ||||
| 					text[cc] = u'>' | ||||
| 				elif c == u"'": | ||||
| 					text[cc] = u''' | ||||
| 				else: | ||||
| 					text[cc] = u'"' | ||||
| 			cc += 1 | ||||
| 		return ''.join(text) | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,4 +1,4 @@ | ||||
| #!/usr/bin/python2.6 | ||||
| #!/usr/bin/env python | ||||
| import unittest | ||||
| import logging | ||||
| import sys | ||||
| @@ -13,7 +13,7 @@ class testoverall(unittest.TestCase): | ||||
| 		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\Z'), quiet=True)) | ||||
| 			self.failUnless(compileall.compile_dir('.' + os.sep + 'sleekxmpp', rx=re.compile('/[.]svn|.*26.*'), quiet=True)) | ||||
|  | ||||
| 	def	testTabNanny(self): | ||||
| 		"""Invoking the tabnanny""" | ||||
| @@ -21,7 +21,7 @@ class testoverall(unittest.TestCase): | ||||
| 		self.failIf(tabnanny.check("." + os.sep + 'sleekxmpp')) | ||||
| 		#raise "Help!" | ||||
|  | ||||
| 	def testMethodLength(self): | ||||
| 	def disabled_testMethodLength(self): | ||||
| 		"""Testing for excessive method lengths""" | ||||
| 		import re | ||||
| 		dirs = os.walk(sys.path[0] + os.sep + 'sleekxmpp') | ||||
|   | ||||
							
								
								
									
										110
									
								
								tests/live_test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								tests/live_test.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| from sleekxmpp.test import * | ||||
| import sleekxmpp.plugins.xep_0033 as xep_0033 | ||||
|  | ||||
|  | ||||
| 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.stream_recv_header(sfrom='localhost', sid=None) | ||||
|         self.stream_send_header(sto='localhost') | ||||
|         self.stream_recv_feature(""" | ||||
|           <stream:features> | ||||
|             <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls" /> | ||||
|             <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> | ||||
|               <mechanism>DIGEST-MD5</mechanism> | ||||
|               <mechanism>PLAIN</mechanism> | ||||
|             </mechanisms> | ||||
|             <c xmlns="http://jabber.org/protocol/caps" | ||||
|                node="http://www.process-one.net/en/ejabberd/" | ||||
|                ver="TQ2JFyRoSa70h2G1bpgjzuXb2sU=" hash="sha-1" /> | ||||
|             <register xmlns="http://jabber.org/features/iq-register" /> | ||||
|           </stream:features> | ||||
|         """) | ||||
|         self.stream_send_feature(""" | ||||
|           <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls" /> | ||||
|         """) | ||||
|         self.stream_recv_feature(""" | ||||
|           <proceed xmlns="urn:ietf:params:xml:ns:xmpp-tls" /> | ||||
|         """) | ||||
|         self.stream_send_header(sto='localhost') | ||||
|         self.stream_recv_header(sfrom='localhost', sid=None) | ||||
|         self.stream_recv_feature(""" | ||||
|           <stream:features> | ||||
|             <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> | ||||
|               <mechanism>DIGEST-MD5</mechanism> | ||||
|               <mechanism>PLAIN</mechanism> | ||||
|             </mechanisms> | ||||
|             <c xmlns="http://jabber.org/protocol/caps" | ||||
|                node="http://www.process-one.net/en/ejabberd/" | ||||
|                ver="TQ2JFyRoSa70h2G1bpgjzuXb2sU=" | ||||
|                hash="sha-1" /> | ||||
|             <register xmlns="http://jabber.org/features/iq-register" /> | ||||
|           </stream:features> | ||||
|         """) | ||||
|         self.stream_send_feature(""" | ||||
|           <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" | ||||
|                 mechanism="PLAIN">AHVzZXIAdXNlcg==</auth> | ||||
|         """) | ||||
|         self.stream_recv_feature(""" | ||||
|           <success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" /> | ||||
|         """) | ||||
|         self.stream_send_header(sto='localhost') | ||||
|         self.stream_recv_header(sfrom='localhost', sid=None) | ||||
|         self.stream_recv_feature(""" | ||||
|           <stream:features> | ||||
|             <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind" /> | ||||
|             <session xmlns="urn:ietf:params:xml:ns:xmpp-session" /> | ||||
|             <c xmlns="http://jabber.org/protocol/caps" | ||||
|                node="http://www.process-one.net/en/ejabberd/" | ||||
|                ver="TQ2JFyRoSa70h2G1bpgjzuXb2sU=" | ||||
|                hash="sha-1" /> | ||||
|             <register xmlns="http://jabber.org/features/iq-register" /> | ||||
|           </stream:features> | ||||
|         """) | ||||
|  | ||||
|         # Should really use stream_send_iq, but our Iq stanza objects | ||||
|         # can't handle bind element payloads yet. | ||||
|         self.stream_send_feature(""" | ||||
|           <iq type="set" id="1"> | ||||
|             <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"> | ||||
|               <resource>test</resource> | ||||
|             </bind> | ||||
|           </iq> | ||||
|         """) | ||||
|         self.stream_recv_feature(""" | ||||
|           <iq type="result" id="1"> | ||||
|             <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"> | ||||
|               <jid>user@localhost/test</jid> | ||||
|             </bind> | ||||
|           </iq> | ||||
|         """) | ||||
|         self.stream_close() | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestLiveStream) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     tests = unittest.TestSuite([suite]) | ||||
|     result = unittest.TextTestRunner(verbosity=2).run(tests) | ||||
|     test_ns = 'http://andyet.net/protocol/tests' | ||||
|     print("<tests xmlns='%s' %s %s %s %s />" % ( | ||||
|         test_ns, | ||||
|         'ran="%s"' % result.testsRun, | ||||
|         'errors="%s"' % len(result.errors), | ||||
|         'fails="%s"' % len(result.failures), | ||||
|         'success="%s"' % result.wasSuccessful())) | ||||
							
								
								
									
										72
									
								
								tests/test_events.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								tests/test_events.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| 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) | ||||
							
								
								
									
										128
									
								
								tests/test_jid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								tests/test_jid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| 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) | ||||
| @@ -1,44 +0,0 @@ | ||||
| import unittest | ||||
| from xml.etree import cElementTree as ET | ||||
|  | ||||
| class testmessagestanzas(unittest.TestCase): | ||||
|  | ||||
| 	def setUp(self): | ||||
| 		import sleekxmpp.stanza.message as m | ||||
| 		from sleekxmpp.basexmpp import stanzaPlugin | ||||
| 		from sleekxmpp.stanza.htmlim import HTMLIM | ||||
| 		stanzaPlugin(m.Message, HTMLIM) | ||||
| 		self.m = m | ||||
| 	 | ||||
| 	def testGroupchatReplyRegression(self): | ||||
| 		"Regression groupchat reply should be to barejid" | ||||
| 		msg = self.m.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.m.Message() | ||||
| 		msg.attrib.attrib.attrib['to'] = 'usr@server.tld' | ||||
| 		self.failUnless(str(msg['to']) == 'usr@server.tld') | ||||
| 	 | ||||
| 	def testHTMLPlugin(self): | ||||
| 		"Test message/html/html stanza" | ||||
| 		msgtxt = """<message to="fritzy@netflint.net/sleekxmpp" type="chat"><body>this is the plaintext message</body><html xmlns="http://jabber.org/protocol/xhtml-im"><body xmlns="http://www.w3.org/1999/xhtml"><p>This is the htmlim message</p></body></html></message>""" | ||||
| 		msg = self.m.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']['html'] = p | ||||
| 		msg2 = self.m.Message() | ||||
| 		values = msg.getValues() | ||||
| 		msg2.setValues(values) | ||||
| 		self.failUnless(msgtxt == str(msg) == str(msg2)) | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(testmessagestanzas) | ||||
| @@ -1,15 +0,0 @@ | ||||
| import unittest | ||||
|  | ||||
| class testpresencestanzas(unittest.TestCase): | ||||
|  | ||||
| 	def setUp(self): | ||||
| 		import sleekxmpp.stanza.presence as p | ||||
| 		self.p = p | ||||
| 	 | ||||
| 	def testPresenceShowRegression(self): | ||||
| 		"Regression check presence['type'] = 'dnd' show value working" | ||||
| 		p = self.p.Presence() | ||||
| 		p['type'] = 'dnd' | ||||
| 		self.failUnless(str(p) == "<presence><show>dnd</show></presence>") | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(testpresencestanzas) | ||||
| @@ -1,299 +0,0 @@ | ||||
| import unittest | ||||
| from xml.etree import cElementTree as ET | ||||
| from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath | ||||
| from . import xmlcompare | ||||
|  | ||||
| class testpubsubstanzas(unittest.TestCase): | ||||
|  | ||||
| 	def setUp(self): | ||||
| 		import sleekxmpp.plugins.stanza_pubsub as ps | ||||
| 		self.ps = ps | ||||
|  | ||||
| 	def testAffiliations(self): | ||||
| 		"Testing iq/pubsub/affiliations/affiliation stanzas" | ||||
| 		iq = self.ps.Iq() | ||||
| 		aff1 = self.ps.Affiliation() | ||||
| 		aff1['node'] = 'testnode' | ||||
| 		aff1['affiliation'] = 'owner' | ||||
| 		aff2 = self.ps.Affiliation() | ||||
| 		aff2['node'] = 'testnode2' | ||||
| 		aff2['affiliation'] = 'publisher' | ||||
| 		iq['pubsub']['affiliations'].append(aff1) | ||||
| 		iq['pubsub']['affiliations'].append(aff2) | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><affiliations><affiliation node="testnode" affiliation="owner" /><affiliation node="testnode2" affiliation="publisher" /></affiliations></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3), "3 methods for creating stanza don't match") | ||||
| 		self.failUnless(iq.match('iq@id=0/pubsub/affiliations/affiliation@node=testnode2@affiliation=publisher'), 'Match path failed') | ||||
| 	 | ||||
| 	def testSubscriptions(self): | ||||
| 		"Testing iq/pubsub/subscriptions/subscription stanzas" | ||||
| 		iq = self.ps.Iq() | ||||
| 		sub1 = self.ps.Subscription() | ||||
| 		sub1['node'] = 'testnode' | ||||
| 		sub1['jid'] = 'steve@myserver.tld/someresource' | ||||
| 		sub2 = self.ps.Subscription() | ||||
| 		sub2['node'] = 'testnode2' | ||||
| 		sub2['jid'] = 'boogers@bork.top/bill' | ||||
| 		sub2['subscription'] = 'subscribed' | ||||
| 		iq['pubsub']['subscriptions'].append(sub1) | ||||
| 		iq['pubsub']['subscriptions'].append(sub2) | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscriptions><subscription node="testnode" jid="steve@myserver.tld/someresource" /><subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" /></subscriptions></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
| 	 | ||||
| 	def testOptionalSettings(self): | ||||
| 		"Testing iq/pubsub/subscription/subscribe-options stanzas" | ||||
| 		iq = self.ps.Iq() | ||||
| 		iq['pubsub']['subscription']['suboptions']['required'] = True | ||||
| 		iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas' | ||||
| 		iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp" | ||||
| 		iq['pubsub']['subscription']['subscription'] = 'unconfigured' | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured"><subscribe-options><required /></subscribe-options></subscription></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
| 	 | ||||
| 	def testItems(self): | ||||
| 		"Testing iq/pubsub/items stanzas" | ||||
| 		iq = self.ps.Iq() | ||||
| 		iq['pubsub']['items'] | ||||
| 		payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""") | ||||
| 		payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""") | ||||
| 		item = self.ps.Item() | ||||
| 		item['id'] = 'asdf' | ||||
| 		item['payload'] = payload | ||||
| 		item2 = self.ps.Item() | ||||
| 		item2['id'] = 'asdf2' | ||||
| 		item2['payload'] = payload2 | ||||
| 		iq['pubsub']['items'].append(item) | ||||
| 		iq['pubsub']['items'].append(item2) | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><items><item id="asdf"><thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"><child1 /><child2 foo="bar" normandy="cheese" /></thinger></item><item id="asdf2"><thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"><child12 /><child22 foo="bar2" normandy="cheese2" /></thinger2></item></items></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
| 		 | ||||
| 	def testCreate(self): | ||||
| 		"Testing iq/pubsub/create&configure stanzas" | ||||
| 		from sleekxmpp.plugins import xep_0004 | ||||
| 		iq = self.ps.Iq() | ||||
| 		iq['pubsub']['create']['node'] = 'mynode' | ||||
| 		form = xep_0004.Form() | ||||
| 		form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') | ||||
| 		iq['pubsub']['configure']['config'] = form | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="mynode" /><configure><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></configure></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
| 	 | ||||
| 	def testDefault(self): | ||||
| 		"Testing iq/pubsub_owner/default stanzas" | ||||
| 		from sleekxmpp.plugins import xep_0004 | ||||
| 		iq = self.ps.Iq() | ||||
| 		iq['pubsub_owner']['default'] | ||||
| 		iq['pubsub_owner']['default']['node'] = 'mynode' | ||||
| 		form = xep_0004.Form() | ||||
| 		form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') | ||||
| 		iq['pubsub_owner']['default']['config'] = form | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><default node="mynode"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></default></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
| 	 | ||||
| 	def testSubscribe(self): | ||||
| 		"Testing iq/pubsub/subscribe stanzas" | ||||
| 		from sleekxmpp.plugins import xep_0004 | ||||
| 		iq = self.ps.Iq() | ||||
| 		iq['pubsub']['subscribe']['options'] | ||||
| 		iq['pubsub']['subscribe']['node'] = 'cheese' | ||||
| 		iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp' | ||||
| 		iq['pubsub']['subscribe']['options']['node'] = 'cheese' | ||||
| 		iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp' | ||||
| 		form = xep_0004.Form() | ||||
| 		form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') | ||||
| 		iq['pubsub']['subscribe']['options']['options'] = form | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp"><options node="cheese" jid="fritzy@netflint.net/sleekxmpp"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></options></subscribe></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
| 	 | ||||
| 	def testPublish(self): | ||||
| 		"Testing iq/pubsub/publish stanzas" | ||||
| 		iq = self.ps.Iq() | ||||
| 		iq['pubsub']['publish']['node'] = 'thingers' | ||||
| 		payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""") | ||||
| 		payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""") | ||||
| 		item = self.ps.Item() | ||||
| 		item['id'] = 'asdf' | ||||
| 		item['payload'] = payload | ||||
| 		item2 = self.ps.Item() | ||||
| 		item2['id'] = 'asdf2' | ||||
| 		item2['payload'] = payload2 | ||||
| 		iq['pubsub']['publish'].append(item) | ||||
| 		iq['pubsub']['publish'].append(item2) | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><publish node="thingers"><item id="asdf"><thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"><child1 /><child2 foo="bar" normandy="cheese" /></thinger></item><item id="asdf2"><thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"><child12 /><child22 foo="bar2" normandy="cheese2" /></thinger2></item></publish></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		values = iq2.getValues() | ||||
| 		iq3.setValues(values) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
|  | ||||
| 	def testDelete(self): | ||||
| 		"Testing iq/pubsub_owner/delete stanzas" | ||||
| 		iq = self.ps.Iq() | ||||
| 		iq['pubsub_owner']['delete']['node'] = 'thingers' | ||||
| 		xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><delete node="thingers" /></pubsub></iq>""" | ||||
| 		iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		iq3 = self.ps.Iq() | ||||
| 		iq3.setValues(iq2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) | ||||
| 	 | ||||
| 	def testCreateConfigGet(self): | ||||
| 		"""Testing getting config from full create""" | ||||
| 		xml = """<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7"><pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="testnode2" /><configure><x xmlns="jabber:x:data" type="submit"><field var="FORM_TYPE" type="hidden"><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var="pubsub#node_type" type="list-single" label="Select the node type"><value>leaf</value></field><field var="pubsub#title" type="text-single" label="A friendly name for the node" /><field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications"><value>1</value></field><field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications"><value>1</value></field><field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" /><field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" /><field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node"><value>1</value></field><field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" /><field var="pubsub#persist_items" type="boolean" label="Persist items in storage" /><field var="pubsub#max_items" type="text-single" label="Max # of items to persist"><value>10</value></field><field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions"><value>1</value></field><field var="pubsub#access_model" type="list-single" label="Specify the subscriber model"><value>open</value></field><field var="pubsub#publish_model" type="list-single" label="Specify the publisher model"><value>publishers</value></field><field var="pubsub#send_last_published_item" type="list-single" label="Send last published item"><value>never</value></field><field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" /></x></configure></pubsub></iq>""" | ||||
| 		iq = self.ps.Iq(None, self.ps.ET.fromstring(xml)) | ||||
| 		config = iq['pubsub']['configure']['config'] | ||||
| 		self.failUnless(config.getValues() != {}) | ||||
|  | ||||
| 	def testItemEvent(self): | ||||
| 		"""Testing message/pubsub_event/items/item""" | ||||
| 		msg = self.ps.Message() | ||||
| 		item = self.ps.EventItem() | ||||
| 		pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) | ||||
| 		item['payload'] = pl | ||||
| 		item['id'] = 'abc123' | ||||
| 		msg['pubsub_event']['items'].append(item) | ||||
| 		msg['pubsub_event']['items']['node'] = 'cheese' | ||||
| 		msg['type'] = 'normal' | ||||
| 		xmlstring = """<message type="normal"><event xmlns="http://jabber.org/protocol/pubsub#event"><items node="cheese"><item id="abc123"><test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /></item></items></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) | ||||
|  | ||||
| 	def testItemsEvent(self): | ||||
| 		"""Testing multiple message/pubsub_event/items/item""" | ||||
| 		msg = self.ps.Message() | ||||
| 		item = self.ps.EventItem() | ||||
| 		item2 = self.ps.EventItem() | ||||
| 		pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) | ||||
| 		pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) | ||||
| 		item2['payload'] = pl2 | ||||
| 		item['payload'] = pl | ||||
| 		item['id'] = 'abc123' | ||||
| 		item2['id'] = '123abc' | ||||
| 		msg['pubsub_event']['items'].append(item) | ||||
| 		msg['pubsub_event']['items'].append(item2) | ||||
| 		msg['pubsub_event']['items']['node'] = 'cheese' | ||||
| 		msg['type'] = 'normal' | ||||
| 		xmlstring = """<message type="normal"><event xmlns="http://jabber.org/protocol/pubsub#event"><items node="cheese"><item id="abc123"><test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /></item><item id="123abc"><test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" /></item></items></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) | ||||
|  | ||||
| 	def testItemsEvent(self): | ||||
| 		"""Testing message/pubsub_event/items/item & retract mix""" | ||||
| 		msg = self.ps.Message() | ||||
| 		item = self.ps.EventItem() | ||||
| 		item2 = self.ps.EventItem() | ||||
| 		pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) | ||||
| 		pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) | ||||
| 		item2['payload'] = pl2 | ||||
| 		retract = self.ps.EventRetract() | ||||
| 		retract['id'] = 'aabbcc' | ||||
| 		item['payload'] = pl | ||||
| 		item['id'] = 'abc123' | ||||
| 		item2['id'] = '123abc' | ||||
| 		msg['pubsub_event']['items'].append(item) | ||||
| 		msg['pubsub_event']['items'].append(retract) | ||||
| 		msg['pubsub_event']['items'].append(item2) | ||||
| 		msg['pubsub_event']['items']['node'] = 'cheese' | ||||
| 		msg['type'] = 'normal' | ||||
| 		xmlstring = """<message type="normal"><event xmlns="http://jabber.org/protocol/pubsub#event"><items node="cheese"><item id="abc123"><test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /></item><retract id="aabbcc" /><item id="123abc"><test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" /></item></items></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) | ||||
| 	 | ||||
| 	def testCollectionAssociate(self): | ||||
| 		"""Testing message/pubsub_event/collection/associate""" | ||||
| 		msg = self.ps.Message() | ||||
| 		msg['pubsub_event']['collection']['associate']['node'] = 'cheese' | ||||
| 		msg['pubsub_event']['collection']['node'] = 'cheeseburger' | ||||
| 		msg['type'] = 'headline' | ||||
| 		xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><associate node="cheese" /></collection></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) | ||||
|  | ||||
| 	def testCollectionDisassociate(self): | ||||
| 		"""Testing message/pubsub_event/collection/disassociate""" | ||||
| 		msg = self.ps.Message() | ||||
| 		msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese' | ||||
| 		msg['pubsub_event']['collection']['node'] = 'cheeseburger' | ||||
| 		msg['type'] = 'headline' | ||||
| 		xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><disassociate node="cheese" /></collection></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) | ||||
|  | ||||
| 	def testEventConfiguration(self): | ||||
| 		"""Testing message/pubsub_event/configuration/config""" | ||||
| 		msg = self.ps.Message() | ||||
| 		from sleekxmpp.plugins import xep_0004 | ||||
| 		form = xep_0004.Form() | ||||
| 		form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') | ||||
| 		msg['pubsub_event']['configuration']['node'] = 'cheese' | ||||
| 		msg['pubsub_event']['configuration']['config'] = form | ||||
| 		msg['type'] = 'headline' | ||||
| 		xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><configuration node="cheese"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></configuration></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) | ||||
| 	 | ||||
| 	def testEventPurge(self): | ||||
| 		"""Testing message/pubsub_event/purge""" | ||||
| 		msg = self.ps.Message() | ||||
| 		msg['pubsub_event']['purge']['node'] = 'pickles' | ||||
| 		msg['type'] = 'headline' | ||||
| 		xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><purge node="pickles" /></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) | ||||
| 	 | ||||
| 	def testEventSubscription(self): | ||||
| 		"""Testing message/pubsub_event/subscription""" | ||||
| 		msg = self.ps.Message() | ||||
| 		msg['pubsub_event']['subscription']['node'] = 'pickles' | ||||
| 		msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test' | ||||
| 		msg['pubsub_event']['subscription']['subid'] = 'aabb1122' | ||||
| 		msg['pubsub_event']['subscription']['subscription'] = 'subscribed' | ||||
| 		msg['pubsub_event']['subscription']['expiry'] = 'presence' | ||||
| 		msg['type'] = 'headline' | ||||
| 		xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><subscription node="pickles" subid="aabb1122" jid="fritzy@netflint.net/test" subscription="subscribed" expiry="presence" /></event></message>""" | ||||
| 		msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) | ||||
| 		msg3 = self.ps.Message() | ||||
| 		msg3.setValues(msg2.getValues()) | ||||
| 		self.failUnless(xmlcompare.comparemany([xmlstring, str(msg), str(msg2), str(msg3)])) | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(testpubsubstanzas) | ||||
							
								
								
									
										79
									
								
								tests/test_stanza_base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								tests/test_stanza_base.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| 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) | ||||
							
								
								
									
										660
									
								
								tests/test_stanza_element.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										660
									
								
								tests/test_stanza_element.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,660 @@ | ||||
| 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(TestStanza, stanza, """ | ||||
|           <foo xmlns="test"> | ||||
|             <bar> | ||||
|               <baz /> | ||||
|             </bar> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|     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')) | ||||
|             subitem = set((TestSubStanza,)) | ||||
|  | ||||
|         register_stanza_plugin(TestStanza, TestStanzaPlugin) | ||||
|  | ||||
|         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')) | ||||
|             subitem = set((TestSubStanza,)) | ||||
|  | ||||
|         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.setStanzaValues(values) | ||||
|  | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo" bar="a"> | ||||
|             <pluginfoo baz="b" /> | ||||
|             <pluginfoo2 bar="d" baz="e" /> | ||||
|             <subfoo bar="c" /> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|     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',)) | ||||
|  | ||||
|         TestStanza.subitem = (TestStanza,) | ||||
|         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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo" bar="attribute!"> | ||||
|             <baz>element!</baz> | ||||
|             <foobar foobar="plugin" /> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|     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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo" baz="b" qux="c"> | ||||
|             <bar>a</bar> | ||||
|             <foobar foobar="d" /> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         del stanza['bar'] | ||||
|         del stanza['baz'] | ||||
|         del stanza['qux'] | ||||
|         del stanza['foobar'] | ||||
|  | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo" qux="c" /> | ||||
|         """) | ||||
|  | ||||
|     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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo" /> | ||||
|         """) | ||||
|  | ||||
|         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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo" bar="a" baz="b" /> | ||||
|         """) | ||||
|  | ||||
|         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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo" /> | ||||
|         """) | ||||
|  | ||||
|         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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <wrapper> | ||||
|               <bar>found</bar> | ||||
|             </wrapper> | ||||
|           </foo> | ||||
|         """) | ||||
|         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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <wrapper> | ||||
|               <bar>a</bar> | ||||
|               <baz>b</baz> | ||||
|             </wrapper> | ||||
|           </foo> | ||||
|         """) | ||||
|         stanza._set_sub_text('wrapper/bar', text='', keep=True) | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <wrapper> | ||||
|               <bar /> | ||||
|               <baz>b</baz> | ||||
|             </wrapper> | ||||
|           </foo> | ||||
|         """, use_values=False) | ||||
|  | ||||
|         stanza['bar'] = 'a' | ||||
|         stanza._set_sub_text('wrapper/bar', text='') | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <wrapper> | ||||
|               <baz>b</baz> | ||||
|             </wrapper> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|     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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <path> | ||||
|               <to> | ||||
|                 <only> | ||||
|                   <bar>a</bar> | ||||
|                 </only> | ||||
|                 <just> | ||||
|                   <baz>b</baz> | ||||
|                 </just> | ||||
|               </to> | ||||
|             </path> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|         del stanza['bar'] | ||||
|         del stanza['baz'] | ||||
|  | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <path> | ||||
|               <to> | ||||
|                 <only /> | ||||
|                 <just /> | ||||
|               </to> | ||||
|             </path> | ||||
|           </foo> | ||||
|         """, use_values=False) | ||||
|  | ||||
|         stanza['bar'] = 'a' | ||||
|         stanza['baz'] = 'b' | ||||
|  | ||||
|         stanza._del_sub('path/to/only/bar', all=True) | ||||
|  | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <path> | ||||
|               <to> | ||||
|                 <just> | ||||
|                   <baz>b</baz> | ||||
|                 </just> | ||||
|               </to> | ||||
|             </path> | ||||
|           </foo> | ||||
|         """) | ||||
|  | ||||
|     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',)) | ||||
|             subitem = (TestSubStanza,) | ||||
|  | ||||
|             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, 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')) | ||||
|             subitem = (TestSubStanza,) | ||||
|  | ||||
|         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(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <foobar qux="a" /> | ||||
|           </foo> | ||||
|         """, use_values=False) | ||||
|         self.failUnless(len(stanza) == 1, | ||||
|             "Incorrect stanza size with 1 substanza.") | ||||
|  | ||||
|         stanza.append(substanza2) | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <foobar qux="a" /> | ||||
|             <foobar qux="b" /> | ||||
|           </foo> | ||||
|         """, use_values=False) | ||||
|         self.failUnless(len(stanza) == 2, | ||||
|             "Incorrect stanza size with 2 substanzas.") | ||||
|  | ||||
|         # Test popping substanzas | ||||
|         stanza.pop(0) | ||||
|         self.check_stanza(TestStanza, stanza, """ | ||||
|           <foo xmlns="foo"> | ||||
|             <foobar qux="b" /> | ||||
|           </foo> | ||||
|         """, 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.") | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase) | ||||
							
								
								
									
										76
									
								
								tests/test_stanza_error.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								tests/test_stanza_error.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| from sleekxmpp.test import * | ||||
|  | ||||
|  | ||||
| class TestErrorStanzas(SleekTest): | ||||
|  | ||||
|     def testSetup(self): | ||||
|         """Test setting initial values in error stanza.""" | ||||
|         msg = self.Message() | ||||
|         msg.enable('error') | ||||
|         self.check_message(msg, """ | ||||
|           <message type="error"> | ||||
|             <error type="cancel"> | ||||
|               <feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|             </error> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testCondition(self): | ||||
|         """Test modifying the error condition.""" | ||||
|         msg = self.Message() | ||||
|         msg['error']['condition'] = 'item-not-found' | ||||
|  | ||||
|         self.check_message(msg, """ | ||||
|           <message type="error"> | ||||
|             <error type="cancel"> | ||||
|               <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|             </error> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         self.failUnless(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.") | ||||
|  | ||||
|         msg['error']['condition'] = 'resource-constraint' | ||||
|  | ||||
|         self.check_message(msg, """ | ||||
|           <message type="error"> | ||||
|             <error type="cancel"> | ||||
|               <resource-constraint xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|             </error> | ||||
|           </message> | ||||
|          """) | ||||
|  | ||||
|     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_message(msg, """ | ||||
|           <message type="error"> | ||||
|             <error type="cancel"> | ||||
|               <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Error!</text> | ||||
|             </error> | ||||
|           </message> | ||||
|         """, 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_message(msg, """ | ||||
|           <message type="error"> | ||||
|             <error type="cancel"> | ||||
|               <internal-server-error xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|             </error> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestErrorStanzas) | ||||
							
								
								
									
										88
									
								
								tests/test_stanza_gmail.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								tests/test_stanza_gmail.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| 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(iq, """ | ||||
|           <iq type="get"> | ||||
|             <query xmlns="google:mail:notify" | ||||
|                    newer-than-time="1140638252542" | ||||
|                    newer-than-tid="11134623426430234" | ||||
|                    q="is:starred" /> | ||||
|           </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(""" | ||||
|           <iq type="result"> | ||||
|             <mailbox xmlns="google:mail:notify" | ||||
|                      result-time='1118012394209' | ||||
|                      url='http://mail.google.com/mail' | ||||
|                      total-matched='95' | ||||
|                      total-estimate='0'> | ||||
|               <mail-thread-info tid='1172320964060972012' | ||||
|                                 participation='1' | ||||
|                                 messages='28' | ||||
|                                 date='1118012394209' | ||||
|                                 url='http://mail.google.com/mail?view=cv'> | ||||
|                 <senders> | ||||
|                   <sender name='Me' address='romeo@gmail.com' originator='1' /> | ||||
|                   <sender name='Benvolio' address='benvolio@gmail.com' /> | ||||
|                   <sender name='Mercutio' address='mercutio@gmail.com' unread='1'/> | ||||
|                 </senders> | ||||
|                 <labels>act1scene3</labels> | ||||
|                 <subject>Put thy rapier up.</subject> | ||||
|                 <snippet>Ay, ay, a scratch, a scratch; marry, 'tis enough.</snippet> | ||||
|               </mail-thread-info> | ||||
|             </mailbox> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|         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) | ||||
							
								
								
									
										90
									
								
								tests/test_stanza_iq.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								tests/test_stanza_iq.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| 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(iq, """ | ||||
|           <iq id="0" /> | ||||
|         """) | ||||
|  | ||||
|     def testPayload(self): | ||||
|         """Test setting Iq stanza payload.""" | ||||
|         iq = self.Iq() | ||||
|         iq.setPayload(ET.Element('{test}tester')) | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <tester xmlns="test" /> | ||||
|           </iq> | ||||
|         """, use_values=False) | ||||
|  | ||||
|  | ||||
|     def testUnhandled(self): | ||||
|         """Test behavior for Iq.unhandled.""" | ||||
|         self.stream_start() | ||||
|         self.stream_recv(""" | ||||
|           <iq id="test" type="get"> | ||||
|             <query xmlns="test" /> | ||||
|            </iq> | ||||
|         """) | ||||
|  | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = 'test' | ||||
|         iq['error']['condition'] = 'feature-not-implemented' | ||||
|         iq['error']['text'] = 'No handlers registered for this request.' | ||||
|  | ||||
|         self.stream_send_iq(iq, """ | ||||
|           <iq id="test" type="error"> | ||||
|             <error type="cancel"> | ||||
|               <feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|               <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> | ||||
|                 No handlers registered for this request. | ||||
|               </text> | ||||
|             </error> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testQuery(self): | ||||
|         """Test modifying query element of Iq stanzas.""" | ||||
|         iq = self.Iq() | ||||
|  | ||||
|         iq['query'] = 'query_ns' | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="query_ns" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|         iq['query'] = 'query_ns2' | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="query_ns2" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|         self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match") | ||||
|  | ||||
|         del iq['query'] | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0" /> | ||||
|         """) | ||||
|  | ||||
|     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(iq, """ | ||||
|           <iq id="0" type="result" /> | ||||
|         """) | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestIqStanzas) | ||||
							
								
								
									
										57
									
								
								tests/test_stanza_message.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/test_stanza_message.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| 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_message(msg, """ | ||||
|           <message to="fritzy@netflint.net/sleekxmpp" type="chat"> | ||||
|             <body>this is the plaintext message</body> | ||||
|             <html xmlns="http://jabber.org/protocol/xhtml-im"> | ||||
|               <body xmlns="http://www.w3.org/1999/xhtml"> | ||||
|                 <p>This is the htmlim message</p> | ||||
|              </body> | ||||
|             </html> | ||||
|           </message>""") | ||||
|  | ||||
|     def testNickPlugin(self): | ||||
|         "Test message/nick/nick stanza." | ||||
|         msg = self.Message() | ||||
|         msg['nick']['nick'] = 'A nickname!' | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <nick xmlns="http://jabber.org/nick/nick">A nickname!</nick> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestMessageStanzas) | ||||
							
								
								
									
										66
									
								
								tests/test_stanza_presence.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								tests/test_stanza_presence.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| from sleekxmpp.test import * | ||||
| from sleekxmpp.stanza.presence import Presence | ||||
|  | ||||
|  | ||||
| class TestPresenceStanzas(SleekTest): | ||||
|  | ||||
|     def testPresenceShowRegression(self): | ||||
|         """Regression check presence['type'] = 'dnd' show value working""" | ||||
|         p = self.Presence() | ||||
|         p['type'] = 'dnd' | ||||
|         self.check_presence(p, "<presence><show>dnd</show></presence>") | ||||
|  | ||||
|     def testPresenceType(self): | ||||
|         """Test manipulating presence['type']""" | ||||
|         p = self.Presence() | ||||
|         p['type'] = 'available' | ||||
|         self.check_presence(p, "<presence />") | ||||
|         self.failUnless(p['type'] == 'available', | ||||
|             "Incorrect presence['type'] for type 'available': %s" % p['type']) | ||||
|  | ||||
|         for showtype in ['away', 'chat', 'dnd', 'xa']: | ||||
|             p['type'] = showtype | ||||
|             self.check_presence(p, """ | ||||
|               <presence><show>%s</show></presence> | ||||
|             """ % showtype) | ||||
|             self.failUnless(p['type'] == showtype, | ||||
|                 "Incorrect presence['type'] for type '%s'" % showtype) | ||||
|  | ||||
|         p['type'] = None | ||||
|         self.check_presence(p, "<presence />") | ||||
|  | ||||
|     def testPresenceUnsolicitedOffline(self): | ||||
|         """ | ||||
|         Unsolicted offline presence does not spawn changed_status | ||||
|         or update the roster. | ||||
|         """ | ||||
|         p = self.Presence() | ||||
|         p['type'] = 'unavailable' | ||||
|         p['from'] = 'bill@chadmore.com/gmail15af' | ||||
|  | ||||
|         c = sleekxmpp.ClientXMPP('crap@wherever', 'password') | ||||
|         happened = [] | ||||
|  | ||||
|         def handlechangedpresence(event): | ||||
|             happened.append(True) | ||||
|  | ||||
|         c.add_event_handler("changed_status", handlechangedpresence) | ||||
|         c._handle_presence(p) | ||||
|  | ||||
|         self.failUnless(happened == [], | ||||
|             "changed_status event triggered for extra unavailable presence") | ||||
|         self.failUnless(c.roster == {}, | ||||
|             "Roster updated for superfulous unavailable presence") | ||||
|  | ||||
|     def testNickPlugin(self): | ||||
|         """Test presence/nick/nick stanza.""" | ||||
|         p = self.Presence() | ||||
|         p['nick']['nick'] = 'A nickname!' | ||||
|         self.check_presence(p, """ | ||||
|           <presence> | ||||
|             <nick xmlns="http://jabber.org/nick/nick">A nickname!</nick> | ||||
|           </presence> | ||||
|         """) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestPresenceStanzas) | ||||
							
								
								
									
										84
									
								
								tests/test_stanza_roster.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/test_stanza_roster.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| from sleekxmpp.test import * | ||||
| from sleekxmpp.stanza.roster import Roster | ||||
|  | ||||
|  | ||||
| class TestRosterStanzas(SleekTest): | ||||
|  | ||||
|     def testAddItems(self): | ||||
|         """Test adding items to a roster stanza.""" | ||||
|         iq = self.Iq() | ||||
|         iq['roster'].setItems({ | ||||
|             'user@example.com': { | ||||
|                 'name': 'User', | ||||
|                 'subscription': 'both', | ||||
|                 'groups': ['Friends', 'Coworkers']}, | ||||
|             'otheruser@example.com': { | ||||
|                 'name': 'Other User', | ||||
|                 'subscription': 'both', | ||||
|                 'groups': []}}) | ||||
|         self.check_iq(iq, """ | ||||
|           <iq> | ||||
|             <query xmlns="jabber:iq:roster"> | ||||
|               <item jid="user@example.com" name="User" subscription="both"> | ||||
|                 <group>Friends</group> | ||||
|                 <group>Coworkers</group> | ||||
|               </item> | ||||
|               <item jid="otheruser@example.com" name="Other User" | ||||
|                     subscription="both" /> | ||||
|             </query> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testGetItems(self): | ||||
|         """Test retrieving items from a roster stanza.""" | ||||
|         xml_string = """ | ||||
|           <iq> | ||||
|             <query xmlns="jabber:iq:roster"> | ||||
|               <item jid="user@example.com" name="User" subscription="both"> | ||||
|                 <group>Friends</group> | ||||
|                 <group>Coworkers</group> | ||||
|               </item> | ||||
|               <item jid="otheruser@example.com" name="Other User" | ||||
|                     subscription="both" /> | ||||
|             </query> | ||||
|           </iq> | ||||
|         """ | ||||
|         iq = self.Iq(ET.fromstring(xml_string)) | ||||
|         expected = { | ||||
|             'user@example.com': { | ||||
|                 'name': 'User', | ||||
|                 'subscription': 'both', | ||||
|                 'groups': ['Friends', 'Coworkers']}, | ||||
|             'otheruser@example.com': { | ||||
|                 'name': 'Other User', | ||||
|                 'subscription': 'both', | ||||
|                 'groups': []}} | ||||
|         debug = "Roster items don't match after retrieval." | ||||
|         debug += "\nReturned: %s" % str(iq['roster']['items']) | ||||
|         debug += "\nExpected: %s" % str(expected) | ||||
|         self.failUnless(iq['roster']['items'] == expected, debug) | ||||
|  | ||||
|     def testDelItems(self): | ||||
|         """Test clearing items from a roster stanza.""" | ||||
|         xml_string = """ | ||||
|           <iq> | ||||
|             <query xmlns="jabber:iq:roster"> | ||||
|               <item jid="user@example.com" name="User" subscription="both"> | ||||
|                 <group>Friends</group> | ||||
|                 <group>Coworkers</group> | ||||
|               </item> | ||||
|               <item jid="otheruser@example.com" name="Other User" | ||||
|                     subscription="both" /> | ||||
|             </query> | ||||
|           </iq> | ||||
|         """ | ||||
|         iq = self.Iq(ET.fromstring(xml_string)) | ||||
|         del iq['roster']['items'] | ||||
|         self.check_iq(iq, """ | ||||
|           <iq> | ||||
|             <query xmlns="jabber:iq:roster" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestRosterStanzas) | ||||
							
								
								
									
										115
									
								
								tests/test_stanza_xep_0004.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								tests/test_stanza_xep_0004.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| from sleekxmpp.test import * | ||||
| import sleekxmpp.plugins.xep_0004 as xep_0004 | ||||
|  | ||||
|  | ||||
| class TestDataForms(SleekTest): | ||||
|  | ||||
|     def setUp(self): | ||||
|         register_stanza_plugin(Message, xep_0004.Form) | ||||
|         register_stanza_plugin(xep_0004.Form, xep_0004.FormField) | ||||
|         register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption) | ||||
|  | ||||
|     def testMultipleInstructions(self): | ||||
|         """Testing using multiple instructions elements in a data form.""" | ||||
|         msg = self.Message() | ||||
|         msg['form']['instructions'] = "Instructions\nSecond batch" | ||||
|  | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <x xmlns="jabber:x:data" type="form"> | ||||
|               <instructions>Instructions</instructions> | ||||
|               <instructions>Second batch</instructions> | ||||
|             </x> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testAddField(self): | ||||
|         """Testing adding fields to a data form.""" | ||||
|  | ||||
|         msg = self.Message() | ||||
|         form = msg['form'] | ||||
|         form.addField(var='f1', | ||||
|                       ftype='text-single', | ||||
|                       label='Text', | ||||
|                       desc='A text field', | ||||
|                       required=True, | ||||
|                       value='Some text!') | ||||
|  | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <x xmlns="jabber:x:data" type="form"> | ||||
|               <field var="f1" type="text-single" label="Text"> | ||||
|                 <desc>A text field</desc> | ||||
|                 <required /> | ||||
|                 <value>Some text!</value> | ||||
|               </field> | ||||
|             </x> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         form['fields'] = [('f1', {'type': 'text-single', | ||||
|                                   'label': 'Username', | ||||
|                                   'required': True}), | ||||
|                           ('f2', {'type': 'text-private', | ||||
|                                   'label': 'Password', | ||||
|                                   'required': True}), | ||||
|                           ('f3', {'type': 'text-multi', | ||||
|                                   'label': 'Message', | ||||
|                                   'value': 'Enter message.\nA long one even.'}), | ||||
|                           ('f4', {'type': 'list-single', | ||||
|                                   'label': 'Message Type', | ||||
|                                   'options': [{'label': 'Cool!', | ||||
|                                                'value': 'cool'}, | ||||
|                                               {'label': 'Urgh!', | ||||
|                                                'value': 'urgh'}]})] | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <x xmlns="jabber:x:data" type="form"> | ||||
|               <field var="f1" type="text-single" label="Username"> | ||||
|                 <required /> | ||||
|               </field> | ||||
|               <field var="f2" type="text-private" label="Password"> | ||||
|                 <required /> | ||||
|               </field> | ||||
|               <field var="f3" type="text-multi" label="Message"> | ||||
|                 <value>Enter message.</value> | ||||
|                 <value>A long one even.</value> | ||||
|               </field> | ||||
|               <field var="f4" type="list-single" label="Message Type"> | ||||
|                 <option label="Cool!"> | ||||
|                   <value>cool</value> | ||||
|                 </option> | ||||
|                 <option label="Urgh!"> | ||||
|                   <value>urgh</value> | ||||
|                 </option> | ||||
|               </field> | ||||
|             </x> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testSetValues(self): | ||||
|         """Testing setting form values""" | ||||
|  | ||||
|         msg = self.Message() | ||||
|         form = msg['form'] | ||||
|         form.setFields([ | ||||
|                 ('foo', {'type': 'text-single'}), | ||||
|                 ('bar', {'type': 'list-multi'})]) | ||||
|  | ||||
|         form.setValues({'foo': 'Foo!', | ||||
|                         'bar': ['a', 'b']}) | ||||
|  | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <x xmlns="jabber:x:data" type="form"> | ||||
|               <field var="foo" type="text-single"> | ||||
|                 <value>Foo!</value> | ||||
|               </field> | ||||
|               <field var="bar" type="list-multi"> | ||||
|                 <value>a</value> | ||||
|                 <value>b</value> | ||||
|               </field> | ||||
|             </x> | ||||
|           </message>""") | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms) | ||||
							
								
								
									
										176
									
								
								tests/test_stanza_xep_0030.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								tests/test_stanza_xep_0030.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | ||||
| from sleekxmpp.test import * | ||||
| import sleekxmpp.plugins.xep_0030 as xep_0030 | ||||
|  | ||||
|  | ||||
| class TestDisco(SleekTest): | ||||
|  | ||||
|     def setUp(self): | ||||
|         register_stanza_plugin(Iq, xep_0030.DiscoInfo) | ||||
|         register_stanza_plugin(Iq, xep_0030.DiscoItems) | ||||
|  | ||||
|     def testCreateInfoQueryNoNode(self): | ||||
|         """Testing disco#info query with no node.""" | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = "0" | ||||
|         iq['disco_info']['node'] = '' | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="http://jabber.org/protocol/disco#info" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testCreateInfoQueryWithNode(self): | ||||
|         """Testing disco#info query with a node.""" | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = "0" | ||||
|         iq['disco_info']['node'] = 'foo' | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="http://jabber.org/protocol/disco#info" node="foo" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testCreateInfoQueryNoNode(self): | ||||
|         """Testing disco#items query with no node.""" | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = "0" | ||||
|         iq['disco_items']['node'] = '' | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="http://jabber.org/protocol/disco#items" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testCreateItemsQueryWithNode(self): | ||||
|         """Testing disco#items query with a node.""" | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = "0" | ||||
|         iq['disco_items']['node'] = 'foo' | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="http://jabber.org/protocol/disco#items" node="foo" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testInfoIdentities(self): | ||||
|         """Testing adding identities to disco#info.""" | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = "0" | ||||
|         iq['disco_info']['node'] = 'foo' | ||||
|         iq['disco_info'].addIdentity('conference', 'text', 'Chatroom') | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="http://jabber.org/protocol/disco#info" node="foo"> | ||||
|               <identity category="conference" type="text" name="Chatroom" /> | ||||
|             </query> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testInfoFeatures(self): | ||||
|         """Testing adding features to disco#info.""" | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = "0" | ||||
|         iq['disco_info']['node'] = 'foo' | ||||
|         iq['disco_info'].addFeature('foo') | ||||
|         iq['disco_info'].addFeature('bar') | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="http://jabber.org/protocol/disco#info" node="foo"> | ||||
|               <feature var="foo" /> | ||||
|               <feature var="bar" /> | ||||
|             </query> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testItems(self): | ||||
|         """Testing adding features to disco#info.""" | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = "0" | ||||
|         iq['disco_items']['node'] = 'foo' | ||||
|         iq['disco_items'].addItem('user@localhost') | ||||
|         iq['disco_items'].addItem('user@localhost', 'foo') | ||||
|         iq['disco_items'].addItem('user@localhost', 'bar', 'Testing') | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <query xmlns="http://jabber.org/protocol/disco#items" node="foo"> | ||||
|               <item jid="user@localhost" /> | ||||
|               <item node="foo" jid="user@localhost" /> | ||||
|               <item node="bar" jid="user@localhost" name="Testing" /> | ||||
|             </query> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|     def testAddRemoveIdentities(self): | ||||
|         """Test adding and removing identities to disco#info stanza""" | ||||
|         ids = [('automation', 'commands', 'AdHoc'), | ||||
|                ('conference', 'text', 'ChatRoom')] | ||||
|  | ||||
|         info = xep_0030.DiscoInfo() | ||||
|         info.addIdentity(*ids[0]) | ||||
|         self.failUnless(info.getIdentities() == [ids[0]]) | ||||
|  | ||||
|         info.delIdentity('automation', 'commands') | ||||
|         self.failUnless(info.getIdentities() == []) | ||||
|  | ||||
|         info.setIdentities(ids) | ||||
|         self.failUnless(info.getIdentities() == ids) | ||||
|  | ||||
|         info.delIdentity('automation', 'commands') | ||||
|         self.failUnless(info.getIdentities() == [ids[1]]) | ||||
|  | ||||
|         info.delIdentities() | ||||
|         self.failUnless(info.getIdentities() == []) | ||||
|  | ||||
|     def testAddRemoveFeatures(self): | ||||
|         """Test adding and removing features to disco#info stanza""" | ||||
|         features = ['foo', 'bar', 'baz'] | ||||
|  | ||||
|         info = xep_0030.DiscoInfo() | ||||
|         info.addFeature(features[0]) | ||||
|         self.failUnless(info.getFeatures() == [features[0]]) | ||||
|  | ||||
|         info.delFeature('foo') | ||||
|         self.failUnless(info.getFeatures() == []) | ||||
|  | ||||
|         info.setFeatures(features) | ||||
|         self.failUnless(info.getFeatures() == features) | ||||
|  | ||||
|         info.delFeature('bar') | ||||
|         self.failUnless(info.getFeatures() == ['foo', 'baz']) | ||||
|  | ||||
|         info.delFeatures() | ||||
|         self.failUnless(info.getFeatures() == []) | ||||
|  | ||||
|     def testAddRemoveItems(self): | ||||
|         """Test adding and removing items to disco#items stanza""" | ||||
|         items = [('user@localhost', None, None), | ||||
|              ('user@localhost', 'foo', None), | ||||
|              ('user@localhost', 'bar', 'Test')] | ||||
|  | ||||
|         info = xep_0030.DiscoItems() | ||||
|         self.failUnless(True, ""+str(items[0])) | ||||
|  | ||||
|         info.addItem(*(items[0])) | ||||
|         self.failUnless(info.getItems() == [items[0]], info.getItems()) | ||||
|  | ||||
|         info.delItem('user@localhost') | ||||
|         self.failUnless(info.getItems() == []) | ||||
|  | ||||
|         info.setItems(items) | ||||
|         self.failUnless(info.getItems() == items) | ||||
|  | ||||
|         info.delItem('user@localhost', 'foo') | ||||
|         self.failUnless(info.getItems() == [items[0], items[2]]) | ||||
|  | ||||
|         info.delItems() | ||||
|         self.failUnless(info.getItems() == []) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestDisco) | ||||
							
								
								
									
										111
									
								
								tests/test_stanza_xep_0033.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								tests/test_stanza_xep_0033.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| from sleekxmpp.test import * | ||||
| import sleekxmpp.plugins.xep_0033 as xep_0033 | ||||
|  | ||||
|  | ||||
| class TestAddresses(SleekTest): | ||||
|  | ||||
|     def setUp(self): | ||||
|         register_stanza_plugin(Message, xep_0033.Addresses) | ||||
|  | ||||
|     def testAddAddress(self): | ||||
|         """Testing adding extended stanza address.""" | ||||
|         msg = self.Message() | ||||
|         msg['addresses'].addAddress(atype='to', jid='to@header1.org') | ||||
|         self.check_message(msg, """ | ||||
|         <message> | ||||
|           <addresses xmlns="http://jabber.org/protocol/address"> | ||||
|             <address jid="to@header1.org" type="to" /> | ||||
|           </addresses> | ||||
|         </message> | ||||
|         """) | ||||
|  | ||||
|         msg = self.Message() | ||||
|         msg['addresses'].addAddress(atype='replyto', | ||||
|                                     jid='replyto@header1.org', | ||||
|                                     desc='Reply address') | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <addresses xmlns="http://jabber.org/protocol/address"> | ||||
|               <address jid="replyto@header1.org" type="replyto" desc="Reply address" /> | ||||
|             </addresses> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testAddAddresses(self): | ||||
|         """Testing adding multiple extended stanza addresses.""" | ||||
|  | ||||
|         xmlstring = """ | ||||
|           <message> | ||||
|             <addresses xmlns="http://jabber.org/protocol/address"> | ||||
|               <address jid="replyto@header1.org" type="replyto" desc="Reply address" /> | ||||
|               <address jid="cc@header2.org" type="cc" /> | ||||
|               <address jid="bcc@header2.org" type="bcc" /> | ||||
|             </addresses> | ||||
|           </message> | ||||
|         """ | ||||
|  | ||||
|         msg = self.Message() | ||||
|         msg['addresses'].setAddresses([ | ||||
|             {'type':'replyto', | ||||
|              'jid':'replyto@header1.org', | ||||
|              'desc':'Reply address'}, | ||||
|             {'type':'cc', | ||||
|              'jid':'cc@header2.org'}, | ||||
|             {'type':'bcc', | ||||
|              'jid':'bcc@header2.org'}]) | ||||
|         self.check_message(msg, xmlstring) | ||||
|  | ||||
|         msg = self.Message() | ||||
|         msg['addresses']['replyto'] = [{'jid':'replyto@header1.org', | ||||
|                                                 'desc':'Reply address'}] | ||||
|         msg['addresses']['cc'] = [{'jid':'cc@header2.org'}] | ||||
|         msg['addresses']['bcc'] = [{'jid':'bcc@header2.org'}] | ||||
|         self.check_message(msg, xmlstring) | ||||
|  | ||||
|     def testAddURI(self): | ||||
|         """Testing adding URI attribute to extended stanza address.""" | ||||
|  | ||||
|         msg = self.Message() | ||||
|         addr = msg['addresses'].addAddress(atype='to', | ||||
|                                            jid='to@header1.org', | ||||
|                                            node='foo') | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <addresses xmlns="http://jabber.org/protocol/address"> | ||||
|               <address node="foo" jid="to@header1.org" type="to" /> | ||||
|             </addresses> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         addr['uri'] = 'mailto:to@header2.org' | ||||
|         self.check_message(msg, """ | ||||
|           <message> | ||||
|             <addresses xmlns="http://jabber.org/protocol/address"> | ||||
|               <address type="to" uri="mailto:to@header2.org" /> | ||||
|             </addresses> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testDelivered(self): | ||||
|         """Testing delivered attribute of extended stanza addresses.""" | ||||
|  | ||||
|         xmlstring = """ | ||||
|           <message> | ||||
|             <addresses xmlns="http://jabber.org/protocol/address"> | ||||
|               <address %s jid="to@header1.org" type="to" /> | ||||
|             </addresses> | ||||
|           </message> | ||||
|         """ | ||||
|  | ||||
|         msg = self.Message() | ||||
|         addr = msg['addresses'].addAddress(jid='to@header1.org', atype='to') | ||||
|         self.check_message(msg, xmlstring % '') | ||||
|  | ||||
|         addr['delivered'] = True | ||||
|         self.check_message(msg, xmlstring % 'delivered="true"') | ||||
|  | ||||
|         addr['delivered'] = False | ||||
|         self.check_message(msg, xmlstring % '') | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestAddresses) | ||||
							
								
								
									
										511
									
								
								tests/test_stanza_xep_0060.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										511
									
								
								tests/test_stanza_xep_0060.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,511 @@ | ||||
| from sleekxmpp.test import * | ||||
| import sleekxmpp.plugins.xep_0004 as xep_0004 | ||||
| import sleekxmpp.plugins.stanza_pubsub as pubsub | ||||
|  | ||||
|  | ||||
| class TestPubsubStanzas(SleekTest): | ||||
|  | ||||
|     def testAffiliations(self): | ||||
|         "Testing iq/pubsub/affiliations/affiliation stanzas" | ||||
|         iq = self.Iq() | ||||
|         aff1 = pubsub.Affiliation() | ||||
|         aff1['node'] = 'testnode' | ||||
|         aff1['affiliation'] = 'owner' | ||||
|         aff2 = pubsub.Affiliation() | ||||
|         aff2['node'] = 'testnode2' | ||||
|         aff2['affiliation'] = 'publisher' | ||||
|         iq['pubsub']['affiliations'].append(aff1) | ||||
|         iq['pubsub']['affiliations'].append(aff2) | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|               <affiliations> | ||||
|                 <affiliation node="testnode" affiliation="owner" /> | ||||
|                 <affiliation node="testnode2" affiliation="publisher" /> | ||||
|               </affiliations> | ||||
|             </pubsub> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testSubscriptions(self): | ||||
|         "Testing iq/pubsub/subscriptions/subscription stanzas" | ||||
|         iq = self.Iq() | ||||
|         sub1 = pubsub.Subscription() | ||||
|         sub1['node'] = 'testnode' | ||||
|         sub1['jid'] = 'steve@myserver.tld/someresource' | ||||
|         sub2 = pubsub.Subscription() | ||||
|         sub2['node'] = 'testnode2' | ||||
|         sub2['jid'] = 'boogers@bork.top/bill' | ||||
|         sub2['subscription'] = 'subscribed' | ||||
|         iq['pubsub']['subscriptions'].append(sub1) | ||||
|         iq['pubsub']['subscriptions'].append(sub2) | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|               <subscriptions> | ||||
|                 <subscription node="testnode" jid="steve@myserver.tld/someresource" /> | ||||
|                 <subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" /> | ||||
|               </subscriptions> | ||||
|             </pubsub> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testOptionalSettings(self): | ||||
|         "Testing iq/pubsub/subscription/subscribe-options stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['pubsub']['subscription']['suboptions']['required'] = True | ||||
|         iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas' | ||||
|         iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp" | ||||
|         iq['pubsub']['subscription']['subscription'] = 'unconfigured' | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|               <subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured"> | ||||
|               <subscribe-options> | ||||
|                 <required /> | ||||
|                 </subscribe-options> | ||||
|               </subscription> | ||||
|             </pubsub> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testItems(self): | ||||
|         "Testing iq/pubsub/items stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['pubsub']['items']['node'] = 'crap' | ||||
|         payload = ET.fromstring(""" | ||||
|           <thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'> | ||||
|             <child1 /> | ||||
|             <child2 normandy='cheese' foo='bar' /> | ||||
|           </thinger>""") | ||||
|         payload2 = ET.fromstring(""" | ||||
|           <thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'> | ||||
|             <child12 /> | ||||
|             <child22 normandy='cheese2' foo='bar2' /> | ||||
|           </thinger2>""") | ||||
|         item = pubsub.Item() | ||||
|         item['id'] = 'asdf' | ||||
|         item['payload'] = payload | ||||
|         item2 = pubsub.Item() | ||||
|         item2['id'] = 'asdf2' | ||||
|         item2['payload'] = payload2 | ||||
|         iq['pubsub']['items'].append(item) | ||||
|         iq['pubsub']['items'].append(item2) | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|               <items node="crap"> | ||||
|                 <item id="asdf"> | ||||
|                   <thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"> | ||||
|                     <child1 /> | ||||
|                     <child2 foo="bar" normandy="cheese" /> | ||||
|                   </thinger> | ||||
|                 </item> | ||||
|                 <item id="asdf2"> | ||||
|                   <thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"> | ||||
|                     <child12 /> | ||||
|                     <child22 foo="bar2" normandy="cheese2" /> | ||||
|                   </thinger2> | ||||
|                 </item> | ||||
|               </items> | ||||
|             </pubsub> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testCreate(self): | ||||
|         "Testing iq/pubsub/create&configure stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['pubsub']['create']['node'] = 'mynode' | ||||
|         iq['pubsub']['configure']['form'].addField('pubsub#title', | ||||
|                                                    ftype='text-single', | ||||
|                                                    value='This thing is awesome') | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|               <create node="mynode" /> | ||||
|                 <configure> | ||||
|                   <x xmlns="jabber:x:data" type="form"> | ||||
|                     <field var="pubsub#title" type="text-single"> | ||||
|                       <value>This thing is awesome</value> | ||||
|                     </field> | ||||
|                   </x> | ||||
|                 </configure> | ||||
|               </pubsub> | ||||
|             </iq>""") | ||||
|  | ||||
|     def testState(self): | ||||
|         "Testing iq/psstate stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['psstate']['node']= 'mynode' | ||||
|         iq['psstate']['item']= 'myitem' | ||||
|         pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed') | ||||
|         iq['psstate']['payload'] = pl | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem"> | ||||
|               <claimed xmlns="http://andyet.net/protocol/pubsubqueue" /> | ||||
|             </state> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testDefault(self): | ||||
|         "Testing iq/pubsub_owner/default stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['pubsub_owner']['default'] | ||||
|         iq['pubsub_owner']['default']['node'] = 'mynode' | ||||
|         iq['pubsub_owner']['default']['type'] = 'leaf' | ||||
|         iq['pubsub_owner']['default']['form'].addField('pubsub#title', | ||||
|                                                        ftype='text-single', | ||||
|                                                        value='This thing is awesome') | ||||
|         self.check_iq(iq, """ | ||||
| 	      <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub#owner"> | ||||
|               <default node="mynode" type="leaf"> | ||||
|                 <x xmlns="jabber:x:data" type="form"> | ||||
|                   <field var="pubsub#title" type="text-single"> | ||||
|                     <value>This thing is awesome</value> | ||||
|                   </field> | ||||
|                 </x> | ||||
|              </default> | ||||
|            </pubsub> | ||||
|          </iq>""", use_values=False) | ||||
|  | ||||
|     def testSubscribe(self): | ||||
|         "testing iq/pubsub/subscribe stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['pubsub']['subscribe']['options'] | ||||
|         iq['pubsub']['subscribe']['node'] = 'cheese' | ||||
|         iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp' | ||||
|         iq['pubsub']['subscribe']['options']['node'] = 'cheese' | ||||
|         iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp' | ||||
|         form = xep_0004.Form() | ||||
|         form.addField('pubsub#title', ftype='text-single', value='this thing is awesome') | ||||
|         iq['pubsub']['subscribe']['options']['options'] = form | ||||
|         self.check_iq(iq, """ | ||||
|         <iq id="0"> | ||||
|           <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|             <subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp"> | ||||
|               <options node="cheese" jid="fritzy@netflint.net/sleekxmpp"> | ||||
|                 <x xmlns="jabber:x:data" type="form"> | ||||
|                   <field var="pubsub#title" type="text-single"> | ||||
|                     <value>this thing is awesome</value> | ||||
|                   </field> | ||||
|                 </x> | ||||
|               </options> | ||||
|             </subscribe> | ||||
|           </pubsub> | ||||
|         </iq>""", use_values=False) | ||||
|  | ||||
|     def testPublish(self): | ||||
|         "Testing iq/pubsub/publish stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['pubsub']['publish']['node'] = 'thingers' | ||||
|         payload = ET.fromstring(""" | ||||
|           <thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'> | ||||
|              <child1 /> | ||||
|              <child2 normandy='cheese' foo='bar' /> | ||||
|            </thinger>""") | ||||
|         payload2 = ET.fromstring(""" | ||||
|           <thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'> | ||||
|             <child12 /> | ||||
|             <child22 normandy='cheese2' foo='bar2' /> | ||||
|            </thinger2>""") | ||||
|         item = pubsub.Item() | ||||
|         item['id'] = 'asdf' | ||||
|         item['payload'] = payload | ||||
|         item2 = pubsub.Item() | ||||
|         item2['id'] = 'asdf2' | ||||
|         item2['payload'] = payload2 | ||||
|         iq['pubsub']['publish'].append(item) | ||||
|         iq['pubsub']['publish'].append(item2) | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|               <publish node="thingers"> | ||||
|                 <item id="asdf"> | ||||
|                   <thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"> | ||||
|                     <child1 /> | ||||
|                     <child2 foo="bar" normandy="cheese" /> | ||||
|                   </thinger> | ||||
|                 </item> | ||||
|                 <item id="asdf2"> | ||||
|                   <thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"> | ||||
|                     <child12 /> | ||||
|                     <child22 foo="bar2" normandy="cheese2" /> | ||||
|                   </thinger2> | ||||
|                 </item> | ||||
|               </publish> | ||||
|             </pubsub> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testDelete(self): | ||||
|         "Testing iq/pubsub_owner/delete stanzas" | ||||
|         iq = self.Iq() | ||||
|         iq['pubsub_owner']['delete']['node'] = 'thingers' | ||||
|         self.check_iq(iq, """ | ||||
|           <iq id="0"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub#owner"> | ||||
|               <delete node="thingers" /> | ||||
|             </pubsub> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testCreateConfigGet(self): | ||||
|         """Testing getting config from full create""" | ||||
|         iq = self.Iq() | ||||
|         iq['to'] = 'pubsub.asdf' | ||||
|         iq['from'] = 'fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7' | ||||
|         iq['type'] = 'set' | ||||
|         iq['id'] = 'E' | ||||
|  | ||||
|         pub = iq['pubsub'] | ||||
|         pub['create']['node'] = 'testnode2' | ||||
|         pub['configure']['form']['type'] = 'submit' | ||||
|         pub['configure']['form'].setFields([ | ||||
|                 ('FORM_TYPE', {'type': 'hidden', | ||||
|                                'value': 'http://jabber.org/protocol/pubsub#node_config'}), | ||||
|                 ('pubsub#node_type', {'type': 'list-single', | ||||
|                                       'label': 'Select the node type', | ||||
|                                       'value': 'leaf'}), | ||||
|                 ('pubsub#title', {'type': 'text-single', | ||||
|                                   'label': 'A friendly name for the node'}), | ||||
|                 ('pubsub#deliver_notifications', {'type': 'boolean', | ||||
|                                                   'label': 'Deliver event notifications', | ||||
|                                                   'value': True}), | ||||
|                 ('pubsub#deliver_payloads', {'type': 'boolean', | ||||
|                                              'label': 'Deliver payloads with event notifications', | ||||
|                                              'value': True}), | ||||
|                 ('pubsub#notify_config', {'type': 'boolean', | ||||
|                                           'label': 'Notify subscribers when the node configuration changes'}), | ||||
|                 ('pubsub#notify_delete', {'type': 'boolean', | ||||
|                                           'label': 'Notify subscribers when the node is deleted'}), | ||||
|                 ('pubsub#notify_retract', {'type': 'boolean', | ||||
|                                            'label': 'Notify subscribers when items are removed from the node', | ||||
|                                            'value': True}), | ||||
|                 ('pubsub#notify_sub', {'type': 'boolean', | ||||
|                                        'label': 'Notify owners about new subscribers and unsubscribes'}), | ||||
|                 ('pubsub#persist_items', {'type': 'boolean', | ||||
|                                           'label': 'Persist items in storage'}), | ||||
|                 ('pubsub#max_items', {'type': 'text-single', | ||||
|                                       'label': 'Max # of items to persist', | ||||
|                                       'value': '10'}), | ||||
|                 ('pubsub#subscribe', {'type': 'boolean', | ||||
|                                       'label': 'Whether to allow subscriptions', | ||||
|                                       'value': True}), | ||||
|                 ('pubsub#access_model', {'type': 'list-single', | ||||
|                                          'label': 'Specify the subscriber model', | ||||
|                                          'value': 'open'}), | ||||
|                 ('pubsub#publish_model', {'type': 'list-single', | ||||
|                                           'label': 'Specify the publisher model', | ||||
|                                           'value': 'publishers'}), | ||||
|                 ('pubsub#send_last_published_item', {'type': 'list-single', | ||||
|                                                      'label': 'Send last published item', | ||||
|                                                      'value': 'never'}), | ||||
|                 ('pubsub#presence_based_delivery', {'type': 'boolean', | ||||
|                                                     'label': 'Deliver notification only to available users'}), | ||||
|                 ]) | ||||
|  | ||||
|         self.check_iq(iq, """ | ||||
|           <iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7"> | ||||
|             <pubsub xmlns="http://jabber.org/protocol/pubsub"> | ||||
|               <create node="testnode2" /> | ||||
|               <configure> | ||||
|                 <x xmlns="jabber:x:data" type="submit"> | ||||
|                   <field var="FORM_TYPE" type="hidden"> | ||||
|                     <value>http://jabber.org/protocol/pubsub#node_config</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#node_type" type="list-single" label="Select the node type"> | ||||
|                     <value>leaf</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#title" type="text-single" label="A friendly name for the node" /> | ||||
|                   <field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications"> | ||||
|                     <value>1</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications"> | ||||
|                     <value>1</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" /> | ||||
|                   <field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" /> | ||||
|                   <field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node"> | ||||
|                     <value>1</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" /> | ||||
|                   <field var="pubsub#persist_items" type="boolean" label="Persist items in storage" /> | ||||
|                   <field var="pubsub#max_items" type="text-single" label="Max # of items to persist"> | ||||
|                     <value>10</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions"> | ||||
|                     <value>1</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#access_model" type="list-single" label="Specify the subscriber model"> | ||||
|                     <value>open</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#publish_model" type="list-single" label="Specify the publisher model"> | ||||
|                     <value>publishers</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#send_last_published_item" type="list-single" label="Send last published item"> | ||||
|                     <value>never</value> | ||||
|                   </field> | ||||
|                   <field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" /> | ||||
|                 </x> | ||||
|               </configure> | ||||
|             </pubsub> | ||||
|           </iq>""") | ||||
|  | ||||
|     def testItemEvent(self): | ||||
|         """Testing message/pubsub_event/items/item""" | ||||
|         msg = self.Message() | ||||
|         item = pubsub.EventItem() | ||||
|         pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) | ||||
|         item['payload'] = pl | ||||
|         item['id'] = 'abc123' | ||||
|         msg['pubsub_event']['items'].append(item) | ||||
|         msg['pubsub_event']['items']['node'] = 'cheese' | ||||
|         msg['type'] = 'normal' | ||||
|         self.check_message(msg, """ | ||||
|           <message type="normal"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <items node="cheese"> | ||||
|                 <item id="abc123"> | ||||
|                   <test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /> | ||||
|                 </item> | ||||
|               </items> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
|     def testItemsEvent(self): | ||||
|         """Testing multiple message/pubsub_event/items/item""" | ||||
|         msg = self.Message() | ||||
|         item = pubsub.EventItem() | ||||
|         item2 = pubsub.EventItem() | ||||
|         pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) | ||||
|         pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) | ||||
|         item2['payload'] = pl2 | ||||
|         item['payload'] = pl | ||||
|         item['id'] = 'abc123' | ||||
|         item2['id'] = '123abc' | ||||
|         msg['pubsub_event']['items'].append(item) | ||||
|         msg['pubsub_event']['items'].append(item2) | ||||
|         msg['pubsub_event']['items']['node'] = 'cheese' | ||||
|         msg['type'] = 'normal' | ||||
|         self.check_message(msg, """ | ||||
|           <message type="normal"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <items node="cheese"> | ||||
|                 <item id="abc123"> | ||||
|                   <test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /> | ||||
|                 </item> | ||||
|                 <item id="123abc"> | ||||
|                   <test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" /> | ||||
|                 </item> | ||||
|               </items> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
|     def testItemsEvent(self): | ||||
|         """Testing message/pubsub_event/items/item & retract mix""" | ||||
|         msg = self.Message() | ||||
|         item = pubsub.EventItem() | ||||
|         item2 = pubsub.EventItem() | ||||
|         pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) | ||||
|         pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) | ||||
|         item2['payload'] = pl2 | ||||
|         retract = pubsub.EventRetract() | ||||
|         retract['id'] = 'aabbcc' | ||||
|         item['payload'] = pl | ||||
|         item['id'] = 'abc123' | ||||
|         item2['id'] = '123abc' | ||||
|         msg['pubsub_event']['items'].append(item) | ||||
|         msg['pubsub_event']['items'].append(retract) | ||||
|         msg['pubsub_event']['items'].append(item2) | ||||
|         msg['pubsub_event']['items']['node'] = 'cheese' | ||||
|         msg['type'] = 'normal' | ||||
|         self.check_message(msg, """ | ||||
|           <message type="normal"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <items node="cheese"> | ||||
|                 <item id="abc123"> | ||||
|                   <test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /> | ||||
|                 </item><retract id="aabbcc" /> | ||||
|                 <item id="123abc"> | ||||
|                   <test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" /> | ||||
|                 </item> | ||||
|               </items> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
|     def testCollectionAssociate(self): | ||||
|         """Testing message/pubsub_event/collection/associate""" | ||||
|         msg = self.Message() | ||||
|         msg['pubsub_event']['collection']['associate']['node'] = 'cheese' | ||||
|         msg['pubsub_event']['collection']['node'] = 'cheeseburger' | ||||
|         msg['type'] = 'headline' | ||||
|         self.check_message(msg, """ | ||||
|           <message type="headline"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <collection node="cheeseburger"> | ||||
|                 <associate node="cheese" /> | ||||
|               </collection> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
|     def testCollectionDisassociate(self): | ||||
|         """Testing message/pubsub_event/collection/disassociate""" | ||||
|         msg = self.Message() | ||||
|         msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese' | ||||
|         msg['pubsub_event']['collection']['node'] = 'cheeseburger' | ||||
|         msg['type'] = 'headline' | ||||
|         self.check_message(msg, """ | ||||
|           <message type="headline"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <collection node="cheeseburger"> | ||||
|                 <disassociate node="cheese" /> | ||||
|               </collection> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
|     def testEventConfiguration(self): | ||||
|         """Testing message/pubsub_event/configuration/config""" | ||||
|         msg = self.Message() | ||||
|         msg['pubsub_event']['configuration']['node'] = 'cheese' | ||||
|         msg['pubsub_event']['configuration']['form'].addField('pubsub#title', | ||||
|                                                               ftype='text-single', | ||||
|                                                               value='This thing is awesome') | ||||
|         msg['type'] = 'headline' | ||||
|         self.check_message(msg, """ | ||||
|         <message type="headline"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <configuration node="cheese"> | ||||
|                 <x xmlns="jabber:x:data" type="form"> | ||||
|                   <field var="pubsub#title" type="text-single"> | ||||
|                     <value>This thing is awesome</value> | ||||
|                   </field> | ||||
|                 </x> | ||||
|               </configuration> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
|     def testEventPurge(self): | ||||
|         """Testing message/pubsub_event/purge""" | ||||
|         msg = self.Message() | ||||
|         msg['pubsub_event']['purge']['node'] = 'pickles' | ||||
|         msg['type'] = 'headline' | ||||
|         self.check_message(msg, """ | ||||
|           <message type="headline"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <purge node="pickles" /> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
|     def testEventSubscription(self): | ||||
|         """Testing message/pubsub_event/subscription""" | ||||
|         msg = self.Message() | ||||
|         msg['pubsub_event']['subscription']['node'] = 'pickles' | ||||
|         msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test' | ||||
|         msg['pubsub_event']['subscription']['subid'] = 'aabb1122' | ||||
|         msg['pubsub_event']['subscription']['subscription'] = 'subscribed' | ||||
|         msg['pubsub_event']['subscription']['expiry'] = 'presence' | ||||
|         msg['type'] = 'headline' | ||||
|         self.check_message(msg, """ | ||||
|           <message type="headline"> | ||||
|             <event xmlns="http://jabber.org/protocol/pubsub#event"> | ||||
|               <subscription node="pickles" subid="aabb1122" jid="fritzy@netflint.net/test" subscription="subscribed" expiry="presence" /> | ||||
|             </event> | ||||
|           </message>""") | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubStanzas) | ||||
							
								
								
									
										44
									
								
								tests/test_stanza_xep_0085.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								tests/test_stanza_xep_0085.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| from sleekxmpp.test import * | ||||
| import sleekxmpp.plugins.xep_0085 as xep_0085 | ||||
|  | ||||
| class TestChatStates(SleekTest): | ||||
|  | ||||
|     def setUp(self): | ||||
|         register_stanza_plugin(Message, xep_0085.Active) | ||||
|         register_stanza_plugin(Message, xep_0085.Composing) | ||||
|         register_stanza_plugin(Message, xep_0085.Gone) | ||||
|         register_stanza_plugin(Message, xep_0085.Inactive) | ||||
|         register_stanza_plugin(Message, xep_0085.Paused) | ||||
|  | ||||
|     def testCreateChatState(self): | ||||
|         """Testing creating chat states.""" | ||||
|  | ||||
|         xmlstring = """ | ||||
|           <message> | ||||
|             <%s xmlns="http://jabber.org/protocol/chatstates" /> | ||||
|           </message> | ||||
|         """ | ||||
|  | ||||
|         msg = self.Message() | ||||
|         msg['chat_state'].active() | ||||
|         self.check_message(msg, xmlstring % 'active', | ||||
|                           use_values=False) | ||||
|  | ||||
|         msg['chat_state'].composing() | ||||
|         self.check_message(msg, xmlstring % 'composing', | ||||
|                           use_values=False) | ||||
|  | ||||
|  | ||||
|         msg['chat_state'].gone() | ||||
|         self.check_message(msg, xmlstring % 'gone', | ||||
|                           use_values=False) | ||||
|  | ||||
|         msg['chat_state'].inactive() | ||||
|         self.check_message(msg, xmlstring % 'inactive', | ||||
|                           use_values=False) | ||||
|  | ||||
|         msg['chat_state'].paused() | ||||
|         self.check_message(msg, xmlstring % 'paused', | ||||
|                           use_values=False) | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestChatStates) | ||||
							
								
								
									
										60
									
								
								tests/test_stream.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								tests/test_stream.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| from sleekxmpp.test import * | ||||
| import sleekxmpp.plugins.xep_0033 as xep_0033 | ||||
|  | ||||
|  | ||||
| class TestStreamTester(SleekTest): | ||||
|     """ | ||||
|     Test that we can simulate and test a stanza stream. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testClientEcho(self): | ||||
|         """Test that we can interact with a ClientXMPP instance.""" | ||||
|         self.stream_start(mode='client') | ||||
|  | ||||
|         def echo(msg): | ||||
|             msg.reply('Thanks for sending: %(body)s' % msg).send() | ||||
|  | ||||
|         self.xmpp.add_event_handler('message', echo) | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <message to="tester@localhost" from="user@localhost"> | ||||
|             <body>Hi!</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         self.stream_send_message(""" | ||||
|           <message to="user@localhost"> | ||||
|             <body>Thanks for sending: Hi!</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testComponentEcho(self): | ||||
|         """Test that we can interact with a ComponentXMPP instance.""" | ||||
|         self.stream_start(mode='component') | ||||
|  | ||||
|         def echo(msg): | ||||
|             msg.reply('Thanks for sending: %(body)s' % msg).send() | ||||
|  | ||||
|         self.xmpp.add_event_handler('message', echo) | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <message to="tester.localhost" from="user@localhost"> | ||||
|             <body>Hi!</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         self.stream_send_message(""" | ||||
|           <message to="user@localhost" from="tester.localhost"> | ||||
|             <body>Thanks for sending: Hi!</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testSendStreamHeader(self): | ||||
|         """Test that we can check a sent stream header.""" | ||||
|         self.stream_start(mode='client', skip=False) | ||||
|         self.stream_send_header(sto='localhost') | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamTester) | ||||
							
								
								
									
										110
									
								
								tests/test_stream_exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								tests/test_stream_exceptions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| import sys | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.test import * | ||||
|  | ||||
|  | ||||
| class TestStreamExceptions(SleekTest): | ||||
|     """ | ||||
|     Test handling roster updates. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testXMPPErrorException(self): | ||||
|         """Test raising an XMPPError exception.""" | ||||
|  | ||||
|         def message(msg): | ||||
|             raise XMPPError(condition='feature-not-implemented', | ||||
|                             text="We don't do things that way here.", | ||||
|                             etype='cancel', | ||||
|                             extension='foo', | ||||
|                             extension_ns='foo:error', | ||||
|                             extension_args={'test': 'true'}) | ||||
|  | ||||
|         self.stream_start() | ||||
|         self.xmpp.add_event_handler('message', message) | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <message> | ||||
|             <body>This is going to cause an error.</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         self.stream_send_message(""" | ||||
|           <message type="error"> | ||||
|             <error type="cancel"> | ||||
|               <feature-not-implemented | ||||
|                   xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|               <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> | ||||
|                 We don't do things that way here. | ||||
|               </text> | ||||
|               <foo xmlns="foo:error" test="true" /> | ||||
|             </error> | ||||
|           </message> | ||||
|         """, use_values=False) | ||||
|  | ||||
|     def testThreadedXMPPErrorException(self): | ||||
|         """Test raising an XMPPError exception in a threaded handler.""" | ||||
|  | ||||
|         def message(msg): | ||||
|             raise XMPPError(condition='feature-not-implemented', | ||||
|                             text="We don't do things that way here.", | ||||
|                             etype='cancel') | ||||
|  | ||||
|         self.stream_start() | ||||
|         self.xmpp.add_event_handler('message', message, | ||||
|                                     threaded=True) | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <message> | ||||
|             <body>This is going to cause an error.</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         self.stream_send_message(""" | ||||
|           <message type="error"> | ||||
|             <error type="cancel"> | ||||
|               <feature-not-implemented | ||||
|                   xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|               <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> | ||||
|                 We don't do things that way here. | ||||
|               </text> | ||||
|             </error> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|     def testUnknownException(self): | ||||
|         """Test raising an generic exception in a threaded handler.""" | ||||
|  | ||||
|         def message(msg): | ||||
|             raise ValueError("Did something wrong") | ||||
|  | ||||
|         self.stream_start() | ||||
|         self.xmpp.add_event_handler('message', message) | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <message> | ||||
|             <body>This is going to cause an error.</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         if sys.version_info < (3, 0): | ||||
|             self.stream_send_message(""" | ||||
|               <message type="error"> | ||||
|                 <error type="cancel"> | ||||
|                   <undefined-condition | ||||
|                       xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|                   <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> | ||||
|                     SleekXMPP got into trouble. | ||||
|                   </text> | ||||
|                 </error> | ||||
|               </message> | ||||
|             """) | ||||
|         else: | ||||
|             # Unfortunately, tracebacks do not make for very portable tests. | ||||
|             pass | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamExceptions) | ||||
							
								
								
									
										112
									
								
								tests/test_stream_handlers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								tests/test_stream_handlers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| from sleekxmpp.test import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
|  | ||||
|  | ||||
| class TestHandlers(SleekTest): | ||||
|     """ | ||||
|     Test using handlers and waiters. | ||||
|     """ | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.stream_start() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testCallback(self): | ||||
|         """Test using stream callback handlers.""" | ||||
|  | ||||
|         def callback_handler(stanza): | ||||
|             self.xmpp.sendRaw(""" | ||||
|               <message> | ||||
|                 <body>Success!</body> | ||||
|               </message> | ||||
|             """) | ||||
|  | ||||
|         callback = Callback('Test Callback', | ||||
|                             MatchXPath('{test}tester'), | ||||
|                             callback_handler) | ||||
|  | ||||
|         self.xmpp.registerHandler(callback) | ||||
|  | ||||
|         self.stream_recv("""<tester xmlns="test" />""") | ||||
|  | ||||
|         msg = self.Message() | ||||
|         msg['body'] = 'Success!' | ||||
|         self.stream_send_message(msg) | ||||
|  | ||||
|     def testWaiter(self): | ||||
|         """Test using stream waiter handler.""" | ||||
|  | ||||
|         def waiter_handler(stanza): | ||||
|             iq = self.xmpp.Iq() | ||||
|             iq['id'] = 'test' | ||||
|             iq['type'] = 'set' | ||||
|             iq['query'] = 'test' | ||||
|             reply = iq.send(block=True) | ||||
|             if reply: | ||||
|                 self.xmpp.sendRaw(""" | ||||
|                   <message> | ||||
|                     <body>Successful: %s</body> | ||||
|                   </message> | ||||
|                 """ % reply['query']) | ||||
|  | ||||
|         self.xmpp.add_event_handler('message', waiter_handler, threaded=True) | ||||
|  | ||||
|         # Send message to trigger waiter_handler | ||||
|         self.stream_recv(""" | ||||
|           <message> | ||||
|             <body>Testing</body> | ||||
|           </message> | ||||
|         """) | ||||
|  | ||||
|         # Check that Iq was sent by waiter_handler | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = 'test' | ||||
|         iq['type'] = 'set' | ||||
|         iq['query'] = 'test' | ||||
|         self.stream_send_iq(iq) | ||||
|  | ||||
|         # Send the reply Iq | ||||
|         self.stream_recv(""" | ||||
|           <iq id="test" type="result"> | ||||
|             <query xmlns="test" /> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|         # Check that waiter_handler received the reply | ||||
|         msg = self.Message() | ||||
|         msg['body'] = 'Successful: test' | ||||
|         self.stream_send_message(msg) | ||||
|  | ||||
|     def testWaiterTimeout(self): | ||||
|         """Test that waiter handler is removed after timeout.""" | ||||
|  | ||||
|         def waiter_handler(stanza): | ||||
|             iq = self.xmpp.Iq() | ||||
|             iq['id'] = 'test2' | ||||
|             iq['type'] = 'set' | ||||
|             iq['query'] = 'test2' | ||||
|             reply = iq.send(block=True, timeout=0) | ||||
|  | ||||
|         self.xmpp.add_event_handler('message', waiter_handler, threaded=True) | ||||
|  | ||||
|         # Start test by triggerig waiter_handler | ||||
|         self.stream_recv("""<message><body>Start Test</body></message>""") | ||||
|  | ||||
|         # Check that Iq was sent to trigger start of timeout period | ||||
|         iq = self.Iq() | ||||
|         iq['id'] = 'test2' | ||||
|         iq['type'] = 'set' | ||||
|         iq['query'] = 'test2' | ||||
|         self.stream_send_iq(iq) | ||||
|  | ||||
|         # Check that the waiter is no longer registered | ||||
|         waiter_exists = self.xmpp.removeHandler('IqWait_test2') | ||||
|  | ||||
|         self.failUnless(waiter_exists == False, | ||||
|             "Waiter handler was not removed.") | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestHandlers) | ||||
							
								
								
									
										188
									
								
								tests/test_stream_presence.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								tests/test_stream_presence.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| import time | ||||
| from sleekxmpp.test import * | ||||
|  | ||||
|  | ||||
| class TestStreamPresence(SleekTest): | ||||
|     """ | ||||
|     Test handling roster updates. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testInitialUnavailablePresences(self): | ||||
|         """ | ||||
|         Test receiving unavailable presences from JIDs that | ||||
|         are not online. | ||||
|         """ | ||||
|         events = set() | ||||
|  | ||||
|         def got_offline(presence): | ||||
|             # The got_offline event should not be triggered. | ||||
|             events.add('got_offline') | ||||
|  | ||||
|         def unavailable(presence): | ||||
|             # The presence_unavailable event should be triggered. | ||||
|             events.add('unavailable') | ||||
|  | ||||
|         self.stream_start() | ||||
|         self.xmpp.add_event_handler('got_offline', got_offline) | ||||
|         self.xmpp.add_event_handler('presence_unavailable', unavailable) | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <presence type="unavailable" from="otheruser@localhost" /> | ||||
|         """) | ||||
|  | ||||
|         # Give event queue time to process. | ||||
|         time.sleep(0.1) | ||||
|  | ||||
|         self.assertEqual(events, set(('unavailable',)), | ||||
|                 "Got offline incorrectly triggered: %s." % events) | ||||
|  | ||||
|     def testGotOffline(self): | ||||
|         """Test that got_offline is triggered properly.""" | ||||
|         events = [] | ||||
|  | ||||
|         def got_offline(presence): | ||||
|             events.append('got_offline') | ||||
|  | ||||
|         self.stream_start() | ||||
|         self.xmpp.add_event_handler('got_offline', got_offline) | ||||
|  | ||||
|         # Setup roster. Use a 'set' instead of 'result' so we | ||||
|         # don't have to handle get_roster() blocking. | ||||
|         # | ||||
|         # We use the stream to initialize the roster to make | ||||
|         # the test independent of the roster implementation. | ||||
|         self.stream_recv(""" | ||||
|           <iq type="set"> | ||||
|             <query xmlns="jabber:iq:roster"> | ||||
|               <item jid="otheruser@localhost" | ||||
|                     name="Other User" | ||||
|                     subscription="both"> | ||||
|                 <group>Testers</group> | ||||
|               </item> | ||||
|             </query> | ||||
|           </iq> | ||||
|         """) | ||||
|  | ||||
|         # Contact comes online. | ||||
|         self.stream_recv(""" | ||||
|           <presence from="otheruser@localhost/foobar" /> | ||||
|         """) | ||||
|  | ||||
|         # Contact goes offline, should trigger got_offline. | ||||
|         self.stream_recv(""" | ||||
|           <presence from="otheruser@localhost/foobar" | ||||
|                     type="unavailable" /> | ||||
|         """) | ||||
|  | ||||
|         # Give event queue time to process. | ||||
|         time.sleep(0.1) | ||||
|  | ||||
|         self.assertEqual(events, ['got_offline'], | ||||
|                 "Got offline incorrectly triggered: %s" % events) | ||||
|  | ||||
|     def testGotOnline(self): | ||||
|         """Test that got_online is triggered properly.""" | ||||
|  | ||||
|         events = set() | ||||
|  | ||||
|         def presence_available(p): | ||||
|             events.add('presence_available') | ||||
|  | ||||
|         def got_online(p): | ||||
|             events.add('got_online') | ||||
|  | ||||
|         self.stream_start() | ||||
|         self.xmpp.add_event_handler('presence_available', presence_available) | ||||
|         self.xmpp.add_event_handler('got_online', got_online) | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <presence from="user@localhost" /> | ||||
|         """) | ||||
|  | ||||
|         # Give event queue time to process. | ||||
|         time.sleep(0.1) | ||||
|  | ||||
|         expected = set(('presence_available', 'got_online')) | ||||
|         self.assertEqual(events, expected, | ||||
|                 "Incorrect events triggered: %s" % events) | ||||
|  | ||||
|     def testAutoAuthorizeAndSubscribe(self): | ||||
|         """ | ||||
|         Test auto authorizing and auto subscribing | ||||
|         to subscription requests. | ||||
|         """ | ||||
|  | ||||
|         events = set() | ||||
|  | ||||
|         def presence_subscribe(p): | ||||
|             events.add('presence_subscribe') | ||||
|  | ||||
|         def changed_subscription(p): | ||||
|             events.add('changed_subscription') | ||||
|  | ||||
|         self.stream_start(jid='tester@localhost') | ||||
|  | ||||
|         self.xmpp.add_event_handler('changed_subscription', | ||||
|                                     changed_subscription) | ||||
|         self.xmpp.add_event_handler('presence_subscribe', | ||||
|                                     presence_subscribe) | ||||
|  | ||||
|         # With these settings we should accept a subscription | ||||
|         # and request a subscription in return. | ||||
|         self.xmpp.auto_authorize = True | ||||
|         self.xmpp.auto_subscribe = True | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <presence from="user@localhost" type="subscribe" /> | ||||
|         """) | ||||
|  | ||||
|         self.stream_send_presence(""" | ||||
|           <presence to="user@localhost" type="subscribed" /> | ||||
|         """) | ||||
|  | ||||
|         self.stream_send_presence(""" | ||||
|           <presence to="user@localhost" type="subscribe" /> | ||||
|         """) | ||||
|  | ||||
|         expected = set(('presence_subscribe', 'changed_subscription')) | ||||
|         self.assertEqual(events, expected, | ||||
|                 "Incorrect events triggered: %s" % events) | ||||
|  | ||||
|     def testNoAutoAuthorize(self): | ||||
|         """Test auto rejecting subscription requests.""" | ||||
|  | ||||
|         events = set() | ||||
|  | ||||
|         def presence_subscribe(p): | ||||
|             events.add('presence_subscribe') | ||||
|  | ||||
|         def changed_subscription(p): | ||||
|             events.add('changed_subscription') | ||||
|  | ||||
|         self.stream_start(jid='tester@localhost') | ||||
|  | ||||
|         self.xmpp.add_event_handler('changed_subscription', | ||||
|                                     changed_subscription) | ||||
|         self.xmpp.add_event_handler('presence_subscribe', | ||||
|                                     presence_subscribe) | ||||
|  | ||||
|         # With this setting we should reject all subscriptions. | ||||
|         self.xmpp.auto_authorize = False | ||||
|  | ||||
|         self.stream_recv(""" | ||||
|           <presence from="user@localhost" type="subscribe" /> | ||||
|         """) | ||||
|  | ||||
|         self.stream_send_presence(""" | ||||
|           <presence to="user@localhost" type="unsubscribed" /> | ||||
|         """) | ||||
|  | ||||
|         expected = set(('presence_subscribe', 'changed_subscription')) | ||||
|         self.assertEqual(events, expected, | ||||
|                 "Incorrect events triggered: %s" % events) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamPresence) | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user