Compare commits
	
		
			66 Commits
		
	
	
		
			1.0-Beta6
			...
			0.9-conn-f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8bdfa77024 | ||
|   | 15ac3e9fba | ||
|   | e8d37b409c | ||
|   | 898f96f265 | ||
|   | 4fccd77685 | ||
|   | bf2bf29fc6 | ||
|   | 34dc236126 | ||
|   | 9464736551 | ||
|   | 47f1fb1690 | ||
|   | 66cf0c2021 | ||
|   | e7c37c4ec5 | ||
|   | 919c8c5633 | ||
|   | f54501a346 | ||
|   | d20cd6b3e6 | ||
|   | da6e1e47dc | ||
|   | 2f0f18a8c6 | ||
|   | 1c32668e18 | ||
|   | 77bff9cce7 | ||
|   | 1f3cfb98f1 | ||
|   | 4295a66c70 | ||
|   | 8227affd7f | ||
|   | 3a2f989c5e | ||
|   | 85a2715c7d | ||
|   | b03e6168a8 | ||
|   | 2a43f59a58 | ||
|   | 184f7cb8a4 | ||
|   | e1aa4d0b93 | ||
|   | 7930ed22f2 | ||
|   | 060b4c3938 | ||
|   | 49f5767aea | ||
|   | 4eb210bff5 | ||
|   | 1780ca900a | ||
|   | e6c2fde283 | ||
|   | ecf902bf16 | ||
|   | d76c0931ef | ||
|   | e18793152f | ||
|   | e388680269 | ||
|   | bee42e4a2f | ||
|   | 8e3227ae5e | ||
|   | 257bcadd96 | ||
|   | 3e5cdc8664 | ||
|   | 194e6bcb51 | ||
|   | 2e7024419a | ||
|   | 5235313aab | ||
|   | a2719b0bb0 | ||
|   | 71ad715caa | ||
|   | d452085049 | ||
|   | 8b3b8aca9e | ||
|   | e00dea7c0c | ||
|   | 520bf72e11 | ||
|   | 040f426f1a | ||
|   | 226b0e4297 | ||
|   | 0b2cd176b1 | ||
|   | 56b5cbe5b1 | ||
|   | 3e83b16a58 | ||
|   | de4d611d30 | ||
|   | e8d0fc37dc | ||
|   | 3f41fdd231 | ||
|   | 8e95ae2948 | ||
|   | 341c110b6a | ||
|   | 7522839141 | ||
|   | 4c410dd48a | ||
|   | a92075a659 | ||
|   | 7552efee5c | ||
|   | 6bc6ebb95d | ||
|   | e0c32b6d9b | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,6 @@ | |||||||
| *.pyc | *.pyc | ||||||
|  | .project | ||||||
| build/ | build/ | ||||||
|  | *.swp | ||||||
|  | .pydevproject | ||||||
|  | .settings | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,12 +1,8 @@ | |||||||
| Pre-requisites: | Pre-requisites: | ||||||
| - Python 3.1 or 2.6 | Python 3.1 or 2.6 | ||||||
|  |  | ||||||
| Install: | Install: | ||||||
| > python3 setup.py install | python3 setup.py install | ||||||
|  |  | ||||||
| Root 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] |  | ||||||
|   | |||||||
							
								
								
									
										123
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,125 +1,4 @@ | |||||||
| Copyright (c) 2010 Nathanael C. Fritz | Copyright (c) 2010 ICRL | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
|  |  | ||||||
| The above copyright notice and this permission notice shall be included in |  | ||||||
| all copies or substantial portions of the Software. |  | ||||||
|  |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |  | ||||||
| THE SOFTWARE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Licences of Bundled Third Pary Code |  | ||||||
| ----------------------------------- |  | ||||||
|  |  | ||||||
| dateutil - Extensions to the standard python 2.3+ datetime module. |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net> |  | ||||||
|  |  | ||||||
| All rights reserved. |  | ||||||
|  |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are met: |  | ||||||
|  |  | ||||||
|     * Redistributions of source code must retain the above copyright notice, |  | ||||||
|       this list of conditions and the following disclaimer. |  | ||||||
|     * Redistributions in binary form must reproduce the above copyright notice, |  | ||||||
|       this list of conditions and the following disclaimer in the documentation |  | ||||||
|       and/or other materials provided with the distribution. |  | ||||||
|     * Neither the name of the copyright holder nor the names of its |  | ||||||
|       contributors may be used to endorse or promote products derived from |  | ||||||
|       this software without specific prior written permission. |  | ||||||
|  |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |  | ||||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |  | ||||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |  | ||||||
| CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |  | ||||||
| EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |  | ||||||
| PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |  | ||||||
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |  | ||||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |  | ||||||
| NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |  | ||||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fixed_datetime |  | ||||||
| ~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Copyright (c) 2008, Red Innovation Ltd., Finland |  | ||||||
| All rights reserved. |  | ||||||
|  |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are met: |  | ||||||
|     * Redistributions of source code must retain the above copyright |  | ||||||
|       notice, this list of conditions and the following disclaimer. |  | ||||||
|     * Redistributions in binary form must reproduce the above copyright |  | ||||||
|       notice, this list of conditions and the following disclaimer in the |  | ||||||
|       documentation and/or other materials provided with the distribution. |  | ||||||
|     * Neither the name of Red Innovation nor the names of its contributors  |  | ||||||
|       may be used to endorse or promote products derived from this software  |  | ||||||
|       without specific prior written permission. |  | ||||||
|  |  | ||||||
| THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY |  | ||||||
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |  | ||||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |  | ||||||
| DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY |  | ||||||
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |  | ||||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |  | ||||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |  | ||||||
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |  | ||||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| OrderedDict - A port of the Python 2.7+ OrderedDict to Python 2.6 |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Copyright (c) 2009 Raymond Hettinger |  | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person |  | ||||||
| obtaining a copy of this software and associated documentation files |  | ||||||
| (the "Software"), to deal in the Software without restriction, |  | ||||||
| including without limitation the rights to use, copy, modify, merge, |  | ||||||
| publish, distribute, sublicense, and/or sell copies of the Software, |  | ||||||
| and to permit persons to whom the Software is furnished to do so, |  | ||||||
| subject to the following conditions: |  | ||||||
|  |  | ||||||
|     The above copyright notice and this permission notice shall be |  | ||||||
|     included in all copies or substantial portions of the Software. |  | ||||||
|  |  | ||||||
|     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
|     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |  | ||||||
|     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
|     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |  | ||||||
|     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |  | ||||||
|     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |  | ||||||
|     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |  | ||||||
|     OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| This software is subject to "The MIT License" |  | ||||||
|  |  | ||||||
| Copyright 2007-2010 David Alan Cridland |  | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								MANIFEST
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								MANIFEST
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | setup.py | ||||||
|  | sleekxmpp/__init__.py | ||||||
|  | sleekxmpp/basexmpp.py | ||||||
|  | sleekxmpp/clientxmpp.py | ||||||
|  | sleekxmpp/example.py | ||||||
|  | sleekxmpp/plugins/__init__.py | ||||||
|  | sleekxmpp/plugins/base.py | ||||||
|  | sleekxmpp/plugins/gmail_notify.py | ||||||
|  | sleekxmpp/plugins/xep_0004.py | ||||||
|  | sleekxmpp/plugins/xep_0009.py | ||||||
|  | sleekxmpp/plugins/xep_0030.py | ||||||
|  | sleekxmpp/plugins/xep_0045.py | ||||||
|  | sleekxmpp/plugins/xep_0050.py | ||||||
|  | sleekxmpp/plugins/xep_0060.py | ||||||
|  | sleekxmpp/plugins/xep_0078.py | ||||||
|  | sleekxmpp/plugins/xep_0086.py | ||||||
|  | sleekxmpp/plugins/xep_0092.py | ||||||
|  | sleekxmpp/plugins/xep_0199.py | ||||||
|  | sleekxmpp/stanza/__init__.py | ||||||
|  | sleekxmpp/stanza/iq.py | ||||||
|  | sleekxmpp/stanza/message.py | ||||||
|  | sleekxmpp/stanza/presence.py | ||||||
|  | sleekxmpp/xmlstream/__init__.py | ||||||
|  | sleekxmpp/xmlstream/stanzabase.py | ||||||
|  | sleekxmpp/xmlstream/statemachine.py | ||||||
|  | sleekxmpp/xmlstream/test.py | ||||||
|  | sleekxmpp/xmlstream/testclient.py | ||||||
|  | sleekxmpp/xmlstream/xmlstream.py | ||||||
|  | sleekxmpp/xmlstream/handler/__init__.py | ||||||
|  | sleekxmpp/xmlstream/handler/base.py | ||||||
|  | sleekxmpp/xmlstream/handler/callback.py | ||||||
|  | sleekxmpp/xmlstream/handler/waiter.py | ||||||
|  | sleekxmpp/xmlstream/handler/xmlcallback.py | ||||||
|  | sleekxmpp/xmlstream/handler/xmlwaiter.py | ||||||
|  | sleekxmpp/xmlstream/matcher/__init__.py | ||||||
|  | sleekxmpp/xmlstream/matcher/base.py | ||||||
|  | sleekxmpp/xmlstream/matcher/many.py | ||||||
|  | sleekxmpp/xmlstream/matcher/xmlmask.py | ||||||
|  | sleekxmpp/xmlstream/matcher/xpath.py | ||||||
							
								
								
									
										8
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								README
									
									
									
									
									
								
							| @@ -4,11 +4,6 @@ Hosted at http://wiki.github.com/fritzy/SleekXMPP/ | |||||||
| Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre | 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 | 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: | SleekXMPP has several design goals/philosophies: | ||||||
| - Low number of dependencies. | - Low number of dependencies. | ||||||
| - Every XEP as a plugin. | - Every XEP as a plugin. | ||||||
| @@ -42,9 +37,6 @@ Main Author: Nathan Fritz fritz@netflint.net | |||||||
| Contributors: Kevin Smith & Lance Stout | Contributors: Kevin Smith & Lance Stout | ||||||
| Patches: Remko Tronçon | Patches: Remko Tronçon | ||||||
|  |  | ||||||
| Dave Cridland, for his Suelta SASL library. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Feel free to add fritzy@netflint.net to your roster for direct support and comments. | 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 sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion. | ||||||
| Join sleek@conference.jabber.org for groupchat discussion. | Join sleek@conference.jabber.org for groupchat discussion. | ||||||
|   | |||||||
							
								
								
									
										48
									
								
								example.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								example.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | # coding=utf8 | ||||||
|  |  | ||||||
|  | import sleekxmpp | ||||||
|  | import logging | ||||||
|  | from optparse import OptionParser | ||||||
|  | import time | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | if sys.version_info < (3,0): | ||||||
|  | 	reload(sys) | ||||||
|  | 	sys.setdefaultencoding('utf8') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Example(sleekxmpp.ClientXMPP): | ||||||
|  | 	 | ||||||
|  | 	def __init__(self, jid, password): | ||||||
|  | 		sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  | 		self.add_event_handler("session_start", self.start) | ||||||
|  | 		self.add_event_handler("message", self.message) | ||||||
|  | 	 | ||||||
|  | 	def start(self, event): | ||||||
|  | 		self.getRoster() | ||||||
|  | 		self.sendPresence() | ||||||
|  |  | ||||||
|  | 	def message(self, msg): | ||||||
|  | 		msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  | 	#parse command line arguements | ||||||
|  | 	optp = OptionParser() | ||||||
|  | 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||||
|  | 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||||
|  | 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||||
|  | 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||||
|  | 	opts,args = optp.parse_args() | ||||||
|  | 	 | ||||||
|  | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  | 	xmpp = Example('user@gmail.com/sleekxmpp', 'password') | ||||||
|  | 	xmpp.registerPlugin('xep_0030')	 | ||||||
|  | 	xmpp.registerPlugin('xep_0004') | ||||||
|  | 	xmpp.registerPlugin('xep_0060') | ||||||
|  | 	xmpp.registerPlugin('xep_0199') | ||||||
|  | 	if xmpp.connect(('talk.google.com', 5222)): | ||||||
|  | 		xmpp.process(threaded=False) | ||||||
|  | 		print("done") | ||||||
|  | 	else: | ||||||
|  | 		print("Unable to connect.") | ||||||
| @@ -1,199 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| import getpass |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CommandBot(sleekxmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP bot that provides a basic |  | ||||||
|     adhoc command. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can intialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an intial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         # We add the command after session_start has fired |  | ||||||
|         # to ensure that the correct full JID is used. |  | ||||||
|  |  | ||||||
|         # If using a component, may also pass jid keyword parameter. |  | ||||||
|  |  | ||||||
|         self['xep_0050'].add_command(node='greeting', |  | ||||||
|                                      name='Greeting', |  | ||||||
|                                      handler=self._handle_command) |  | ||||||
|  |  | ||||||
|     def _handle_command(self, iq, session): |  | ||||||
|         """ |  | ||||||
|         Respond to the intial request for a command. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The iq stanza containing the command request. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|         form = self['xep_0004'].makeForm('form', 'Greeting') |  | ||||||
|         form.addField(var='greeting', |  | ||||||
|                       ftype='text-single', |  | ||||||
|                       label='Your greeting') |  | ||||||
|  |  | ||||||
|         session['payload'] = form |  | ||||||
|         session['next'] = self._handle_command_complete |  | ||||||
|         session['has_next'] = False |  | ||||||
|  |  | ||||||
|         # Other useful session values: |  | ||||||
|         # session['to']                    -- The JID that received the |  | ||||||
|         #                                     command request. |  | ||||||
|         # session['from']                  -- The JID that sent the |  | ||||||
|         #                                     command request. |  | ||||||
|         # session['has_next'] = True       -- There are more steps to complete |  | ||||||
|         # session['allow_complete'] = True -- Allow user to finish immediately |  | ||||||
|         #                                     and possibly skip steps |  | ||||||
|         # session['cancel'] = handler      -- Assign a handler for if the user |  | ||||||
|         #                                     cancels the command. |  | ||||||
|         # session['notes'] = [             -- Add informative notes about the |  | ||||||
|         #   ('info', 'Info message'),         command's results. |  | ||||||
|         #   ('warning', 'Warning message'), |  | ||||||
|         #   ('error', 'Error message')] |  | ||||||
|  |  | ||||||
|         return session |  | ||||||
|  |  | ||||||
|     def _handle_command_complete(self, payload, session): |  | ||||||
|         """ |  | ||||||
|         Process a command result from the user. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             payload -- Either a single item, such as a form, or a list |  | ||||||
|                        of items or forms if more than one form was |  | ||||||
|                        provided to the user. The payload may be any |  | ||||||
|                        stanza, such as jabber:x:oob for out of band |  | ||||||
|                        data, or jabber:x:data for typical data forms. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         # In this case (as is typical), the payload is a form |  | ||||||
|         form = payload |  | ||||||
|  |  | ||||||
|         greeting = form['values']['greeting'] |  | ||||||
|         self.send_message(mto=session['from'], |  | ||||||
|                           mbody="%s, World!" % greeting) |  | ||||||
|  |  | ||||||
|         # Having no return statement is the same as unsetting the 'payload' |  | ||||||
|         # and 'next' session values and returning the session. |  | ||||||
|  |  | ||||||
|         # Unless it is the final step, always return the session dictionary. |  | ||||||
|  |  | ||||||
|         session['payload'] = None |  | ||||||
|         session['next'] = None |  | ||||||
|  |  | ||||||
|         return session |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |  | ||||||
|                     help="JID to use") |  | ||||||
|     optp.add_option("-p", "--password", dest="password", |  | ||||||
|                     help="password to use") |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if opts.jid is None: |  | ||||||
|         opts.jid = raw_input("Username: ") |  | ||||||
|     if opts.password is None: |  | ||||||
|         opts.password = getpass.getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the CommandBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = CommandBot(opts.jid, opts.password) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands |  | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |  | ||||||
|     # to adjust the SSL version used: |  | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |  | ||||||
|  |  | ||||||
|     # If you want to verify the SSL certificates offered by a server: |  | ||||||
|     # xmpp.ca_certs = "path/to/ca/cert" |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         # If you do not have the pydns library installed, you will need |  | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
| @@ -1,208 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| import getpass |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CommandUserBot(sleekxmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP bot that uses the adhoc command |  | ||||||
|     provided by the adhoc_provider.py example. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, other, greeting): |  | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.command_provider = other |  | ||||||
|         self.greeting = greeting |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can intialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an intial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         # We first create a session dictionary containing: |  | ||||||
|         #   'next'  -- the handler to execute on a successful response |  | ||||||
|         #   'error' -- the handler to execute if an error occurs |  | ||||||
|  |  | ||||||
|         # The session may also contain custom data. |  | ||||||
|  |  | ||||||
|         session = {'greeting': self.greeting, |  | ||||||
|                    'next': self._command_start, |  | ||||||
|                    'error': self._command_error} |  | ||||||
|  |  | ||||||
|         self['xep_0050'].start_command(jid=self.command_provider, |  | ||||||
|                                        node='greeting', |  | ||||||
|                                        session=session) |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. |  | ||||||
|         """ |  | ||||||
|         logging.info(msg['body']) |  | ||||||
|  |  | ||||||
|     def _command_start(self, iq, session): |  | ||||||
|         """ |  | ||||||
|         Process the initial command result. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The iq stanza containing the command result. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         # The greeting command provides a form with a single field: |  | ||||||
|         # <x xmlns="jabber:x:data" type="form"> |  | ||||||
|         #   <field var="greeting" |  | ||||||
|         #          type="text-single" |  | ||||||
|         #          label="Your greeting" /> |  | ||||||
|         # </x> |  | ||||||
|  |  | ||||||
|         form = self['xep_0004'].makeForm(ftype='submit') |  | ||||||
|         form.addField(var='greeting', |  | ||||||
|                       value=session['greeting']) |  | ||||||
|  |  | ||||||
|         session['payload'] = form |  | ||||||
|  |  | ||||||
|         # We don't need to process the next result. |  | ||||||
|         session['next'] = None |  | ||||||
|  |  | ||||||
|         # Other options include using: |  | ||||||
|         # continue_command() -- Continue to the next step in the workflow |  | ||||||
|         # cancel_command()   -- Stop command execution. |  | ||||||
|  |  | ||||||
|         self['xep_0050'].complete_command(session) |  | ||||||
|  |  | ||||||
|     def _command_error(self, iq, session): |  | ||||||
|         """ |  | ||||||
|         Process an error that occurs during command execution. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The iq stanza containing the error. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|         logging.error("COMMAND: %s %s" % (iq['error']['condition'], |  | ||||||
|                                           iq['error']['text'])) |  | ||||||
|  |  | ||||||
|         # Terminate the command's execution and clear its session. |  | ||||||
|         # The session will automatically be cleared if no error |  | ||||||
|         # handler is provided. |  | ||||||
|         self['xep_0050'].terminate_command(session) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |  | ||||||
|                     help="JID to use") |  | ||||||
|     optp.add_option("-p", "--password", dest="password", |  | ||||||
|                     help="password to use") |  | ||||||
|     optp.add_option("-o", "--other", dest="other", |  | ||||||
|                     help="JID providing commands") |  | ||||||
|     optp.add_option("-g", "--greeting", dest="greeting", |  | ||||||
|                     help="Greeting") |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if opts.jid is None: |  | ||||||
|         opts.jid = raw_input("Username: ") |  | ||||||
|     if opts.password is None: |  | ||||||
|         opts.password = getpass.getpass("Password: ") |  | ||||||
|     if opts.other is None: |  | ||||||
|         opts.other = raw_input("JID Providing Commands: ") |  | ||||||
|     if opts.greeting is None: |  | ||||||
|         opts.other = raw_input("Greeting: ") |  | ||||||
|  |  | ||||||
|     # Setup the CommandBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = CommandUserBot(opts.jid, opts.password, opts.other, opts.greeting) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands |  | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |  | ||||||
|     # to adjust the SSL version used: |  | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |  | ||||||
|  |  | ||||||
|     # If you want to verify the SSL certificates offered by a server: |  | ||||||
|     # xmpp.ca_certs = "path/to/ca/cert" |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         # If you do not have the pydns library installed, you will need |  | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| <config xmlns="sleekxmpp:config"> |  | ||||||
|   <jid>component.localhost</jid> |  | ||||||
|   <secret>ssshh</secret> |  | ||||||
|   <server>localhost</server> |  | ||||||
|   <port>8888</port> |  | ||||||
|  |  | ||||||
|   <query xmlns="jabber:iq:roster"> |  | ||||||
|     <item jid="user@example.com" subscription="both" /> |  | ||||||
|   </query> |  | ||||||
| </config> |  | ||||||
| @@ -1,190 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp.componentxmpp import ComponentXMPP |  | ||||||
| from sleekxmpp.stanza.roster import Roster |  | ||||||
| from sleekxmpp.xmlstream import ElementBase |  | ||||||
| from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Config(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     In order to make loading and manipulating an XML config |  | ||||||
|     file easier, we will create a custom stanza object for |  | ||||||
|     our config XML file contents. See the documentation |  | ||||||
|     on stanza objects for more information on how to create |  | ||||||
|     and use stanza objects and stanza plugins. |  | ||||||
|  |  | ||||||
|     We will reuse the IQ roster query stanza to store roster |  | ||||||
|     information since it already exists. |  | ||||||
|  |  | ||||||
|     Example config XML: |  | ||||||
|       <config xmlns="sleekxmpp:config"> |  | ||||||
|         <jid>component.localhost</jid> |  | ||||||
|         <secret>ssshh</secret> |  | ||||||
|         <server>localhost</server> |  | ||||||
|         <port>8888</port> |  | ||||||
|  |  | ||||||
|         <query xmlns="jabber:iq:roster"> |  | ||||||
|           <item jid="user@example.com" subscription="both" /> |  | ||||||
|         </query> |  | ||||||
|       </config> |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = "config" |  | ||||||
|     namespace = "sleekxmpp:config" |  | ||||||
|     interfaces = set(('jid', 'secret', 'server', 'port')) |  | ||||||
|     sub_interfaces = interfaces |  | ||||||
|  |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Config, Roster) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ConfigComponent(ComponentXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP component that uses an external XML |  | ||||||
|     file to store its configuration data. To make testing |  | ||||||
|     that the component works, it will also echo messages sent |  | ||||||
|     to it. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, config): |  | ||||||
|         """ |  | ||||||
|         Create a ConfigComponent. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             config      -- The XML contents of the config file. |  | ||||||
|             config_file -- The XML config file object itself. |  | ||||||
|         """ |  | ||||||
|         ComponentXMPP.__init__(self, config['jid'], |  | ||||||
|                                      config['secret'], |  | ||||||
|                                      config['server'], |  | ||||||
|                                      config['port']) |  | ||||||
|  |  | ||||||
|         # Store the roster information. |  | ||||||
|         self.roster = config['roster']['items'] |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the component establishes its connection with the |  | ||||||
|         # server and the XML streams are ready for use. We |  | ||||||
|         # want to listen for this event so that we we can |  | ||||||
|         # broadcast any needed initial presence stanzas. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The message event is triggered whenever a message |  | ||||||
|         # stanza is received. Be aware that that includes |  | ||||||
|         # MUC messages and error messages. |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         The typical action for the session_start event in a component |  | ||||||
|         is to broadcast presence stanzas to all subscribers to the |  | ||||||
|         component. Note that the component does not have a roster |  | ||||||
|         provided by the XMPP server. In this case, we have possibly |  | ||||||
|         saved a roster in the component's configuration file. |  | ||||||
|  |  | ||||||
|         Since the component may use any number of JIDs, you should |  | ||||||
|         also include the JID that is sending the presence. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         for jid in self.roster: |  | ||||||
|             if self.roster[jid]['subscription'] != 'none': |  | ||||||
|                 self.sendPresence(pfrom=self.jid, pto=jid) |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. Be aware that this also |  | ||||||
|         includes MUC messages and error messages. It is usually |  | ||||||
|         a good idea to check the messages's type before processing |  | ||||||
|         or sending replies. |  | ||||||
|  |  | ||||||
|         Since a component may send messages from any number of JIDs, |  | ||||||
|         it is best to always include a from JID. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         # The reply method will use the messages 'to' JID as the |  | ||||||
|         # outgoing reply's 'from' JID. |  | ||||||
|         msg.reply("Thanks for sending\n%(body)s" % msg).send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # Component name and secret options. |  | ||||||
|     optp.add_option("-c", "--config", help="path to config file", |  | ||||||
|                     dest="config", default="config.xml") |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     # Load configuration data. |  | ||||||
|     config_file = open(opts.config, 'r+') |  | ||||||
|     config_data = "\n".join([line for line in config_file]) |  | ||||||
|     config = Config(xml=ET.fromstring(config_data)) |  | ||||||
|     config_file.close() |  | ||||||
|  |  | ||||||
|     # Setup the ConfigComponent and register plugins. Note that while plugins |  | ||||||
|     # may have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = ConfigComponent(config) |  | ||||||
|     xmpp.registerPlugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.registerPlugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.registerPlugin('xep_0060') # PubSub |  | ||||||
|     xmpp.registerPlugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
| @@ -1,198 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import time |  | ||||||
| import logging |  | ||||||
| import getpass |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Disco(sleekxmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A demonstration for using basic service discovery. |  | ||||||
|  |  | ||||||
|     Send a disco#info and disco#items request to a JID/node combination, |  | ||||||
|     and print out the results. |  | ||||||
|  |  | ||||||
|     May also request only particular info categories such as just features, |  | ||||||
|     or just items. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, target_jid, target_node='', get=''): |  | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # Using service discovery requires the XEP-0030 plugin. |  | ||||||
|         self.register_plugin('xep_0030') |  | ||||||
|  |  | ||||||
|         self.get = get |  | ||||||
|         self.target_jid = target_jid |  | ||||||
|         self.target_node = target_node |  | ||||||
|  |  | ||||||
|         # Values to control which disco entities are reported |  | ||||||
|         self.info_types = ['', 'all', 'info', 'identities', 'features'] |  | ||||||
|         self.identity_types = ['', 'all', 'info', 'identities'] |  | ||||||
|         self.feature_types = ['', 'all', 'info', 'features'] |  | ||||||
|         self.items_types = ['', 'all', 'items'] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can intialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an intial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         In this case, we send disco#info and disco#items |  | ||||||
|         stanzas to the requested JID and print the results. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.get_roster() |  | ||||||
|         self.send_presence() |  | ||||||
|  |  | ||||||
|         if self.get in self.info_types: |  | ||||||
|             # By using block=True, the result stanza will be |  | ||||||
|             # returned. Execution will block until the reply is |  | ||||||
|             # received. Non-blocking options would be to listen |  | ||||||
|             # for the disco_info event, or passing a handler |  | ||||||
|             # function using the callback parameter. |  | ||||||
|             info = self['xep_0030'].get_info(jid=self.target_jid, |  | ||||||
|                                              node=self.target_node, |  | ||||||
|                                              block=True) |  | ||||||
|         if self.get in self.items_types: |  | ||||||
|             # The same applies from above. Listen for the |  | ||||||
|             # disco_items event or pass a callback function |  | ||||||
|             # if you need to process a non-blocking request. |  | ||||||
|             items = self['xep_0030'].get_items(jid=self.target_jid, |  | ||||||
|                                                node=self.target_node, |  | ||||||
|                                                block=True) |  | ||||||
|         else: |  | ||||||
|             logging.error("Invalid disco request type.") |  | ||||||
|             self.disconnect() |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         header = 'XMPP Service Discovery: %s' % self.target_jid |  | ||||||
|         print(header) |  | ||||||
|         print('-' * len(header)) |  | ||||||
|         if self.target_node != '': |  | ||||||
|             print('Node: %s' % self.target_node) |  | ||||||
|             print('-' * len(header)) |  | ||||||
|  |  | ||||||
|         if self.get in self.identity_types: |  | ||||||
|             print('Identities:') |  | ||||||
|             for identity in info['disco_info']['identities']: |  | ||||||
|                 print('  - %s' % str(identity)) |  | ||||||
|  |  | ||||||
|         if self.get in self.feature_types: |  | ||||||
|             print('Features:') |  | ||||||
|             for feature in info['disco_info']['features']: |  | ||||||
|                 print('  - %s' % feature) |  | ||||||
|  |  | ||||||
|         if self.get in self.items_types: |  | ||||||
|             print('Items:') |  | ||||||
|             for item in items['disco_items']['items']: |  | ||||||
|                 print('  - %s' % str(item)) |  | ||||||
|  |  | ||||||
|         self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|     optp.version = '%%prog 0.1' |  | ||||||
|     optp.usage = "Usage: %%prog [options] %s <jid> [<node>]" % \ |  | ||||||
|                              'all|info|items|identities|features' |  | ||||||
|  |  | ||||||
|     optp.add_option('-q','--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', |  | ||||||
|                     dest='loglevel', |  | ||||||
|                     const=logging.ERROR, |  | ||||||
|                     default=logging.ERROR) |  | ||||||
|     optp.add_option('-d','--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', |  | ||||||
|                     dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, |  | ||||||
|                     default=logging.ERROR) |  | ||||||
|     optp.add_option('-v','--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', |  | ||||||
|                     dest='loglevel', |  | ||||||
|                     const=5, |  | ||||||
|                     default=logging.ERROR) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |  | ||||||
|                     help="JID to use") |  | ||||||
|     optp.add_option("-p", "--password", dest="password", |  | ||||||
|                     help="password to use") |  | ||||||
|     opts,args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if len(args) < 2: |  | ||||||
|         optp.print_help() |  | ||||||
|         exit() |  | ||||||
|  |  | ||||||
|     if len(args) == 2: |  | ||||||
|         args = (args[0], args[1], '') |  | ||||||
|  |  | ||||||
|     if opts.jid is None: |  | ||||||
|         opts.jid = raw_input("Username: ") |  | ||||||
|     if opts.password is None: |  | ||||||
|         opts.password = getpass.getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the Disco browser. |  | ||||||
|     xmpp = Disco(opts.jid, opts.password, args[1], args[2], args[0]) |  | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |  | ||||||
|     # to adjust the SSL version used: |  | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |  | ||||||
|  |  | ||||||
|     # If you want to verify the SSL certificates offered by a server: |  | ||||||
|     # xmpp.ca_certs = "path/to/ca/cert" |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         # If you do not have the pydns library installed, you will need |  | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
| @@ -1,142 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| import getpass |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EchoBot(sleekxmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP bot that will echo messages it |  | ||||||
|     receives, along with a short thank you message. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can intialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The message event is triggered whenever a message |  | ||||||
|         # stanza is received. Be aware that that includes |  | ||||||
|         # MUC messages and error messages. |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an intial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. Be aware that this also |  | ||||||
|         includes MUC messages and error messages. It is usually |  | ||||||
|         a good idea to check the messages's type before processing |  | ||||||
|         or sending replies. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         msg.reply("Thanks for sending\n%(body)s" % msg).send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |  | ||||||
|                     help="JID to use") |  | ||||||
|     optp.add_option("-p", "--password", dest="password", |  | ||||||
|                     help="password to use") |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if opts.jid is None: |  | ||||||
|         opts.jid = raw_input("Username: ") |  | ||||||
|     if opts.password is None: |  | ||||||
|         opts.password = getpass.getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the EchoBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = EchoBot(opts.jid, opts.password) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060') # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |  | ||||||
|     # to adjust the SSL version used: |  | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |  | ||||||
|  |  | ||||||
|     # If you want to verify the SSL certificates offered by a server: |  | ||||||
|     # xmpp.ca_certs = "path/to/ca/cert" |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         # If you do not have the pydns library installed, you will need |  | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
							
								
								
									
										186
									
								
								examples/muc.py
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								examples/muc.py
									
									
									
									
									
								
							| @@ -1,186 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MUCBot(sleekxmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP bot that will greets those |  | ||||||
|     who enter the room, and acknowledge any messages |  | ||||||
|     that mentions the bot's nickname. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, room, nick): |  | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.room = room |  | ||||||
|         self.nick = nick |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can intialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The groupchat_message event is triggered whenever a message |  | ||||||
|         # stanza is received from any chat room. If you also also |  | ||||||
|         # register a handler for the 'message' event, MUC messages |  | ||||||
|         # will be processed by both handlers. |  | ||||||
|         self.add_event_handler("groupchat_message", self.muc_message) |  | ||||||
|  |  | ||||||
|         # The groupchat_presence event is triggered whenever a |  | ||||||
|         # presence stanza is received from any chat room, including |  | ||||||
|         # any presences you send yourself. To limit event handling |  | ||||||
|         # to a single room, use the events muc::room@server::presence, |  | ||||||
|         # muc::room@server::got_online, or muc::room@server::got_offline. |  | ||||||
|         self.add_event_handler("muc::%s::got_online" % self.room, |  | ||||||
|                                self.muc_online) |  | ||||||
|          |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an intial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.getRoster() |  | ||||||
|         self.sendPresence() |  | ||||||
|         self.plugin['xep_0045'].joinMUC(self.room,  |  | ||||||
|                                         self.nick,  |  | ||||||
|                                         # If a room password is needed, use: |  | ||||||
|                                         # password=the_room_password, |  | ||||||
|                                         wait=True) |  | ||||||
|  |  | ||||||
|     def muc_message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas from any chat room. Be aware  |  | ||||||
|         that if you also have any handlers for the 'message' event, |  | ||||||
|         message stanzas may be processed by both handlers, so check |  | ||||||
|         the 'type' attribute when using a 'message' event handler. |  | ||||||
|  |  | ||||||
|         Whenever the bot's nickname is mentioned, respond to |  | ||||||
|         the message. |  | ||||||
|  |  | ||||||
|         IMPORTANT: Always check that a message is not from yourself, |  | ||||||
|                    otherwise you will create an infinite loop responding |  | ||||||
|                    to your own messages. |  | ||||||
|  |  | ||||||
|         This handler will reply to messages that mention  |  | ||||||
|         the bot's nickname. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         if msg['mucnick'] != self.nick and self.nick in msg['body']: |  | ||||||
|             self.send_message(mto=msg['from'].bare, |  | ||||||
|                               mbody="I heard that, %s." % msg['mucnick'], |  | ||||||
|                               mtype='groupchat') |  | ||||||
|  |  | ||||||
|     def muc_online(self, presence): |  | ||||||
|         """ |  | ||||||
|         Process a presence stanza from a chat room. In this case, |  | ||||||
|         presences from users that have just come online are  |  | ||||||
|         handled by sending a welcome message that includes |  | ||||||
|         the user's nickname and role in the room. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             presence -- The received presence stanza. See the  |  | ||||||
|                         documentation for the Presence stanza |  | ||||||
|                         to see how else it may be used. |  | ||||||
|         """ |  | ||||||
|         if presence['muc']['nick'] != self.nick: |  | ||||||
|             self.send_message(mto=presence['from'].bare, |  | ||||||
|                               mbody="Hello, %s %s" % (presence['muc']['role'], |  | ||||||
|                                                       presence['muc']['nick']), |  | ||||||
|                               mtype='groupchat') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |  | ||||||
|                     help="JID to use") |  | ||||||
|     optp.add_option("-p", "--password", dest="password", |  | ||||||
|                     help="password to use") |  | ||||||
|     optp.add_option("-r", "--room", dest="room", |  | ||||||
|                     help="MUC room to join") |  | ||||||
|     optp.add_option("-n", "--nick", dest="nick", |  | ||||||
|                     help="MUC nickname") |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if None in [opts.jid, opts.password, opts.room, opts.nick]: |  | ||||||
|         optp.print_help() |  | ||||||
|         sys.exit(1) |  | ||||||
|  |  | ||||||
|     # Setup the MUCBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = MUCBot(opts.jid, opts.password, opts.room, opts.nick) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0045') # Multi-User Chat |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         # If you do not have the pydns library installed, you will need |  | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
							
								
								
									
										140
									
								
								examples/ping.py
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								examples/ping.py
									
									
									
									
									
								
							| @@ -1,140 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| import getpass |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PingTest(sleekxmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP bot that will send a ping request |  | ||||||
|     to a given JID. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, pingjid): |  | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|         if pingjid is None: |  | ||||||
|             pingjid = self.jid |  | ||||||
|         self.pingjid = pingjid |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can intialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an intial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|         result = self['xep_0199'].send_ping(self.pingjid, |  | ||||||
|                                             timeout=10, |  | ||||||
|                                             errorfalse=True) |  | ||||||
|         logging.info("Pinging...") |  | ||||||
|         if result is False: |  | ||||||
|             logging.info("Couldn't ping.") |  | ||||||
|             self.disconnect() |  | ||||||
|             sys.exit(1) |  | ||||||
|         else: |  | ||||||
|             logging.info("Success! RTT: %s" % str(result)) |  | ||||||
|             self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|     optp.add_option('-t', '--pingto', help='set jid to ping', |  | ||||||
|                     action='store', type='string', dest='pingjid', |  | ||||||
|                     default=None) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |  | ||||||
|                     help="JID to use") |  | ||||||
|     optp.add_option("-p", "--password", dest="password", |  | ||||||
|                     help="password to use") |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if opts.jid is None: |  | ||||||
|         opts.jid = raw_input("Username: ") |  | ||||||
|     if opts.password is None: |  | ||||||
|         opts.password = getpass.getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the PingTest and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = PingTest(opts.jid, opts.password, opts.pingjid) |  | ||||||
|     xmpp.register_plugin('xep_0030')  # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004')  # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060')  # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199')  # XMPP Ping |  | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |  | ||||||
|     # to adjust the SSL version used: |  | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |  | ||||||
|  |  | ||||||
|     # If you want to verify the SSL certificates offered by a server: |  | ||||||
|     # xmpp.ca_certs = "path/to/ca/cert" |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         # If you do not have the pydns library installed, you will need |  | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
| @@ -1,167 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| import getpass |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EchoBot(sleekxmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP bot that will echo messages it |  | ||||||
|     receives, along with a short thank you message. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can intialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The message event is triggered whenever a message |  | ||||||
|         # stanza is received. Be aware that that includes |  | ||||||
|         # MUC messages and error messages. |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an intial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. Be aware that this also |  | ||||||
|         includes MUC messages and error messages. It is usually |  | ||||||
|         a good idea to check the messages's type before processing |  | ||||||
|         or sending replies. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         msg.reply("Thanks for sending\n%(body)s" % msg).send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |  | ||||||
|                     help="JID to use") |  | ||||||
|     optp.add_option("-p", "--password", dest="password", |  | ||||||
|                     help="password to use") |  | ||||||
|     optp.add_option("--phost", dest="proxy_host", |  | ||||||
|                     help="Proxy hostname") |  | ||||||
|     optp.add_option("--pport", dest="proxy_port", |  | ||||||
|                     help="Proxy port") |  | ||||||
|     optp.add_option("--puser", dest="proxy_user", |  | ||||||
|                     help="Proxy username") |  | ||||||
|     optp.add_option("--ppass", dest="proxy_pass", |  | ||||||
|                     help="Proxy password") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if opts.jid is None: |  | ||||||
|         opts.jid = raw_input("Username: ") |  | ||||||
|     if opts.password is None: |  | ||||||
|         opts.password = getpass.getpass("Password: ") |  | ||||||
|     if opts.proxy_host is None: |  | ||||||
|         opts.proxy_host = raw_input("Proxy host: ") |  | ||||||
|     if opts.proxy_port is None: |  | ||||||
|         opts.proxy_port = raw_input("Proxy port: ") |  | ||||||
|     if opts.proxy_user is None: |  | ||||||
|         opts.proxy_user = raw_input("Proxy username: ") |  | ||||||
|     if opts.proxy_pass is None and opts.proxy_user: |  | ||||||
|         opts.proxy_pass = getpass.getpass("Proxy password: ") |  | ||||||
|  |  | ||||||
|     # Setup the EchoBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = EchoBot(opts.jid, opts.password) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060') # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |  | ||||||
|     # to adjust the SSL version used: |  | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |  | ||||||
|  |  | ||||||
|     # If you want to verify the SSL certificates offered by a server: |  | ||||||
|     # xmpp.ca_certs = "path/to/ca/cert" |  | ||||||
|  |  | ||||||
|     xmpp.use_proxy = True |  | ||||||
|     xmpp.proxy_config = { |  | ||||||
|         'host': opts.proxy_host, |  | ||||||
|         'port': int(opts.proxy_port), |  | ||||||
|         'username': opts.proxy_user, |  | ||||||
|         'password': opts.proxy_pass} |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         # If you do not have the pydns library installed, you will need |  | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Dann Martens |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \ |  | ||||||
|     ANY_ALL, Future |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| class Boomerang(Endpoint): |  | ||||||
|      |  | ||||||
|     def FQN(self): |  | ||||||
|         return 'boomerang' |  | ||||||
|            |  | ||||||
|     @remote |  | ||||||
|     def throw(self): |  | ||||||
|         print "Duck!" |  | ||||||
|              |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def main(): |  | ||||||
|  |  | ||||||
|     session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****') |  | ||||||
|  |  | ||||||
|     session.new_handler(ANY_ALL, Boomerang)     |  | ||||||
|      |  | ||||||
|     boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang) |  | ||||||
|          |  | ||||||
|     callback = Future() |  | ||||||
|          |  | ||||||
|     boomerang.async(callback).throw() |  | ||||||
|      |  | ||||||
|     time.sleep(10) |  | ||||||
|      |  | ||||||
|     session.close() |  | ||||||
|      |  | ||||||
|      |  | ||||||
|      |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     main() |  | ||||||
|      |  | ||||||
| @@ -1,53 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Dann Martens |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \ |  | ||||||
|     ANY_ALL |  | ||||||
| import threading |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| class Thermostat(Endpoint): |  | ||||||
|      |  | ||||||
|     def FQN(self): |  | ||||||
|         return 'thermostat' |  | ||||||
|      |  | ||||||
|     def __init(self, initial_temperature): |  | ||||||
|         self._temperature = initial_temperature |  | ||||||
|         self._event = threading.Event()         |  | ||||||
|      |  | ||||||
|     @remote |  | ||||||
|     def set_temperature(self, temperature): |  | ||||||
|         return NotImplemented |  | ||||||
|      |  | ||||||
|     @remote |  | ||||||
|     def get_temperature(self): |  | ||||||
|         return NotImplemented |  | ||||||
|  |  | ||||||
|     @remote(False) |  | ||||||
|     def release(self): |  | ||||||
|         return NotImplemented |  | ||||||
|          |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def main(): |  | ||||||
|  |  | ||||||
|     session = Remote.new_session('operator@xmpp.org/rpc', '*****') |  | ||||||
|      |  | ||||||
|     thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat) |  | ||||||
|      |  | ||||||
|     print("Current temperature is %s" % thermostat.get_temperature()) |  | ||||||
|      |  | ||||||
|     thermostat.set_temperature(20) |  | ||||||
|      |  | ||||||
|     time.sleep(10) |  | ||||||
|      |  | ||||||
|     session.close() |  | ||||||
|      |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     main() |  | ||||||
|      |  | ||||||
| @@ -1,52 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Dann Martens |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \ |  | ||||||
|     ANY_ALL |  | ||||||
| import threading |  | ||||||
|  |  | ||||||
| class Thermostat(Endpoint): |  | ||||||
|      |  | ||||||
|     def FQN(self): |  | ||||||
|         return 'thermostat' |  | ||||||
|      |  | ||||||
|     def __init(self, initial_temperature): |  | ||||||
|         self._temperature = initial_temperature |  | ||||||
|         self._event = threading.Event()         |  | ||||||
|      |  | ||||||
|     @remote |  | ||||||
|     def set_temperature(self, temperature): |  | ||||||
|         print("Setting temperature to %s" % temperature) |  | ||||||
|         self._temperature = temperature |  | ||||||
|      |  | ||||||
|     @remote |  | ||||||
|     def get_temperature(self): |  | ||||||
|         return self._temperature |  | ||||||
|  |  | ||||||
|     @remote(False) |  | ||||||
|     def release(self): |  | ||||||
|         self._event.set()  |  | ||||||
|          |  | ||||||
|     def wait_for_release(self): |  | ||||||
|         self._event.wait()         |  | ||||||
|      |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def main(): |  | ||||||
|  |  | ||||||
|     session = Remote.new_session('sleek@xmpp.org/rpc', '*****') |  | ||||||
|      |  | ||||||
|     thermostat = session.new_handler(ANY_ALL, Thermostat, 18) |  | ||||||
|      |  | ||||||
|     thermostat.wait_for_release() |  | ||||||
|      |  | ||||||
|     session.close() |  | ||||||
|      |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     main() |  | ||||||
|      |  | ||||||
							
								
								
									
										56
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								setup.py
									
									
									
									
									
								
							| @@ -12,22 +12,20 @@ | |||||||
| from distutils.core import setup | from distutils.core import setup | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
|  |  | ||||||
| # if 'cygwin' in sys.platform.lower(): | # if 'cygwin' in sys.platform.lower(): | ||||||
| #     min_version = '0.6c6' | #     min_version = '0.6c6' | ||||||
| # else: | # else: | ||||||
| #     min_version = '0.6a9' | #     min_version = '0.6a9' | ||||||
| # | #  | ||||||
| # try: | # try: | ||||||
| #     use_setuptools(min_version=min_version) | #     use_setuptools(min_version=min_version) | ||||||
| # except TypeError: | # except TypeError: | ||||||
| #     # locally installed ez_setup won't have min_version | #     # locally installed ez_setup won't have min_version | ||||||
| #     use_setuptools() | #     use_setuptools() | ||||||
| # | #  | ||||||
| # from setuptools import setup, find_packages, Extension, Feature | # from setuptools import setup, find_packages, Extension, Feature | ||||||
|  |  | ||||||
| VERSION          = sleekxmpp.__version__ | VERSION          = '0.2.3.1' | ||||||
| DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' | DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' | ||||||
| LONG_DESCRIPTION = """ | LONG_DESCRIPTION = """ | ||||||
| SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc). | SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc). | ||||||
| @@ -39,41 +37,17 @@ CLASSIFIERS      = [ 'Intended Audience :: Developers', | |||||||
|                      'Topic :: Software Development :: Libraries :: Python Modules', |                      'Topic :: Software Development :: Libraries :: Python Modules', | ||||||
|                    ] |                    ] | ||||||
|  |  | ||||||
| packages     = [ 'sleekxmpp', | packages     = [ 'sleekxmpp',  | ||||||
|                  'sleekxmpp/stanza', | 				 'sleekxmpp/plugins', | ||||||
|                  'sleekxmpp/test', | 				 'sleekxmpp/stanza', | ||||||
|                  'sleekxmpp/xmlstream', | 				 'sleekxmpp/xmlstream', | ||||||
|                  'sleekxmpp/xmlstream/matcher', | 				 'sleekxmpp/xmlstream/matcher', | ||||||
|                  'sleekxmpp/xmlstream/handler', | 				 'sleekxmpp/xmlstream/handler' ] | ||||||
|                  'sleekxmpp/plugins', |  | ||||||
|                  'sleekxmpp/plugins/xep_0009', | if sys.version_info < (3, 0): | ||||||
|                  'sleekxmpp/plugins/xep_0009/stanza', | 	packages.append('sleekxmpp/xmlstream/tostring26') | ||||||
|                  'sleekxmpp/plugins/xep_0030', | else: | ||||||
|                  'sleekxmpp/plugins/xep_0030/stanza', | 	packages.append('sleekxmpp/xmlstream/tostring') | ||||||
|                  'sleekxmpp/plugins/xep_0050', |  | ||||||
|                  'sleekxmpp/plugins/xep_0059', |  | ||||||
|                  'sleekxmpp/plugins/xep_0060', |  | ||||||
|                  'sleekxmpp/plugins/xep_0060/stanza', |  | ||||||
|                  'sleekxmpp/plugins/xep_0066', |  | ||||||
|                  'sleekxmpp/plugins/xep_0085', |  | ||||||
|                  'sleekxmpp/plugins/xep_0086', |  | ||||||
|                  'sleekxmpp/plugins/xep_0092', |  | ||||||
|                  'sleekxmpp/plugins/xep_0128', |  | ||||||
|                  'sleekxmpp/plugins/xep_0199', |  | ||||||
|                  'sleekxmpp/plugins/xep_0202', |  | ||||||
|                  'sleekxmpp/plugins/xep_0203', |  | ||||||
|                  'sleekxmpp/plugins/xep_0224', |  | ||||||
|                  'sleekxmpp/plugins/xep_0249', |  | ||||||
|                  'sleekxmpp/features', |  | ||||||
|                  'sleekxmpp/features/feature_mechanisms', |  | ||||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', |  | ||||||
|                  'sleekxmpp/features/feature_starttls', |  | ||||||
|                  'sleekxmpp/features/feature_bind', |  | ||||||
|                  'sleekxmpp/features/feature_session', |  | ||||||
|                  'sleekxmpp/thirdparty', |  | ||||||
|                  'sleekxmpp/thirdparty/suelta', |  | ||||||
|                  'sleekxmpp/thirdparty/suelta/mechanisms', |  | ||||||
|                  ] |  | ||||||
|  |  | ||||||
| setup( | setup( | ||||||
|     name             = "sleekxmpp", |     name             = "sleekxmpp", | ||||||
| @@ -85,7 +59,7 @@ setup( | |||||||
|     url          = 'http://code.google.com/p/sleekxmpp', |     url          = 'http://code.google.com/p/sleekxmpp', | ||||||
|     license      = 'MIT', |     license      = 'MIT', | ||||||
|     platforms    = [ 'any' ], |     platforms    = [ 'any' ], | ||||||
|     packages     = packages, | 	packages	 = packages, | ||||||
|     requires     = [ 'tlslite', 'pythondns' ], |     requires     = [ 'tlslite', 'pythondns' ], | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,19 +1,275 @@ | |||||||
|  | #!/usr/bin/python2.5 | ||||||
|  |  | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library | 	SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz | 	Copyright (C) 2010  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. | 	This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. | 	See the file license.txt for copying permission. | ||||||
| """ | """ | ||||||
|  | from __future__ import absolute_import, unicode_literals | ||||||
|  | from . basexmpp import basexmpp | ||||||
|  | from xml.etree import cElementTree as ET | ||||||
|  | from . xmlstream.xmlstream import XMLStream | ||||||
|  | from . xmlstream.xmlstream import RestartStream | ||||||
|  | from . xmlstream.matcher.xmlmask import MatchXMLMask | ||||||
|  | from . xmlstream.matcher.xpath import MatchXPath | ||||||
|  | from . xmlstream.matcher.many import MatchMany | ||||||
|  | from . xmlstream.handler.callback import Callback | ||||||
|  | from . xmlstream.stanzabase import StanzaBase | ||||||
|  | from . xmlstream import xmlstream as xmlstreammod | ||||||
|  | from . stanza.message import Message | ||||||
|  | from . stanza.iq import Iq | ||||||
|  | import time | ||||||
|  | import logging | ||||||
|  | import base64 | ||||||
|  | import sys | ||||||
|  | import random | ||||||
|  | import copy | ||||||
|  | from . import plugins | ||||||
|  | #from . import stanza | ||||||
|  | srvsupport = True | ||||||
|  | try: | ||||||
|  | 	import dns.resolver | ||||||
|  | 	import dns.rdatatype | ||||||
|  | except ImportError: | ||||||
|  | 	srvsupport = False | ||||||
|  |  | ||||||
| from sleekxmpp.basexmpp import BaseXMPP |  | ||||||
| from sleekxmpp.clientxmpp import ClientXMPP |  | ||||||
| from sleekxmpp.componentxmpp import ComponentXMPP |  | ||||||
| from sleekxmpp.stanza import Message, Presence, Iq |  | ||||||
| from sleekxmpp.xmlstream.handler import * |  | ||||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream |  | ||||||
| from sleekxmpp.xmlstream.matcher import * |  | ||||||
| from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET |  | ||||||
|  |  | ||||||
| __version__ = '1.0beta6' |  | ||||||
| __version_info__ = (1, 0, 0, 'beta6', 0) | #class PresenceStanzaType(object): | ||||||
|  | #	 | ||||||
|  | #	def fromXML(self, xml): | ||||||
|  | #		self.ptype = xml.get('type') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ClientXMPP(basexmpp, XMLStream): | ||||||
|  | 	"""SleekXMPP's client class.  Use only for good, not evil.""" | ||||||
|  |  | ||||||
|  | 	def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True): | ||||||
|  | 		global srvsupport | ||||||
|  | 		XMLStream.__init__(self) | ||||||
|  | 		self.default_ns = 'jabber:client' | ||||||
|  | 		basexmpp.__init__(self) | ||||||
|  | 		self.plugin_config = plugin_config | ||||||
|  | 		self.escape_quotes = escape_quotes | ||||||
|  | 		self.set_jid(jid) | ||||||
|  | 		self.server = None | ||||||
|  | 		self.port = 5222 # not used if DNS SRV is used | ||||||
|  | 		self.plugin_whitelist = plugin_whitelist | ||||||
|  | 		self.auto_reconnect = True | ||||||
|  | 		self.srvsupport = srvsupport | ||||||
|  | 		self.password = password | ||||||
|  | 		self.registered_features = [] | ||||||
|  | 		self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.domain,self.default_ns) | ||||||
|  | 		self.stream_footer = "</stream:stream>" | ||||||
|  | 		#self.map_namespace('http://etherx.jabber.org/streams', 'stream') | ||||||
|  | 		#self.map_namespace('jabber:client', '') | ||||||
|  | 		self.features = [] | ||||||
|  | 		#TODO: Use stream state here | ||||||
|  | 		self.authenticated = False | ||||||
|  | 		self.sessionstarted = False | ||||||
|  | 		self.bound = False | ||||||
|  | 		self.bindfail = False | ||||||
|  | 		self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) | ||||||
|  | 		self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) | ||||||
|  | 		#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True)) | ||||||
|  | 		self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True) | ||||||
|  | 		self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True) | ||||||
|  | 		self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource) | ||||||
|  | 		self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session) | ||||||
|  | 		 | ||||||
|  | 		#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType) | ||||||
|  | 		#self.register_plugins() | ||||||
|  | 	 | ||||||
|  | 	def __getitem__(self, key): | ||||||
|  | 		if key in self.plugin: | ||||||
|  | 			return self.plugin[key] | ||||||
|  | 		else: | ||||||
|  | 			logging.warning("""Plugin "%s" is not loaded.""" % key) | ||||||
|  | 			return False | ||||||
|  | 	 | ||||||
|  | 	def get(self, key, default): | ||||||
|  | 		return self.plugin.get(key, default) | ||||||
|  |  | ||||||
|  | 	def connect(self, host=None, port=None): | ||||||
|  | 		"""Connect to the Jabber Server.  Attempts SRV lookup, and if it fails, uses | ||||||
|  | 		the JID server.""" | ||||||
|  |  | ||||||
|  | 		if self.state['connected']: return True | ||||||
|  |  | ||||||
|  | 		if host: | ||||||
|  | 			self.server = host | ||||||
|  | 			if port is None: port = self.port | ||||||
|  | 		else: | ||||||
|  | 			if not self.srvsupport: | ||||||
|  | 				logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org).  Continuing to attempt connection, using domain from JID.") | ||||||
|  | 			else: | ||||||
|  | 				logging.debug("Since no address is supplied, attempting SRV lookup.") | ||||||
|  | 				try: | ||||||
|  | 					answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.domain,  | ||||||
|  | 					        dns.rdatatype.SRV ) | ||||||
|  | 				except dns.resolver.NXDOMAIN: | ||||||
|  | 					logging.debug("No appropriate SRV record found.  Using JID server name.") | ||||||
|  | 				else: | ||||||
|  | 					# pick a random answer, weighted by priority | ||||||
|  | 					# there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway  | ||||||
|  | 					# suggestions are welcome | ||||||
|  | 					addresses = {} | ||||||
|  | 					intmax = 0 | ||||||
|  | 					priorities = [] | ||||||
|  | 					for answer in answers: | ||||||
|  | 						intmax += answer.priority | ||||||
|  | 						addresses[intmax] = (answer.target.to_text()[:-1], answer.port) | ||||||
|  | 						priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort() | ||||||
|  | 					picked = random.randint(0, intmax) | ||||||
|  | 					for priority in priorities: | ||||||
|  | 						if picked <= priority: | ||||||
|  | 							(host,port) = addresses[priority] | ||||||
|  | 							break | ||||||
|  | 					# if SRV lookup was successful, we aren't using a particular server. | ||||||
|  | 					self.server = None  | ||||||
|  |  | ||||||
|  | 		if not host: | ||||||
|  | 			# if all else fails take server from JID. | ||||||
|  | 			(host,port) = (self.domain, self.port) | ||||||
|  | 			self.server = None | ||||||
|  |  | ||||||
|  | 		logging.debug('Attempting connection to %s:%d', host, port ) | ||||||
|  | 		#TODO option to not use TLS? | ||||||
|  | 		result = XMLStream.connect(self, host, port, use_tls=True) | ||||||
|  | 		if result: | ||||||
|  | 			self.event("connected") | ||||||
|  | 		else: | ||||||
|  | 			logging.warning("Failed to connect") | ||||||
|  | 			self.event("disconnected") | ||||||
|  | 		return result | ||||||
|  | 	 | ||||||
|  | 	# overriding reconnect and disconnect so that we can get some events | ||||||
|  | 	# should events be part of or required by xmlstream?  Maybe that would be cleaner | ||||||
|  | 	def reconnect(self): | ||||||
|  | 		self.disconnect(reconnect=True) | ||||||
|  | 	 | ||||||
|  | 	def disconnect(self, reconnect=False): | ||||||
|  | 		self.event("disconnected") | ||||||
|  | 		self.authenticated = False | ||||||
|  | 		self.sessionstarted = False | ||||||
|  | 		XMLStream.disconnect(self, reconnect) | ||||||
|  | 	 | ||||||
|  | 	def registerFeature(self, mask, pointer, breaker = False): | ||||||
|  | 		"""Register a stream feature.""" | ||||||
|  | 		self.registered_features.append((MatchXMLMask(mask), pointer, breaker)) | ||||||
|  |  | ||||||
|  | 	def updateRoster(self, jid, name=None, subscription=None, groups=[]): | ||||||
|  | 		"""Add or change a roster item.""" | ||||||
|  | 		iq = self.Iq().setValues({'type': 'set'}) | ||||||
|  | 		iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} | ||||||
|  | 		#self.send(iq, self.Iq().setValues({'id': iq['id']})) | ||||||
|  | 		r = iq.send() | ||||||
|  | 		return r['type'] == 'result' | ||||||
|  | 	 | ||||||
|  | 	def getRoster(self): | ||||||
|  | 		"""Request the roster be sent.""" | ||||||
|  | 		iq = self.Iq().setValues({'type': 'get'}).enable('roster').send() | ||||||
|  | 		self._handleRoster(iq, request=True) | ||||||
|  | 	 | ||||||
|  | 	def _handleStreamFeatures(self, features): | ||||||
|  | 		logging.debug('handling stream features') | ||||||
|  | 		self.features = [] | ||||||
|  | 		for sub in features.xml: | ||||||
|  | 			self.features.append(sub.tag) | ||||||
|  | 		for subelement in features.xml: | ||||||
|  | 			for feature in self.registered_features: | ||||||
|  | 				if feature[0].match(subelement): | ||||||
|  | 				#if self.maskcmp(subelement, feature[0], True): | ||||||
|  | 					# This calls the feature handler & optionally breaks | ||||||
|  | 					if feature[1](subelement) and feature[2]: #if breaker, don't continue | ||||||
|  | 						return True | ||||||
|  | 	 | ||||||
|  | 	def handler_starttls(self, xml): | ||||||
|  | 		logging.debug( 'TLS start handler; SSL support: %s', self.ssl_support ) | ||||||
|  | 		if not self.authenticated and self.ssl_support: | ||||||
|  | 			_stanza = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />" | ||||||
|  | 			if not self.event_handlers.get(_stanza,None): # don't add handler > once | ||||||
|  | 				self.add_handler( _stanza, self.handler_tls_start, instream=True ) | ||||||
|  | 			self.sendXML(xml) | ||||||
|  | 			return True | ||||||
|  | 		else: | ||||||
|  | 			logging.warning("The module tlslite is required in to some servers, and has not been found.") | ||||||
|  | 			return False | ||||||
|  |  | ||||||
|  | 	def handler_tls_start(self, xml): | ||||||
|  | 		logging.debug("Starting TLS") | ||||||
|  | 		if self.startTLS(): | ||||||
|  | 			raise RestartStream() | ||||||
|  | 	 | ||||||
|  | 	def handler_sasl_auth(self, xml): | ||||||
|  | 		if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: | ||||||
|  | 			return False | ||||||
|  | 		logging.debug("Starting SASL Auth") | ||||||
|  | 		self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True) | ||||||
|  | 		self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, instream=True) | ||||||
|  | 		sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') | ||||||
|  | 		if len(sasl_mechs): | ||||||
|  | 			for sasl_mech in sasl_mechs: | ||||||
|  | 				self.features.append("sasl:%s" % sasl_mech.text) | ||||||
|  | 			if 'sasl:PLAIN' in self.features: | ||||||
|  | 				if sys.version_info < (3,0): | ||||||
|  | 					self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) | ||||||
|  | 				else: | ||||||
|  | 					self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) | ||||||
|  | 			else: | ||||||
|  | 				logging.error("No appropriate login method.") | ||||||
|  | 				self.disconnect() | ||||||
|  | 				#if 'sasl:DIGEST-MD5' in self.features: | ||||||
|  | 				#	self._auth_digestmd5() | ||||||
|  | 		return True | ||||||
|  | 	 | ||||||
|  | 	def handler_auth_success(self, xml): | ||||||
|  | 		logging.debug("Authentication successful.") | ||||||
|  | 		self.authenticated = True | ||||||
|  | 		self.features = [] | ||||||
|  | 		raise RestartStream() | ||||||
|  |  | ||||||
|  | 	def handler_auth_fail(self, xml): | ||||||
|  | 		logging.warning("Authentication failed.") | ||||||
|  | 		self.disconnect() | ||||||
|  | 		self.event("failed_auth") | ||||||
|  | 	 | ||||||
|  | 	def handler_bind_resource(self, xml): | ||||||
|  | 		logging.debug("Requesting resource: %s" % self.resource) | ||||||
|  | 		iq = self.Iq(stype='set') | ||||||
|  | 		res = ET.Element('resource') | ||||||
|  | 		res.text = self.resource | ||||||
|  | 		xml.append(res) | ||||||
|  | 		iq.append(xml) | ||||||
|  | 		response = iq.send() | ||||||
|  | 		#response = self.send(iq, self.Iq(sid=iq['id'])) | ||||||
|  | 		self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text) | ||||||
|  | 		self.bound = True | ||||||
|  | 		logging.info("Node set to: %s" % self.fulljid) | ||||||
|  | 		if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail: | ||||||
|  | 			logging.debug("Established Session") | ||||||
|  | 			self.sessionstarted = True | ||||||
|  | 			self.event("session_start") | ||||||
|  | 	 | ||||||
|  | 	def handler_start_session(self, xml): | ||||||
|  | 		if self.authenticated and self.bound: | ||||||
|  | 			iq = self.makeIqSet(xml) | ||||||
|  | 			response = iq.send() | ||||||
|  | 			logging.debug("Established Session") | ||||||
|  | 			self.sessionstarted = True | ||||||
|  | 			self.event("session_start") | ||||||
|  | 		else: | ||||||
|  | 			#bind probably hasn't happened yet | ||||||
|  | 			self.bindfail = True | ||||||
|  | 	 | ||||||
|  | 	def _handleRoster(self, iq, request=False): | ||||||
|  | 		if iq['type'] == 'set' or (iq['type'] == 'result' and request): | ||||||
|  | 			for jid in iq['roster']['items']: | ||||||
|  | 				if not jid in self.roster: | ||||||
|  | 					self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} | ||||||
|  | 				self.roster[jid].update(iq['roster']['items'][jid]) | ||||||
|  | 			if iq['type'] == 'set': | ||||||
|  | 				self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) | ||||||
|  | 		self.event("roster_update", iq) | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,335 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from __future__ import absolute_import, unicode_literals |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| import base64 |  | ||||||
| import sys |  | ||||||
| import hashlib |  | ||||||
| import random |  | ||||||
| import threading |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp import plugins |  | ||||||
| from sleekxmpp import stanza |  | ||||||
| from sleekxmpp import features |  | ||||||
| from sleekxmpp.basexmpp import BaseXMPP |  | ||||||
| from sleekxmpp.stanza import * |  | ||||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream |  | ||||||
| from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin |  | ||||||
| from sleekxmpp.xmlstream.matcher import * |  | ||||||
| from sleekxmpp.xmlstream.handler import * |  | ||||||
|  |  | ||||||
| # Flag indicating if DNS SRV records are available for use. |  | ||||||
| SRV_SUPPORT = True |  | ||||||
| try: |  | ||||||
|     import dns.resolver |  | ||||||
| except: |  | ||||||
|     SRV_SUPPORT = False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ClientXMPP(BaseXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     SleekXMPP's client class. ( Use only for good, not for evil.) |  | ||||||
|  |  | ||||||
|     Typical Use: |  | ||||||
|     xmpp = ClientXMPP('user@server.tld/resource', 'password') |  | ||||||
|     xmpp.process(block=False) // when block is True, it blocks the current |  | ||||||
|     //                           thread. False by default. |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         connect          -- Overrides XMLStream.connect. |  | ||||||
|         del_roster_item  -- Delete a roster item. |  | ||||||
|         get_roster       -- Retrieve the roster from the server. |  | ||||||
|         register_feature -- Register a stream feature. |  | ||||||
|         update_roster    -- Update a roster item. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, ssl=False, plugin_config={}, |  | ||||||
|                  plugin_whitelist=[], escape_quotes=True): |  | ||||||
|         """ |  | ||||||
|         Create a new SleekXMPP client. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid              -- The JID of the XMPP user account. |  | ||||||
|             password         -- The password for the XMPP user account. |  | ||||||
|             ssl              -- Deprecated. |  | ||||||
|             plugin_config    -- A dictionary of plugin configurations. |  | ||||||
|             plugin_whitelist -- A list of approved plugins that will be loaded |  | ||||||
|                                 when calling register_plugins. |  | ||||||
|             escape_quotes    -- Deprecated. |  | ||||||
|         """ |  | ||||||
|         BaseXMPP.__init__(self, 'jabber:client') |  | ||||||
|  |  | ||||||
|         self.set_jid(jid) |  | ||||||
|         self.password = password |  | ||||||
|         self.escape_quotes = escape_quotes |  | ||||||
|         self.plugin_config = plugin_config |  | ||||||
|         self.plugin_whitelist = plugin_whitelist |  | ||||||
|         self.srv_support = SRV_SUPPORT |  | ||||||
|  |  | ||||||
|         self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( |  | ||||||
|                 self.boundjid.host, |  | ||||||
|                 "xmlns:stream='%s'" % self.stream_ns, |  | ||||||
|                 "xmlns='%s'" % self.default_ns) |  | ||||||
|         self.stream_footer = "</stream:stream>" |  | ||||||
|  |  | ||||||
|         self.features = set() |  | ||||||
|         self._stream_feature_handlers = {} |  | ||||||
|         self._stream_feature_order = [] |  | ||||||
|  |  | ||||||
|         #TODO: Use stream state here |  | ||||||
|         self.authenticated = False |  | ||||||
|         self.sessionstarted = False |  | ||||||
|         self.bound = False |  | ||||||
|         self.bindfail = False |  | ||||||
|  |  | ||||||
|         self.add_event_handler('connected', self._handle_connected) |  | ||||||
|  |  | ||||||
|         self.register_stanza(StreamFeatures) |  | ||||||
|  |  | ||||||
|         self.register_handler( |  | ||||||
|                 Callback('Stream Features', |  | ||||||
|                          MatchXPath('{%s}features' % self.stream_ns), |  | ||||||
|                          self._handle_stream_features)) |  | ||||||
|         self.register_handler( |  | ||||||
|                 Callback('Roster Update', |  | ||||||
|                          MatchXPath('{%s}iq/{%s}query' % ( |  | ||||||
|                              self.default_ns, |  | ||||||
|                              'jabber:iq:roster')), |  | ||||||
|                          self._handle_roster)) |  | ||||||
|  |  | ||||||
|         # Setup default stream features |  | ||||||
|         self.register_plugin('feature_starttls') |  | ||||||
|         self.register_plugin('feature_mechanisms') |  | ||||||
|         self.register_plugin('feature_bind') |  | ||||||
|         self.register_plugin('feature_session') |  | ||||||
|  |  | ||||||
|     def connect(self, address=tuple(), reattempt=True, use_tls=True): |  | ||||||
|         """ |  | ||||||
|         Connect to the XMPP server. |  | ||||||
|  |  | ||||||
|         When no address is given, a SRV lookup for the server will |  | ||||||
|         be attempted. If that fails, the server user in the JID |  | ||||||
|         will be used. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             address   -- A tuple containing the server's host and port. |  | ||||||
|             reattempt -- If True, reattempt the connection if an |  | ||||||
|                          error occurs. Defaults to True. |  | ||||||
|             use_tls   -- Indicates if TLS should be used for the |  | ||||||
|                          connection. Defaults to True. |  | ||||||
|         """ |  | ||||||
|         self.session_started_event.clear() |  | ||||||
|         if not address or len(address) < 2: |  | ||||||
|             if not self.srv_support: |  | ||||||
|                 log.debug("Did not supply (address, port) to connect" + \ |  | ||||||
|                               " to and no SRV support is installed" + \ |  | ||||||
|                               " (http://www.dnspython.org)." + \ |  | ||||||
|                               " Continuing to attempt connection, using" + \ |  | ||||||
|                               " server hostname from JID.") |  | ||||||
|             else: |  | ||||||
|                 log.debug("Since no address is supplied," + \ |  | ||||||
|                               "attempting SRV lookup.") |  | ||||||
|                 try: |  | ||||||
|                     xmpp_srv = "_xmpp-client._tcp.%s" % self.boundjid.host |  | ||||||
|                     answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) |  | ||||||
|                 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): |  | ||||||
|                     log.debug("No appropriate SRV record found." + \ |  | ||||||
|                                   " Using JID server name.") |  | ||||||
|                 except (dns.exception.Timeout,): |  | ||||||
|                     log.debug("DNS resolution timed out.") |  | ||||||
|                 else: |  | ||||||
|                     # Pick a random server, weighted by priority. |  | ||||||
|  |  | ||||||
|                     addresses = {} |  | ||||||
|                     intmax = 0 |  | ||||||
|                     topprio = 65535 |  | ||||||
|                     for answer in answers: |  | ||||||
|                         topprio = min(topprio, answer.priority) |  | ||||||
|                     for answer in answers: |  | ||||||
|                         if answer.priority == topprio: |  | ||||||
|                             intmax += answer.weight |  | ||||||
|                             addresses[intmax] = (answer.target.to_text()[:-1], |  | ||||||
|                                              answer.port) |  | ||||||
|  |  | ||||||
|                     #python3 returns a generator for dictionary keys |  | ||||||
|                     items = [x for x in addresses.keys()] |  | ||||||
|                     items.sort() |  | ||||||
|  |  | ||||||
|                     picked = random.randint(0, intmax) |  | ||||||
|                     for item in items: |  | ||||||
|                         if picked <= item: |  | ||||||
|                             address = addresses[item] |  | ||||||
|                             break |  | ||||||
|  |  | ||||||
|         if not address: |  | ||||||
|             # If all else fails, use the server from the JID. |  | ||||||
|             address = (self.boundjid.host, 5222) |  | ||||||
|  |  | ||||||
|         return XMLStream.connect(self, address[0], address[1], |  | ||||||
|                                  use_tls=use_tls, reattempt=reattempt) |  | ||||||
|  |  | ||||||
|     def register_feature(self, name, handler, restart=False, order=5000): |  | ||||||
|         """ |  | ||||||
|         Register a stream feature. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             name    -- The name of the stream feature. |  | ||||||
|             handler -- The function to execute if the feature is received. |  | ||||||
|             restart -- Indicates if feature processing should halt with |  | ||||||
|                        this feature. Defaults to False. |  | ||||||
|             order   -- The relative ordering in which the feature should |  | ||||||
|                        be negotiated. Lower values will be attempted |  | ||||||
|                        earlier when available. |  | ||||||
|         """ |  | ||||||
|         self._stream_feature_handlers[name] = (handler, restart) |  | ||||||
|         self._stream_feature_order.append((order, name)) |  | ||||||
|         self._stream_feature_order.sort() |  | ||||||
|  |  | ||||||
|     def update_roster(self, jid, name=None, subscription=None, groups=[], |  | ||||||
|                             block=True, timeout=None, callback=None): |  | ||||||
|         """ |  | ||||||
|         Add or change a roster item. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid          -- The JID of the entry to modify. |  | ||||||
|             name         -- The user's nickname for this JID. |  | ||||||
|             subscription -- The subscription status. May be one of |  | ||||||
|                             'to', 'from', 'both', or 'none'. If set |  | ||||||
|                             to 'remove', the entry will be deleted. |  | ||||||
|             groups       -- The roster groups that contain this item. |  | ||||||
|             block        -- Specify if the roster request will block |  | ||||||
|                             until a response is received, or a timeout |  | ||||||
|                             occurs. Defaults to True. |  | ||||||
|             timeout      -- The length of time (in seconds) to wait |  | ||||||
|                             for a response before continuing if blocking |  | ||||||
|                             is used. Defaults to self.response_timeout. |  | ||||||
|             callback     -- Optional reference to a stream handler function. |  | ||||||
|                             Will be executed when the roster is received. |  | ||||||
|                             Implies block=False. |  | ||||||
|         """ |  | ||||||
|         iq = self.Iq() |  | ||||||
|         iq['type'] = 'set' |  | ||||||
|         iq['roster']['items'] = {jid: {'name': name, |  | ||||||
|                                        'subscription': subscription, |  | ||||||
|                                        'groups': groups}} |  | ||||||
|         response = iq.send(block, timeout, callback) |  | ||||||
|         if response in [False, None] or not isinstance(response, Iq): |  | ||||||
|             return response |  | ||||||
|         return response['type'] == 'result' |  | ||||||
|  |  | ||||||
|     def del_roster_item(self, jid): |  | ||||||
|         """ |  | ||||||
|         Remove an item from the roster by setting its subscription |  | ||||||
|         status to 'remove'. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid -- The JID of the item to remove. |  | ||||||
|         """ |  | ||||||
|         return self.update_roster(jid, subscription='remove') |  | ||||||
|  |  | ||||||
|     def get_roster(self, block=True, timeout=None, callback=None): |  | ||||||
|         """ |  | ||||||
|         Request the roster from the server. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             block    -- Specify if the roster request will block until a |  | ||||||
|                         response is received, or a timeout occurs. |  | ||||||
|                         Defaults to True. |  | ||||||
|             timeout  -- The length of time (in seconds) to wait for a response |  | ||||||
|                         before continuing if blocking is used. |  | ||||||
|                         Defaults to self.response_timeout. |  | ||||||
|             callback -- Optional reference to a stream handler function. Will |  | ||||||
|                         be executed when the roster is received. |  | ||||||
|                         Implies block=False. |  | ||||||
|         """ |  | ||||||
|         iq = self.Iq() |  | ||||||
|         iq['type'] = 'get' |  | ||||||
|         iq.enable('roster') |  | ||||||
|         response = iq.send(block, timeout, callback) |  | ||||||
|  |  | ||||||
|         if response == False: |  | ||||||
|             self.event('roster_timeout') |  | ||||||
|  |  | ||||||
|         if response in [False, None] or not isinstance(response, Iq): |  | ||||||
|             return response |  | ||||||
|         else: |  | ||||||
|             return self._handle_roster(response, request=True) |  | ||||||
|  |  | ||||||
|     def _handle_connected(self, event=None): |  | ||||||
|         #TODO: Use stream state here |  | ||||||
|         self.authenticated = False |  | ||||||
|         self.sessionstarted = False |  | ||||||
|         self.bound = False |  | ||||||
|         self.bindfail = False |  | ||||||
|         self.features = set() |  | ||||||
|  |  | ||||||
|         def session_timeout(): |  | ||||||
|             if not self.session_started_event.isSet(): |  | ||||||
|                 log.debug("Session start has taken more than 15 seconds") |  | ||||||
|                 self.disconnect(reconnect=self.auto_reconnect) |  | ||||||
|  |  | ||||||
|         self.schedule("session timeout checker", 15, session_timeout) |  | ||||||
|  |  | ||||||
|     def _handle_stream_features(self, features): |  | ||||||
|         """ |  | ||||||
|         Process the received stream features. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             features -- The features stanza. |  | ||||||
|         """ |  | ||||||
|         for order, name in self._stream_feature_order: |  | ||||||
|             if name in features['features']: |  | ||||||
|                 handler, restart = self._stream_feature_handlers[name] |  | ||||||
|                 if handler(features) and restart: |  | ||||||
|                     # Don't continue if the feature requires |  | ||||||
|                     # restarting the XML stream. |  | ||||||
|                     return True |  | ||||||
|  |  | ||||||
|     def _handle_roster(self, iq, request=False): |  | ||||||
|         """ |  | ||||||
|         Update the roster after receiving a roster stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The roster stanza. |  | ||||||
|             request -- Indicates if this stanza is a response |  | ||||||
|                        to a request for the roster. |  | ||||||
|         """ |  | ||||||
|         if iq['type'] == 'set' or (iq['type'] == 'result' and request): |  | ||||||
|             for jid in iq['roster']['items']: |  | ||||||
|                 if not jid in self.roster: |  | ||||||
|                     self.roster[jid] = {'groups': [], |  | ||||||
|                                         'name': '', |  | ||||||
|                                         'subscription': 'none', |  | ||||||
|                                         'presence': {}, |  | ||||||
|                                         'in_roster': True} |  | ||||||
|                 self.roster[jid].update(iq['roster']['items'][jid]) |  | ||||||
|             self.event('roster_received', iq) |  | ||||||
|  |  | ||||||
|         self.event("roster_update", iq) |  | ||||||
|         if iq['type'] == 'set': |  | ||||||
|             iq.reply() |  | ||||||
|             iq.enable('roster') |  | ||||||
|             iq.send() |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # To comply with PEP8, method names now use underscores. |  | ||||||
| # Deprecated method names are re-mapped for backwards compatibility. |  | ||||||
| ClientXMPP.updateRoster = ClientXMPP.update_roster |  | ||||||
| ClientXMPP.delRosterItem = ClientXMPP.del_roster_item |  | ||||||
| ClientXMPP.getRoster = ClientXMPP.get_roster |  | ||||||
| ClientXMPP.registerFeature = ClientXMPP.register_feature |  | ||||||
							
								
								
									
										41
									
								
								sleekxmpp/component_example.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								sleekxmpp/component_example.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | import sleekxmpp.componentxmpp | ||||||
|  | import logging | ||||||
|  | from optparse import OptionParser | ||||||
|  | import time | ||||||
|  |  | ||||||
|  | class Example(sleekxmpp.componentxmpp.ComponentXMPP): | ||||||
|  | 	 | ||||||
|  | 	def __init__(self, jid, password): | ||||||
|  | 		sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'vm1', 5230) | ||||||
|  | 		self.add_event_handler("session_start", self.start) | ||||||
|  | 		self.add_event_handler("message", self.message) | ||||||
|  | 	 | ||||||
|  | 	def start(self, event): | ||||||
|  | 		#self.getRoster() | ||||||
|  | 		#self.sendPresence(pto='admin@tigase.netflint.net/sarkozy') | ||||||
|  | 		#self.sendPresence(pto='tigase.netflint.net') | ||||||
|  | 		pass | ||||||
|  |  | ||||||
|  | 	def message(self, event): | ||||||
|  | 		self.sendMessage("%s/%s" % (event['jid'], event['resource']), "Thanks for sending me, \"%s\"." % event['message'], mtype=event['type']) | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  | 	#parse command line arguements | ||||||
|  | 	optp = OptionParser() | ||||||
|  | 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||||
|  | 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||||
|  | 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||||
|  | 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||||
|  | 	opts,args = optp.parse_args() | ||||||
|  | 	 | ||||||
|  | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  | 	xmpp = Example('component.vm1', 'secreteating') | ||||||
|  | 	xmpp.registerPlugin('xep_0004') | ||||||
|  | 	xmpp.registerPlugin('xep_0030') | ||||||
|  | 	xmpp.registerPlugin('xep_0060') | ||||||
|  | 	xmpp.registerPlugin('xep_0199') | ||||||
|  | 	if xmpp.connect(): | ||||||
|  | 		xmpp.process(threaded=False) | ||||||
|  | 		print("done") | ||||||
|  | 	else: | ||||||
|  | 		print("Unable to connect.") | ||||||
							
								
								
									
										198
									
								
								sleekxmpp/componentxmpp.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										198
									
								
								sleekxmpp/componentxmpp.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,142 +1,88 @@ | |||||||
|  | #!/usr/bin/python2.6 | ||||||
|  |  | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |     See the file license.txt for copying permission. | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||||
|  | from . basexmpp import basexmpp | ||||||
|  | from xml.etree import cElementTree as ET | ||||||
|  |  | ||||||
|  | from . xmlstream.xmlstream import XMLStream | ||||||
|  | from . xmlstream.xmlstream import RestartStream | ||||||
|  | from . xmlstream.matcher.xmlmask import MatchXMLMask | ||||||
|  | from . xmlstream.matcher.xpath import MatchXPath | ||||||
|  | from . xmlstream.matcher.many import MatchMany | ||||||
|  | from . xmlstream.handler.callback import Callback | ||||||
|  | from . xmlstream.stanzabase import StanzaBase | ||||||
|  | from . xmlstream import xmlstream as xmlstreammod | ||||||
|  | import time | ||||||
| import logging | import logging | ||||||
| import base64 | import base64 | ||||||
| import sys | import sys | ||||||
|  | import random | ||||||
|  | import copy | ||||||
|  | from . import plugins | ||||||
|  | from . import stanza | ||||||
| import hashlib | import hashlib | ||||||
|  | srvsupport = True | ||||||
| from sleekxmpp import plugins | try: | ||||||
| from sleekxmpp import stanza | 	import dns.resolver | ||||||
| from sleekxmpp.basexmpp import BaseXMPP | except ImportError: | ||||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | 	srvsupport = False | ||||||
| from sleekxmpp.xmlstream import StanzaBase, ET |  | ||||||
| from sleekxmpp.xmlstream.matcher import * |  | ||||||
| from sleekxmpp.xmlstream.handler import * |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | class ComponentXMPP(basexmpp, XMLStream): | ||||||
|  | 	"""SleekXMPP's client class.  Use only for good, not evil.""" | ||||||
|  |  | ||||||
|  | 	def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False): | ||||||
|  | 		XMLStream.__init__(self) | ||||||
|  | 		if use_jc_ns: | ||||||
|  | 			self.default_ns = 'jabber:client' | ||||||
|  | 		else: | ||||||
|  | 			self.default_ns = 'jabber:component:accept' | ||||||
|  | 		basexmpp.__init__(self) | ||||||
|  | 		self.auto_authorize = None | ||||||
|  | 		self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid | ||||||
|  | 		self.stream_footer = "</stream:stream>" | ||||||
|  | 		self.server_host = host | ||||||
|  | 		self.server_port = port | ||||||
|  | 		self.set_jid(jid) | ||||||
|  | 		self.secret = secret | ||||||
|  | 		self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) | ||||||
|  | 	 | ||||||
|  | 	def __getitem__(self, key): | ||||||
|  | 		if key in self.plugin: | ||||||
|  | 			return self.plugin[key] | ||||||
|  | 		else: | ||||||
|  | 			logging.warning("""Plugin "%s" is not loaded.""" % key) | ||||||
|  | 			return False | ||||||
|  | 	 | ||||||
|  | 	def get(self, key, default): | ||||||
|  | 		return self.plugin.get(key, default) | ||||||
|  | 	 | ||||||
|  | 	def incoming_filter(self, xmlobj): | ||||||
|  | 		if xmlobj.tag.startswith('{jabber:client}'): | ||||||
|  | 			xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns) | ||||||
|  | 		for sub in xmlobj: | ||||||
|  | 			self.incoming_filter(sub) | ||||||
|  | 		return xmlobj | ||||||
|  |  | ||||||
| class ComponentXMPP(BaseXMPP): | 	def start_stream_handler(self, xml): | ||||||
|  | 		sid = xml.get('id', '') | ||||||
|     """ | 		handshake = ET.Element('{jabber:component:accept}handshake') | ||||||
|     SleekXMPP's basic XMPP server component. | 		if sys.version_info < (3,0): | ||||||
|  | 			handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower() | ||||||
|     Use only for good, not for evil. | 		else: | ||||||
|  | 			handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower() | ||||||
|     Methods: | 		self.sendXML(handshake) | ||||||
|         connect              -- Overrides XMLStream.connect. | 	 | ||||||
|         incoming_filter      -- Overrides XMLStream.incoming_filter. | 	def _handleHandshake(self, xml): | ||||||
|         start_stream_handler -- Overrides XMLStream.start_stream_handler. | 		self.event("session_start") | ||||||
|     """ | 	 | ||||||
|  | 	def connect(self): | ||||||
|     def __init__(self, jid, secret, host, port, | 		logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port)) | ||||||
|                  plugin_config={}, plugin_whitelist=[], use_jc_ns=False): | 		return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port) | ||||||
|         """ |  | ||||||
|         Arguments: |  | ||||||
|             jid              -- The JID of the component. |  | ||||||
|             secret           -- The secret or password for the component. |  | ||||||
|             host             -- The server accepting the component. |  | ||||||
|             port             -- The port used to connect to the server. |  | ||||||
|             plugin_config    -- A dictionary of plugin configurations. |  | ||||||
|             plugin_whitelist -- A list of desired plugins to load |  | ||||||
|                                 when using register_plugins. |  | ||||||
|             use_js_ns        -- Indicates if the 'jabber:client' namespace |  | ||||||
|                                 should be used instead of the standard |  | ||||||
|                                 'jabber:component:accept' namespace. |  | ||||||
|                                 Defaults to False. |  | ||||||
|         """ |  | ||||||
|         if use_jc_ns: |  | ||||||
|             default_ns = 'jabber:client' |  | ||||||
|         else: |  | ||||||
|             default_ns = 'jabber:component:accept' |  | ||||||
|         BaseXMPP.__init__(self, default_ns) |  | ||||||
|  |  | ||||||
|         self.auto_authorize = None |  | ||||||
|         self.stream_header = "<stream:stream %s %s to='%s'>" % ( |  | ||||||
|                 'xmlns="jabber:component:accept"', |  | ||||||
|                 'xmlns:stream="%s"' % self.stream_ns, |  | ||||||
|                 jid) |  | ||||||
|         self.stream_footer = "</stream:stream>" |  | ||||||
|         self.server_host = host |  | ||||||
|         self.server_port = port |  | ||||||
|         self.set_jid(jid) |  | ||||||
|         self.secret = secret |  | ||||||
|         self.plugin_config = plugin_config |  | ||||||
|         self.plugin_whitelist = plugin_whitelist |  | ||||||
|         self.is_component = True |  | ||||||
|  |  | ||||||
|         self.register_handler( |  | ||||||
|                 Callback('Handshake', |  | ||||||
|                          MatchXPath('{jabber:component:accept}handshake'), |  | ||||||
|                          self._handle_handshake)) |  | ||||||
|  |  | ||||||
|     def connect(self): |  | ||||||
|         """ |  | ||||||
|         Connect to the server. |  | ||||||
|  |  | ||||||
|         Overrides XMLStream.connect. |  | ||||||
|         """ |  | ||||||
|         log.debug("Connecting to %s:%s" % (self.server_host, |  | ||||||
|                                                self.server_port)) |  | ||||||
|         return XMLStream.connect(self, self.server_host, |  | ||||||
|                                        self.server_port) |  | ||||||
|  |  | ||||||
|     def incoming_filter(self, xml): |  | ||||||
|         """ |  | ||||||
|         Pre-process incoming XML stanzas by converting any 'jabber:client' |  | ||||||
|         namespaced elements to the component's default namespace. |  | ||||||
|  |  | ||||||
|         Overrides XMLStream.incoming_filter. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xml -- The XML stanza to pre-process. |  | ||||||
|         """ |  | ||||||
|         if xml.tag.startswith('{jabber:client}'): |  | ||||||
|             xml.tag = xml.tag.replace('jabber:client', self.default_ns) |  | ||||||
|  |  | ||||||
|         # The incoming_filter call is only made on top level stanza |  | ||||||
|         # elements. So we manually continue filtering on sub-elements. |  | ||||||
|         for sub in xml: |  | ||||||
|             self.incoming_filter(sub) |  | ||||||
|  |  | ||||||
|         return xml |  | ||||||
|  |  | ||||||
|     def start_stream_handler(self, xml): |  | ||||||
|         """ |  | ||||||
|         Once the streams are established, attempt to handshake |  | ||||||
|         with the server to be accepted as a component. |  | ||||||
|  |  | ||||||
|         Overrides XMLStream.start_stream_handler. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xml -- The incoming stream's root element. |  | ||||||
|         """ |  | ||||||
|         # Construct a hash of the stream ID and the component secret. |  | ||||||
|         sid = xml.get('id', '') |  | ||||||
|         pre_hash = '%s%s' % (sid, self.secret) |  | ||||||
|         if sys.version_info >= (3, 0): |  | ||||||
|             # Handle Unicode byte encoding in Python 3. |  | ||||||
|             pre_hash = bytes(pre_hash, 'utf-8') |  | ||||||
|  |  | ||||||
|         handshake = ET.Element('{jabber:component:accept}handshake') |  | ||||||
|         handshake.text = hashlib.sha1(pre_hash).hexdigest().lower() |  | ||||||
|         self.send_xml(handshake, now=True) |  | ||||||
|  |  | ||||||
|     def _handle_handshake(self, xml): |  | ||||||
|         """ |  | ||||||
|         The handshake has been accepted. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xml -- The reply handshake stanza. |  | ||||||
|         """ |  | ||||||
|         self.session_started_event.set() |  | ||||||
|         self.event("session_start") |  | ||||||
|   | |||||||
| @@ -3,52 +3,14 @@ | |||||||
|     Copyright (C) 2010  Nathanael C. Fritz |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. | See the file license.txt for copying permission. | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  |  | ||||||
| class XMPPError(Exception): | class XMPPError(Exception): | ||||||
|  | 	def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None): | ||||||
|     """ | 		self.condition = condition | ||||||
|     A generic exception that may be raised while processing an XMPP stanza | 		self.text = text | ||||||
|     to indicate that an error response stanza should be sent. | 		self.etype = etype | ||||||
|  | 		self.extension = extension | ||||||
|     The exception method for stanza objects extending RootStanza will create | 		self.extension_ns = extension_ns | ||||||
|     an error stanza and initialize any additional substanzas using the | 		self.extension_args = extension_args | ||||||
|     extension information included in the exception. |  | ||||||
|  |  | ||||||
|     Meant for use in SleekXMPP plugins and applications using SleekXMPP. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, condition='undefined-condition', text=None, etype=None, |  | ||||||
|                  extension=None, extension_ns=None, extension_args=None, |  | ||||||
|                  clear=True): |  | ||||||
|         """ |  | ||||||
|         Create a new XMPPError exception. |  | ||||||
|  |  | ||||||
|         Extension information can be included to add additional XML elements |  | ||||||
|         to the generated error stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             condition      -- The XMPP defined error condition. |  | ||||||
|             text           -- Human readable text describing the error. |  | ||||||
|             etype          -- The XMPP error type, such as cancel or modify. |  | ||||||
|             extension      -- Tag name of the extension's XML content. |  | ||||||
|             extension_ns   -- XML namespace of the extensions' XML content. |  | ||||||
|             extension_args -- Content and attributes for the extension |  | ||||||
|                               element. Same as the additional arguments to |  | ||||||
|                               the ET.Element constructor. |  | ||||||
|             clear          -- Indicates if the stanza's contents should be |  | ||||||
|                               removed before replying with an error. |  | ||||||
|                               Defaults to True. |  | ||||||
|         """ |  | ||||||
|         if extension_args is None: |  | ||||||
|             extension_args = {} |  | ||||||
|  |  | ||||||
|         self.condition = condition |  | ||||||
|         self.text = text |  | ||||||
|         self.etype = etype |  | ||||||
|         self.clear = clear |  | ||||||
|         self.extension = extension |  | ||||||
|         self.extension_ns = extension_ns |  | ||||||
|         self.extension_args = extension_args |  | ||||||
|   | |||||||
| @@ -1,9 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| __all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind'] |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.features.feature_bind.bind import feature_bind |  | ||||||
| from sleekxmpp.features.feature_bind.stanza import Bind |  | ||||||
| @@ -1,64 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Iq, StreamFeatures |  | ||||||
| from sleekxmpp.features.feature_bind import stanza |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
| from sleekxmpp.xmlstream.matcher import * |  | ||||||
| from sleekxmpp.xmlstream.handler import * |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class feature_bind(base_plugin): |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.name = 'Bind Resource' |  | ||||||
|         self.rfc = '6120' |  | ||||||
|         self.description = 'Resource Binding Stream Feature' |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         self.xmpp.register_feature('bind', |  | ||||||
|                 self._handle_bind_resource, |  | ||||||
|                 restart=False, |  | ||||||
|                 order=10000) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, stanza.Bind) |  | ||||||
|         register_stanza_plugin(StreamFeatures, stanza.Bind) |  | ||||||
|  |  | ||||||
|     def _handle_bind_resource(self, features): |  | ||||||
|         """ |  | ||||||
|         Handle requesting a specific resource. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             features -- The stream features stanza. |  | ||||||
|         """ |  | ||||||
|         log.debug("Requesting resource: %s" % self.xmpp.boundjid.resource) |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         iq['type'] = 'set' |  | ||||||
|         iq.enable('bind') |  | ||||||
|         if self.xmpp.boundjid.resource: |  | ||||||
|             iq['bind']['resource'] = self.xmpp.boundjid.resource |  | ||||||
|         response = iq.send(now=True) |  | ||||||
|  |  | ||||||
|         self.xmpp.set_jid(response['bind']['jid']) |  | ||||||
|         self.xmpp.bound = True |  | ||||||
|  |  | ||||||
|         self.xmpp.features.add('bind') |  | ||||||
|  |  | ||||||
|         log.info("Node set to: %s" % self.xmpp.boundjid.full) |  | ||||||
|  |  | ||||||
|         if 'session' not in features['features']: |  | ||||||
|             log.debug("Established Session") |  | ||||||
|             self.xmpp.sessionstarted = True |  | ||||||
|             self.xmpp.session_started_event.set() |  | ||||||
|             self.xmpp.event("session_start") |  | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Iq, StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Bind(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'bind' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-bind' |  | ||||||
|     interfaces = set(('resource', 'jid')) |  | ||||||
|     sub_interfaces = interfaces |  | ||||||
|     plugin_attrib = 'bind' |  | ||||||
| @@ -1,13 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.features.feature_mechanisms.mechanisms import feature_mechanisms |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza import Auth |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza import Success |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza import Failure |  | ||||||
| @@ -1,126 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from sleekxmpp.thirdparty import suelta |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin |  | ||||||
| from sleekxmpp.xmlstream.matcher import * |  | ||||||
| from sleekxmpp.xmlstream.handler import * |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.features.feature_mechanisms import stanza |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class feature_mechanisms(base_plugin): |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.name = 'SASL Mechanisms' |  | ||||||
|         self.rfc = '6120' |  | ||||||
|         self.description = "SASL Stream Feature" |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         def tls_active(): |  | ||||||
|             return 'starttls' in self.xmpp.features |  | ||||||
|  |  | ||||||
|         def basic_callback(mech, values): |  | ||||||
|             if 'username' in values: |  | ||||||
|                 values['username'] = self.xmpp.boundjid.user |  | ||||||
|             if 'password' in values: |  | ||||||
|                 values['password'] = self.xmpp.password |  | ||||||
|             mech.fulfill(values) |  | ||||||
|  |  | ||||||
|         sasl_callback = self.config.get('sasl_callback', None) |  | ||||||
|         if sasl_callback is None: |  | ||||||
|             sasl_callback = basic_callback |  | ||||||
|  |  | ||||||
|         self.mech = None |  | ||||||
|         self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp', |  | ||||||
|                                 username=self.xmpp.boundjid.user, |  | ||||||
|                                 sec_query=suelta.sec_query_allow, |  | ||||||
|                                 request_values=sasl_callback, |  | ||||||
|                                 tls_active=tls_active) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(StreamFeatures, stanza.Mechanisms) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_stanza(stanza.Success) |  | ||||||
|         self.xmpp.register_stanza(stanza.Failure) |  | ||||||
|         self.xmpp.register_stanza(stanza.Auth) |  | ||||||
|         self.xmpp.register_stanza(stanza.Challenge) |  | ||||||
|         self.xmpp.register_stanza(stanza.Response) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('SASL Success', |  | ||||||
|                          MatchXPath(stanza.Success.tag_name()), |  | ||||||
|                          self._handle_success, |  | ||||||
|                          instream=True, |  | ||||||
|                          once=True)) |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('SASL Failure', |  | ||||||
|                          MatchXPath(stanza.Failure.tag_name()), |  | ||||||
|                          self._handle_fail, |  | ||||||
|                          instream=True, |  | ||||||
|                          once=True)) |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('SASL Challenge', |  | ||||||
|                          MatchXPath(stanza.Challenge.tag_name()), |  | ||||||
|                          self._handle_challenge)) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_feature('mechanisms', |  | ||||||
|                 self._handle_sasl_auth, |  | ||||||
|                 restart=True, |  | ||||||
|                 order=self.config.get('order', 100)) |  | ||||||
|  |  | ||||||
|     def _handle_sasl_auth(self, features): |  | ||||||
|         """ |  | ||||||
|         Handle authenticating using SASL. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             features -- The stream features stanza. |  | ||||||
|         """ |  | ||||||
|         if 'mechanisms' in self.xmpp.features: |  | ||||||
|             # SASL authentication has already succeeded, but the |  | ||||||
|             # server has incorrectly offered it again. |  | ||||||
|             return False |  | ||||||
|  |  | ||||||
|         mech_list = features['mechanisms'] |  | ||||||
|         self.mech = self.sasl.choose_mechanism(mech_list) |  | ||||||
|  |  | ||||||
|         if self.mech is not None: |  | ||||||
|             resp = stanza.Auth(self.xmpp) |  | ||||||
|             resp['mechanism'] = self.mech.name |  | ||||||
|             resp['value'] = self.mech.process() |  | ||||||
|             resp.send(now=True) |  | ||||||
|         else: |  | ||||||
|             log.error("No appropriate login method.") |  | ||||||
|             self.xmpp.event("no_auth", direct=True) |  | ||||||
|             self.xmpp.disconnect() |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def _handle_challenge(self, stanza): |  | ||||||
|         """SASL challenge received. Process and send response.""" |  | ||||||
|         resp = self.stanza.Response(self.xmpp) |  | ||||||
|         resp['value'] = self.mech.process(stanza['value']) |  | ||||||
|         resp.send(now=True) |  | ||||||
|  |  | ||||||
|     def _handle_success(self, stanza): |  | ||||||
|         """SASL authentication succeeded. Restart the stream.""" |  | ||||||
|         self.xmpp.authenticated = True |  | ||||||
|         self.xmpp.features.add('mechanisms') |  | ||||||
|         raise RestartStream() |  | ||||||
|  |  | ||||||
|     def _handle_fail(self, stanza): |  | ||||||
|         """SASL authentication failed. Disconnect and shutdown.""" |  | ||||||
|         log.info("Authentication failed: %s" % stanza['condition']) |  | ||||||
|         self.xmpp.event("failed_auth", stanza, direct=True) |  | ||||||
|         self.xmpp.disconnect() |  | ||||||
|         return True |  | ||||||
| @@ -1,15 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza.mechanisms import Mechanisms |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza.auth import Auth |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza.success import Success |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge |  | ||||||
| from sleekxmpp.features.feature_mechanisms.stanza.response import Response |  | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import base64 |  | ||||||
|  |  | ||||||
| from sleekxmpp.thirdparty.suelta.util import bytes |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Auth(StanzaBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'auth' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' |  | ||||||
|     interfaces = set(('mechanism', 'value')) |  | ||||||
|     plugin_attrib = name |  | ||||||
|  |  | ||||||
|     def setup(self, xml): |  | ||||||
|         StanzaBase.setup(self, xml) |  | ||||||
|         self.xml.tag = self.tag_name() |  | ||||||
|  |  | ||||||
|     def get_value(self): |  | ||||||
|         return base64.b64decode(bytes(self.xml.text)) |  | ||||||
|  |  | ||||||
|     def set_value(self, values): |  | ||||||
|         self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') |  | ||||||
|  |  | ||||||
|     def del_value(self): |  | ||||||
|         self.xml.text = '' |  | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import base64 |  | ||||||
|  |  | ||||||
| from sleekxmpp.thirdparty.suelta.util import bytes |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Challenge(StanzaBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'challenge' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' |  | ||||||
|     interfaces = set(('value',)) |  | ||||||
|     plugin_attrib = name |  | ||||||
|  |  | ||||||
|     def setup(self, xml): |  | ||||||
|         StanzaBase.setup(self, xml) |  | ||||||
|         self.xml.tag = self.tag_name() |  | ||||||
|  |  | ||||||
|     def get_value(self): |  | ||||||
|         return base64.b64decode(bytes(self.xml.text)) |  | ||||||
|  |  | ||||||
|     def set_value(self, values): |  | ||||||
|         self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') |  | ||||||
|  |  | ||||||
|     def del_value(self): |  | ||||||
|         self.xml.text = '' |  | ||||||
| @@ -1,78 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Failure(StanzaBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'failure' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' |  | ||||||
|     interfaces = set(('condition', 'text')) |  | ||||||
|     plugin_attrib = name |  | ||||||
|     sub_interfaces = set(('text',)) |  | ||||||
|     conditions = set(('aborted', 'account-disabled', 'credentials-expired', |  | ||||||
|         'encryption-required', 'incorrect-encoding', 'invalid-authzid', |  | ||||||
|         'invalid-mechanism', 'malformed-request', 'mechansism-too-weak', |  | ||||||
|         'not-authorized', 'temporary-auth-failure')) |  | ||||||
|  |  | ||||||
|     def setup(self, xml=None): |  | ||||||
|         """ |  | ||||||
|         Populate the stanza object using an optional XML object. |  | ||||||
|  |  | ||||||
|         Overrides ElementBase.setup. |  | ||||||
|  |  | ||||||
|         Sets a default error type and condition, and changes the |  | ||||||
|         parent stanza's type to 'error'. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xml -- Use an existing XML object for the stanza's values. |  | ||||||
|         """ |  | ||||||
|         # StanzaBase overrides self.namespace |  | ||||||
|         self.namespace = Failure.namespace |  | ||||||
|  |  | ||||||
|         if StanzaBase.setup(self, xml): |  | ||||||
|             #If we had to generate XML then set default values. |  | ||||||
|             self['condition'] = 'not-authorized' |  | ||||||
|  |  | ||||||
|         self.xml.tag = self.tag_name() |  | ||||||
|  |  | ||||||
|     def get_condition(self): |  | ||||||
|         """Return the condition element's name.""" |  | ||||||
|         for child in self.xml.getchildren(): |  | ||||||
|             if "{%s}" % self.namespace in child.tag: |  | ||||||
|                 cond = child.tag.split('}', 1)[-1] |  | ||||||
|                 if cond in self.conditions: |  | ||||||
|                     return cond |  | ||||||
|         return 'not-authorized' |  | ||||||
|  |  | ||||||
|     def set_condition(self, value): |  | ||||||
|         """ |  | ||||||
|         Set the tag name of the condition element. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|            value -- The tag name of the condition element. |  | ||||||
|         """ |  | ||||||
|         if value in self.conditions: |  | ||||||
|             del self['condition'] |  | ||||||
|             self.xml.append(ET.Element("{%s}%s" % (self.namespace, value))) |  | ||||||
|         return self |  | ||||||
|  |  | ||||||
|     def del_condition(self): |  | ||||||
|         """Remove the condition element.""" |  | ||||||
|         for child in self.xml.getchildren(): |  | ||||||
|             if "{%s}" % self.condition_ns in child.tag: |  | ||||||
|                 tag = child.tag.split('}', 1)[-1] |  | ||||||
|                 if tag in self.conditions: |  | ||||||
|                     self.xml.remove(child) |  | ||||||
|         return self |  | ||||||
| @@ -1,55 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Mechanisms(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'mechanisms' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' |  | ||||||
|     interfaces = set(('mechanisms', 'required')) |  | ||||||
|     plugin_attrib = name |  | ||||||
|     is_extension = True |  | ||||||
|  |  | ||||||
|     def get_required(self): |  | ||||||
|         """ |  | ||||||
|         """ |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def get_mechanisms(self): |  | ||||||
|         """ |  | ||||||
|         """ |  | ||||||
|         results = [] |  | ||||||
|         mechs = self.findall('{%s}mechanism' % self.namespace) |  | ||||||
|         if mechs: |  | ||||||
|             for mech in mechs: |  | ||||||
|                 results.append(mech.text) |  | ||||||
|         return results |  | ||||||
|  |  | ||||||
|     def set_mechanisms(self, values): |  | ||||||
|         """ |  | ||||||
|         """ |  | ||||||
|         self.del_mechanisms() |  | ||||||
|         for val in values: |  | ||||||
|             mech = ET.Element('{%s}mechanism' % self.namespace) |  | ||||||
|             mech.text = val |  | ||||||
|             self.append(mech) |  | ||||||
|  |  | ||||||
|     def del_mechanisms(self): |  | ||||||
|         """ |  | ||||||
|         """ |  | ||||||
|         mechs = self.findall('{%s}mechanism' % self.namespace) |  | ||||||
|         if mechs: |  | ||||||
|             for mech in mechs: |  | ||||||
|                 self.xml.remove(mech) |  | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import base64 |  | ||||||
|  |  | ||||||
| from sleekxmpp.thirdparty.suelta.util import bytes |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Response(StanzaBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'response' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' |  | ||||||
|     interfaces = set(('value',)) |  | ||||||
|     plugin_attrib = name |  | ||||||
|  |  | ||||||
|     def setup(self, xml): |  | ||||||
|         StanzaBase.setup(self, xml) |  | ||||||
|         self.xml.tag = self.tag_name() |  | ||||||
|  |  | ||||||
|     def get_value(self): |  | ||||||
|         return base64.b64decode(bytes(self.xml.text)) |  | ||||||
|  |  | ||||||
|     def set_value(self, values): |  | ||||||
|         self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') |  | ||||||
|  |  | ||||||
|     def del_value(self): |  | ||||||
|         self.xml.text = '' |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Success(StanzaBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'success' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' |  | ||||||
|     interfaces = set() |  | ||||||
|     plugin_attrib = name |  | ||||||
|  |  | ||||||
|     def setup(self, xml): |  | ||||||
|         StanzaBase.setup(self, xml) |  | ||||||
|         self.xml.tag = self.tag_name() |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.features.feature_session.session import feature_session |  | ||||||
| from sleekxmpp.features.feature_session.stanza import Session |  | ||||||
| @@ -1,56 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Iq, StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
| from sleekxmpp.xmlstream.matcher import * |  | ||||||
| from sleekxmpp.xmlstream.handler import * |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
|  |  | ||||||
| from sleekxmpp.features.feature_session import stanza |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class feature_session(base_plugin): |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.name = 'Start Session' |  | ||||||
|         self.rfc = '3920' |  | ||||||
|         self.description = 'Start Session Stream Feature' |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         self.xmpp.register_feature('session', |  | ||||||
|                 self._handle_start_session, |  | ||||||
|                 restart=False, |  | ||||||
|                 order=10001) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, stanza.Session) |  | ||||||
|         register_stanza_plugin(StreamFeatures, stanza.Session) |  | ||||||
|  |  | ||||||
|     def _handle_start_session(self, features): |  | ||||||
|         """ |  | ||||||
|         Handle the start of the session. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             feature -- The stream features element. |  | ||||||
|         """ |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         iq['type'] = 'set' |  | ||||||
|         iq.enable('session') |  | ||||||
|         response = iq.send(now=True) |  | ||||||
|  |  | ||||||
|         self.xmpp.features.add('session') |  | ||||||
|  |  | ||||||
|         log.debug("Established Session") |  | ||||||
|         self.xmpp.sessionstarted = True |  | ||||||
|         self.xmpp.session_started_event.set() |  | ||||||
|         self.xmpp.event("session_start") |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Iq, StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Session(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'session' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-session' |  | ||||||
|     interfaces = set() |  | ||||||
|     plugin_attrib = 'session' |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.features.feature_starttls.starttls import feature_starttls |  | ||||||
| from sleekxmpp.features.feature_starttls.stanza import * |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import StanzaBase, ElementBase |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class STARTTLS(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'starttls' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-tls' |  | ||||||
|     interfaces = set(('required',)) |  | ||||||
|     plugin_attrib = name |  | ||||||
|  |  | ||||||
|     def get_required(self): |  | ||||||
|         """ |  | ||||||
|         """ |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Proceed(StanzaBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'proceed' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-tls' |  | ||||||
|     interfaces = set() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Failure(StanzaBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'failure' |  | ||||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-tls' |  | ||||||
|     interfaces = set() |  | ||||||
| @@ -1,70 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures |  | ||||||
| from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin |  | ||||||
| from sleekxmpp.xmlstream.matcher import * |  | ||||||
| from sleekxmpp.xmlstream.handler import * |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.features.feature_starttls import stanza |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class feature_starttls(base_plugin): |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.name = "STARTTLS" |  | ||||||
|         self.rfc = '6120' |  | ||||||
|         self.description = "STARTTLS Stream Feature" |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('STARTTLS Proceed', |  | ||||||
|                         MatchXPath(stanza.Proceed.tag_name()), |  | ||||||
|                         self._handle_starttls_proceed, |  | ||||||
|                         instream=True)) |  | ||||||
|         self.xmpp.register_feature('starttls', |  | ||||||
|                 self._handle_starttls, |  | ||||||
|                 restart=True, |  | ||||||
|                 order=self.config.get('order', 0)) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_stanza(stanza.Proceed) |  | ||||||
|         self.xmpp.register_stanza(stanza.Failure) |  | ||||||
|         register_stanza_plugin(StreamFeatures, stanza.STARTTLS) |  | ||||||
|  |  | ||||||
|     def _handle_starttls(self, features): |  | ||||||
|         """ |  | ||||||
|         Handle notification that the server supports TLS. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             features -- The stream:features element. |  | ||||||
|         """ |  | ||||||
|         if 'starttls' in self.xmpp.features: |  | ||||||
|             # We have already negotiated TLS, but the server is |  | ||||||
|             # offering it again, against spec. |  | ||||||
|             return False |  | ||||||
|         elif not self.xmpp.use_tls: |  | ||||||
|             return False |  | ||||||
|         elif self.xmpp.ssl_support: |  | ||||||
|             self.xmpp.send(features['starttls'], now=True) |  | ||||||
|             return True |  | ||||||
|         else: |  | ||||||
|             log.warning("The module tlslite is required to log in" +\ |  | ||||||
|                             " to some servers, and has not been found.") |  | ||||||
|             return False |  | ||||||
|  |  | ||||||
|     def _handle_starttls_proceed(self, proceed): |  | ||||||
|         """Restart the XML stream when TLS is accepted.""" |  | ||||||
|         log.debug("Starting TLS") |  | ||||||
|         if self.xmpp.start_tls(): |  | ||||||
|             self.xmpp.features.add('starttls') |  | ||||||
|             raise RestartStream() |  | ||||||
| @@ -1,11 +1,20 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz |     Copyright (C) 2007  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |     SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  |     it under the terms of the GNU General Public License as published by | ||||||
|  |     the Free Software Foundation; either version 2 of the License, or | ||||||
|  |     (at your option) any later version. | ||||||
|  |  | ||||||
|  |     SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  |     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |     GNU General Public License for more details. | ||||||
|  |  | ||||||
|  |     You should have received a copy of the GNU General Public License | ||||||
|  |     along with SleekXMPP; if not, write to the Free Software | ||||||
|  |     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
| """ | """ | ||||||
| __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', | __all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] | ||||||
|            'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', |  | ||||||
|            'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', |  | ||||||
|            'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] |  | ||||||
|   | |||||||
| @@ -1,91 +1,36 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library | 	SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz | 	Copyright (C) 2007  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. | 	This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. | 	SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  | 	it under the terms of the GNU General Public License as published by | ||||||
|  | 	the Free Software Foundation; either version 2 of the License, or | ||||||
|  | 	(at your option) any later version. | ||||||
|  |  | ||||||
|  | 	SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  | 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | 	GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | 	You should have received a copy of the GNU General Public License | ||||||
|  | 	along with SleekXMPP; if not, write to the Free Software | ||||||
|  | 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  |  | ||||||
| class base_plugin(object): | class base_plugin(object): | ||||||
|  | 	 | ||||||
|     """ | 	def __init__(self, xmpp, config): | ||||||
|     The base_plugin class serves as a base for user created plugins | 		self.xep = 'base' | ||||||
|     that provide support for existing or experimental XEPS. | 		self.description = 'Base Plugin' | ||||||
|  | 		self.xmpp = xmpp | ||||||
|     Each plugin has a dictionary for configuration options, as well | 		self.config = config | ||||||
|     as a name and description. | 		self.post_inited = False | ||||||
|  | 		self.enable = config.get('enable', True) | ||||||
|     The lifecycle of a plugin is: | 		if self.enable: | ||||||
|         1. The plugin is instantiated during registration. | 			self.plugin_init() | ||||||
|         2. Once the XML stream begins processing, the method | 	 | ||||||
|            plugin_init() is called (if the plugin is configured | 	def plugin_init(self): | ||||||
|            as enabled with {'enable': True}). | 		pass | ||||||
|         3. After all plugins have been initialized, the | 	 | ||||||
|            method post_init() is called. | 	def post_init(self): | ||||||
|  | 		self.post_inited = True | ||||||
|     Recommended event handlers: |  | ||||||
|         session_start -- Plugins which require the use of the current |  | ||||||
|                          bound JID SHOULD wait for the session_start |  | ||||||
|                          event to perform any initialization (or |  | ||||||
|                          resetting). This is a transitive recommendation, |  | ||||||
|                          plugins that use other plugins which use the |  | ||||||
|                          bound JID should also wait for session_start |  | ||||||
|                          before making such calls. |  | ||||||
|         session_end   -- If the plugin keeps any per-session state, |  | ||||||
|                          such as joined MUC rooms, such state SHOULD |  | ||||||
|                          be cleared when the session_end event is raised. |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|         xep          -- The XEP number the plugin implements, if any. |  | ||||||
|         description  -- A short description of the plugin, typically |  | ||||||
|                         the long name of the implemented XEP. |  | ||||||
|         xmpp         -- The main SleekXMPP instance. |  | ||||||
|         config       -- A dictionary of custom configuration values. |  | ||||||
|                         The value 'enable' is special and controls |  | ||||||
|                         whether or not the plugin is initialized |  | ||||||
|                         after registration. |  | ||||||
|         post_initted -- Executed after all plugins have been initialized |  | ||||||
|                         to handle any cross-plugin interactions, such as |  | ||||||
|                         registering service discovery items. |  | ||||||
|         enable       -- Indicates that the plugin is enabled for use and |  | ||||||
|                         will be initialized after registration. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         plugin_init  -- Initialize the plugin state. |  | ||||||
|         post_init    -- Handle any cross-plugin concerns. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, xmpp, config=None): |  | ||||||
|         """ |  | ||||||
|         Instantiate a new plugin and store the given configuration. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xmpp   -- The main SleekXMPP instance. |  | ||||||
|             config -- A dictionary of configuration values. |  | ||||||
|         """ |  | ||||||
|         if config is None: |  | ||||||
|             config = {} |  | ||||||
|         self.xep = None |  | ||||||
|         self.rfc = None |  | ||||||
|         self.description = 'Base Plugin' |  | ||||||
|         self.xmpp = xmpp |  | ||||||
|         self.config = config |  | ||||||
|         self.post_inited = False |  | ||||||
|         self.enable = config.get('enable', True) |  | ||||||
|         if self.enable: |  | ||||||
|             self.plugin_init() |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         """ |  | ||||||
|         Initialize plugin state, such as registering any stream or |  | ||||||
|         event handlers, or new stanza types. |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         """ |  | ||||||
|         Perform any cross-plugin interactions, such as registering |  | ||||||
|         service discovery identities or items. |  | ||||||
|         """ |  | ||||||
|         self.post_inited = True |  | ||||||
|   | |||||||
| @@ -1,149 +1,57 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library | 	SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | 	Copyright (C) 2007  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. | 	This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. | 	SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  | 	it under the terms of the GNU General Public License as published by | ||||||
|  | 	the Free Software Foundation; either version 2 of the License, or | ||||||
|  | 	(at your option) any later version. | ||||||
|  |  | ||||||
|  | 	SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  | 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | 	GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | 	You should have received a copy of the GNU General Public License | ||||||
|  | 	along with SleekXMPP; if not, write to the Free Software | ||||||
|  | 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
| """ | """ | ||||||
|  | from __future__ import with_statement | ||||||
| import logging |  | ||||||
| from . import base | from . import base | ||||||
| from .. xmlstream.handler.callback import Callback | import logging | ||||||
| from .. xmlstream.matcher.xpath import MatchXPath | from xml.etree import cElementTree as ET | ||||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID | import traceback | ||||||
| from .. stanza.iq import Iq | import time | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GmailQuery(ElementBase): |  | ||||||
|     namespace = 'google:mail:notify' |  | ||||||
|     name = 'query' |  | ||||||
|     plugin_attrib = 'gmail' |  | ||||||
|     interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) |  | ||||||
|  |  | ||||||
|     def getSearch(self): |  | ||||||
|         return self['q'] |  | ||||||
|  |  | ||||||
|     def setSearch(self, search): |  | ||||||
|         self['q'] = search |  | ||||||
|  |  | ||||||
|     def delSearch(self): |  | ||||||
|         del self['q'] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MailBox(ElementBase): |  | ||||||
|     namespace = 'google:mail:notify' |  | ||||||
|     name = 'mailbox' |  | ||||||
|     plugin_attrib = 'mailbox' |  | ||||||
|     interfaces = set(('result-time', 'total-matched', 'total-estimate', |  | ||||||
|                       'url', 'threads', 'matched', 'estimate')) |  | ||||||
|  |  | ||||||
|     def getThreads(self): |  | ||||||
|         threads = [] |  | ||||||
|         for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, |  | ||||||
|                                                       MailThread.name)): |  | ||||||
|             threads.append(MailThread(xml=threadXML, parent=None)) |  | ||||||
|         return threads |  | ||||||
|  |  | ||||||
|     def getMatched(self): |  | ||||||
|         return self['total-matched'] |  | ||||||
|  |  | ||||||
|     def getEstimate(self): |  | ||||||
|         return self['total-estimate'] == '1' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MailThread(ElementBase): |  | ||||||
|     namespace = 'google:mail:notify' |  | ||||||
|     name = 'mail-thread-info' |  | ||||||
|     plugin_attrib = 'thread' |  | ||||||
|     interfaces = set(('tid', 'participation', 'messages', 'date', |  | ||||||
|                       'senders', 'url', 'labels', 'subject', 'snippet')) |  | ||||||
|     sub_interfaces = set(('labels', 'subject', 'snippet')) |  | ||||||
|  |  | ||||||
|     def getSenders(self): |  | ||||||
|         senders = [] |  | ||||||
|         sendersXML = self.xml.find('{%s}senders' % self.namespace) |  | ||||||
|         if sendersXML is not None: |  | ||||||
|             for senderXML in sendersXML.findall('{%s}sender' % self.namespace): |  | ||||||
|                 senders.append(MailSender(xml=senderXML, parent=None)) |  | ||||||
|         return senders |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MailSender(ElementBase): |  | ||||||
|     namespace = 'google:mail:notify' |  | ||||||
|     name = 'sender' |  | ||||||
|     plugin_attrib = 'sender' |  | ||||||
|     interfaces = set(('address', 'name', 'originator', 'unread')) |  | ||||||
|  |  | ||||||
|     def getOriginator(self): |  | ||||||
|         return self.xml.attrib.get('originator', '0') == '1' |  | ||||||
|  |  | ||||||
|     def getUnread(self): |  | ||||||
|         return self.xml.attrib.get('unread', '0') == '1' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class NewMail(ElementBase): |  | ||||||
|     namespace = 'google:mail:notify' |  | ||||||
|     name = 'new-mail' |  | ||||||
|     plugin_attrib = 'new-mail' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class gmail_notify(base.base_plugin): | class gmail_notify(base.base_plugin): | ||||||
|     """ | 	 | ||||||
|     Google Talk: Gmail Notifications | 	def plugin_init(self): | ||||||
|     """ | 		self.description = 'Google Talk Gmail Notification' | ||||||
|  | 		self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True) | ||||||
|     def plugin_init(self): | 		self.emails = [] | ||||||
|         self.description = 'Google Talk: Gmail Notifications' | 	 | ||||||
|  | 	def handler_gmailcheck(self, payload): | ||||||
|         self.xmpp.registerHandler( | 		#TODO XEP 30 should cache results and have getFeature | ||||||
|             Callback('Gmail Result', | 		result = self.xmpp['xep_0030'].getInfo(self.xmpp.server) | ||||||
|                      MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, | 		features = [] | ||||||
|                                                    MailBox.namespace, | 		for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'): | ||||||
|                                                    MailBox.name)), | 			features.append(feature.get('var')) | ||||||
|                      self.handle_gmail)) | 		if 'google:mail:notify' in features: | ||||||
|  | 			logging.debug("Server supports Gmail Notify") | ||||||
|         self.xmpp.registerHandler( | 			self.xmpp.add_handler("<iq type='set' xmlns='%s'><new-mail xmlns='google:mail:notify' /></iq>" % self.xmpp.default_ns, self.handler_notify) | ||||||
|             Callback('Gmail New Mail', | 			self.getEmail() | ||||||
|                      MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, | 	 | ||||||
|                                                    NewMail.namespace, | 	def handler_notify(self, xml): | ||||||
|                                                    NewMail.name)), | 		logging.info("New Gmail recieved!") | ||||||
|                      self.handle_new_mail)) | 		self.xmpp.event('gmail_notify') | ||||||
|  | 		 | ||||||
|         registerStanzaPlugin(Iq, GmailQuery) | 	def getEmail(self): | ||||||
|         registerStanzaPlugin(Iq, MailBox) | 		iq = self.xmpp.makeIqGet() | ||||||
|         registerStanzaPlugin(Iq, NewMail) | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
|  | 		iq.attrib['to'] = self.xmpp.jid | ||||||
|         self.last_result_time = None | 		self.xmpp.makeIqQuery(iq, 'google:mail:notify') | ||||||
|  | 		emails = iq.send() | ||||||
|     def handle_gmail(self, iq): | 		mailbox = emails.find('{google:mail:notify}mailbox') | ||||||
|         mailbox = iq['mailbox'] | 		total = int(mailbox.get('total-matched', 0)) | ||||||
|         approx = ' approximately' if mailbox['estimated'] else '' | 		logging.info("%s New Gmail Messages" % total) | ||||||
|         log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) |  | ||||||
|         self.last_result_time = mailbox['result-time'] |  | ||||||
|         self.xmpp.event('gmail_messages', iq) |  | ||||||
|  |  | ||||||
|     def handle_new_mail(self, iq): |  | ||||||
|         log.info("Gmail: New emails received!") |  | ||||||
|         self.xmpp.event('gmail_notify') |  | ||||||
|         self.checkEmail() |  | ||||||
|  |  | ||||||
|     def getEmail(self, query=None): |  | ||||||
|         return self.search(query) |  | ||||||
|  |  | ||||||
|     def checkEmail(self): |  | ||||||
|         return self.search(newer=self.last_result_time) |  | ||||||
|  |  | ||||||
|     def search(self, query=None, newer=None): |  | ||||||
|         if query is None: |  | ||||||
|             log.info("Gmail: Checking for new emails") |  | ||||||
|         else: |  | ||||||
|             log.info('Gmail: Searching for emails matching: "%s"' % query) |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         iq['type'] = 'get' |  | ||||||
|         iq['to'] = self.xmpp.boundjid.bare |  | ||||||
|         iq['gmail']['q'] = query |  | ||||||
|         iq['gmail']['newer-than-time'] = newer |  | ||||||
|         return iq.send() |  | ||||||
|   | |||||||
| @@ -2,19 +2,15 @@ from . import base | |||||||
| import logging | import logging | ||||||
| from xml.etree import cElementTree as ET | from xml.etree import cElementTree as ET | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class jobs(base.base_plugin): | class jobs(base.base_plugin): | ||||||
| 	def plugin_init(self): | 	def plugin_init(self): | ||||||
| 		self.xep = 'pubsubjob' | 		self.xep = 'pubsubjob' | ||||||
| 		self.description = "Job distribution over Pubsub" | 		self.description = "Job distribution over Pubsub" | ||||||
|  | 	 | ||||||
| 	def post_init(self): | 	def post_init(self): | ||||||
| 		pass | 		pass | ||||||
| 		#TODO add event | 		#TODO add event | ||||||
|  | 	 | ||||||
| 	def createJobNode(self, host, jid, node, config=None): | 	def createJobNode(self, host, jid, node, config=None): | ||||||
| 		pass | 		pass | ||||||
|  |  | ||||||
| @@ -24,7 +20,7 @@ class jobs(base.base_plugin): | |||||||
| 	def claimJob(self, host, node, jobid, ifrom=None): | 	def claimJob(self, host, node, jobid, ifrom=None): | ||||||
| 		return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed')) | 		return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed')) | ||||||
|  |  | ||||||
| 	def unclaimJob(self, host, node, jobid): | 	def unclaimJob(self, jobid): | ||||||
| 		return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed')) | 		return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed')) | ||||||
|  |  | ||||||
| 	def finishJob(self, host, node, jobid, payload=None): | 	def finishJob(self, host, node, jobid, payload=None): | ||||||
| @@ -42,8 +38,7 @@ class jobs(base.base_plugin): | |||||||
| 		iq['psstate']['item'] = jobid | 		iq['psstate']['item'] = jobid | ||||||
| 		iq['psstate']['payload'] = state | 		iq['psstate']['payload'] = state | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or type(result) == bool or result['type'] != 'result': | 		if result is None or result['type'] != 'result': | ||||||
| 			log.error("Unable to change %s:%s to %s" % (node, jobid, state)) |  | ||||||
| 			return False | 			return False | ||||||
| 		return True | 		return True | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,421 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
| from . import base |  | ||||||
| import logging |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
| import copy |  | ||||||
| import logging |  | ||||||
| #TODO support item groups and results |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class old_0004(base.base_plugin): |  | ||||||
|  |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = '0004' |  | ||||||
| 		self.description = '*Deprecated Data Forms' |  | ||||||
| 		self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form') |  | ||||||
|  |  | ||||||
| 	def post_init(self): |  | ||||||
| 		base.base_plugin.post_init(self) |  | ||||||
| 		self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') |  | ||||||
| 		log.warning("This implementation of XEP-0004 is deprecated.") |  | ||||||
|  |  | ||||||
| 	def handler_message_xform(self, xml): |  | ||||||
| 		object = self.handle_form(xml) |  | ||||||
| 		self.xmpp.event("message_form", object) |  | ||||||
|  |  | ||||||
| 	def handler_presence_xform(self, xml): |  | ||||||
| 		object = self.handle_form(xml) |  | ||||||
| 		self.xmpp.event("presence_form", object) |  | ||||||
|  |  | ||||||
| 	def handle_form(self, xml): |  | ||||||
| 		xmlform = xml.find('{jabber:x:data}x') |  | ||||||
| 		object = self.buildForm(xmlform) |  | ||||||
| 		self.xmpp.event("message_xform", object) |  | ||||||
| 		return object |  | ||||||
|  |  | ||||||
| 	def buildForm(self, xml): |  | ||||||
| 		form = Form(ftype=xml.attrib['type']) |  | ||||||
| 		form.fromXML(xml) |  | ||||||
| 		return form |  | ||||||
|  |  | ||||||
| 	def makeForm(self, ftype='form', title='', instructions=''): |  | ||||||
| 		return Form(self.xmpp, ftype, title, instructions) |  | ||||||
|  |  | ||||||
| class FieldContainer(object): |  | ||||||
| 	def __init__(self, stanza = 'form'): |  | ||||||
| 		self.fields = [] |  | ||||||
| 		self.field = {} |  | ||||||
| 		self.stanza = stanza |  | ||||||
|  |  | ||||||
| 	def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): |  | ||||||
| 		self.field[var] = FormField(var, ftype, label, desc, required, value) |  | ||||||
| 		self.fields.append(self.field[var]) |  | ||||||
| 		return self.field[var] |  | ||||||
|  |  | ||||||
| 	def buildField(self, xml): |  | ||||||
| 		self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) |  | ||||||
| 		self.fields.append(self.field[xml.get('var', '__unnamed__')]) |  | ||||||
| 		self.field[xml.get('var', '__unnamed__')].buildField(xml) |  | ||||||
|  |  | ||||||
| 	def buildContainer(self, xml): |  | ||||||
| 		self.stanza = xml.tag |  | ||||||
| 		for field in xml.findall('{jabber:x:data}field'): |  | ||||||
| 			self.buildField(field) |  | ||||||
|  |  | ||||||
| 	def getXML(self, ftype): |  | ||||||
| 		container = ET.Element(self.stanza) |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			container.append(field.getXML(ftype)) |  | ||||||
| 		return container |  | ||||||
|  |  | ||||||
| class Form(FieldContainer): |  | ||||||
| 	types = ('form', 'submit', 'cancel', 'result') |  | ||||||
| 	def __init__(self, xmpp=None, ftype='form', title='', instructions=''): |  | ||||||
| 		if not ftype in self.types: |  | ||||||
| 			raise ValueError("Invalid Form Type") |  | ||||||
| 		FieldContainer.__init__(self) |  | ||||||
| 		self.xmpp = xmpp |  | ||||||
| 		self.type = ftype |  | ||||||
| 		self.title = title |  | ||||||
| 		self.instructions = instructions |  | ||||||
| 		self.reported = [] |  | ||||||
| 		self.items = [] |  | ||||||
|  |  | ||||||
| 	def merge(self, form2): |  | ||||||
| 		form1 = Form(ftype=self.type) |  | ||||||
| 		form1.fromXML(self.getXML(self.type)) |  | ||||||
| 		for field in form2.fields: |  | ||||||
| 			if not field.var in form1.field: |  | ||||||
| 				form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) |  | ||||||
| 			else: |  | ||||||
| 				form1.field[field.var].value = field.value |  | ||||||
| 			for option, label in field.options: |  | ||||||
| 				if (option, label) not in form1.field[field.var].options: |  | ||||||
| 					form1.fields[field.var].addOption(option, label) |  | ||||||
| 		return form1 |  | ||||||
|  |  | ||||||
| 	def copy(self): |  | ||||||
| 		newform = Form(ftype=self.type) |  | ||||||
| 		newform.fromXML(self.getXML(self.type)) |  | ||||||
| 		return newform |  | ||||||
|  |  | ||||||
| 	def update(self, form): |  | ||||||
| 		values = form.getValues() |  | ||||||
| 		for var in values: |  | ||||||
| 			if var in self.fields: |  | ||||||
| 				self.fields[var].setValue(self.fields[var]) |  | ||||||
|  |  | ||||||
| 	def getValues(self): |  | ||||||
| 		result = {} |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			value = field.value |  | ||||||
| 			if len(value) == 1: |  | ||||||
| 				value = value[0] |  | ||||||
| 			result[field.var] = value |  | ||||||
| 		return result |  | ||||||
|  |  | ||||||
| 	def setValues(self, values={}): |  | ||||||
| 		for field in values: |  | ||||||
| 			if field in self.field: |  | ||||||
| 				if isinstance(values[field], list) or isinstance(values[field], tuple): |  | ||||||
| 					for value in values[field]: |  | ||||||
| 						self.field[field].setValue(value) |  | ||||||
| 				else: |  | ||||||
| 					self.field[field].setValue(values[field]) |  | ||||||
|  |  | ||||||
| 	def fromXML(self, xml): |  | ||||||
| 		self.buildForm(xml) |  | ||||||
|  |  | ||||||
| 	def addItem(self): |  | ||||||
| 		newitem = FieldContainer('item') |  | ||||||
| 		self.items.append(newitem) |  | ||||||
| 		return newitem |  | ||||||
|  |  | ||||||
| 	def buildItem(self, xml): |  | ||||||
| 		newitem = self.addItem() |  | ||||||
| 		newitem.buildContainer(xml) |  | ||||||
|  |  | ||||||
| 	def addReported(self): |  | ||||||
| 		reported = FieldContainer('reported') |  | ||||||
| 		self.reported.append(reported) |  | ||||||
| 		return reported |  | ||||||
|  |  | ||||||
| 	def buildReported(self, xml): |  | ||||||
| 		reported = self.addReported() |  | ||||||
| 		reported.buildContainer(xml) |  | ||||||
|  |  | ||||||
| 	def setTitle(self, title): |  | ||||||
| 		self.title = title |  | ||||||
|  |  | ||||||
| 	def setInstructions(self, instructions): |  | ||||||
| 		self.instructions = instructions |  | ||||||
|  |  | ||||||
| 	def setType(self, ftype): |  | ||||||
| 		self.type = ftype |  | ||||||
|  |  | ||||||
| 	def getXMLMessage(self, to): |  | ||||||
| 		msg = self.xmpp.makeMessage(to) |  | ||||||
| 		msg.append(self.getXML()) |  | ||||||
| 		return msg |  | ||||||
|  |  | ||||||
| 	def buildForm(self, xml): |  | ||||||
| 		self.type = xml.get('type', 'form') |  | ||||||
| 		if xml.find('{jabber:x:data}title') is not None: |  | ||||||
| 			self.setTitle(xml.find('{jabber:x:data}title').text) |  | ||||||
| 		if xml.find('{jabber:x:data}instructions') is not None: |  | ||||||
| 			self.setInstructions(xml.find('{jabber:x:data}instructions').text) |  | ||||||
| 		for field in xml.findall('{jabber:x:data}field'): |  | ||||||
| 			self.buildField(field) |  | ||||||
| 		for reported in xml.findall('{jabber:x:data}reported'): |  | ||||||
| 			self.buildReported(reported) |  | ||||||
| 		for item in xml.findall('{jabber:x:data}item'): |  | ||||||
| 			self.buildItem(item) |  | ||||||
|  |  | ||||||
| 	#def getXML(self, tostring = False): |  | ||||||
| 	def getXML(self, ftype=None): |  | ||||||
| 		if ftype: |  | ||||||
| 			self.type = ftype |  | ||||||
| 		form = ET.Element('{jabber:x:data}x') |  | ||||||
| 		form.attrib['type'] = self.type |  | ||||||
| 		if self.title and self.type in ('form', 'result'): |  | ||||||
| 			title = ET.Element('{jabber:x:data}title') |  | ||||||
| 			title.text = self.title |  | ||||||
| 			form.append(title) |  | ||||||
| 		if self.instructions and self.type == 'form': |  | ||||||
| 			instructions = ET.Element('{jabber:x:data}instructions') |  | ||||||
| 			instructions.text = self.instructions |  | ||||||
| 			form.append(instructions) |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			form.append(field.getXML(self.type)) |  | ||||||
| 		for reported in self.reported: |  | ||||||
| 			form.append(reported.getXML('{jabber:x:data}reported')) |  | ||||||
| 		for item in self.items: |  | ||||||
| 			form.append(item.getXML(self.type)) |  | ||||||
| 		#if tostring: |  | ||||||
| 		#	form = self.xmpp.tostring(form) |  | ||||||
| 		return form |  | ||||||
|  |  | ||||||
| 	def getXHTML(self): |  | ||||||
| 		form = ET.Element('{http://www.w3.org/1999/xhtml}form') |  | ||||||
| 		if self.title: |  | ||||||
| 			title = ET.Element('h2') |  | ||||||
| 			title.text = self.title |  | ||||||
| 			form.append(title) |  | ||||||
| 		if self.instructions: |  | ||||||
| 			instructions = ET.Element('p') |  | ||||||
| 			instructions.text = self.instructions |  | ||||||
| 			form.append(instructions) |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			form.append(field.getXHTML()) |  | ||||||
| 		for field in self.reported: |  | ||||||
| 			form.append(field.getXHTML()) |  | ||||||
| 		for field in self.items: |  | ||||||
| 			form.append(field.getXHTML()) |  | ||||||
| 		return form |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	def makeSubmit(self): |  | ||||||
| 		self.setType('submit') |  | ||||||
|  |  | ||||||
| class FormField(object): |  | ||||||
| 	types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single') |  | ||||||
| 	listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single') |  | ||||||
| 	lbtypes = ('fixed', 'text-multi') |  | ||||||
| 	def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None): |  | ||||||
| 		if not ftype in self.types: |  | ||||||
| 			raise ValueError("Invalid Field Type") |  | ||||||
| 		self.type = ftype |  | ||||||
| 		self.var = var |  | ||||||
| 		self.label = label |  | ||||||
| 		self.desc = desc |  | ||||||
| 		self.options = [] |  | ||||||
| 		self.required = False |  | ||||||
| 		self.value = [] |  | ||||||
| 		if self.type in self.listtypes: |  | ||||||
| 			self.islist = True |  | ||||||
| 		else: |  | ||||||
| 			self.islist = False |  | ||||||
| 		if self.type in self.lbtypes: |  | ||||||
| 			self.islinebreak = True |  | ||||||
| 		else: |  | ||||||
| 			self.islinebreak = False |  | ||||||
| 		if value: |  | ||||||
| 			self.setValue(value) |  | ||||||
|  |  | ||||||
| 	def addOption(self, value, label): |  | ||||||
| 		if self.islist: |  | ||||||
| 			self.options.append((value, label)) |  | ||||||
| 		else: |  | ||||||
| 			raise ValueError("Cannot add options to non-list type field.") |  | ||||||
|  |  | ||||||
| 	def setTrue(self): |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			self.value = [True] |  | ||||||
|  |  | ||||||
| 	def setFalse(self): |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			self.value = [False] |  | ||||||
|  |  | ||||||
| 	def require(self): |  | ||||||
| 		self.required = True |  | ||||||
|  |  | ||||||
| 	def setDescription(self, desc): |  | ||||||
| 		self.desc = desc |  | ||||||
|  |  | ||||||
| 	def setValue(self, value): |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			if value in ('1', 1, True, 'true', 'True', 'yes'): |  | ||||||
| 				value = True |  | ||||||
| 			else: |  | ||||||
| 				value = False |  | ||||||
| 		if self.islinebreak and value is not None: |  | ||||||
| 			self.value += value.split('\n') |  | ||||||
| 		else: |  | ||||||
| 			if len(self.value) and (not self.islist or self.type == 'list-single'): |  | ||||||
| 				self.value = [value] |  | ||||||
| 			else: |  | ||||||
| 				self.value.append(value) |  | ||||||
|  |  | ||||||
| 	def delValue(self, value): |  | ||||||
| 		if type(self.value) == type([]): |  | ||||||
| 			try: |  | ||||||
| 				idx = self.value.index(value) |  | ||||||
| 				if idx != -1: |  | ||||||
| 					self.value.pop(idx) |  | ||||||
| 			except ValueError: |  | ||||||
| 				pass |  | ||||||
| 		else: |  | ||||||
| 			self.value = '' |  | ||||||
|  |  | ||||||
| 	def setAnswer(self, value): |  | ||||||
| 		self.setValue(value) |  | ||||||
|  |  | ||||||
| 	def buildField(self, xml): |  | ||||||
| 		self.type = xml.get('type', 'text-single') |  | ||||||
| 		self.label = xml.get('label', '') |  | ||||||
| 		for option in xml.findall('{jabber:x:data}option'): |  | ||||||
| 			self.addOption(option.find('{jabber:x:data}value').text, option.get('label', '')) |  | ||||||
| 		for value in xml.findall('{jabber:x:data}value'): |  | ||||||
| 			self.setValue(value.text) |  | ||||||
| 		if xml.find('{jabber:x:data}required') is not None: |  | ||||||
| 			self.require() |  | ||||||
| 		if xml.find('{jabber:x:data}desc') is not None: |  | ||||||
| 			self.setDescription(xml.find('{jabber:x:data}desc').text) |  | ||||||
|  |  | ||||||
| 	def getXML(self, ftype): |  | ||||||
| 		field = ET.Element('{jabber:x:data}field') |  | ||||||
| 		if ftype != 'result': |  | ||||||
| 			field.attrib['type'] = self.type |  | ||||||
| 		if self.type != 'fixed': |  | ||||||
| 			if self.var: |  | ||||||
| 				field.attrib['var'] = self.var |  | ||||||
| 			if self.label: |  | ||||||
| 				field.attrib['label'] = self.label |  | ||||||
| 		if ftype == 'form': |  | ||||||
| 			for option in self.options: |  | ||||||
| 				optionxml = ET.Element('{jabber:x:data}option') |  | ||||||
| 				optionxml.attrib['label'] = option[1] |  | ||||||
| 				optionval = ET.Element('{jabber:x:data}value') |  | ||||||
| 				optionval.text = option[0] |  | ||||||
| 				optionxml.append(optionval) |  | ||||||
| 				field.append(optionxml) |  | ||||||
| 			if self.required: |  | ||||||
| 				required = ET.Element('{jabber:x:data}required') |  | ||||||
| 				field.append(required) |  | ||||||
| 			if self.desc: |  | ||||||
| 				desc = ET.Element('{jabber:x:data}desc') |  | ||||||
| 				desc.text = self.desc |  | ||||||
| 				field.append(desc) |  | ||||||
| 		for value in self.value: |  | ||||||
| 			valuexml = ET.Element('{jabber:x:data}value') |  | ||||||
| 			if value is True or value is False: |  | ||||||
| 				if value: |  | ||||||
| 					valuexml.text = '1' |  | ||||||
| 				else: |  | ||||||
| 					valuexml.text = '0' |  | ||||||
| 			else: |  | ||||||
| 				valuexml.text = value |  | ||||||
| 			field.append(valuexml) |  | ||||||
| 		return field |  | ||||||
|  |  | ||||||
| 	def getXHTML(self): |  | ||||||
| 		field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) |  | ||||||
| 		if self.label: |  | ||||||
| 			label = ET.Element('p') |  | ||||||
| 			label.text = "%s: " % self.label |  | ||||||
| 		else: |  | ||||||
| 			label = ET.Element('p') |  | ||||||
| 			label.text = "%s: " % self.var |  | ||||||
| 		field.append(label) |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			formf = ET.Element('input', {'type': 'checkbox', 'name': self.var}) |  | ||||||
| 			if len(self.value) and self.value[0] in (True, 'true', '1'): |  | ||||||
| 				formf.attrib['checked'] = 'checked' |  | ||||||
| 		elif self.type == 'fixed': |  | ||||||
| 			formf = ET.Element('p') |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 			field.append(formf) |  | ||||||
| 			formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		elif self.type == 'hidden': |  | ||||||
| 			formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		elif self.type in ('jid-multi', 'list-multi'): |  | ||||||
| 			formf = ET.Element('select', {'name': self.var}) |  | ||||||
| 			for option in self.options: |  | ||||||
| 				optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'}) |  | ||||||
| 				optf.text = option[1] |  | ||||||
| 				if option[1] in self.value: |  | ||||||
| 					optf.attrib['selected'] = 'selected' |  | ||||||
| 				formf.append(option) |  | ||||||
| 		elif self.type in ('jid-single', 'text-single'): |  | ||||||
| 			formf = ET.Element('input', {'type': 'text', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.attrib['value'] = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		elif self.type == 'list-single': |  | ||||||
| 			formf = ET.Element('select', {'name': self.var}) |  | ||||||
| 			for option in self.options: |  | ||||||
| 				optf = ET.Element('option', {'value': option[0]}) |  | ||||||
| 				optf.text = option[1] |  | ||||||
| 				if not optf.text: |  | ||||||
| 					optf.text = option[0] |  | ||||||
| 				if option[1] in self.value: |  | ||||||
| 					optf.attrib['selected'] = 'selected' |  | ||||||
| 				formf.append(optf) |  | ||||||
| 		elif self.type == 'text-multi': |  | ||||||
| 			formf = ET.Element('textarea', {'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 			if not formf.text: |  | ||||||
| 				formf.text = ' ' |  | ||||||
| 		elif self.type == 'text-private': |  | ||||||
| 			formf = ET.Element('input', {'type': 'password', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.attrib['value'] = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		label.append(formf) |  | ||||||
| 		return field |  | ||||||
|  |  | ||||||
							
								
								
									
										585
									
								
								sleekxmpp/plugins/stanza_pubsub.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								sleekxmpp/plugins/stanza_pubsub.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,585 @@ | |||||||
|  | from .. xmlstream.stanzabase import ElementBase, ET, JID | ||||||
|  | from .. stanza.iq import Iq | ||||||
|  | from .. stanza.message import Message | ||||||
|  | from .. basexmpp import basexmpp | ||||||
|  | from .. xmlstream.xmlstream import XMLStream | ||||||
|  | import logging | ||||||
|  | from . import xep_0004 | ||||||
|  |  | ||||||
|  | def stanzaPlugin(stanza, plugin):                                                                        | ||||||
|  | 	stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin                                              | ||||||
|  | 	stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin  | ||||||
|  |  | ||||||
|  | class PubsubState(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/psstate' | ||||||
|  | 	name = 'state' | ||||||
|  | 	plugin_attrib = 'psstate' | ||||||
|  | 	interfaces = set(('node', 'item', 'payload')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def setPayload(self, value): | ||||||
|  | 		self.xml.append(value) | ||||||
|  | 	 | ||||||
|  | 	def getPayload(self): | ||||||
|  | 		childs = self.xml.getchildren() | ||||||
|  | 		if len(childs) > 0: | ||||||
|  | 			return childs[0] | ||||||
|  | 	 | ||||||
|  | 	def delPayload(self): | ||||||
|  | 		for child in self.xml.getchildren(): | ||||||
|  | 			self.xml.remove(child) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Iq, PubsubState) | ||||||
|  |  | ||||||
|  | class PubsubStateEvent(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/psstate#event' | ||||||
|  | 	name = 'event' | ||||||
|  | 	plugin_attrib = 'psstate_event' | ||||||
|  | 	intefaces = set(tuple()) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Message, PubsubStateEvent) | ||||||
|  | stanzaPlugin(PubsubStateEvent, PubsubState) | ||||||
|  |  | ||||||
|  | class Pubsub(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'pubsub' | ||||||
|  | 	plugin_attrib = 'pubsub' | ||||||
|  | 	interfaces = set(tuple()) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Iq, Pubsub) | ||||||
|  |  | ||||||
|  | class PubsubOwner(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	name = 'pubsub' | ||||||
|  | 	plugin_attrib = 'pubsub_owner' | ||||||
|  | 	interfaces = set(tuple()) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Iq, PubsubOwner) | ||||||
|  |  | ||||||
|  | class Affiliation(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'affiliation' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node', 'affiliation')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | class Affiliations(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'affiliations' | ||||||
|  | 	plugin_attrib = 'affiliations' | ||||||
|  | 	interfaces = set(tuple()) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	subitem = (Affiliation,) | ||||||
|  |  | ||||||
|  | 	def append(self, affiliation): | ||||||
|  | 		if not isinstance(affiliation, Affiliation): | ||||||
|  | 			raise TypeError | ||||||
|  | 		self.xml.append(affiliation.xml) | ||||||
|  | 		return self.iterables.append(affiliation) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Affiliations) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Subscription(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'subscription' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('jid', 'node', 'subscription', 'subid')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | 	def setjid(self, value): | ||||||
|  | 		self._setattr('jid', str(value)) | ||||||
|  | 	 | ||||||
|  | 	def getjid(self): | ||||||
|  | 		return jid(self._getattr('jid')) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Subscription) | ||||||
|  |  | ||||||
|  | class Subscriptions(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'subscriptions' | ||||||
|  | 	plugin_attrib = 'subscriptions' | ||||||
|  | 	interfaces = set(tuple()) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	subitem = (Subscription,) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Subscriptions) | ||||||
|  |  | ||||||
|  | class OptionalSetting(object): | ||||||
|  | 	interfaces = set(('required',)) | ||||||
|  |  | ||||||
|  | 	def setRequired(self, value): | ||||||
|  | 		value = bool(value) | ||||||
|  | 		if value and not self['required']: | ||||||
|  | 			self.xml.append(ET.Element("{%s}required" % self.namespace)) | ||||||
|  | 		elif not value and self['required']: | ||||||
|  | 			self.delRequired() | ||||||
|  | 	 | ||||||
|  | 	def getRequired(self): | ||||||
|  | 		required = self.xml.find("{%s}required" % self.namespace) | ||||||
|  | 		if required is not None: | ||||||
|  | 			return True | ||||||
|  | 		else: | ||||||
|  | 			return False | ||||||
|  | 	 | ||||||
|  | 	def delRequired(self): | ||||||
|  | 		required = self.xml.find("{%s}required" % self.namespace) | ||||||
|  | 		if required is not None: | ||||||
|  | 			self.xml.remove(required) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SubscribeOptions(ElementBase, OptionalSetting): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'subscribe-options' | ||||||
|  | 	plugin_attrib = 'suboptions' | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	interfaces = set(('required',)) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Subscription, SubscribeOptions) | ||||||
|  |  | ||||||
|  | class Item(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'item' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('id', 'payload')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | 	def setPayload(self, value): | ||||||
|  | 		self.xml.append(value) | ||||||
|  | 	 | ||||||
|  | 	def getPayload(self): | ||||||
|  | 		childs = self.xml.getchildren() | ||||||
|  | 		if len(childs) > 0: | ||||||
|  | 			return childs[0] | ||||||
|  | 	 | ||||||
|  | 	def delPayload(self): | ||||||
|  | 		for child in self.xml.getchildren(): | ||||||
|  | 			self.xml.remove(child) | ||||||
|  |  | ||||||
|  | class Items(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'items' | ||||||
|  | 	plugin_attrib = 'items' | ||||||
|  | 	interfaces = set(tuple()) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	subitem = (Item,) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Items) | ||||||
|  |  | ||||||
|  | class Create(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'create' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Create) | ||||||
|  |  | ||||||
|  | #class Default(ElementBase): | ||||||
|  | #	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | #	name = 'default' | ||||||
|  | #	plugin_attrib = name | ||||||
|  | #	interfaces = set(('node', 'type')) | ||||||
|  | #	plugin_attrib_map = {} | ||||||
|  | #	plugin_tag_map = {} | ||||||
|  | # | ||||||
|  | #	def getType(self): | ||||||
|  | #		t = self._getAttr('type') | ||||||
|  | #		if not t: t == 'leaf' | ||||||
|  | #		return t | ||||||
|  | # | ||||||
|  | #stanzaPlugin(Pubsub, Default) | ||||||
|  |  | ||||||
|  | class Publish(Items): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'publish' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	subitem = (Item,) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Publish) | ||||||
|  |  | ||||||
|  | class Retract(Items): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'retract' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node', 'notify')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Retract) | ||||||
|  |  | ||||||
|  | class Unsubscribe(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'unsubscribe' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node', 'jid')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def setJid(self, value): | ||||||
|  | 		self._setAttr('jid', str(value)) | ||||||
|  | 	 | ||||||
|  | 	def getJid(self): | ||||||
|  | 		return JID(self._getAttr('jid')) | ||||||
|  |  | ||||||
|  | class Subscribe(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'subscribe' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node', 'jid')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | 	def setJid(self, value): | ||||||
|  | 		self._setAttr('jid', str(value)) | ||||||
|  | 	 | ||||||
|  | 	def getJid(self): | ||||||
|  | 		return JID(self._getAttr('jid')) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Subscribe) | ||||||
|  |  | ||||||
|  | class Configure(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'configure' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node', 'type', 'config')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | 	def getType(self): | ||||||
|  | 		t = self._getAttr('type') | ||||||
|  | 		if not t: t == 'leaf' | ||||||
|  | 		return t | ||||||
|  | 	 | ||||||
|  | 	def getConfig(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		form = xep_0004.Form() | ||||||
|  | 		if config is not None: | ||||||
|  | 			form.fromXML(config) | ||||||
|  | 		return form | ||||||
|  | 	 | ||||||
|  | 	def setConfig(self, value): | ||||||
|  | 		self.xml.append(value.getXML()) | ||||||
|  | 		return self | ||||||
|  | 	 | ||||||
|  | 	def delConfig(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		self.xml.remove(config) | ||||||
|  | 	 | ||||||
|  | stanzaPlugin(Pubsub, Configure) | ||||||
|  |  | ||||||
|  | class DefaultConfig(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	name = 'default' | ||||||
|  | 	plugin_attrib = 'default' | ||||||
|  | 	interfaces = set(('node', 'type', 'config')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		ElementBase.__init__(self, *args, **kwargs) | ||||||
|  | 		 | ||||||
|  | 	def getConfig(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		form = xep_0004.Form() | ||||||
|  | 		if config is not None: | ||||||
|  | 			form.fromXML(config) | ||||||
|  | 		return form | ||||||
|  | 	 | ||||||
|  | 	def setConfig(self, value): | ||||||
|  | 		self.xml.append(value.getXML()) | ||||||
|  | 		return self | ||||||
|  | 	 | ||||||
|  | 	def delConfig(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		self.xml.remove(config) | ||||||
|  |  | ||||||
|  | 	def getType(self): | ||||||
|  | 		t = self._getAttr('type') | ||||||
|  | 		if not t: t = 'leaf' | ||||||
|  | 		return t | ||||||
|  |  | ||||||
|  | stanzaPlugin(PubsubOwner, DefaultConfig) | ||||||
|  |  | ||||||
|  | class Options(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub' | ||||||
|  | 	name = 'options' | ||||||
|  | 	plugin_attrib = 'options' | ||||||
|  | 	interfaces = set(('jid', 'node', 'options')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		ElementBase.__init__(self, *args, **kwargs) | ||||||
|  | 		 | ||||||
|  | 	def getOptions(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		form = xep_0004.Form() | ||||||
|  | 		if config is not None: | ||||||
|  | 			form.fromXML(config) | ||||||
|  | 		return form | ||||||
|  | 	 | ||||||
|  | 	def setOptions(self, value): | ||||||
|  | 		self.xml.append(value.getXML()) | ||||||
|  | 		return self | ||||||
|  | 	 | ||||||
|  | 	def delOptions(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		self.xml.remove(config) | ||||||
|  | 	 | ||||||
|  | 	def setJid(self, value): | ||||||
|  | 		self._setAttr('jid', str(value)) | ||||||
|  | 	 | ||||||
|  | 	def getJid(self): | ||||||
|  | 		return JID(self._getAttr('jid')) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Pubsub, Options) | ||||||
|  | stanzaPlugin(Subscribe, Options) | ||||||
|  |  | ||||||
|  | class OwnerAffiliations(Affiliations): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	interfaces = set(('node')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def append(self, affiliation): | ||||||
|  | 		if not isinstance(affiliation, OwnerAffiliation): | ||||||
|  | 			raise TypeError | ||||||
|  | 		self.xml.append(affiliation.xml) | ||||||
|  | 		return self.affiliations.append(affiliation) | ||||||
|  |  | ||||||
|  | stanzaPlugin(PubsubOwner, OwnerAffiliations) | ||||||
|  |  | ||||||
|  | class OwnerAffiliation(Affiliation): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	interfaces = set(('affiliation', 'jid')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | class OwnerConfigure(Configure): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	interfaces = set(('node', 'config')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(PubsubOwner, OwnerConfigure) | ||||||
|  |  | ||||||
|  | class OwnerDefault(OwnerConfigure): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	interfaces = set(('node', 'config')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(PubsubOwner, OwnerDefault) | ||||||
|  |  | ||||||
|  | class OwnerDelete(ElementBase, OptionalSetting): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	name = 'delete' | ||||||
|  | 	plugin_attrib = 'delete' | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  |  | ||||||
|  | stanzaPlugin(PubsubOwner, OwnerDelete) | ||||||
|  |  | ||||||
|  | class OwnerPurge(ElementBase, OptionalSetting): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	name = 'purge' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(PubsubOwner, OwnerPurge) | ||||||
|  |  | ||||||
|  | class OwnerRedirect(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	name = 'redirect' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node', 'jid')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def setJid(self, value): | ||||||
|  | 		self._setAttr('jid', str(value)) | ||||||
|  | 	 | ||||||
|  | 	def getJid(self): | ||||||
|  | 		return JID(self._getAttr('jid')) | ||||||
|  |  | ||||||
|  | stanzaPlugin(OwnerDelete, OwnerRedirect) | ||||||
|  |  | ||||||
|  | class OwnerSubscriptions(Subscriptions): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def append(self, subscription): | ||||||
|  | 		if not isinstance(subscription, OwnerSubscription): | ||||||
|  | 			raise TypeError | ||||||
|  | 		self.xml.append(subscription.xml) | ||||||
|  | 		return self.subscriptions.append(subscription) | ||||||
|  |  | ||||||
|  | stanzaPlugin(PubsubOwner, OwnerSubscriptions) | ||||||
|  |  | ||||||
|  | class OwnerSubscription(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  | 	name = 'subscription' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('jid', 'subscription')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | 	def setJid(self, value): | ||||||
|  | 		self._setAttr('jid', str(value)) | ||||||
|  | 	 | ||||||
|  | 	def getJid(self): | ||||||
|  | 		return JID(self._getAttr('from')) | ||||||
|  |  | ||||||
|  | class Event(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'event' | ||||||
|  | 	plugin_attrib = 'pubsub_event' | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Message, Event) | ||||||
|  |  | ||||||
|  | class EventItem(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'item' | ||||||
|  | 	plugin_attrib = 'item' | ||||||
|  | 	interfaces = set(('id', 'payload')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | 	def setPayload(self, value): | ||||||
|  | 		self.xml.append(value) | ||||||
|  | 	 | ||||||
|  | 	def getPayload(self): | ||||||
|  | 		childs = self.xml.getchildren() | ||||||
|  | 		if len(childs) > 0: | ||||||
|  | 			return childs[0] | ||||||
|  | 	 | ||||||
|  | 	def delPayload(self): | ||||||
|  | 		for child in self.xml.getchildren(): | ||||||
|  | 			self.xml.remove(child) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class EventRetract(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'retract' | ||||||
|  | 	plugin_attrib = 'retract' | ||||||
|  | 	interfaces = set(('id',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | class EventItems(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'items' | ||||||
|  | 	plugin_attrib = 'items' | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	subitem = (EventItem, EventRetract) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Event, EventItems) | ||||||
|  |  | ||||||
|  | class EventCollection(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'collection' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Event, EventCollection) | ||||||
|  |  | ||||||
|  | class EventAssociate(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'associate' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(EventCollection, EventAssociate) | ||||||
|  |  | ||||||
|  | class EventDisassociate(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'disassociate' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(EventCollection, EventDisassociate) | ||||||
|  |  | ||||||
|  | class EventConfiguration(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'configuration' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node', 'config')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def getConfig(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		form = xep_0004.Form() | ||||||
|  | 		if config is not None: | ||||||
|  | 			form.fromXML(config) | ||||||
|  | 		return form | ||||||
|  | 	 | ||||||
|  | 	def setConfig(self, value): | ||||||
|  | 		self.xml.append(value.getXML()) | ||||||
|  | 		return self | ||||||
|  | 	 | ||||||
|  | 	def delConfig(self): | ||||||
|  | 		config = self.xml.find('{jabber:x:data}x') | ||||||
|  | 		self.xml.remove(config) | ||||||
|  | 	 | ||||||
|  | stanzaPlugin(Event, EventConfiguration) | ||||||
|  |  | ||||||
|  | class EventPurge(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'purge' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node',)) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  |  | ||||||
|  | stanzaPlugin(Event, EventPurge) | ||||||
|  |  | ||||||
|  | class EventSubscription(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||||
|  | 	name = 'subscription' | ||||||
|  | 	plugin_attrib = name | ||||||
|  | 	interfaces = set(('node','expiry', 'jid', 'subid', 'subscription')) | ||||||
|  | 	plugin_attrib_map = {} | ||||||
|  | 	plugin_tag_map = {} | ||||||
|  | 	 | ||||||
|  | 	def setJid(self, value): | ||||||
|  | 		self._setAttr('jid', str(value)) | ||||||
|  | 	 | ||||||
|  | 	def getJid(self): | ||||||
|  | 		return JID(self._getAttr('jid')) | ||||||
|  |  | ||||||
|  | stanzaPlugin(Event, EventSubscription) | ||||||
| @@ -1,395 +1,427 @@ | |||||||
| """ | """ | ||||||
| 	SleekXMPP: The Sleek XMPP Library | 	SleekXMPP: The Sleek XMPP Library | ||||||
| 	Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | 	Copyright (C) 2007  Nathanael C. Fritz | ||||||
| 	This file is part of SleekXMPP. | 	This file is part of SleekXMPP. | ||||||
|  |  | ||||||
| 	See the file LICENSE for copying permission. | 	SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  | 	it under the terms of the GNU General Public License as published by | ||||||
|  | 	the Free Software Foundation; either version 2 of the License, or | ||||||
|  | 	(at your option) any later version. | ||||||
|  |  | ||||||
|  | 	SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  | 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | 	GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | 	You should have received a copy of the GNU General Public License | ||||||
|  | 	along with SleekXMPP; if not, write to the Free Software | ||||||
|  | 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
| """ | """ | ||||||
|  |  | ||||||
| import logging |  | ||||||
| import copy |  | ||||||
| from . import base | from . import base | ||||||
| from .. xmlstream.handler.callback import Callback | import logging | ||||||
| from .. xmlstream.matcher.xpath import MatchXPath | from xml.etree import cElementTree as ET | ||||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID | import copy | ||||||
| from .. stanza.message import Message | #TODO support item groups and results | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Form(ElementBase): |  | ||||||
| 	namespace = 'jabber:x:data' |  | ||||||
| 	name = 'x' |  | ||||||
| 	plugin_attrib = 'form' |  | ||||||
| 	interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values')) |  | ||||||
| 	sub_interfaces = set(('title',)) |  | ||||||
| 	form_types = set(('cancel', 'form', 'result', 'submit')) |  | ||||||
|  |  | ||||||
| 	def __init__(self, *args, **kwargs): |  | ||||||
| 		title = None |  | ||||||
| 		if 'title' in kwargs: |  | ||||||
| 			title = kwargs['title'] |  | ||||||
| 			del kwargs['title'] |  | ||||||
| 		ElementBase.__init__(self, *args, **kwargs) |  | ||||||
| 		if title is not None: |  | ||||||
| 			self['title'] = title |  | ||||||
| 		self.field = FieldAccessor(self) |  | ||||||
|  |  | ||||||
| 	def setup(self, xml=None): |  | ||||||
| 		if ElementBase.setup(self, xml): #if we had to generate xml |  | ||||||
| 			self['type'] = 'form' |  | ||||||
|  |  | ||||||
| 	def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs): |  | ||||||
| 		kwtype = kwargs.get('type', None) |  | ||||||
| 		if kwtype is None: |  | ||||||
| 			kwtype = ftype |  | ||||||
|  |  | ||||||
| 		field = FormField(parent=self) |  | ||||||
| 		field['var'] = var |  | ||||||
| 		field['type'] = kwtype |  | ||||||
| 		field['label'] = label |  | ||||||
| 		field['desc'] = desc |  | ||||||
| 		field['required'] = required |  | ||||||
| 		field['value'] = value |  | ||||||
| 		if options is not None: |  | ||||||
| 			field['options'] = options |  | ||||||
| 		return field |  | ||||||
|  |  | ||||||
| 	def getXML(self, type='submit'): |  | ||||||
| 		self['type'] = type |  | ||||||
| 		log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") |  | ||||||
| 		return self.xml |  | ||||||
|  |  | ||||||
| 	def fromXML(self, xml): |  | ||||||
| 		log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") |  | ||||||
| 		n = Form(xml=xml) |  | ||||||
| 		return n |  | ||||||
|  |  | ||||||
| 	def addItem(self, values): |  | ||||||
| 		itemXML = ET.Element('{%s}item' % self.namespace) |  | ||||||
| 		self.xml.append(itemXML) |  | ||||||
| 		reported_vars = self['reported'].keys() |  | ||||||
| 		for var in reported_vars: |  | ||||||
| 			fieldXML = ET.Element('{%s}field' % FormField.namespace) |  | ||||||
| 			itemXML.append(fieldXML) |  | ||||||
| 			field = FormField(xml=fieldXML) |  | ||||||
| 			field['var'] = var |  | ||||||
| 			field['value'] = values.get(var, None) |  | ||||||
|  |  | ||||||
| 	def addReported(self, var, ftype=None, label='', desc='', **kwargs): |  | ||||||
| 		kwtype = kwargs.get('type', None) |  | ||||||
| 		if kwtype is None: |  | ||||||
| 			kwtype = ftype |  | ||||||
| 		reported = self.xml.find('{%s}reported' % self.namespace) |  | ||||||
| 		if reported is None: |  | ||||||
| 			reported = ET.Element('{%s}reported' % self.namespace) |  | ||||||
| 			self.xml.append(reported) |  | ||||||
| 		fieldXML = ET.Element('{%s}field' % FormField.namespace) |  | ||||||
| 		reported.append(fieldXML) |  | ||||||
| 		field = FormField(xml=fieldXML) |  | ||||||
| 		field['var'] = var |  | ||||||
| 		field['type'] = kwtype |  | ||||||
| 		field['label'] = label |  | ||||||
| 		field['desc'] = desc |  | ||||||
| 		return field |  | ||||||
|  |  | ||||||
| 	def cancel(self): |  | ||||||
| 		self['type'] = 'cancel' |  | ||||||
|  |  | ||||||
| 	def delFields(self): |  | ||||||
| 		fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) |  | ||||||
| 		for fieldXML in fieldsXML: |  | ||||||
| 			self.xml.remove(fieldXML) |  | ||||||
|  |  | ||||||
| 	def delInstructions(self): |  | ||||||
| 		instsXML = self.xml.findall('{%s}instructions') |  | ||||||
| 		for instXML in instsXML: |  | ||||||
| 			self.xml.remove(instXML) |  | ||||||
|  |  | ||||||
| 	def delItems(self): |  | ||||||
| 		itemsXML = self.xml.find('{%s}item' % self.namespace) |  | ||||||
| 		for itemXML in itemsXML: |  | ||||||
| 			self.xml.remove(itemXML) |  | ||||||
|  |  | ||||||
| 	def delReported(self): |  | ||||||
| 		reportedXML = self.xml.find('{%s}reported' % self.namespace) |  | ||||||
| 		if reportedXML is not None: |  | ||||||
| 			self.xml.remove(reportedXML) |  | ||||||
|  |  | ||||||
| 	def getFields(self, use_dict=False): |  | ||||||
| 		fields = {} if use_dict else [] |  | ||||||
| 		fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) |  | ||||||
| 		for fieldXML in fieldsXML: |  | ||||||
| 			field = FormField(xml=fieldXML) |  | ||||||
| 			if use_dict: |  | ||||||
| 				fields[field['var']] = field |  | ||||||
| 			else: |  | ||||||
| 				fields.append((field['var'], field)) |  | ||||||
| 		return fields |  | ||||||
|  |  | ||||||
| 	def getInstructions(self): |  | ||||||
| 		instructions = '' |  | ||||||
| 		instsXML = self.xml.findall('{%s}instructions' % self.namespace) |  | ||||||
| 		return "\n".join([instXML.text for instXML in instsXML]) |  | ||||||
|  |  | ||||||
| 	def getItems(self): |  | ||||||
| 		items = [] |  | ||||||
| 		itemsXML = self.xml.findall('{%s}item' % self.namespace) |  | ||||||
| 		for itemXML in itemsXML: |  | ||||||
| 			item = {} |  | ||||||
| 			fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) |  | ||||||
| 			for fieldXML in fieldsXML: |  | ||||||
| 				field = FormField(xml=fieldXML) |  | ||||||
| 				item[field['var']] = field['value'] |  | ||||||
| 			items.append(item) |  | ||||||
| 		return items |  | ||||||
|  |  | ||||||
| 	def getReported(self): |  | ||||||
| 		fields = {} |  | ||||||
| 		fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, |  | ||||||
| 									 FormField.namespace)) |  | ||||||
| 		for fieldXML in fieldsXML: |  | ||||||
| 			field = FormField(xml=fieldXML) |  | ||||||
| 			fields[field['var']] = field |  | ||||||
| 		return fields |  | ||||||
|  |  | ||||||
| 	def getValues(self): |  | ||||||
| 		values = {} |  | ||||||
| 		fields = self.getFields(use_dict=True) |  | ||||||
| 		for var in fields: |  | ||||||
| 			values[var] = fields[var]['value'] |  | ||||||
| 		return values |  | ||||||
|  |  | ||||||
| 	def reply(self): |  | ||||||
| 		if self['type'] == 'form': |  | ||||||
| 			self['type'] = 'submit' |  | ||||||
| 		elif self['type'] == 'submit': |  | ||||||
| 			self['type'] = 'result' |  | ||||||
|  |  | ||||||
| 	def setFields(self, fields, default=None): |  | ||||||
| 		del self['fields'] |  | ||||||
| 		for field_data in fields: |  | ||||||
| 			var = field_data[0] |  | ||||||
| 			field = field_data[1] |  | ||||||
| 			field['var'] = var |  | ||||||
|  |  | ||||||
| 			self.addField(**field) |  | ||||||
|  |  | ||||||
| 	def setInstructions(self, instructions): |  | ||||||
| 		del self['instructions'] |  | ||||||
| 		if instructions in [None, '']: |  | ||||||
| 			return |  | ||||||
| 		instructions = instructions.split('\n') |  | ||||||
| 		for instruction in instructions: |  | ||||||
| 			inst = ET.Element('{%s}instructions' % self.namespace) |  | ||||||
| 			inst.text = instruction |  | ||||||
| 			self.xml.append(inst) |  | ||||||
|  |  | ||||||
| 	def setItems(self, items): |  | ||||||
| 		for item in items: |  | ||||||
| 			self.addItem(item) |  | ||||||
|  |  | ||||||
| 	def setReported(self, reported, default=None): |  | ||||||
| 		for var in reported: |  | ||||||
| 			field = reported[var] |  | ||||||
| 			field['var'] = var |  | ||||||
| 			self.addReported(var, **field) |  | ||||||
|  |  | ||||||
| 	def setValues(self, values): |  | ||||||
| 		fields = self.getFields(use_dict=True) |  | ||||||
| 		for field in values: |  | ||||||
| 			fields[field]['value'] = values[field] |  | ||||||
|  |  | ||||||
| 	def merge(self, other): |  | ||||||
| 		new = copy.copy(self) |  | ||||||
| 		if type(other) == dict: |  | ||||||
| 			new.setValues(other) |  | ||||||
| 			return new |  | ||||||
| 		nfields = new.getFields(use_dict=True) |  | ||||||
| 		ofields = other.getFields(use_dict=True) |  | ||||||
| 		nfields.update(ofields) |  | ||||||
| 		new.setFields([(x, nfields[x]) for x in nfields]) |  | ||||||
| 		return new |  | ||||||
|  |  | ||||||
| class FieldAccessor(object): |  | ||||||
| 	def __init__(self, form): |  | ||||||
| 		self.form = form |  | ||||||
|  |  | ||||||
| 	def __getitem__(self, key): |  | ||||||
| 		return self.form.getFields(use_dict=True)[key] |  | ||||||
|  |  | ||||||
| 	def __contains__(self, key): |  | ||||||
| 		return key in self.form.getFields(use_dict=True) |  | ||||||
|  |  | ||||||
| 	def has_key(self, key): |  | ||||||
| 		return key in self.form.getFields(use_dict=True) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FormField(ElementBase): |  | ||||||
| 	namespace = 'jabber:x:data' |  | ||||||
| 	name = 'field' |  | ||||||
| 	plugin_attrib = 'field' |  | ||||||
| 	interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var')) |  | ||||||
| 	sub_interfaces = set(('desc',)) |  | ||||||
| 	field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', |  | ||||||
| 			   'list-single', 'text-multi', 'text-private', 'text-single')) |  | ||||||
| 	multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi')) |  | ||||||
| 	multi_line_types = set(('hidden', 'text-multi')) |  | ||||||
| 	option_types = set(('list-multi', 'list-single')) |  | ||||||
| 	true_values = set((True, '1', 'true')) |  | ||||||
|  |  | ||||||
| 	def addOption(self, label='', value=''): |  | ||||||
| 		if self['type'] in self.option_types: |  | ||||||
| 			opt = FieldOption(parent=self) |  | ||||||
| 			opt['label'] = label |  | ||||||
| 			opt['value'] = value |  | ||||||
| 		else: |  | ||||||
| 			raise ValueError("Cannot add options to a %s field." % self['type']) |  | ||||||
|  |  | ||||||
| 	def delOptions(self): |  | ||||||
| 		optsXML = self.xml.findall('{%s}option' % self.namespace) |  | ||||||
| 		for optXML in optsXML: |  | ||||||
| 			self.xml.remove(optXML) |  | ||||||
|  |  | ||||||
| 	def delRequired(self): |  | ||||||
| 		reqXML = self.xml.find('{%s}required' % self.namespace) |  | ||||||
| 		if reqXML is not None: |  | ||||||
| 			self.xml.remove(reqXML) |  | ||||||
|  |  | ||||||
| 	def delValue(self): |  | ||||||
| 		valsXML = self.xml.findall('{%s}value' % self.namespace) |  | ||||||
| 		for valXML in valsXML: |  | ||||||
| 			self.xml.remove(valXML) |  | ||||||
|  |  | ||||||
| 	def getAnswer(self): |  | ||||||
| 		return self.getValue() |  | ||||||
|  |  | ||||||
| 	def getOptions(self): |  | ||||||
| 		options = [] |  | ||||||
| 		optsXML = self.xml.findall('{%s}option' % self.namespace) |  | ||||||
| 		for optXML in optsXML: |  | ||||||
| 			opt = FieldOption(xml=optXML) |  | ||||||
| 			options.append({'label': opt['label'], 'value':opt['value']}) |  | ||||||
| 		return options |  | ||||||
|  |  | ||||||
| 	def getRequired(self): |  | ||||||
| 		reqXML = self.xml.find('{%s}required' % self.namespace) |  | ||||||
| 		return reqXML is not None |  | ||||||
|  |  | ||||||
| 	def getValue(self): |  | ||||||
| 		valsXML = self.xml.findall('{%s}value' % self.namespace) |  | ||||||
| 		if len(valsXML) == 0: |  | ||||||
| 			return None |  | ||||||
| 		elif self['type'] == 'boolean': |  | ||||||
| 			return valsXML[0].text in self.true_values |  | ||||||
| 		elif self['type'] in self.multi_value_types: |  | ||||||
| 			values = [] |  | ||||||
| 			for valXML in valsXML: |  | ||||||
| 				if valXML.text is None: |  | ||||||
| 					valXML.text = '' |  | ||||||
| 				values.append(valXML.text) |  | ||||||
| 			if self['type'] == 'text-multi': |  | ||||||
| 				values = "\n".join(values) |  | ||||||
| 			return values |  | ||||||
| 		else: |  | ||||||
| 			return valsXML[0].text |  | ||||||
|  |  | ||||||
| 	def setAnswer(self, answer): |  | ||||||
| 		self.setValue(answer) |  | ||||||
|  |  | ||||||
| 	def setFalse(self): |  | ||||||
| 		self.setValue(False) |  | ||||||
|  |  | ||||||
| 	def setOptions(self, options): |  | ||||||
| 		for value in options: |  | ||||||
| 			if isinstance(value, dict): |  | ||||||
| 				self.addOption(**value) |  | ||||||
| 			else: |  | ||||||
| 				self.addOption(value=value) |  | ||||||
|  |  | ||||||
| 	def setRequired(self, required): |  | ||||||
| 		exists = self.getRequired() |  | ||||||
| 		if not exists and required: |  | ||||||
| 			self.xml.append(ET.Element('{%s}required' % self.namespace)) |  | ||||||
| 		elif exists and not required: |  | ||||||
| 			self.delRequired() |  | ||||||
|  |  | ||||||
| 	def setTrue(self): |  | ||||||
| 		self.setValue(True) |  | ||||||
|  |  | ||||||
| 	def setValue(self, value): |  | ||||||
| 		self.delValue() |  | ||||||
| 		valXMLName = '{%s}value' % self.namespace |  | ||||||
|  |  | ||||||
| 		if self['type'] == 'boolean': |  | ||||||
| 			if value in self.true_values: |  | ||||||
| 				valXML = ET.Element(valXMLName) |  | ||||||
| 				valXML.text = '1' |  | ||||||
| 				self.xml.append(valXML) |  | ||||||
| 			else: |  | ||||||
| 				valXML = ET.Element(valXMLName) |  | ||||||
| 				valXML.text = '0' |  | ||||||
| 				self.xml.append(valXML) |  | ||||||
| 		elif self['type'] in self.multi_value_types or self['type'] in ['', None]: |  | ||||||
| 			if self['type'] in self.multi_line_types and isinstance(value, str): |  | ||||||
| 				value = value.split('\n') |  | ||||||
| 			if not isinstance(value, list): |  | ||||||
| 				value = [value] |  | ||||||
| 			for val in value: |  | ||||||
| 				if self['type'] in ['', None] and val in self.true_values: |  | ||||||
| 					val = '1' |  | ||||||
| 				valXML = ET.Element(valXMLName) |  | ||||||
| 				valXML.text = val |  | ||||||
| 				self.xml.append(valXML) |  | ||||||
| 		else: |  | ||||||
| 			if isinstance(value, list): |  | ||||||
| 				raise ValueError("Cannot add multiple values to a %s field." % self['type']) |  | ||||||
| 			valXML = ET.Element(valXMLName) |  | ||||||
| 			valXML.text = value |  | ||||||
| 			self.xml.append(valXML) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FieldOption(ElementBase): |  | ||||||
| 	namespace = 'jabber:x:data' |  | ||||||
| 	name = 'option' |  | ||||||
| 	plugin_attrib = 'option' |  | ||||||
| 	interfaces = set(('label', 'value')) |  | ||||||
| 	sub_interfaces = set(('value',)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0004(base.base_plugin): | class xep_0004(base.base_plugin): | ||||||
| 	""" | 	 | ||||||
| 	XEP-0004: Data Forms |  | ||||||
| 	""" |  | ||||||
|  |  | ||||||
| 	def plugin_init(self): | 	def plugin_init(self): | ||||||
| 		self.xep = '0004' | 		self.xep = '0004' | ||||||
| 		self.description = 'Data Forms' | 		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): | 	def post_init(self): | ||||||
| 		base.base_plugin.post_init(self) | 		base.base_plugin.post_init(self) | ||||||
| 		self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') | 		self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') | ||||||
|  | 	 | ||||||
| 	def handle_form(self, message): | 	def handler_message_xform(self, xml): | ||||||
| 		self.xmpp.event("message_xform", message) | 		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): | 	def buildForm(self, xml): | ||||||
| 		return Form(xml=xml) | 		form = Form(ftype=xml.attrib['type']) | ||||||
|  | 		form.fromXML(xml) | ||||||
|  | 		return form | ||||||
|  |  | ||||||
|  | 	def makeForm(self, ftype='form', title='', instructions=''): | ||||||
|  | 		return Form(self.xmpp, ftype, title, instructions) | ||||||
|  |  | ||||||
|  | class FieldContainer(object): | ||||||
|  | 	def __init__(self, stanza = 'form'): | ||||||
|  | 		self.fields = [] | ||||||
|  | 		self.field = {} | ||||||
|  | 		self.stanza = stanza | ||||||
|  | 	 | ||||||
|  | 	def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): | ||||||
|  | 		self.field[var] = FormField(var, ftype, label, desc, required, value) | ||||||
|  | 		self.fields.append(self.field[var]) | ||||||
|  | 		return self.field[var] | ||||||
|  | 	 | ||||||
|  | 	def buildField(self, xml): | ||||||
|  | 		self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) | ||||||
|  | 		self.fields.append(self.field[xml.get('var', '__unnamed__')]) | ||||||
|  | 		self.field[xml.get('var', '__unnamed__')].buildField(xml) | ||||||
|  |  | ||||||
|  | 	def buildContainer(self, xml): | ||||||
|  | 		self.stanza = xml.tag | ||||||
|  | 		for field in xml.findall('{jabber:x:data}field'): | ||||||
|  | 			self.buildField(field) | ||||||
|  | 	 | ||||||
|  | 	def getXML(self, ftype): | ||||||
|  | 		container = ET.Element(self.stanza) | ||||||
|  | 		for field in self.fields: | ||||||
|  | 			container.append(field.getXML(ftype)) | ||||||
|  | 		return container | ||||||
|  | 	 | ||||||
|  | class Form(FieldContainer): | ||||||
|  | 	types = ('form', 'submit', 'cancel', 'result') | ||||||
|  | 	def __init__(self, xmpp=None, ftype='form', title='', instructions=''): | ||||||
|  | 		if not ftype in self.types: | ||||||
|  | 			raise ValueError("Invalid Form Type") | ||||||
|  | 		FieldContainer.__init__(self) | ||||||
|  | 		self.xmpp = xmpp | ||||||
|  | 		self.type = ftype | ||||||
|  | 		self.title = title | ||||||
|  | 		self.instructions = instructions | ||||||
|  | 		self.reported = [] | ||||||
|  | 		self.items = [] | ||||||
|  | 	 | ||||||
|  | 	def merge(self, form2): | ||||||
|  | 		form1 = Form(ftype=self.type) | ||||||
|  | 		form1.fromXML(self.getXML(self.type)) | ||||||
|  | 		for field in form2.fields: | ||||||
|  | 			if not field.var in form1.field: | ||||||
|  | 				form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) | ||||||
|  | 			else: | ||||||
|  | 				form1.field[field.var].value = field.value | ||||||
|  | 			for option, label in field.options: | ||||||
|  | 				if (option, label) not in form1.field[field.var].options: | ||||||
|  | 					form1.fields[field.var].addOption(option, label) | ||||||
|  | 		return form1 | ||||||
|  | 	 | ||||||
|  | 	def copy(self): | ||||||
|  | 		newform = Form(ftype=self.type) | ||||||
|  | 		newform.fromXML(self.getXML(self.type)) | ||||||
|  | 		return newform | ||||||
|  | 	 | ||||||
|  | 	def update(self, form): | ||||||
|  | 		values = form.getValues() | ||||||
|  | 		for var in values: | ||||||
|  | 			if var in self.fields: | ||||||
|  | 				self.fields[var].setValue(self.fields[var]) | ||||||
|  | 	 | ||||||
|  | 	def getValues(self): | ||||||
|  | 		result = {} | ||||||
|  | 		for field in self.fields: | ||||||
|  | 			value = field.value | ||||||
|  | 			if len(value) == 1: | ||||||
|  | 				value = value[0] | ||||||
|  | 			result[field.var] = value | ||||||
|  | 		return result | ||||||
|  | 	 | ||||||
|  | 	def setValues(self, values={}): | ||||||
|  | 		for field in values: | ||||||
|  | 			if field in self.field: | ||||||
|  | 				if isinstance(values[field], list) or isinstance(values[field], tuple): | ||||||
|  | 					for value in values[field]: | ||||||
|  | 						self.field[field].setValue(value) | ||||||
|  | 				else: | ||||||
|  | 					self.field[field].setValue(values[field]) | ||||||
|  | 	 | ||||||
|  | 	def fromXML(self, xml): | ||||||
|  | 		self.buildForm(xml) | ||||||
|  | 	 | ||||||
|  | 	def addItem(self): | ||||||
|  | 		newitem = FieldContainer('item') | ||||||
|  | 		self.items.append(newitem) | ||||||
|  | 		return newitem | ||||||
|  |  | ||||||
|  | 	def buildItem(self, xml): | ||||||
|  | 		newitem = self.addItem() | ||||||
|  | 		newitem.buildContainer(xml) | ||||||
|  |  | ||||||
|  | 	def addReported(self): | ||||||
|  | 		reported = FieldContainer('reported') | ||||||
|  | 		self.reported.append(reported) | ||||||
|  | 		return reported | ||||||
|  |  | ||||||
|  | 	def buildReported(self, xml): | ||||||
|  | 		reported = self.addReported() | ||||||
|  | 		reported.buildContainer(xml) | ||||||
|  | 	 | ||||||
|  | 	def setTitle(self, title): | ||||||
|  | 		self.title = title | ||||||
|  | 	 | ||||||
|  | 	def setInstructions(self, instructions): | ||||||
|  | 		self.instructions = instructions | ||||||
|  | 	 | ||||||
|  | 	def setType(self, ftype): | ||||||
|  | 		self.type = ftype | ||||||
|  | 	 | ||||||
|  | 	def getXMLMessage(self, to): | ||||||
|  | 		msg = self.xmpp.makeMessage(to) | ||||||
|  | 		msg.append(self.getXML()) | ||||||
|  | 		return msg | ||||||
|  | 	 | ||||||
|  | 	def buildForm(self, xml): | ||||||
|  | 		self.type = xml.get('type', 'form') | ||||||
|  | 		if xml.find('{jabber:x:data}title') is not None: | ||||||
|  | 			self.setTitle(xml.find('{jabber:x:data}title').text) | ||||||
|  | 		if xml.find('{jabber:x:data}instructions') is not None: | ||||||
|  | 			self.setInstructions(xml.find('{jabber:x:data}instructions').text) | ||||||
|  | 		for field in xml.findall('{jabber:x:data}field'): | ||||||
|  | 			self.buildField(field) | ||||||
|  | 		for reported in xml.findall('{jabber:x:data}reported'): | ||||||
|  | 			self.buildReported(reported) | ||||||
|  | 		for item in xml.findall('{jabber:x:data}item'): | ||||||
|  | 			self.buildItem(item) | ||||||
|  | 	 | ||||||
|  | 	#def getXML(self, tostring = False): | ||||||
|  | 	def getXML(self, ftype=None): | ||||||
|  | 		if ftype: | ||||||
|  | 			self.type = ftype | ||||||
|  | 		form = ET.Element('{jabber:x:data}x') | ||||||
|  | 		form.attrib['type'] = self.type | ||||||
|  | 		if self.title and self.type in ('form', 'result'): | ||||||
|  | 			title = ET.Element('{jabber:x:data}title') | ||||||
|  | 			title.text = self.title | ||||||
|  | 			form.append(title) | ||||||
|  | 		if self.instructions and self.type == 'form': | ||||||
|  | 			instructions = ET.Element('{jabber:x:data}instructions') | ||||||
|  | 			instructions.text = self.instructions | ||||||
|  | 			form.append(instructions) | ||||||
|  | 		for field in self.fields: | ||||||
|  | 			form.append(field.getXML(self.type)) | ||||||
|  | 		for reported in self.reported: | ||||||
|  | 			form.append(reported.getXML('{jabber:x:data}reported')) | ||||||
|  | 		for item in self.items: | ||||||
|  | 			form.append(item.getXML(self.type)) | ||||||
|  | 		#if tostring: | ||||||
|  | 		#	form = self.xmpp.tostring(form) | ||||||
|  | 		return form | ||||||
|  | 	 | ||||||
|  | 	def getXHTML(self): | ||||||
|  | 		form = ET.Element('{http://www.w3.org/1999/xhtml}form') | ||||||
|  | 		if self.title: | ||||||
|  | 			title = ET.Element('h2') | ||||||
|  | 			title.text = self.title | ||||||
|  | 			form.append(title) | ||||||
|  | 		if self.instructions: | ||||||
|  | 			instructions = ET.Element('p') | ||||||
|  | 			instructions.text = self.instructions | ||||||
|  | 			form.append(instructions) | ||||||
|  | 		for field in self.fields: | ||||||
|  | 			form.append(field.getXHTML()) | ||||||
|  | 		for field in self.reported: | ||||||
|  | 			form.append(field.getXHTML()) | ||||||
|  | 		for field in self.items: | ||||||
|  | 			form.append(field.getXHTML()) | ||||||
|  | 		return form | ||||||
|  | 		 | ||||||
|  | 	 | ||||||
|  | 	def makeSubmit(self): | ||||||
|  | 		self.setType('submit') | ||||||
|  |  | ||||||
|  | class FormField(object): | ||||||
|  | 	types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single') | ||||||
|  | 	listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single') | ||||||
|  | 	lbtypes = ('fixed', 'text-multi') | ||||||
|  | 	def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None): | ||||||
|  | 		if not ftype in self.types: | ||||||
|  | 			raise ValueError("Invalid Field Type") | ||||||
|  | 		self.type = ftype | ||||||
|  | 		self.var = var | ||||||
|  | 		self.label = label | ||||||
|  | 		self.desc = desc | ||||||
|  | 		self.options = [] | ||||||
|  | 		self.required = False | ||||||
|  | 		self.value = [] | ||||||
|  | 		if self.type in self.listtypes: | ||||||
|  | 			self.islist = True | ||||||
|  | 		else: | ||||||
|  | 			self.islist = False | ||||||
|  | 		if self.type in self.lbtypes: | ||||||
|  | 			self.islinebreak = True | ||||||
|  | 		else: | ||||||
|  | 			self.islinebreak = False | ||||||
|  | 		if value: | ||||||
|  | 			self.setValue(value) | ||||||
|  | 	 | ||||||
|  | 	def addOption(self, value, label): | ||||||
|  | 		if self.islist: | ||||||
|  | 			self.options.append((value, label)) | ||||||
|  | 		else: | ||||||
|  | 			raise ValueError("Cannot add options to non-list type field.") | ||||||
|  | 	 | ||||||
|  | 	def setTrue(self): | ||||||
|  | 		if self.type == 'boolean': | ||||||
|  | 			self.value = [True] | ||||||
|  |  | ||||||
|  | 	def setFalse(self): | ||||||
|  | 		if self.type == 'boolean': | ||||||
|  | 			self.value = [False] | ||||||
|  |  | ||||||
|  | 	def require(self): | ||||||
|  | 		self.required = True | ||||||
|  | 	 | ||||||
|  | 	def setDescription(self, desc): | ||||||
|  | 		self.desc = desc | ||||||
|  | 	 | ||||||
|  | 	def setValue(self, value): | ||||||
|  | 		if self.type == 'boolean': | ||||||
|  | 			if value in ('1', 1, True, 'true', 'True', 'yes'): | ||||||
|  | 				value = True | ||||||
|  | 			else: | ||||||
|  | 				value = False | ||||||
|  | 		if self.islinebreak and value is not None: | ||||||
|  | 			self.value += value.split('\n') | ||||||
|  | 		else: | ||||||
|  | 			if len(self.value) and (not self.islist or self.type == 'list-single'): | ||||||
|  | 				self.value = [value] | ||||||
|  | 			else: | ||||||
|  | 				self.value.append(value) | ||||||
|  |  | ||||||
|  | 	def delValue(self, value): | ||||||
|  | 		if type(self.value) == type([]): | ||||||
|  | 			try: | ||||||
|  | 				idx = self.value.index(value) | ||||||
|  | 				if idx != -1: | ||||||
|  | 					self.value.pop(idx) | ||||||
|  | 			except ValueError: | ||||||
|  | 				pass | ||||||
|  | 		else: | ||||||
|  | 			self.value = '' | ||||||
|  | 	 | ||||||
|  | 	def setAnswer(self, value): | ||||||
|  | 		self.setValue(value) | ||||||
|  | 	 | ||||||
|  | 	def buildField(self, xml): | ||||||
|  | 		self.type = xml.get('type', 'text-single') | ||||||
|  | 		self.label = xml.get('label', '') | ||||||
|  | 		for option in xml.findall('{jabber:x:data}option'): | ||||||
|  | 			self.addOption(option.find('{jabber:x:data}value').text, option.get('label', '')) | ||||||
|  | 		for value in xml.findall('{jabber:x:data}value'): | ||||||
|  | 			self.setValue(value.text) | ||||||
|  | 		if xml.find('{jabber:x:data}required') is not None: | ||||||
|  | 			self.require() | ||||||
|  | 		if xml.find('{jabber:x:data}desc') is not None: | ||||||
|  | 			self.setDescription(xml.find('{jabber:x:data}desc').text) | ||||||
|  | 	 | ||||||
|  | 	def getXML(self, ftype): | ||||||
|  | 		field = ET.Element('{jabber:x:data}field') | ||||||
|  | 		if ftype != 'result': | ||||||
|  | 			field.attrib['type'] = self.type | ||||||
|  | 		if self.type != 'fixed': | ||||||
|  | 			if self.var: | ||||||
|  | 				field.attrib['var'] = self.var | ||||||
|  | 			if self.label: | ||||||
|  | 				field.attrib['label'] = self.label | ||||||
|  | 		if ftype == 'form': | ||||||
|  | 			for option in self.options: | ||||||
|  | 				optionxml = ET.Element('{jabber:x:data}option') | ||||||
|  | 				optionxml.attrib['label'] = option[1] | ||||||
|  | 				optionval = ET.Element('{jabber:x:data}value') | ||||||
|  | 				optionval.text = option[0] | ||||||
|  | 				optionxml.append(optionval) | ||||||
|  | 				field.append(optionxml) | ||||||
|  | 			if self.required: | ||||||
|  | 				required = ET.Element('{jabber:x:data}required') | ||||||
|  | 				field.append(required) | ||||||
|  | 			if self.desc: | ||||||
|  | 				desc = ET.Element('{jabber:x:data}desc') | ||||||
|  | 				desc.text = self.desc | ||||||
|  | 				field.append(desc) | ||||||
|  | 		for value in self.value: | ||||||
|  | 			valuexml = ET.Element('{jabber:x:data}value') | ||||||
|  | 			if value is True or value is False: | ||||||
|  | 				if value: | ||||||
|  | 					valuexml.text = '1' | ||||||
|  | 				else: | ||||||
|  | 					valuexml.text = '0' | ||||||
|  | 			else: | ||||||
|  | 				valuexml.text = value | ||||||
|  | 			field.append(valuexml) | ||||||
|  | 		return field | ||||||
|  | 	 | ||||||
|  | 	def getXHTML(self): | ||||||
|  | 		field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) | ||||||
|  | 		if self.label: | ||||||
|  | 			label = ET.Element('p') | ||||||
|  | 			label.text = "%s: " % self.label | ||||||
|  | 		else: | ||||||
|  | 			label = ET.Element('p') | ||||||
|  | 			label.text = "%s: " % self.var | ||||||
|  | 		field.append(label) | ||||||
|  | 		if self.type == 'boolean': | ||||||
|  | 			formf = ET.Element('input', {'type': 'checkbox', 'name': self.var}) | ||||||
|  | 			if len(self.value) and self.value[0] in (True, 'true', '1'): | ||||||
|  | 				formf.attrib['checked'] = 'checked' | ||||||
|  | 		elif self.type == 'fixed': | ||||||
|  | 			formf = ET.Element('p') | ||||||
|  | 			try: | ||||||
|  | 				formf.text = ', '.join(self.value) | ||||||
|  | 			except: | ||||||
|  | 				pass | ||||||
|  | 			field.append(formf) | ||||||
|  | 			formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) | ||||||
|  | 			try: | ||||||
|  | 				formf.text = ', '.join(self.value) | ||||||
|  | 			except: | ||||||
|  | 				pass | ||||||
|  | 		elif self.type == 'hidden': | ||||||
|  | 			formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) | ||||||
|  | 			try: | ||||||
|  | 				formf.text = ', '.join(self.value) | ||||||
|  | 			except: | ||||||
|  | 				pass | ||||||
|  | 		elif self.type in ('jid-multi', 'list-multi'): | ||||||
|  | 			formf = ET.Element('select', {'name': self.var}) | ||||||
|  | 			for option in self.options: | ||||||
|  | 				optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'}) | ||||||
|  | 				optf.text = option[1] | ||||||
|  | 				if option[1] in self.value: | ||||||
|  | 					optf.attrib['selected'] = 'selected' | ||||||
|  | 				formf.append(option) | ||||||
|  | 		elif self.type in ('jid-single', 'text-single'): | ||||||
|  | 			formf = ET.Element('input', {'type': 'text', 'name': self.var}) | ||||||
|  | 			try: | ||||||
|  | 				formf.attrib['value'] = ', '.join(self.value) | ||||||
|  | 			except: | ||||||
|  | 				pass | ||||||
|  | 		elif self.type == 'list-single': | ||||||
|  | 			formf = ET.Element('select', {'name': self.var}) | ||||||
|  | 			for option in self.options: | ||||||
|  | 				optf = ET.Element('option', {'value': option[0]}) | ||||||
|  | 				optf.text = option[1] | ||||||
|  | 				if not optf.text: | ||||||
|  | 					optf.text = option[0] | ||||||
|  | 				if option[1] in self.value: | ||||||
|  | 					optf.attrib['selected'] = 'selected' | ||||||
|  | 				formf.append(optf) | ||||||
|  | 		elif self.type == 'text-multi': | ||||||
|  | 			formf = ET.Element('textarea', {'name': self.var}) | ||||||
|  | 			try: | ||||||
|  | 				formf.text = ', '.join(self.value) | ||||||
|  | 			except: | ||||||
|  | 				pass | ||||||
|  | 			if not formf.text: | ||||||
|  | 				formf.text = ' ' | ||||||
|  | 		elif self.type == 'text-private': | ||||||
|  | 			formf = ET.Element('input', {'type': 'password', 'name': self.var}) | ||||||
|  | 			try: | ||||||
|  | 				formf.attrib['value'] = ', '.join(self.value) | ||||||
|  | 			except: | ||||||
|  | 				pass | ||||||
|  | 		label.append(formf) | ||||||
|  | 		return field | ||||||
|  | 		 | ||||||
|   | |||||||
| @@ -178,12 +178,9 @@ class xep_0009(base.base_plugin): | |||||||
| 	def plugin_init(self): | 	def plugin_init(self): | ||||||
| 		self.xep = '0009' | 		self.xep = '0009' | ||||||
| 		self.description = 'Jabber-RPC' | 		self.description = 'Jabber-RPC' | ||||||
| 		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",  | 		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", self._callMethod) | ||||||
|                                       self._callMethod, name='Jabber RPC Call') | 		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", self._callResult) | ||||||
| 		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",  | 		self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", self._callError) | ||||||
|                                       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.entries = {} | ||||||
| 		self.activeCalls = [] | 		self.activeCalls = [] | ||||||
| 
 | 
 | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0009 import stanza |  | ||||||
| from sleekxmpp.plugins.xep_0009.rpc import xep_0009 |  | ||||||
| from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse |  | ||||||
| @@ -1,166 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
| import base64 |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
| _namespace = 'jabber:iq:rpc' |  | ||||||
|  |  | ||||||
| def fault2xml(fault): |  | ||||||
|     value = dict() |  | ||||||
|     value['faultCode'] = fault['code'] |  | ||||||
|     value['faultString'] = fault['string'] |  | ||||||
|     fault = ET.Element("fault", {'xmlns': _namespace}) |  | ||||||
|     fault.append(_py2xml((value))) |  | ||||||
|     return fault |  | ||||||
|  |  | ||||||
| def xml2fault(params): |  | ||||||
|     vals = [] |  | ||||||
|     for value in params.findall('{%s}value' % _namespace): |  | ||||||
|         vals.append(_xml2py(value)) |  | ||||||
|     fault = dict() |  | ||||||
|     fault['code'] = vals[0]['faultCode'] |  | ||||||
|     fault['string'] = vals[0]['faultString'] |  | ||||||
|     return fault |  | ||||||
|  |  | ||||||
| def py2xml(*args): |  | ||||||
|     params = ET.Element("{%s}params" % _namespace) |  | ||||||
|     for x in args: |  | ||||||
|         param = ET.Element("{%s}param" % _namespace) |  | ||||||
|         param.append(_py2xml(x)) |  | ||||||
|         params.append(param) #<params><param>... |  | ||||||
|     return params |  | ||||||
|  |  | ||||||
| def _py2xml(*args): |  | ||||||
|     for x in args: |  | ||||||
|         val = ET.Element("value") |  | ||||||
|         if x is None: |  | ||||||
|             nil = ET.Element("nil") |  | ||||||
|             val.append(nil) |  | ||||||
|         elif type(x) is int: |  | ||||||
|             i4 = ET.Element("i4") |  | ||||||
|             i4.text = str(x) |  | ||||||
|             val.append(i4) |  | ||||||
|         elif type(x) is bool: |  | ||||||
|             boolean = ET.Element("boolean") |  | ||||||
|             boolean.text = str(int(x)) |  | ||||||
|             val.append(boolean) |  | ||||||
|         elif type(x) is str: |  | ||||||
|             string = ET.Element("string") |  | ||||||
|             string.text = x |  | ||||||
|             val.append(string) |  | ||||||
|         elif type(x) is float: |  | ||||||
|             double = ET.Element("double") |  | ||||||
|             double.text = str(x) |  | ||||||
|             val.append(double) |  | ||||||
|         elif type(x) is rpcbase64: |  | ||||||
|             b64 = ET.Element("Base64") |  | ||||||
|             b64.text = x.encoded() |  | ||||||
|             val.append(b64) |  | ||||||
|         elif type(x) is rpctime: |  | ||||||
|             iso = ET.Element("dateTime.iso8601") |  | ||||||
|             iso.text = str(x) |  | ||||||
|             val.append(iso) |  | ||||||
|         elif type(x) in (list, tuple): |  | ||||||
|             array = ET.Element("array") |  | ||||||
|             data = ET.Element("data") |  | ||||||
|             for y in x: |  | ||||||
|                 data.append(_py2xml(y)) |  | ||||||
|             array.append(data) |  | ||||||
|             val.append(array) |  | ||||||
|         elif type(x) is dict: |  | ||||||
|             struct = ET.Element("struct") |  | ||||||
|             for y in x.keys(): |  | ||||||
|                 member = ET.Element("member") |  | ||||||
|                 name = ET.Element("name") |  | ||||||
|                 name.text = y |  | ||||||
|                 member.append(name) |  | ||||||
|                 member.append(_py2xml(x[y])) |  | ||||||
|                 struct.append(member) |  | ||||||
|             val.append(struct) |  | ||||||
|         return val |  | ||||||
|  |  | ||||||
| def xml2py(params): |  | ||||||
|     namespace = 'jabber:iq:rpc' |  | ||||||
|     vals = [] |  | ||||||
|     for param in params.findall('{%s}param' % namespace): |  | ||||||
|         vals.append(_xml2py(param.find('{%s}value' % namespace))) |  | ||||||
|     return vals |  | ||||||
|  |  | ||||||
| def _xml2py(value): |  | ||||||
|     namespace = 'jabber:iq:rpc' |  | ||||||
|     if value.find('{%s}nil' % namespace) is not None: |  | ||||||
|         return None |  | ||||||
|     if value.find('{%s}i4' % namespace) is not None: |  | ||||||
|         return int(value.find('{%s}i4' % namespace).text) |  | ||||||
|     if value.find('{%s}int' % namespace) is not None: |  | ||||||
|         return int(value.find('{%s}int' % namespace).text) |  | ||||||
|     if value.find('{%s}boolean' % namespace) is not None: |  | ||||||
|         return bool(value.find('{%s}boolean' % namespace).text) |  | ||||||
|     if value.find('{%s}string' % namespace) is not None: |  | ||||||
|         return value.find('{%s}string' % namespace).text |  | ||||||
|     if value.find('{%s}double' % namespace) is not None: |  | ||||||
|         return float(value.find('{%s}double' % namespace).text) |  | ||||||
|     if value.find('{%s}Base64') is not None: |  | ||||||
|         return rpcbase64(value.find('Base64' % namespace).text) |  | ||||||
|     if value.find('{%s}dateTime.iso8601') is not None: |  | ||||||
|         return rpctime(value.find('{%s}dateTime.iso8601')) |  | ||||||
|     if value.find('{%s}struct' % namespace) is not None: |  | ||||||
|         struct = {} |  | ||||||
|         for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace): |  | ||||||
|             struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace)) |  | ||||||
|         return struct |  | ||||||
|     if value.find('{%s}array' % namespace) is not None: |  | ||||||
|         array = [] |  | ||||||
|         for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace): |  | ||||||
|             array.append(_xml2py(val)) |  | ||||||
|         return array |  | ||||||
|     raise ValueError() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class rpcbase64(object): |  | ||||||
|  |  | ||||||
|     def __init__(self, data): |  | ||||||
|         #base 64 encoded string |  | ||||||
|         self.data = data |  | ||||||
|  |  | ||||||
|     def decode(self): |  | ||||||
|         return base64.decodestring(self.data) |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return self.decode() |  | ||||||
|  |  | ||||||
|     def encoded(self): |  | ||||||
|         return self.data |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class rpctime(object): |  | ||||||
|  |  | ||||||
|     def __init__(self,data=None): |  | ||||||
|         #assume string data is in iso format YYYYMMDDTHH:MM:SS |  | ||||||
|         if type(data) is str: |  | ||||||
|             self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S") |  | ||||||
|         elif type(data) is time.struct_time: |  | ||||||
|             self.timestamp = data |  | ||||||
|         elif data is None: |  | ||||||
|             self.timestamp = time.gmtime() |  | ||||||
|         else: |  | ||||||
|             raise ValueError() |  | ||||||
|  |  | ||||||
|     def iso8601(self): |  | ||||||
|         #return a iso8601 string |  | ||||||
|         return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp) |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return self.iso8601() |  | ||||||
| @@ -1,739 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from binding import py2xml, xml2py, xml2fault, fault2xml |  | ||||||
| from threading import RLock |  | ||||||
| import abc |  | ||||||
| import inspect |  | ||||||
| import logging |  | ||||||
| import sleekxmpp |  | ||||||
| import sys |  | ||||||
| import threading |  | ||||||
| import traceback |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
| def _intercept(method, name, public): |  | ||||||
|     def _resolver(instance, *args, **kwargs): |  | ||||||
|         log.debug("Locally calling %s.%s with arguments %s." % (instance.FQN(), method.__name__, args)) |  | ||||||
|         try: |  | ||||||
|             value = method(instance, *args, **kwargs) |  | ||||||
|             if value == NotImplemented: |  | ||||||
|                 raise InvocationException("Local handler does not implement %s.%s!" % (instance.FQN(), method.__name__)) |  | ||||||
|             return value |  | ||||||
|         except InvocationException: |  | ||||||
|             raise |  | ||||||
|         except Exception as e: |  | ||||||
|             raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e) |  | ||||||
|     _resolver._rpc = public |  | ||||||
|     _resolver._rpc_name = method.__name__ if name is None else name |  | ||||||
|     return _resolver |  | ||||||
|  |  | ||||||
| def remote(function_argument, public = True): |  | ||||||
|     ''' |  | ||||||
|     Decorator for methods which are remotely callable. This decorator |  | ||||||
|     works in conjunction with classes which extend ABC Endpoint. |  | ||||||
|     Example: |  | ||||||
|  |  | ||||||
|         @remote |  | ||||||
|         def remote_method(arg1, arg2) |  | ||||||
|  |  | ||||||
|     Arguments: |  | ||||||
|         function_argument -- a stand-in for either the actual method |  | ||||||
|             OR a new name (string) for the method. In that case the |  | ||||||
|             method is considered mapped: |  | ||||||
|             Example: |  | ||||||
|  |  | ||||||
|             @remote("new_name") |  | ||||||
|             def remote_method(arg1, arg2) |  | ||||||
|  |  | ||||||
|         public -- A flag which indicates if this method should be part |  | ||||||
|             of the known dictionary of remote methods. Defaults to True. |  | ||||||
|             Example: |  | ||||||
|  |  | ||||||
|             @remote(False) |  | ||||||
|             def remote_method(arg1, arg2) |  | ||||||
|  |  | ||||||
|     Note: renaming and revising (public vs. private) can be combined. |  | ||||||
|     Example: |  | ||||||
|  |  | ||||||
|             @remote("new_name", False) |  | ||||||
|             def remote_method(arg1, arg2) |  | ||||||
|     ''' |  | ||||||
|     if hasattr(function_argument, '__call__'): |  | ||||||
|         return _intercept(function_argument, None, public) |  | ||||||
|     else: |  | ||||||
|         if not isinstance(function_argument, basestring): |  | ||||||
|             if not isinstance(function_argument, bool): |  | ||||||
|                 raise Exception('Expected an RPC method name or visibility modifier!') |  | ||||||
|             else: |  | ||||||
|                 def _wrap_revised(function): |  | ||||||
|                     function = _intercept(function, None, function_argument) |  | ||||||
|                     return function |  | ||||||
|                 return _wrap_revised |  | ||||||
|         def _wrap_remapped(function): |  | ||||||
|             function = _intercept(function, function_argument, public) |  | ||||||
|             return function |  | ||||||
|         return _wrap_remapped |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ACL: |  | ||||||
|     ''' |  | ||||||
|     An Access Control List (ACL) is a list of rules, which are evaluated |  | ||||||
|     in order until a match is found. The policy of the matching rule |  | ||||||
|     is then applied. |  | ||||||
|  |  | ||||||
|     Rules are 3-tuples, consisting of a policy enumerated type, a JID |  | ||||||
|     expression and a RCP resource expression. |  | ||||||
|  |  | ||||||
|     Examples: |  | ||||||
|     [ (ACL.ALLOW, '*', '*') ] allow everyone everything, no restrictions |  | ||||||
|     [ (ACL.DENY, '*', '*') ] deny everyone everything, no restrictions |  | ||||||
|     [ (ACL.ALLOW, 'test@xmpp.org/unit', 'test.*'), |  | ||||||
|       (ACL.DENY, '*', '*') ] deny everyone everything, except named |  | ||||||
|         JID, which is allowed access to endpoint 'test' only. |  | ||||||
|  |  | ||||||
|     The use of wildcards is allowed in expressions, as follows: |  | ||||||
|     '*' everyone, or everything (= all endpoints and methods) |  | ||||||
|     'test@xmpp.org/*' every JID regardless of JID resource |  | ||||||
|     '*@xmpp.org/rpc' every JID from domain xmpp.org with JID res 'rpc' |  | ||||||
|     'frank@*' every 'frank', regardless of domain or JID res |  | ||||||
|     'system.*' all methods of endpoint 'system' |  | ||||||
|     '*.reboot' all methods reboot regardless of endpoint |  | ||||||
|     ''' |  | ||||||
|     ALLOW = True |  | ||||||
|     DENY = False |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def check(cls, rules, jid, resource): |  | ||||||
|         if rules is None: |  | ||||||
|             return cls.DENY                  # No rules means no access! |  | ||||||
|         for rule in rules: |  | ||||||
|             policy = cls._check(rule, jid, resource) |  | ||||||
|             if policy is not None: |  | ||||||
|                 return policy |  | ||||||
|         return cls.DENY   # By default if not rule matches, deny access. |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def _check(cls, rule, jid, resource): |  | ||||||
|         if cls._match(jid, rule[1]) and cls._match(resource, rule[2]): |  | ||||||
|             return rule[0] |  | ||||||
|         else: |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def _next_token(cls, expression, index): |  | ||||||
|         new_index = expression.find('*', index) |  | ||||||
|         if new_index == 0: |  | ||||||
|             return '' |  | ||||||
|         else: |  | ||||||
|             if new_index == -1: |  | ||||||
|                 return expression[index : ] |  | ||||||
|             else: |  | ||||||
|                 return expression[index : new_index] |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def _match(cls, value, expression): |  | ||||||
|         #! print "_match [VALUE] %s [EXPR] %s" % (value, expression) |  | ||||||
|         index = 0 |  | ||||||
|         position = 0 |  | ||||||
|         while index < len(expression): |  | ||||||
|             token = cls._next_token(expression, index) |  | ||||||
|             #! print "[TOKEN] '%s'" % token |  | ||||||
|             size = len(token) |  | ||||||
|             if size > 0: |  | ||||||
|                 token_index = value.find(token, position) |  | ||||||
|                 if token_index == -1: |  | ||||||
|                     return False |  | ||||||
|                 else: |  | ||||||
|                     #! print "[INDEX-OF] %s" % token_index |  | ||||||
|                     position = token_index + len(token) |  | ||||||
|                 pass |  | ||||||
|             if size == 0: |  | ||||||
|                 index += 1 |  | ||||||
|             else: |  | ||||||
|                 index += size |  | ||||||
|         #! print "index %s position %s" % (index, position) |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
| ANY_ALL = [ (ACL.ALLOW, '*', '*') ] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RemoteException(Exception): |  | ||||||
|     ''' |  | ||||||
|     Base exception for RPC. This exception is raised when a problem |  | ||||||
|     occurs in the network layer. |  | ||||||
|     ''' |  | ||||||
|  |  | ||||||
|     def __init__(self, message="", cause=None): |  | ||||||
|         ''' |  | ||||||
|         Initializes a new RemoteException. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             message -- The message accompanying this exception. |  | ||||||
|             cause -- The underlying cause of this exception. |  | ||||||
|         ''' |  | ||||||
|         self._message = message |  | ||||||
|         self._cause = cause |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return repr(self._message) |  | ||||||
|  |  | ||||||
|     def get_message(self): |  | ||||||
|         return self._message |  | ||||||
|  |  | ||||||
|     def get_cause(self): |  | ||||||
|         return self._cause |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvocationException(RemoteException): |  | ||||||
|     ''' |  | ||||||
|     Exception raised when a problem occurs during the remote invocation |  | ||||||
|     of a method. |  | ||||||
|     ''' |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AuthorizationException(RemoteException): |  | ||||||
|     ''' |  | ||||||
|     Exception raised when the caller is not authorized to invoke the |  | ||||||
|     remote method. |  | ||||||
|     ''' |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class TimeoutException(Exception): |  | ||||||
|     ''' |  | ||||||
|     Exception raised when the synchronous execution of a method takes |  | ||||||
|     longer than the given threshold because an underlying asynchronous |  | ||||||
|     reply did not arrive in time. |  | ||||||
|     ''' |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Callback(object): |  | ||||||
|     ''' |  | ||||||
|     A base class for callback handlers. |  | ||||||
|     ''' |  | ||||||
|     __metaclass__ = abc.ABCMeta |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @abc.abstractproperty |  | ||||||
|     def set_value(self, value): |  | ||||||
|         return NotImplemented |  | ||||||
|  |  | ||||||
|     @abc.abstractproperty |  | ||||||
|     def cancel_with_error(self, exception): |  | ||||||
|         return NotImplemented |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Future(Callback): |  | ||||||
|     ''' |  | ||||||
|     Represents the result of an asynchronous computation. |  | ||||||
|     ''' |  | ||||||
|  |  | ||||||
|     def __init__(self): |  | ||||||
|         ''' |  | ||||||
|         Initializes a new Future. |  | ||||||
|         ''' |  | ||||||
|         self._value = None |  | ||||||
|         self._exception = None |  | ||||||
|         self._event = threading.Event() |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def set_value(self, value): |  | ||||||
|         ''' |  | ||||||
|         Sets the value of this Future. Once the value is set, a caller |  | ||||||
|         blocked on get_value will be able to continue. |  | ||||||
|         ''' |  | ||||||
|         self._value = value |  | ||||||
|         self._event.set() |  | ||||||
|  |  | ||||||
|     def get_value(self, timeout=None): |  | ||||||
|         ''' |  | ||||||
|         Gets the value of this Future. This call will block until |  | ||||||
|         the result is available, or until an optional timeout expires. |  | ||||||
|         When this Future is cancelled with an error, |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             timeout -- The maximum waiting time to obtain the value. |  | ||||||
|         ''' |  | ||||||
|         self._event.wait(timeout) |  | ||||||
|         if self._exception: |  | ||||||
|             raise self._exception |  | ||||||
|         if not self._event.is_set(): |  | ||||||
|             raise TimeoutException |  | ||||||
|         return self._value |  | ||||||
|  |  | ||||||
|     def is_done(self): |  | ||||||
|         ''' |  | ||||||
|         Returns true if a value has been returned. |  | ||||||
|         ''' |  | ||||||
|         return self._event.is_set() |  | ||||||
|  |  | ||||||
|     def cancel_with_error(self, exception): |  | ||||||
|         ''' |  | ||||||
|         Cancels the Future because of an error. Once cancelled, a |  | ||||||
|         caller blocked on get_value will be able to continue. |  | ||||||
|         ''' |  | ||||||
|         self._exception = exception |  | ||||||
|         self._event.set() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Endpoint(object): |  | ||||||
|     ''' |  | ||||||
|     The Endpoint class is an abstract base class for all objects |  | ||||||
|     participating in an RPC-enabled XMPP network. |  | ||||||
|  |  | ||||||
|     A user subclassing this class is required to implement the method: |  | ||||||
|         FQN(self) |  | ||||||
|     where FQN stands for Fully Qualified Name, an unambiguous name |  | ||||||
|     which specifies which object an RPC call refers to. It is the |  | ||||||
|     first part in a RPC method name '<fqn>.<method>'. |  | ||||||
|     ''' |  | ||||||
|     __metaclass__ = abc.ABCMeta |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __init__(self, session, target_jid): |  | ||||||
|         ''' |  | ||||||
|         Initialize a new Endpoint. This constructor should never be |  | ||||||
|         invoked by a user, instead it will be called by the factories |  | ||||||
|         which instantiate the RPC-enabled objects, of which only |  | ||||||
|         the classes are provided by the user. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             session -- An RPC session instance. |  | ||||||
|             target_jid -- the identity of the remote XMPP entity. |  | ||||||
|         ''' |  | ||||||
|         self.session = session |  | ||||||
|         self.target_jid = target_jid |  | ||||||
|  |  | ||||||
|     @abc.abstractproperty |  | ||||||
|     def FQN(self): |  | ||||||
|         return NotImplemented |  | ||||||
|  |  | ||||||
|     def get_methods(self): |  | ||||||
|         ''' |  | ||||||
|         Returns a dictionary of all RPC method names provided by this |  | ||||||
|         class. This method returns the actual  method names as found |  | ||||||
|         in the class definition which have been decorated with: |  | ||||||
|  |  | ||||||
|             @remote |  | ||||||
|             def some_rpc_method(arg1, arg2) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         Unless: |  | ||||||
|             (1) the name has been remapped, in which case the new |  | ||||||
|                 name will be returned. |  | ||||||
|  |  | ||||||
|                     @remote("new_name") |  | ||||||
|                     def some_rpc_method(arg1, arg2) |  | ||||||
|  |  | ||||||
|             (2) the method is set to hidden |  | ||||||
|  |  | ||||||
|                     @remote(False) |  | ||||||
|                     def some_hidden_method(arg1, arg2) |  | ||||||
|         ''' |  | ||||||
|         result = dict() |  | ||||||
|         for function in dir(self): |  | ||||||
|             test_attr = getattr(self, function, None) |  | ||||||
|             try: |  | ||||||
|                 if test_attr._rpc: |  | ||||||
|                     result[test_attr._rpc_name] = test_attr |  | ||||||
|             except Exception: |  | ||||||
|                 pass |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Proxy(Endpoint): |  | ||||||
|     ''' |  | ||||||
|     Implementation of the Proxy pattern which is intended to wrap |  | ||||||
|     around Endpoints in order to intercept calls, marshall them and |  | ||||||
|     forward them to the remote object. |  | ||||||
|     ''' |  | ||||||
|  |  | ||||||
|     def __init__(self, endpoint, callback = None): |  | ||||||
|         ''' |  | ||||||
|         Initializes a new Proxy. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             endpoint -- The endpoint which is proxified. |  | ||||||
|         ''' |  | ||||||
|         self._endpoint = endpoint |  | ||||||
|         self._callback = callback |  | ||||||
|  |  | ||||||
|     def __getattribute__(self, name, *args): |  | ||||||
|         if name in ('__dict__', '_endpoint', 'async', '_callback'): |  | ||||||
|             return object.__getattribute__(self, name) |  | ||||||
|         else: |  | ||||||
|             attribute = self._endpoint.__getattribute__(name) |  | ||||||
|             if hasattr(attribute, '__call__'): |  | ||||||
|                 try: |  | ||||||
|                     if attribute._rpc: |  | ||||||
|                         def _remote_call(*args, **kwargs): |  | ||||||
|                             log.debug("Remotely calling '%s.%s' with arguments %s." % (self._endpoint.FQN(), attribute._rpc_name, args)) |  | ||||||
|                             return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs) |  | ||||||
|                         return _remote_call |  | ||||||
|                 except: |  | ||||||
|                     pass   # If the attribute doesn't exist, don't care! |  | ||||||
|             return attribute |  | ||||||
|  |  | ||||||
|     def async(self, callback): |  | ||||||
|         return Proxy(self._endpoint, callback) |  | ||||||
|  |  | ||||||
|     def get_endpoint(self): |  | ||||||
|         ''' |  | ||||||
|         Returns the proxified endpoint. |  | ||||||
|         ''' |  | ||||||
|         return self._endpoint |  | ||||||
|  |  | ||||||
|     def FQN(self): |  | ||||||
|         return self._endpoint.FQN() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class JabberRPCEntry(object): |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __init__(self, endpoint_FQN, call): |  | ||||||
|         self._endpoint_FQN = endpoint_FQN |  | ||||||
|         self._call = call |  | ||||||
|  |  | ||||||
|     def call_method(self, args): |  | ||||||
|         return_value = self._call(*args) |  | ||||||
|         if return_value is None: |  | ||||||
|             return return_value |  | ||||||
|         else: |  | ||||||
|             return self._return(return_value) |  | ||||||
|  |  | ||||||
|     def get_endpoint_FQN(self): |  | ||||||
|         return self._endpoint_FQN |  | ||||||
|  |  | ||||||
|     def _return(self, *args): |  | ||||||
|         return args |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RemoteSession(object): |  | ||||||
|     ''' |  | ||||||
|     A context object for a Jabber-RPC session. |  | ||||||
|     ''' |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __init__(self, client, session_close_callback): |  | ||||||
|         ''' |  | ||||||
|         Initializes a new RPC session. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             client -- The SleekXMPP client associated with this session. |  | ||||||
|             session_close_callback -- A callback called when the |  | ||||||
|                 session is closed. |  | ||||||
|         ''' |  | ||||||
|         self._client = client |  | ||||||
|         self._session_close_callback = session_close_callback |  | ||||||
|         self._event = threading.Event() |  | ||||||
|         self._entries = {} |  | ||||||
|         self._callbacks = {} |  | ||||||
|         self._acls = {} |  | ||||||
|         self._lock = RLock() |  | ||||||
|  |  | ||||||
|     def _wait(self): |  | ||||||
|         self._event.wait() |  | ||||||
|  |  | ||||||
|     def _notify(self, event): |  | ||||||
|         log.debug("RPC Session as %s started." % self._client.boundjid.full) |  | ||||||
|         self._client.sendPresence() |  | ||||||
|         self._event.set() |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def _register_call(self, endpoint, method, name=None): |  | ||||||
|         ''' |  | ||||||
|         Registers a method from an endpoint as remotely callable. |  | ||||||
|         ''' |  | ||||||
|         if name is None: |  | ||||||
|             name = method.__name__ |  | ||||||
|         key = "%s.%s" % (endpoint, name) |  | ||||||
|         log.debug("Registering call handler for %s (%s)." % (key, method)) |  | ||||||
|         with self._lock: |  | ||||||
|             if key in self._entries: |  | ||||||
|                 raise KeyError("A handler for %s has already been regisered!" % endpoint) |  | ||||||
|             self._entries[key] = JabberRPCEntry(endpoint, method) |  | ||||||
|         return key |  | ||||||
|  |  | ||||||
|     def _register_acl(self, endpoint, acl): |  | ||||||
|         log.debug("Registering ACL %s for endpoint %s." % (repr(acl), endpoint)) |  | ||||||
|         with self._lock: |  | ||||||
|             self._acls[endpoint] = acl |  | ||||||
|  |  | ||||||
|     def _register_callback(self, pid, callback): |  | ||||||
|         with self._lock: |  | ||||||
|             self._callbacks[pid] = callback |  | ||||||
|  |  | ||||||
|     def forget_callback(self, callback): |  | ||||||
|         with self._lock: |  | ||||||
|             pid = self._find_key(self._callbacks, callback) |  | ||||||
|             if pid is not None: |  | ||||||
|                 del self._callback[pid] |  | ||||||
|             else: |  | ||||||
|                 raise ValueError("Unknown callback!") |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def _find_key(self, dict, value): |  | ||||||
|         """return the key of dictionary dic given the value""" |  | ||||||
|         search = [k for k, v in dict.iteritems() if v == value] |  | ||||||
|         if len(search) == 0: |  | ||||||
|             return None |  | ||||||
|         else: |  | ||||||
|             return search[0] |  | ||||||
|  |  | ||||||
|     def _unregister_call(self, key): |  | ||||||
|         #removes the registered call |  | ||||||
|         with self._lock: |  | ||||||
|             if self._entries[key]: |  | ||||||
|                 del self._entries[key] |  | ||||||
|             else: |  | ||||||
|                 raise ValueError() |  | ||||||
|  |  | ||||||
|     def new_proxy(self, target_jid, endpoint_cls): |  | ||||||
|         ''' |  | ||||||
|         Instantiates a new proxy object, which proxies to a remote |  | ||||||
|         endpoint. This method uses a class reference without |  | ||||||
|         constructor arguments to instantiate the proxy. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             target_jid -- the XMPP entity ID hosting the endpoint. |  | ||||||
|             endpoint_cls -- The remote (duck) type. |  | ||||||
|         ''' |  | ||||||
|         try: |  | ||||||
|             argspec = inspect.getargspec(endpoint_cls.__init__) |  | ||||||
|             args = [None] * (len(argspec[0]) - 1) |  | ||||||
|             result = endpoint_cls(*args) |  | ||||||
|             Endpoint.__init__(result, self, target_jid) |  | ||||||
|             return Proxy(result) |  | ||||||
|         except: |  | ||||||
|             traceback.print_exc(file=sys.stdout) |  | ||||||
|  |  | ||||||
|     def new_handler(self, acl, handler_cls, *args, **kwargs): |  | ||||||
|         ''' |  | ||||||
|         Instantiates a new handler object, which is called remotely |  | ||||||
|         by others. The user can control the effect of the call by |  | ||||||
|         implementing the remote method in the local endpoint class. The |  | ||||||
|         returned reference can be called locally and will behave as a |  | ||||||
|         regular instance. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             acl -- Access control list (see ACL class) |  | ||||||
|             handler_clss -- The local (duck) type. |  | ||||||
|             *args -- Constructor arguments for the local type. |  | ||||||
|             **kwargs -- Constructor keyworded arguments for the local |  | ||||||
|                 type. |  | ||||||
|         ''' |  | ||||||
|         argspec = inspect.getargspec(handler_cls.__init__) |  | ||||||
|         base_argspec = inspect.getargspec(Endpoint.__init__) |  | ||||||
|         if(argspec == base_argspec): |  | ||||||
|             result = handler_cls(self, self._client.boundjid.full) |  | ||||||
|         else: |  | ||||||
|             result = handler_cls(*args, **kwargs) |  | ||||||
|             Endpoint.__init__(result, self, self._client.boundjid.full) |  | ||||||
|         method_dict = result.get_methods() |  | ||||||
|         for method_name, method in method_dict.iteritems(): |  | ||||||
|             #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name) |  | ||||||
|             self._register_call(result.FQN(), method, method_name) |  | ||||||
|         self._register_acl(result.FQN(), acl) |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
| #    def is_available(self, targetCls, pto): |  | ||||||
| #        return self._client.is_available(pto) |  | ||||||
|  |  | ||||||
|     def _call_remote(self, pto, pmethod, callback, *arguments): |  | ||||||
|         iq = self._client.plugin['xep_0009'].make_iq_method_call(pto, pmethod, py2xml(*arguments)) |  | ||||||
|         pid = iq['id'] |  | ||||||
|         if callback is None: |  | ||||||
|             future = Future() |  | ||||||
|             self._register_callback(pid, future) |  | ||||||
|             iq.send() |  | ||||||
|             return future.get_value(30) |  | ||||||
|         else: |  | ||||||
|             log.debug("[RemoteSession] _call_remote %s" % callback) |  | ||||||
|             self._register_callback(pid, callback) |  | ||||||
|             iq.send() |  | ||||||
|  |  | ||||||
|     def close(self): |  | ||||||
|         ''' |  | ||||||
|         Closes this session. |  | ||||||
|         ''' |  | ||||||
|         self._client.disconnect(False) |  | ||||||
|         self._session_close_callback() |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_method_call(self, iq): |  | ||||||
|         iq.enable('rpc_query') |  | ||||||
|         params = iq['rpc_query']['method_call']['params'] |  | ||||||
|         args = xml2py(params) |  | ||||||
|         pmethod = iq['rpc_query']['method_call']['method_name'] |  | ||||||
|         try: |  | ||||||
|             with self._lock: |  | ||||||
|                 entry = self._entries[pmethod] |  | ||||||
|                 rules = self._acls[entry.get_endpoint_FQN()] |  | ||||||
|             if ACL.check(rules, iq['from'], pmethod): |  | ||||||
|                 return_value = entry.call_method(args) |  | ||||||
|             else: |  | ||||||
|                 raise AuthorizationException("Unauthorized access to %s from %s!" % (pmethod, iq['from'])) |  | ||||||
|             if return_value is None: |  | ||||||
|                 return_value = () |  | ||||||
|             response = self._client.plugin['xep_0009'].make_iq_method_response(iq['id'], iq['from'], py2xml(*return_value)) |  | ||||||
|             response.send() |  | ||||||
|         except InvocationException as ie: |  | ||||||
|             fault = dict() |  | ||||||
|             fault['code'] = 500 |  | ||||||
|             fault['string'] = ie.get_message() |  | ||||||
|             self._client.plugin['xep_0009']._send_fault(iq, fault2xml(fault)) |  | ||||||
|         except AuthorizationException as ae: |  | ||||||
|             log.error(ae.get_message()) |  | ||||||
|             error = self._client.plugin['xep_0009']._forbidden(iq) |  | ||||||
|             error.send() |  | ||||||
|         except Exception as e: |  | ||||||
|             if isinstance(e, KeyError): |  | ||||||
|                 log.error("No handler available for %s!" % pmethod) |  | ||||||
|                 error = self._client.plugin['xep_0009']._item_not_found(iq) |  | ||||||
|             else: |  | ||||||
|                 traceback.print_exc(file=sys.stderr) |  | ||||||
|                 log.error("An unexpected problem occurred invoking method %s!" % pmethod) |  | ||||||
|                 error = self._client.plugin['xep_0009']._undefined_condition(iq) |  | ||||||
|             #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e |  | ||||||
|             error.send() |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_method_response(self, iq): |  | ||||||
|         iq.enable('rpc_query') |  | ||||||
|         args = xml2py(iq['rpc_query']['method_response']['params']) |  | ||||||
|         pid = iq['id'] |  | ||||||
|         with self._lock: |  | ||||||
|             callback = self._callbacks[pid] |  | ||||||
|             del self._callbacks[pid] |  | ||||||
|         if(len(args) > 0): |  | ||||||
|             callback.set_value(args[0]) |  | ||||||
|         else: |  | ||||||
|             callback.set_value(None) |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_method_response2(self, iq): |  | ||||||
|         iq.enable('rpc_query') |  | ||||||
|         if iq['rpc_query']['method_response']['fault'] is not None: |  | ||||||
|             self._on_jabber_rpc_method_fault(iq) |  | ||||||
|         else: |  | ||||||
|             args = xml2py(iq['rpc_query']['method_response']['params']) |  | ||||||
|             pid = iq['id'] |  | ||||||
|             with self._lock: |  | ||||||
|                 callback = self._callbacks[pid] |  | ||||||
|                 del self._callbacks[pid] |  | ||||||
|             if(len(args) > 0): |  | ||||||
|                 callback.set_value(args[0]) |  | ||||||
|             else: |  | ||||||
|                 callback.set_value(None) |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_method_fault(self, iq): |  | ||||||
|         iq.enable('rpc_query') |  | ||||||
|         fault = xml2fault(iq['rpc_query']['method_response']['fault']) |  | ||||||
|         pid = iq['id'] |  | ||||||
|         with self._lock: |  | ||||||
|             callback = self._callbacks[pid] |  | ||||||
|             del self._callbacks[pid] |  | ||||||
|         e = { |  | ||||||
|              500: InvocationException |  | ||||||
|         }[fault['code']](fault['string']) |  | ||||||
|         callback.cancel_with_error(e) |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_error(self, iq): |  | ||||||
|         pid = iq['id'] |  | ||||||
|         pmethod = self._client.plugin['xep_0009']._extract_method(iq['rpc_query']) |  | ||||||
|         code = iq['error']['code'] |  | ||||||
|         type = iq['error']['type'] |  | ||||||
|         condition = iq['error']['condition'] |  | ||||||
|         #! print("['REMOTE.PY']._BINDING_handle_remote_procedure_error -> ERROR! ERROR! ERROR! Condition is '%s'" % condition) |  | ||||||
|         with self._lock: |  | ||||||
|             callback = self._callbacks[pid] |  | ||||||
|             del self._callbacks[pid] |  | ||||||
|         e = { |  | ||||||
|             'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])), |  | ||||||
|             'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])), |  | ||||||
|             'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])), |  | ||||||
|         }[condition] |  | ||||||
|         if e is None: |  | ||||||
|             RemoteException("An unexpected exception occurred at %s!" % iq['from']) |  | ||||||
|         callback.cancel_with_error(e) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Remote(object): |  | ||||||
|     ''' |  | ||||||
|     Bootstrap class for Jabber-RPC sessions. New sessions are openend |  | ||||||
|     with an existing XMPP client, or one is instantiated on demand. |  | ||||||
|     ''' |  | ||||||
|     _instance = None |  | ||||||
|     _sessions = dict() |  | ||||||
|     _lock = threading.RLock() |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def new_session_with_client(cls, client, callback=None): |  | ||||||
|         ''' |  | ||||||
|         Opens a new session with a given client. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             client -- An XMPP client. |  | ||||||
|             callback -- An optional callback which can be used to track |  | ||||||
|                 the starting state of the session. |  | ||||||
|         ''' |  | ||||||
|         with Remote._lock: |  | ||||||
|             if(client.boundjid.bare in cls._sessions): |  | ||||||
|                 raise RemoteException("There already is a session associated with these credentials!") |  | ||||||
|             else: |  | ||||||
|                 cls._sessions[client.boundjid.bare] = client; |  | ||||||
|         def _session_close_callback(): |  | ||||||
|             with Remote._lock: |  | ||||||
|                 del cls._sessions[client.boundjid.bare] |  | ||||||
|         result = RemoteSession(client, _session_close_callback) |  | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call) |  | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response) |  | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault) |  | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error) |  | ||||||
|         if callback is None: |  | ||||||
|             start_event_handler = result._notify |  | ||||||
|         else: |  | ||||||
|             start_event_handler = callback |  | ||||||
|         client.add_event_handler("session_start", start_event_handler) |  | ||||||
|         if client.connect(): |  | ||||||
|             client.process(threaded=True) |  | ||||||
|         else: |  | ||||||
|             raise RemoteException("Could not connect to XMPP server!") |  | ||||||
|         pass |  | ||||||
|         if callback is None: |  | ||||||
|             result._wait() |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
|     @classmethod |  | ||||||
|     def new_session(cls, jid, password, callback=None): |  | ||||||
|         ''' |  | ||||||
|         Opens a new session and instantiates a new XMPP client. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid -- The XMPP JID for logging in. |  | ||||||
|             password -- The password for logging in. |  | ||||||
|             callback -- An optional callback which can be used to track |  | ||||||
|                 the starting state of the session. |  | ||||||
|         ''' |  | ||||||
|         client = sleekxmpp.ClientXMPP(jid, password) |  | ||||||
|         #? Register plug-ins. |  | ||||||
|         client.registerPlugin('xep_0004') # Data Forms |  | ||||||
|         client.registerPlugin('xep_0009') # Jabber-RPC |  | ||||||
|         client.registerPlugin('xep_0030') # Service Discovery |  | ||||||
|         client.registerPlugin('xep_0060') # PubSub |  | ||||||
|         client.registerPlugin('xep_0199') # XMPP Ping |  | ||||||
|         return cls.new_session_with_client(client, callback) |  | ||||||
|  |  | ||||||
| @@ -1,221 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins import base |  | ||||||
| from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse |  | ||||||
| from sleekxmpp.stanza.iq import Iq |  | ||||||
| from sleekxmpp.xmlstream.handler.callback import Callback |  | ||||||
| from sleekxmpp.xmlstream.matcher.xpath import MatchXPath |  | ||||||
| from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0009(base.base_plugin): |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.xep = '0009' |  | ||||||
|         self.description = 'Jabber-RPC' |  | ||||||
|         #self.stanza = sleekxmpp.plugins.xep_0009.stanza |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, RPCQuery) |  | ||||||
|         register_stanza_plugin(RPCQuery, MethodCall) |  | ||||||
|         register_stanza_plugin(RPCQuery, MethodResponse) |  | ||||||
|  |  | ||||||
|         self.xmpp.registerHandler( |  | ||||||
|             Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), |  | ||||||
|             self._handle_method_call) |  | ||||||
|         ) |  | ||||||
|         self.xmpp.registerHandler( |  | ||||||
|             Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), |  | ||||||
|             self._handle_method_response) |  | ||||||
|         ) |  | ||||||
|         self.xmpp.registerHandler( |  | ||||||
|             Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)), |  | ||||||
|             self._handle_error) |  | ||||||
|         ) |  | ||||||
|         self.xmpp.add_event_handler('jabber_rpc_method_call', self._on_jabber_rpc_method_call) |  | ||||||
|         self.xmpp.add_event_handler('jabber_rpc_method_response', self._on_jabber_rpc_method_response) |  | ||||||
|         self.xmpp.add_event_handler('jabber_rpc_method_fault', self._on_jabber_rpc_method_fault) |  | ||||||
|         self.xmpp.add_event_handler('jabber_rpc_error', self._on_jabber_rpc_error) |  | ||||||
|         self.xmpp.add_event_handler('error', self._handle_error) |  | ||||||
|         #self.activeCalls = [] |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         base.base_plugin.post_init(self) |  | ||||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc') |  | ||||||
|         self.xmpp.plugin['xep_0030'].add_identity('automation','rpc') |  | ||||||
|  |  | ||||||
|     def make_iq_method_call(self, pto, pmethod, params): |  | ||||||
|         iq = self.xmpp.makeIqSet() |  | ||||||
|         iq.attrib['to'] = pto |  | ||||||
|         iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
|         iq.enable('rpc_query') |  | ||||||
|         iq['rpc_query']['method_call']['method_name'] = pmethod |  | ||||||
|         iq['rpc_query']['method_call']['params'] = params |  | ||||||
|         return iq; |  | ||||||
|  |  | ||||||
|     def make_iq_method_response(self, pid, pto, params): |  | ||||||
|         iq = self.xmpp.makeIqResult(pid) |  | ||||||
|         iq.attrib['to'] = pto |  | ||||||
|         iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
|         iq.enable('rpc_query') |  | ||||||
|         iq['rpc_query']['method_response']['params'] = params |  | ||||||
|         return iq |  | ||||||
|  |  | ||||||
|     def make_iq_method_response_fault(self, pid, pto, params): |  | ||||||
|         iq = self.xmpp.makeIqResult(pid) |  | ||||||
|         iq.attrib['to'] = pto |  | ||||||
|         iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
|         iq.enable('rpc_query') |  | ||||||
|         iq['rpc_query']['method_response']['params'] = None |  | ||||||
|         iq['rpc_query']['method_response']['fault'] = params |  | ||||||
|         return iq |  | ||||||
|  |  | ||||||
| #    def make_iq_method_error(self, pto, pid, pmethod, params, code, type, condition): |  | ||||||
| #        iq = self.xmpp.makeIqError(pid) |  | ||||||
| #        iq.attrib['to'] = pto |  | ||||||
| #        iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| #        iq['error']['code'] = code |  | ||||||
| #        iq['error']['type'] = type |  | ||||||
| #        iq['error']['condition'] = condition |  | ||||||
| #        iq['rpc_query']['method_call']['method_name'] = pmethod |  | ||||||
| #        iq['rpc_query']['method_call']['params'] = params |  | ||||||
| #        return iq |  | ||||||
|  |  | ||||||
|     def _item_not_found(self, iq): |  | ||||||
|         payload = iq.get_payload() |  | ||||||
|         iq.reply().error().set_payload(payload); |  | ||||||
|         iq['error']['code'] = '404' |  | ||||||
|         iq['error']['type'] = 'cancel' |  | ||||||
|         iq['error']['condition'] = 'item-not-found' |  | ||||||
|         return iq |  | ||||||
|  |  | ||||||
|     def _undefined_condition(self, iq): |  | ||||||
|         payload = iq.get_payload() |  | ||||||
|         iq.reply().error().set_payload(payload) |  | ||||||
|         iq['error']['code'] = '500' |  | ||||||
|         iq['error']['type'] = 'cancel' |  | ||||||
|         iq['error']['condition'] = 'undefined-condition' |  | ||||||
|         return iq |  | ||||||
|  |  | ||||||
|     def _forbidden(self, iq): |  | ||||||
|         payload = iq.get_payload() |  | ||||||
|         iq.reply().error().set_payload(payload) |  | ||||||
|         iq['error']['code'] = '403' |  | ||||||
|         iq['error']['type'] = 'auth' |  | ||||||
|         iq['error']['condition'] = 'forbidden' |  | ||||||
|         return iq |  | ||||||
|  |  | ||||||
|     def _recipient_unvailable(self, iq): |  | ||||||
|         payload = iq.get_payload() |  | ||||||
|         iq.reply().error().set_payload(payload) |  | ||||||
|         iq['error']['code'] = '404' |  | ||||||
|         iq['error']['type'] = 'wait' |  | ||||||
|         iq['error']['condition'] = 'recipient-unavailable' |  | ||||||
|         return iq |  | ||||||
|  |  | ||||||
|     def _handle_method_call(self, iq): |  | ||||||
|         type = iq['type'] |  | ||||||
|         if type == 'set': |  | ||||||
|             log.debug("Incoming Jabber-RPC call from %s" % iq['from']) |  | ||||||
|             self.xmpp.event('jabber_rpc_method_call', iq) |  | ||||||
|         else: |  | ||||||
|             if type == 'error' and ['rpc_query'] is None: |  | ||||||
|                 self.handle_error(iq) |  | ||||||
|             else: |  | ||||||
|                 log.debug("Incoming Jabber-RPC error from %s" % iq['from']) |  | ||||||
|                 self.xmpp.event('jabber_rpc_error', iq) |  | ||||||
|  |  | ||||||
|     def _handle_method_response(self, iq): |  | ||||||
|         if iq['rpc_query']['method_response']['fault'] is not None: |  | ||||||
|             log.debug("Incoming Jabber-RPC fault from %s" % iq['from']) |  | ||||||
|             #self._on_jabber_rpc_method_fault(iq) |  | ||||||
|             self.xmpp.event('jabber_rpc_method_fault', iq) |  | ||||||
|         else: |  | ||||||
|             log.debug("Incoming Jabber-RPC response from %s" % iq['from']) |  | ||||||
|             self.xmpp.event('jabber_rpc_method_response', iq) |  | ||||||
|  |  | ||||||
|     def _handle_error(self, iq): |  | ||||||
|         print("['XEP-0009']._handle_error -> ERROR! Iq is '%s'" % iq) |  | ||||||
|         print("#######################") |  | ||||||
|         print("### NOT IMPLEMENTED ###") |  | ||||||
|         print("#######################") |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_method_call(self, iq, forwarded=False): |  | ||||||
|         """ |  | ||||||
|         A default handler for Jabber-RPC method call. If another |  | ||||||
|         handler is registered, this one will defer and not run. |  | ||||||
|  |  | ||||||
|         If this handler is called by your own custom handler with |  | ||||||
|         forwarded set to True, then it will run as normal. |  | ||||||
|         """ |  | ||||||
|         if not forwarded and self.xmpp.event_handled('jabber_rpc_method_call') > 1: |  | ||||||
|             return |  | ||||||
|         # Reply with error by default |  | ||||||
|         error = self.client.plugin['xep_0009']._item_not_found(iq) |  | ||||||
|         error.send() |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_method_response(self, iq, forwarded=False): |  | ||||||
|         """ |  | ||||||
|         A default handler for Jabber-RPC method response. If another |  | ||||||
|         handler is registered, this one will defer and not run. |  | ||||||
|  |  | ||||||
|         If this handler is called by your own custom handler with |  | ||||||
|         forwarded set to True, then it will run as normal. |  | ||||||
|         """ |  | ||||||
|         if not forwarded and self.xmpp.event_handled('jabber_rpc_method_response') > 1: |  | ||||||
|             return |  | ||||||
|         error = self.client.plugin['xep_0009']._recpient_unavailable(iq) |  | ||||||
|         error.send() |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_method_fault(self, iq, forwarded=False): |  | ||||||
|         """ |  | ||||||
|         A default handler for Jabber-RPC fault response. If another |  | ||||||
|         handler is registered, this one will defer and not run. |  | ||||||
|  |  | ||||||
|         If this handler is called by your own custom handler with |  | ||||||
|         forwarded set to True, then it will run as normal. |  | ||||||
|         """ |  | ||||||
|         if not forwarded and self.xmpp.event_handled('jabber_rpc_method_fault') > 1: |  | ||||||
|             return |  | ||||||
|         error = self.client.plugin['xep_0009']._recpient_unavailable(iq) |  | ||||||
|         error.send() |  | ||||||
|  |  | ||||||
|     def _on_jabber_rpc_error(self, iq, forwarded=False): |  | ||||||
|         """ |  | ||||||
|         A default handler for Jabber-RPC error response. If another |  | ||||||
|         handler is registered, this one will defer and not run. |  | ||||||
|  |  | ||||||
|         If this handler is called by your own custom handler with |  | ||||||
|         forwarded set to True, then it will run as normal. |  | ||||||
|         """ |  | ||||||
|         if not forwarded and self.xmpp.event_handled('jabber_rpc_error') > 1: |  | ||||||
|             return |  | ||||||
|         error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload()) |  | ||||||
|         error.send() |  | ||||||
|  |  | ||||||
|     def _send_fault(self, iq, fault_xml): # |  | ||||||
|         fault = self.make_iq_method_response_fault(iq['id'], iq['from'], fault_xml) |  | ||||||
|         fault.send() |  | ||||||
|  |  | ||||||
|     def _send_error(self, iq): |  | ||||||
|         print("['XEP-0009']._send_error -> ERROR! Iq is '%s'" % iq) |  | ||||||
|         print("#######################") |  | ||||||
|         print("### NOT IMPLEMENTED ###") |  | ||||||
|         print("#######################") |  | ||||||
|  |  | ||||||
|     def _extract_method(self, stanza): |  | ||||||
|         xml = ET.fromstring("%s" % stanza) |  | ||||||
|         return xml.find("./methodCall/methodName").text |  | ||||||
|  |  | ||||||
| @@ -1,64 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.xmlstream.stanzabase import ElementBase |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RPCQuery(ElementBase): |  | ||||||
|     name = 'query' |  | ||||||
|     namespace = 'jabber:iq:rpc' |  | ||||||
|     plugin_attrib = 'rpc_query' |  | ||||||
|     interfaces = set(()) |  | ||||||
|     subinterfaces = set(()) |  | ||||||
|     plugin_attrib_map = {} |  | ||||||
|     plugin_tag_map = {} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MethodCall(ElementBase): |  | ||||||
|     name = 'methodCall' |  | ||||||
|     namespace = 'jabber:iq:rpc' |  | ||||||
|     plugin_attrib = 'method_call' |  | ||||||
|     interfaces = set(('method_name', 'params')) |  | ||||||
|     subinterfaces = set(()) |  | ||||||
|     plugin_attrib_map = {} |  | ||||||
|     plugin_tag_map = {} |  | ||||||
|  |  | ||||||
|     def get_method_name(self): |  | ||||||
|         return self._get_sub_text('methodName') |  | ||||||
|  |  | ||||||
|     def set_method_name(self, value): |  | ||||||
|         return self._set_sub_text('methodName', value) |  | ||||||
|  |  | ||||||
|     def get_params(self): |  | ||||||
|         return self.xml.find('{%s}params' % self.namespace) |  | ||||||
|  |  | ||||||
|     def set_params(self, params): |  | ||||||
|         self.append(params) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MethodResponse(ElementBase): |  | ||||||
|     name = 'methodResponse' |  | ||||||
|     namespace = 'jabber:iq:rpc' |  | ||||||
|     plugin_attrib = 'method_response' |  | ||||||
|     interfaces = set(('params', 'fault')) |  | ||||||
|     subinterfaces = set(()) |  | ||||||
|     plugin_attrib_map = {} |  | ||||||
|     plugin_tag_map = {} |  | ||||||
|  |  | ||||||
|     def get_params(self): |  | ||||||
|         return self.xml.find('{%s}params' % self.namespace) |  | ||||||
|  |  | ||||||
|     def set_params(self, params): |  | ||||||
|         self.append(params) |  | ||||||
|  |  | ||||||
|     def get_fault(self): |  | ||||||
|         return self.xml.find('{%s}fault' % self.namespace) |  | ||||||
|  |  | ||||||
|     def set_fault(self, fault): |  | ||||||
|         self.append(fault) |  | ||||||
| @@ -1,9 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON). |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse |  | ||||||
| @@ -1,118 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from datetime import datetime |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from . import base |  | ||||||
| from .. stanza.iq import Iq |  | ||||||
| from .. xmlstream.handler.callback import Callback |  | ||||||
| from .. xmlstream.matcher.xpath import MatchXPath |  | ||||||
| from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class LastActivity(ElementBase): |  | ||||||
|     name = 'query' |  | ||||||
|     namespace = 'jabber:iq:last' |  | ||||||
|     plugin_attrib = 'last_activity' |  | ||||||
|     interfaces = set(('seconds', 'status')) |  | ||||||
|  |  | ||||||
|     def get_seconds(self): |  | ||||||
|         return int(self._get_attr('seconds')) |  | ||||||
|  |  | ||||||
|     def set_seconds(self, value): |  | ||||||
|         self._set_attr('seconds', str(value)) |  | ||||||
|  |  | ||||||
|     def get_status(self): |  | ||||||
|         return self.xml.text |  | ||||||
|  |  | ||||||
|     def set_status(self, value): |  | ||||||
|         self.xml.text = str(value) |  | ||||||
|  |  | ||||||
|     def del_status(self): |  | ||||||
|         self.xml.text = '' |  | ||||||
|  |  | ||||||
| class xep_0012(base.base_plugin): |  | ||||||
|     """ |  | ||||||
|     XEP-0012 Last Activity |  | ||||||
|     """ |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.description = "Last Activity" |  | ||||||
|         self.xep = "0012" |  | ||||||
|  |  | ||||||
|         self.xmpp.registerHandler( |  | ||||||
|             Callback('Last Activity', |  | ||||||
|                  MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, |  | ||||||
|                                   LastActivity.namespace)), |  | ||||||
|                  self.handle_last_activity_query)) |  | ||||||
|         register_stanza_plugin(Iq, LastActivity) |  | ||||||
|  |  | ||||||
|         self.xmpp.add_event_handler('last_activity_request', self.handle_last_activity) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         base.base_plugin.post_init(self) |  | ||||||
|         if self.xmpp.is_component: |  | ||||||
|             # We are a component, so we track the uptime |  | ||||||
|             self.xmpp.add_event_handler("session_start", self._reset_uptime) |  | ||||||
|             self._start_datetime = datetime.now() |  | ||||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last') |  | ||||||
|  |  | ||||||
|     def _reset_uptime(self, event): |  | ||||||
|         self._start_datetime = datetime.now() |  | ||||||
|  |  | ||||||
|     def handle_last_activity_query(self, iq): |  | ||||||
|         if iq['type'] == 'get': |  | ||||||
|             log.debug("Last activity requested by %s" % iq['from']) |  | ||||||
|             self.xmpp.event('last_activity_request', iq) |  | ||||||
|         elif iq['type'] == 'result': |  | ||||||
|             log.debug("Last activity result from %s" % iq['from']) |  | ||||||
|             self.xmpp.event('last_activity', iq) |  | ||||||
|  |  | ||||||
|     def handle_last_activity(self, iq): |  | ||||||
|         jid = iq['from'] |  | ||||||
|  |  | ||||||
|         if self.xmpp.is_component: |  | ||||||
|             # Send the uptime |  | ||||||
|             result = LastActivity() |  | ||||||
|             td = (datetime.now() - self._start_datetime) |  | ||||||
|             result['seconds'] = td.seconds + td.days * 24 * 3600 |  | ||||||
|             reply = iq.reply().setPayload(result.xml).send() |  | ||||||
|         else: |  | ||||||
|             barejid = JID(jid).bare |  | ||||||
|             if barejid in self.xmpp.roster and ( self.xmpp.roster[barejid]['subscription'] in ('from', 'both') or |  | ||||||
|                                                  barejid == self.xmpp.boundjid.bare ): |  | ||||||
|                 # We don't know how to calculate it |  | ||||||
|                 iq.reply().error().setPayload(iq['last_activity'].xml) |  | ||||||
|                 iq['error']['code'] = '503' |  | ||||||
|                 iq['error']['type'] = 'cancel' |  | ||||||
|                 iq['error']['condition'] = 'service-unavailable' |  | ||||||
|                 iq.send() |  | ||||||
|             else: |  | ||||||
|                 iq.reply().error().setPayload(iq['last_activity'].xml) |  | ||||||
|                 iq['error']['code'] = '403' |  | ||||||
|                 iq['error']['type'] = 'auth' |  | ||||||
|                 iq['error']['condition'] = 'forbidden' |  | ||||||
|                 iq.send() |  | ||||||
|  |  | ||||||
|     def get_last_activity(self, jid): |  | ||||||
|         """Query the LastActivity of jid and return it in seconds""" |  | ||||||
|         iq = self.xmpp.makeIqGet() |  | ||||||
|         query = LastActivity() |  | ||||||
|         iq.append(query.xml) |  | ||||||
|         iq.attrib['to'] = jid |  | ||||||
|         iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
|         id = iq.get('id') |  | ||||||
|         result = iq.send() |  | ||||||
|         if result and result is not None and result.get('type', 'error') != 'error': |  | ||||||
|             return result['last_activity']['seconds'] |  | ||||||
|         else: |  | ||||||
|             return False |  | ||||||
							
								
								
									
										324
									
								
								sleekxmpp/plugins/xep_0030.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								sleekxmpp/plugins/xep_0030.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,324 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file license.txt for copying permissio | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from . import base | ||||||
|  | from .. xmlstream.handler.callback import Callback | ||||||
|  | from .. xmlstream.matcher.xpath import MatchXPath | ||||||
|  | from .. xmlstream.stanzabase import ElementBase, ET, JID | ||||||
|  | from .. stanza.iq import Iq | ||||||
|  |  | ||||||
|  | class DiscoInfo(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/disco#info' | ||||||
|  | 	name = 'query' | ||||||
|  | 	plugin_attrib = 'disco_info' | ||||||
|  | 	interfaces = set(('node', 'features', 'identities')) | ||||||
|  |  | ||||||
|  | 	def getFeatures(self): | ||||||
|  | 		features = [] | ||||||
|  | 		featuresXML = self.xml.findall('{%s}feature' % self.namespace) | ||||||
|  | 		for feature in featuresXML: | ||||||
|  | 			features.append(feature.attrib['var']) | ||||||
|  | 		return features | ||||||
|  |  | ||||||
|  | 	def setFeatures(self, features): | ||||||
|  | 		self.delFeatures() | ||||||
|  | 		for name in features: | ||||||
|  | 			self.addFeature(name) | ||||||
|  |  | ||||||
|  | 	def delFeatures(self): | ||||||
|  | 		featuresXML = self.xml.findall('{%s}feature' % self.namespace) | ||||||
|  | 		for feature in featuresXML: | ||||||
|  | 			self.xml.remove(feature) | ||||||
|  |  | ||||||
|  | 	def addFeature(self, feature): | ||||||
|  | 		featureXML = ET.Element('{%s}feature' % self.namespace,  | ||||||
|  | 					{'var': feature}) | ||||||
|  | 		self.xml.append(featureXML) | ||||||
|  |  | ||||||
|  | 	def delFeature(self, feature): | ||||||
|  | 		featuresXML = self.xml.findall('{%s}feature' % self.namespace) | ||||||
|  | 		for featureXML in featuresXML: | ||||||
|  | 			if featureXML.attrib['var'] == feature: | ||||||
|  | 				self.xml.remove(featureXML) | ||||||
|  |  | ||||||
|  | 	def getIdentities(self): | ||||||
|  | 		ids = [] | ||||||
|  | 		idsXML = self.xml.findall('{%s}identity' % self.namespace) | ||||||
|  | 		for idXML in idsXML: | ||||||
|  | 			idData = (idXML.attrib['category'], | ||||||
|  | 				  idXML.attrib['type'], | ||||||
|  | 				  idXML.attrib.get('name', '')) | ||||||
|  | 			ids.append(idData) | ||||||
|  | 		return ids | ||||||
|  |  | ||||||
|  | 	def setIdentities(self, ids): | ||||||
|  | 		self.delIdentities() | ||||||
|  | 		for idData in ids: | ||||||
|  | 			self.addIdentity(*idData) | ||||||
|  |  | ||||||
|  | 	def delIdentities(self): | ||||||
|  | 		idsXML = self.xml.findall('{%s}identity' % self.namespace) | ||||||
|  | 		for idXML in idsXML: | ||||||
|  | 			self.xml.remove(idXML) | ||||||
|  |  | ||||||
|  | 	def addIdentity(self, category, id_type, name=''): | ||||||
|  | 		idXML = ET.Element('{%s}identity' % self.namespace,  | ||||||
|  | 				   {'category': category, | ||||||
|  | 				    'type': id_type, | ||||||
|  | 				    'name': name}) | ||||||
|  | 		self.xml.append(idXML) | ||||||
|  |  | ||||||
|  | 	def delIdentity(self, category, id_type, name=''): | ||||||
|  | 		idsXML = self.xml.findall('{%s}identity' % self.namespace) | ||||||
|  | 		for idXML in idsXML: | ||||||
|  | 			idData = (idXML.attrib['category'],  | ||||||
|  | 				  idXML.attrib['type']) | ||||||
|  | 			delId = (category, id_type) | ||||||
|  | 			if idData == delId: | ||||||
|  | 				self.xml.remove(idXML) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class DiscoItems(ElementBase): | ||||||
|  | 	namespace = 'http://jabber.org/protocol/disco#items' | ||||||
|  | 	name = 'query' | ||||||
|  | 	plugin_attrib = 'disco_items' | ||||||
|  | 	interfaces = set(('node', 'items')) | ||||||
|  |  | ||||||
|  | 	def getItems(self): | ||||||
|  | 		items = [] | ||||||
|  | 		itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||||
|  | 		for item in itemsXML: | ||||||
|  | 			itemData = (item.attrib['jid'], | ||||||
|  | 				    item.attrib.get('node'), | ||||||
|  | 				    item.attrib.get('name')) | ||||||
|  | 			items.append(itemData) | ||||||
|  | 		return items | ||||||
|  |  | ||||||
|  | 	def setItems(self, items): | ||||||
|  | 		self.delItems() | ||||||
|  | 		for item in items: | ||||||
|  | 			self.addItem(*item) | ||||||
|  |  | ||||||
|  | 	def delItems(self): | ||||||
|  | 		itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||||
|  | 		for item in itemsXML: | ||||||
|  | 			self.xml.remove(item) | ||||||
|  |  | ||||||
|  | 	def addItem(self, jid, node='', name=''): | ||||||
|  | 		itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid}) | ||||||
|  | 		if name: | ||||||
|  | 			itemXML.attrib['name'] = name | ||||||
|  | 		if node: | ||||||
|  | 			itemXML.attrib['node'] = node | ||||||
|  | 		self.xml.append(itemXML) | ||||||
|  |  | ||||||
|  | 	def delItem(self, jid, node=''): | ||||||
|  | 		itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||||
|  | 		for itemXML in itemsXML: | ||||||
|  | 			itemData = (itemXML.attrib['jid'], | ||||||
|  | 				    itemXML.attrib.get('node', '')) | ||||||
|  | 			itemDel = (jid, node) | ||||||
|  | 			if itemData == itemDel: | ||||||
|  | 				self.xml.remove(itemXML) | ||||||
|  | 	 | ||||||
|  |  | ||||||
|  | class DiscoNode(object): | ||||||
|  | 	""" | ||||||
|  | 	Collection object for grouping info and item information | ||||||
|  | 	into nodes. | ||||||
|  | 	""" | ||||||
|  | 	def __init__(self, name): | ||||||
|  | 		self.name = name | ||||||
|  | 		self.info = DiscoInfo() | ||||||
|  | 		self.items = DiscoItems() | ||||||
|  |  | ||||||
|  | 		# This is a bit like poor man's inheritance, but | ||||||
|  | 		# to simplify adding information to the node we  | ||||||
|  | 		# map node functions to either the info or items | ||||||
|  | 		# stanza objects. | ||||||
|  | 		# | ||||||
|  | 		# We don't want to make DiscoNode inherit from  | ||||||
|  | 		# DiscoInfo and DiscoItems because DiscoNode is | ||||||
|  | 		# not an actual stanza, and doing so would create | ||||||
|  | 		# confusion and potential bugs. | ||||||
|  |  | ||||||
|  | 		self._map(self.items, 'items', ['get', 'set', 'del']) | ||||||
|  | 		self._map(self.items, 'item', ['add', 'del']) | ||||||
|  | 		self._map(self.info, 'identities', ['get', 'set', 'del']) | ||||||
|  | 		self._map(self.info, 'identity', ['add', 'del']) | ||||||
|  | 		self._map(self.info, 'features', ['get', 'set', 'del']) | ||||||
|  | 		self._map(self.info, 'feature', ['add', 'del']) | ||||||
|  |  | ||||||
|  | 	def isEmpty(self): | ||||||
|  | 		""" | ||||||
|  | 		Test if the node contains any information. Useful for | ||||||
|  | 		determining if a node can be deleted. | ||||||
|  | 		""" | ||||||
|  | 		ids = self.getIdentities() | ||||||
|  | 		features = self.getFeatures() | ||||||
|  | 		items = self.getItems() | ||||||
|  |  | ||||||
|  | 		if not ids and not features and not items: | ||||||
|  | 			return True | ||||||
|  | 		return False | ||||||
|  |  | ||||||
|  | 	def _map(self, obj, interface, access): | ||||||
|  | 		""" | ||||||
|  | 		Map functions of the form obj.accessInterface | ||||||
|  | 		to self.accessInterface for each given access type. | ||||||
|  | 		""" | ||||||
|  | 		interface = interface.title() | ||||||
|  | 		for access_type in access: | ||||||
|  | 			method = access_type + interface | ||||||
|  | 			if hasattr(obj, method): | ||||||
|  | 				setattr(self, method, getattr(obj, method)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class xep_0030(base.base_plugin): | ||||||
|  | 	""" | ||||||
|  | 	XEP-0030 Service Discovery | ||||||
|  | 	""" | ||||||
|  | 	 | ||||||
|  | 	def plugin_init(self): | ||||||
|  | 		self.xep = '0030' | ||||||
|  | 		self.description = 'Service Discovery' | ||||||
|  |  | ||||||
|  | 		self.xmpp.registerHandler( | ||||||
|  | 			Callback('Disco Items', | ||||||
|  | 				 MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,  | ||||||
|  | 								  DiscoItems.namespace)), | ||||||
|  | 				 self.handle_item_query)) | ||||||
|  |  | ||||||
|  | 		self.xmpp.registerHandler( | ||||||
|  | 			Callback('Disco Info', | ||||||
|  | 				 MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns,  | ||||||
|  | 								  DiscoInfo.namespace)), | ||||||
|  | 				 self.handle_info_query)) | ||||||
|  |  | ||||||
|  | 		self.xmpp.stanzaPlugin(Iq, DiscoInfo) | ||||||
|  | 		self.xmpp.stanzaPlugin(Iq, DiscoItems) | ||||||
|  |  | ||||||
|  | 		self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) | ||||||
|  | 		self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) | ||||||
|  |  | ||||||
|  | 		self.nodes = {'main': DiscoNode('main')} | ||||||
|  |  | ||||||
|  | 	def add_node(self, node): | ||||||
|  | 		if node not in self.nodes: | ||||||
|  | 			self.nodes[node] = DiscoNode(node) | ||||||
|  |  | ||||||
|  | 	def del_node(self, node): | ||||||
|  | 		if node in self.nodes: | ||||||
|  | 			del self.nodes[node] | ||||||
|  |  | ||||||
|  | 	def handle_item_query(self, iq): | ||||||
|  | 		if iq['type'] == 'get': | ||||||
|  | 			logging.debug("Items requested by %s" % iq['from']) | ||||||
|  | 			self.xmpp.event('disco_items_request', iq) | ||||||
|  | 		elif iq['type'] == 'result': | ||||||
|  | 			logging.debug("Items result from %s" % iq['from']) | ||||||
|  | 			self.xmpp.event('disco_items', iq) | ||||||
|  |  | ||||||
|  | 	def handle_info_query(self, iq): | ||||||
|  | 		if iq['type'] == 'get': | ||||||
|  | 			logging.debug("Info requested by %s" % iq['from']) | ||||||
|  | 			self.xmpp.event('disco_info_request', iq) | ||||||
|  | 		elif iq['type'] == 'result': | ||||||
|  | 			logging.debug("Info result from %s" % iq['from']) | ||||||
|  | 			self.xmpp.event('disco_info', iq) | ||||||
|  |  | ||||||
|  | 	def handle_disco_info(self, iq, forwarded=False): | ||||||
|  | 		""" | ||||||
|  | 		A default handler for disco#info requests. If another | ||||||
|  | 		handler is registered, this one will defer and not run. | ||||||
|  | 		""" | ||||||
|  | 		handlers = self.xmpp.event_handlers['disco_info_request'] | ||||||
|  | 		if not forwarded and len(handlers) > 1: | ||||||
|  | 			return | ||||||
|  |  | ||||||
|  | 		node_name = iq['disco_info']['node'] | ||||||
|  | 		if not node_name: | ||||||
|  | 			node_name = 'main' | ||||||
|  |  | ||||||
|  | 		logging.debug("Using default handler for disco#info on node '%s'." % node_name) | ||||||
|  |  | ||||||
|  | 		if node_name in self.nodes: | ||||||
|  | 			node = self.nodes[node_name] | ||||||
|  | 			iq.reply().setPayload(node.info.xml).send() | ||||||
|  | 		else: | ||||||
|  | 			logging.debug("Node %s requested, but does not exist." % node_name) | ||||||
|  | 			iq.reply().error().setPayload(iq['disco_info'].xml) | ||||||
|  | 			iq['error']['code'] = '404' | ||||||
|  | 			iq['error']['type'] = 'cancel' | ||||||
|  | 			iq['error']['condition'] = 'item-not-found' | ||||||
|  | 			iq.send() | ||||||
|  | 			 | ||||||
|  | 	def handle_disco_items(self, iq, forwarded=False): | ||||||
|  | 		""" | ||||||
|  | 		A default handler for disco#items requests. If another | ||||||
|  | 		handler is registered, this one will defer and not run. | ||||||
|  |  | ||||||
|  | 		If this handler is called by your own custom handler with | ||||||
|  | 		forwarded set to True, then it will run as normal. | ||||||
|  | 		""" | ||||||
|  | 		handlers = self.xmpp.event_handlers['disco_items_request'] | ||||||
|  | 		if not forwarded and len(handlers) > 1: | ||||||
|  | 			return | ||||||
|  |  | ||||||
|  | 		node_name = iq['disco_items']['node'] | ||||||
|  | 		if not node_name: | ||||||
|  | 			node_name = 'main' | ||||||
|  |  | ||||||
|  | 		logging.debug("Using default handler for disco#items on node '%s'." % node_name) | ||||||
|  |  | ||||||
|  | 		if node_name in self.nodes: | ||||||
|  | 			node = self.nodes[node_name] | ||||||
|  | 			iq.reply().setPayload(node.items.xml).send() | ||||||
|  | 		else:	 | ||||||
|  | 			logging.debug("Node %s requested, but does not exist." % node_name) | ||||||
|  | 			iq.reply().error().setPayload(iq['disco_items'].xml) | ||||||
|  | 			iq['error']['code'] = '404' | ||||||
|  | 			iq['error']['type'] = 'cancel' | ||||||
|  | 			iq['error']['condition'] = 'item-not-found' | ||||||
|  | 			iq.send() | ||||||
|  |  | ||||||
|  | 	# Older interface methods for backwards compatibility | ||||||
|  |  | ||||||
|  | 	def getInfo(self, jid, node=''): | ||||||
|  | 		iq = self.xmpp.Iq() | ||||||
|  | 		iq['type'] = 'get' | ||||||
|  | 		iq['to'] = jid | ||||||
|  | 		iq['from'] = self.xmpp.fulljid | ||||||
|  | 		iq['disco_info']['node'] = node | ||||||
|  | 		iq.send() | ||||||
|  |  | ||||||
|  | 	def getItems(self, jid, node=''): | ||||||
|  | 		iq = self.xmpp.Iq() | ||||||
|  | 		iq['type'] = 'get' | ||||||
|  | 		iq['to'] = jid | ||||||
|  | 		iq['from'] = self.xmpp.fulljid | ||||||
|  | 		iq['disco_items']['node'] = node | ||||||
|  | 		iq.send() | ||||||
|  | 	 | ||||||
|  | 	def add_feature(self, feature, node='main'): | ||||||
|  | 		self.add_node(node) | ||||||
|  | 		self.nodes[node].addFeature(feature) | ||||||
|  | 	 | ||||||
|  | 	def add_identity(self, category='', itype='', name='', node='main'): | ||||||
|  | 		self.add_node(node) | ||||||
|  | 		self.nodes[node].addIdentity(category=category, | ||||||
|  | 					     id_type=itype, | ||||||
|  | 					     name=name) | ||||||
|  | 	 | ||||||
|  | 	def add_item(self, jid=None, name='', node='main', subnode=''): | ||||||
|  | 		self.add_node(node) | ||||||
|  | 		self.add_node(subnode) | ||||||
|  | 		if jid is None: | ||||||
|  | 			jid = self.xmpp.fulljid | ||||||
|  | 		self.nodes[node].addItem(jid=jid, name=name, node=subnode) | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0030 import stanza |  | ||||||
| from sleekxmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems |  | ||||||
| from sleekxmpp.plugins.xep_0030.static import StaticDisco |  | ||||||
| from sleekxmpp.plugins.xep_0030.disco import xep_0030 |  | ||||||
| @@ -1,623 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp import Iq |  | ||||||
| from sleekxmpp.exceptions import XMPPError |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.xmlstream.handler import Callback |  | ||||||
| from sleekxmpp.xmlstream.matcher import StanzaPath |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID |  | ||||||
| from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems, StaticDisco |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0030(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0030: Service Discovery |  | ||||||
|  |  | ||||||
|     Service discovery in XMPP allows entities to discover information about |  | ||||||
|     other agents in the network, such as the feature sets supported by a |  | ||||||
|     client, or signposts to other, related entities. |  | ||||||
|  |  | ||||||
|     Also see <http://www.xmpp.org/extensions/xep-0030.html>. |  | ||||||
|  |  | ||||||
|     The XEP-0030 plugin works using a hierarchy of dynamic |  | ||||||
|     node handlers, ranging from global handlers to specific |  | ||||||
|     JID+node handlers. The default set of handlers operate |  | ||||||
|     in a static manner, storing disco information in memory. |  | ||||||
|     However, custom handlers may use any available backend |  | ||||||
|     storage mechanism desired, such as SQLite or Redis. |  | ||||||
|  |  | ||||||
|     Node handler hierarchy: |  | ||||||
|         JID   | Node  | Level |  | ||||||
|         --------------------- |  | ||||||
|         None  | None  | Global |  | ||||||
|         Given | None  | All nodes for the JID |  | ||||||
|         None  | Given | Node on self.xmpp.boundjid |  | ||||||
|         Given | Given | A single node |  | ||||||
|  |  | ||||||
|     Stream Handlers: |  | ||||||
|         Disco Info  -- Any Iq stanze that includes a query with the |  | ||||||
|                        namespace http://jabber.org/protocol/disco#info. |  | ||||||
|         Disco Items -- Any Iq stanze that includes a query with the |  | ||||||
|                        namespace http://jabber.org/protocol/disco#items. |  | ||||||
|  |  | ||||||
|     Events: |  | ||||||
|         disco_info         -- Received a disco#info Iq query result. |  | ||||||
|         disco_items        -- Received a disco#items Iq query result. |  | ||||||
|         disco_info_query   -- Received a disco#info Iq query request. |  | ||||||
|         disco_items_query  -- Received a disco#items Iq query request. |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|         stanza           -- A reference to the module containing the |  | ||||||
|                             stanza classes provided by this plugin. |  | ||||||
|         static           -- Object containing the default set of |  | ||||||
|                             static node handlers. |  | ||||||
|         default_handlers -- A dictionary mapping operations to the default |  | ||||||
|                             global handler (by default, the static handlers). |  | ||||||
|         xmpp             -- The main SleekXMPP object. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         set_node_handler -- Assign a handler to a JID/node combination. |  | ||||||
|         del_node_handler -- Remove a handler from a JID/node combination. |  | ||||||
|         get_info         -- Retrieve disco#info data, locally or remote. |  | ||||||
|         get_items        -- Retrieve disco#items data, locally or remote. |  | ||||||
|         set_identities   -- |  | ||||||
|         set_features     -- |  | ||||||
|         set_items        -- |  | ||||||
|         del_items        -- |  | ||||||
|         del_identity     -- |  | ||||||
|         del_feature      -- |  | ||||||
|         del_item         -- |  | ||||||
|         add_identity     -- |  | ||||||
|         add_feature      -- |  | ||||||
|         add_item         -- |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         """ |  | ||||||
|         Start the XEP-0030 plugin. |  | ||||||
|         """ |  | ||||||
|         self.xep = '0030' |  | ||||||
|         self.description = 'Service Discovery' |  | ||||||
|         self.stanza = sleekxmpp.plugins.xep_0030.stanza |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('Disco Info', |  | ||||||
|                          StanzaPath('iq/disco_info'), |  | ||||||
|                          self._handle_disco_info)) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('Disco Items', |  | ||||||
|                          StanzaPath('iq/disco_items'), |  | ||||||
|                          self._handle_disco_items)) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, DiscoInfo) |  | ||||||
|         register_stanza_plugin(Iq, DiscoItems) |  | ||||||
|  |  | ||||||
|         self.static = StaticDisco(self.xmpp) |  | ||||||
|  |  | ||||||
|         self._disco_ops = ['get_info', 'set_identities', 'set_features', |  | ||||||
|                            'get_items', 'set_items', 'del_items', |  | ||||||
|                            'add_identity', 'del_identity', 'add_feature', |  | ||||||
|                            'del_feature', 'add_item', 'del_item', |  | ||||||
|                            'del_identities', 'del_features'] |  | ||||||
|         self.default_handlers = {} |  | ||||||
|         self._handlers = {} |  | ||||||
|         for op in self._disco_ops: |  | ||||||
|             self._add_disco_op(op, getattr(self.static, op)) |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         """Handle cross-plugin dependencies.""" |  | ||||||
|         base_plugin.post_init(self) |  | ||||||
|         if 'xep_0059' in self.xmpp.plugin: |  | ||||||
|             register_stanza_plugin(DiscoItems, |  | ||||||
|                                    self.xmpp['xep_0059'].stanza.Set) |  | ||||||
|  |  | ||||||
|     def _add_disco_op(self, op, default_handler): |  | ||||||
|         self.default_handlers[op] = default_handler |  | ||||||
|         self._handlers[op] = {'global': default_handler, |  | ||||||
|                               'jid': {}, |  | ||||||
|                               'node': {}} |  | ||||||
|  |  | ||||||
|     def set_node_handler(self, htype, jid=None, node=None, handler=None): |  | ||||||
|         """ |  | ||||||
|         Add a node handler for the given hierarchy level and |  | ||||||
|         handler type. |  | ||||||
|  |  | ||||||
|         Node handlers are ordered in a hierarchy where the |  | ||||||
|         most specific handler is executed. Thus, a fallback, |  | ||||||
|         global handler can be used for the majority of cases |  | ||||||
|         with a few node specific handler that override the |  | ||||||
|         global behavior. |  | ||||||
|  |  | ||||||
|         Node handler hierarchy: |  | ||||||
|             JID   | Node  | Level |  | ||||||
|             --------------------- |  | ||||||
|             None  | None  | Global |  | ||||||
|             Given | None  | All nodes for the JID |  | ||||||
|             None  | Given | Node on self.xmpp.boundjid |  | ||||||
|             Given | Given | A single node |  | ||||||
|  |  | ||||||
|         Handler types: |  | ||||||
|             get_info |  | ||||||
|             get_items |  | ||||||
|             set_identities |  | ||||||
|             set_features |  | ||||||
|             set_items |  | ||||||
|             del_items |  | ||||||
|             del_identities |  | ||||||
|             del_identity |  | ||||||
|             del_feature |  | ||||||
|             del_features |  | ||||||
|             del_item |  | ||||||
|             add_identity |  | ||||||
|             add_feature |  | ||||||
|             add_item |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             htype   -- The operation provided by the handler. |  | ||||||
|             jid     -- The JID the handler applies to. May be narrowed |  | ||||||
|                        further if a node is given. |  | ||||||
|             node    -- The particular node the handler is for. If no JID |  | ||||||
|                        is given, then the self.xmpp.boundjid.full is |  | ||||||
|                        assumed. |  | ||||||
|             handler -- The handler function to use. |  | ||||||
|         """ |  | ||||||
|         if htype not in self._disco_ops: |  | ||||||
|             return |  | ||||||
|         if jid is None and node is None: |  | ||||||
|             self._handlers[htype]['global'] = handler |  | ||||||
|         elif node is None: |  | ||||||
|             self._handlers[htype]['jid'][jid] = handler |  | ||||||
|         elif jid is None: |  | ||||||
|             if self.xmpp.is_component: |  | ||||||
|                 jid = self.xmpp.boundjid.full |  | ||||||
|             else: |  | ||||||
|                 jid = self.xmpp.boundjid.bare |  | ||||||
|             self._handlers[htype]['node'][(jid, node)] = handler |  | ||||||
|         else: |  | ||||||
|             self._handlers[htype]['node'][(jid, node)] = handler |  | ||||||
|  |  | ||||||
|     def del_node_handler(self, htype, jid, node): |  | ||||||
|         """ |  | ||||||
|         Remove a handler type for a JID and node combination. |  | ||||||
|  |  | ||||||
|         The next handler in the hierarchy will be used if one |  | ||||||
|         exists. If removing the global handler, make sure that |  | ||||||
|         other handlers exist to process existing nodes. |  | ||||||
|  |  | ||||||
|         Node handler hierarchy: |  | ||||||
|             JID   | Node  | Level |  | ||||||
|             --------------------- |  | ||||||
|             None  | None  | Global |  | ||||||
|             Given | None  | All nodes for the JID |  | ||||||
|             None  | Given | Node on self.xmpp.boundjid |  | ||||||
|             Given | Given | A single node |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             htype -- The type of handler to remove. |  | ||||||
|             jid   -- The JID from which to remove the handler. |  | ||||||
|             node  -- The node from which to remove the handler. |  | ||||||
|         """ |  | ||||||
|         self.set_node_handler(htype, jid, node, None) |  | ||||||
|  |  | ||||||
|     def restore_defaults(self, jid=None, node=None, handlers=None): |  | ||||||
|         """ |  | ||||||
|         Change all or some of a node's handlers to the default |  | ||||||
|         handlers. Useful for manually overriding the contents |  | ||||||
|         of a node that would otherwise be handled by a JID level |  | ||||||
|         or global level dynamic handler. |  | ||||||
|  |  | ||||||
|         The default is to use the built-in static handlers, but that |  | ||||||
|         may be changed by modifying self.default_handlers. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid      -- The JID owning the node to modify. |  | ||||||
|             node     -- The node to change to using static handlers. |  | ||||||
|             handlers -- Optional list of handlers to change to the |  | ||||||
|                         default version. If provided, only these |  | ||||||
|                         handlers will be changed. Otherwise, all |  | ||||||
|                         handlers will use the default version. |  | ||||||
|         """ |  | ||||||
|         if handlers is None: |  | ||||||
|             handlers = self._disco_ops |  | ||||||
|         for op in handlers: |  | ||||||
|             self.del_node_handler(op, jid, node) |  | ||||||
|             self.set_node_handler(op, jid, node, self.default_handlers[op]) |  | ||||||
|  |  | ||||||
|     def get_info(self, jid=None, node=None, local=False, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Retrieve the disco#info results from a given JID/node combination. |  | ||||||
|  |  | ||||||
|         Info may be retrieved from both local resources and remote agents; |  | ||||||
|         the local parameter indicates if the information should be gathered |  | ||||||
|         by executing the local node handlers, or if a disco#info stanza |  | ||||||
|         must be generated and sent. |  | ||||||
|  |  | ||||||
|         If requesting items from a local JID/node, then only a DiscoInfo |  | ||||||
|         stanza will be returned. Otherwise, an Iq stanza will be returned. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid      -- Request info from this JID. |  | ||||||
|             node     -- The particular node to query. |  | ||||||
|             local    -- If true, then the query is for a JID/node |  | ||||||
|                         combination handled by this Sleek instance and |  | ||||||
|                         no stanzas need to be sent. |  | ||||||
|                         Otherwise, a disco stanza must be sent to the |  | ||||||
|                         remove JID to retrieve the info. |  | ||||||
|             ifrom    -- Specifiy the sender's JID. |  | ||||||
|             block    -- If true, block and wait for the stanzas' reply. |  | ||||||
|             timeout  -- The time in seconds to block while waiting for |  | ||||||
|                         a reply. If None, then wait indefinitely. The |  | ||||||
|                         timeout value is only used when block=True. |  | ||||||
|             callback -- Optional callback to execute when a reply is |  | ||||||
|                         received instead of blocking and waiting for |  | ||||||
|                         the reply. |  | ||||||
|         """ |  | ||||||
|         if local or jid is None: |  | ||||||
|             log.debug("Looking up local disco#info data " + \ |  | ||||||
|                       "for %s, node %s." % (jid, node)) |  | ||||||
|             info = self._run_node_handler('get_info', jid, node, kwargs) |  | ||||||
|             return self._fix_default_info(info) |  | ||||||
|  |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         # Check dfrom parameter for backwards compatibility |  | ||||||
|         iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) |  | ||||||
|         iq['to'] = jid |  | ||||||
|         iq['type'] = 'get' |  | ||||||
|         iq['disco_info']['node'] = node if node else '' |  | ||||||
|         return iq.send(timeout=kwargs.get('timeout', None), |  | ||||||
|                        block=kwargs.get('block', True), |  | ||||||
|                        callback=kwargs.get('callback', None)) |  | ||||||
|  |  | ||||||
|     def get_items(self, jid=None, node=None, local=False, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Retrieve the disco#items results from a given JID/node combination. |  | ||||||
|  |  | ||||||
|         Items may be retrieved from both local resources and remote agents; |  | ||||||
|         the local parameter indicates if the items should be gathered by |  | ||||||
|         executing the local node handlers, or if a disco#items stanza must |  | ||||||
|         be generated and sent. |  | ||||||
|  |  | ||||||
|         If requesting items from a local JID/node, then only a DiscoItems |  | ||||||
|         stanza will be returned. Otherwise, an Iq stanza will be returned. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid      -- Request info from this JID. |  | ||||||
|             node     -- The particular node to query. |  | ||||||
|             local    -- If true, then the query is for a JID/node |  | ||||||
|                         combination handled by this Sleek instance and |  | ||||||
|                         no stanzas need to be sent. |  | ||||||
|                         Otherwise, a disco stanza must be sent to the |  | ||||||
|                         remove JID to retrieve the items. |  | ||||||
|             ifrom    -- Specifiy the sender's JID. |  | ||||||
|             block    -- If true, block and wait for the stanzas' reply. |  | ||||||
|             timeout  -- The time in seconds to block while waiting for |  | ||||||
|                         a reply. If None, then wait indefinitely. |  | ||||||
|             callback -- Optional callback to execute when a reply is |  | ||||||
|                         received instead of blocking and waiting for |  | ||||||
|                         the reply. |  | ||||||
|             iterator -- If True, return a result set iterator using |  | ||||||
|                         the XEP-0059 plugin, if the plugin is loaded. |  | ||||||
|                         Otherwise the parameter is ignored. |  | ||||||
|         """ |  | ||||||
|         if local or jid is None: |  | ||||||
|             return self._run_node_handler('get_items', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         # Check dfrom parameter for backwards compatibility |  | ||||||
|         iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) |  | ||||||
|         iq['to'] = jid |  | ||||||
|         iq['type'] = 'get' |  | ||||||
|         iq['disco_items']['node'] = node if node else '' |  | ||||||
|         if kwargs.get('iterator', False) and self.xmpp['xep_0059']: |  | ||||||
|             return self.xmpp['xep_0059'].iterate(iq, 'disco_items') |  | ||||||
|         else: |  | ||||||
|             return iq.send(timeout=kwargs.get('timeout', None), |  | ||||||
|                            block=kwargs.get('block', True), |  | ||||||
|                            callback=kwargs.get('callback', None)) |  | ||||||
|  |  | ||||||
|     def set_items(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Set or replace all items for the specified JID/node combination. |  | ||||||
|  |  | ||||||
|         The given items must be in a list or set where each item is a |  | ||||||
|         tuple of the form: (jid, node, name). |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid   -- The JID to modify. |  | ||||||
|             node  -- Optional node to modify. |  | ||||||
|             items -- A series of items in tuple format. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('set_items', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def del_items(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Remove all items from the given JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid  -- The JID to modify. |  | ||||||
|             node -- Optional node to modify. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('del_items', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def add_item(self, jid='', name='', node=None, subnode='', ijid=None): |  | ||||||
|         """ |  | ||||||
|         Add a new item element to the given JID/node combination. |  | ||||||
|  |  | ||||||
|         Each item is required to have a JID, but may also specify |  | ||||||
|         a node value to reference non-addressable entities. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid  -- The JID for the item. |  | ||||||
|             name  -- Optional name for the item. |  | ||||||
|             node  -- The node to modify. |  | ||||||
|             subnode -- Optional node for the item. |  | ||||||
|             ijid   -- The JID to modify. |  | ||||||
|         """ |  | ||||||
|         if not jid: |  | ||||||
|             jid = self.xmpp.boundjid.full |  | ||||||
|         kwargs = {'ijid': jid, |  | ||||||
|                   'name': name, |  | ||||||
|                   'inode': subnode} |  | ||||||
|         self._run_node_handler('add_item', ijid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def del_item(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Remove a single item from the given JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid   -- The JID to modify. |  | ||||||
|             node  -- The node to modify. |  | ||||||
|             ijid  -- The item's JID. |  | ||||||
|             inode -- The item's node. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('del_item', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def add_identity(self, category='', itype='', name='', |  | ||||||
|                      node=None, jid=None, lang=None): |  | ||||||
|         """ |  | ||||||
|         Add a new identity to the given JID/node combination. |  | ||||||
|  |  | ||||||
|         Each identity must be unique in terms of all four identity |  | ||||||
|         components: category, type, name, and language. |  | ||||||
|  |  | ||||||
|         Multiple, identical category/type pairs are allowed only |  | ||||||
|         if the xml:lang values are different. Likewise, multiple |  | ||||||
|         category/type/xml:lang pairs are allowed so long as the |  | ||||||
|         names are different. A category and type is always required. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             category -- The identity's category. |  | ||||||
|             itype    -- The identity's type. |  | ||||||
|             name     -- Optional name for the identity. |  | ||||||
|             lang     -- Optional two-letter language code. |  | ||||||
|             node     -- The node to modify. |  | ||||||
|             jid      -- The JID to modify. |  | ||||||
|         """ |  | ||||||
|         kwargs = {'category': category, |  | ||||||
|                   'itype': itype, |  | ||||||
|                   'name': name, |  | ||||||
|                   'lang': lang} |  | ||||||
|         self._run_node_handler('add_identity', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def add_feature(self, feature, node=None, jid=None): |  | ||||||
|         """ |  | ||||||
|         Add a feature to a JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             feature -- The namespace of the supported feature. |  | ||||||
|             node    -- The node to modify. |  | ||||||
|             jid     -- The JID to modify. |  | ||||||
|         """ |  | ||||||
|         kwargs = {'feature': feature} |  | ||||||
|         self._run_node_handler('add_feature', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def del_identity(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Remove an identity from the given JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid      -- The JID to modify. |  | ||||||
|             node     -- The node to modify. |  | ||||||
|             category -- The identity's category. |  | ||||||
|             itype    -- The identity's type value. |  | ||||||
|             name     -- Optional, human readable name for the identity. |  | ||||||
|             lang     -- Optional, the identity's xml:lang value. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('del_identity', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def del_feature(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Remove a feature from a given JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid     -- The JID to modify. |  | ||||||
|             node    -- The node to modify. |  | ||||||
|             feature -- The feature's namespace. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('del_feature', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def set_identities(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Add or replace all identities for the given JID/node combination. |  | ||||||
|  |  | ||||||
|         The identities must be in a set where each identity is a tuple |  | ||||||
|         of the form: (category, type, lang, name) |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid        -- The JID to modify. |  | ||||||
|             node       -- The node to modify. |  | ||||||
|             identities -- A set of identities in tuple form. |  | ||||||
|             lang       -- Optional, xml:lang value. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('set_identities', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def del_identities(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Remove all identities for a JID/node combination. |  | ||||||
|  |  | ||||||
|         If a language is specified, only identities using that |  | ||||||
|         language will be removed. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid  -- The JID to modify. |  | ||||||
|             node -- The node to modify. |  | ||||||
|             lang -- Optional. If given, only remove identities |  | ||||||
|                     using this xml:lang value. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('del_identities', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def set_features(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Add or replace the set of supported features |  | ||||||
|         for a JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid      -- The JID to modify. |  | ||||||
|             node     -- The node to modify. |  | ||||||
|             features -- The new set of supported features. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('set_features', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def del_features(self, jid=None, node=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Remove all features from a JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid  -- The JID to modify. |  | ||||||
|             node -- The node to modify. |  | ||||||
|         """ |  | ||||||
|         self._run_node_handler('del_features', jid, node, kwargs) |  | ||||||
|  |  | ||||||
|     def _run_node_handler(self, htype, jid, node, data={}): |  | ||||||
|         """ |  | ||||||
|         Execute the most specific node handler for the given |  | ||||||
|         JID/node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             htype -- The handler type to execute. |  | ||||||
|             jid   -- The JID requested. |  | ||||||
|             node  -- The node requested. |  | ||||||
|             data  -- Optional, custom data to pass to the handler. |  | ||||||
|         """ |  | ||||||
|         if jid is None: |  | ||||||
|             if self.xmpp.is_component: |  | ||||||
|                 jid = self.xmpp.boundjid.full |  | ||||||
|             else: |  | ||||||
|                 jid = self.xmpp.boundjid.bare |  | ||||||
|         if node is None: |  | ||||||
|             node = '' |  | ||||||
|  |  | ||||||
|         if self._handlers[htype]['node'].get((jid, node), False): |  | ||||||
|             return self._handlers[htype]['node'][(jid, node)](jid, node, data) |  | ||||||
|         elif self._handlers[htype]['jid'].get(jid, False): |  | ||||||
|             return self._handlers[htype]['jid'][jid](jid, node, data) |  | ||||||
|         elif self._handlers[htype]['global']: |  | ||||||
|             return self._handlers[htype]['global'](jid, node, data) |  | ||||||
|         else: |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|     def _handle_disco_info(self, iq): |  | ||||||
|         """ |  | ||||||
|         Process an incoming disco#info stanza. If it is a get |  | ||||||
|         request, find and return the appropriate identities |  | ||||||
|         and features. If it is an info result, fire the |  | ||||||
|         disco_info event. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The incoming disco#items stanza. |  | ||||||
|         """ |  | ||||||
|         if iq['type'] == 'get': |  | ||||||
|             log.debug("Received disco info query from " + \ |  | ||||||
|                       "<%s> to <%s>." % (iq['from'], iq['to'])) |  | ||||||
|             if self.xmpp.is_component: |  | ||||||
|                 jid = iq['to'].full |  | ||||||
|             else: |  | ||||||
|                 jid = iq['to'].bare |  | ||||||
|             info = self._run_node_handler('get_info', |  | ||||||
|                                           jid, |  | ||||||
|                                           iq['disco_info']['node'], |  | ||||||
|                                           iq) |  | ||||||
|             iq.reply() |  | ||||||
|             if info: |  | ||||||
|                 info = self._fix_default_info(info) |  | ||||||
|                 iq.set_payload(info.xml) |  | ||||||
|             iq.send() |  | ||||||
|         elif iq['type'] == 'result': |  | ||||||
|             log.debug("Received disco info result from" + \ |  | ||||||
|                       "%s to %s." % (iq['from'], iq['to'])) |  | ||||||
|             self.xmpp.event('disco_info', iq) |  | ||||||
|  |  | ||||||
|     def _handle_disco_items(self, iq): |  | ||||||
|         """ |  | ||||||
|         Process an incoming disco#items stanza. If it is a get |  | ||||||
|         request, find and return the appropriate items. If it |  | ||||||
|         is an items result, fire the disco_items event. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The incoming disco#items stanza. |  | ||||||
|         """ |  | ||||||
|         if iq['type'] == 'get': |  | ||||||
|             log.debug("Received disco items query from " + \ |  | ||||||
|                       "<%s> to <%s>." % (iq['from'], iq['to'])) |  | ||||||
|             if self.xmpp.is_component: |  | ||||||
|                 jid = iq['to'].full |  | ||||||
|             else: |  | ||||||
|                 jid = iq['to'].bare |  | ||||||
|             items = self._run_node_handler('get_items', |  | ||||||
|                                           jid, |  | ||||||
|                                           iq['disco_items']['node']) |  | ||||||
|             iq.reply() |  | ||||||
|             if items: |  | ||||||
|                 iq.set_payload(items.xml) |  | ||||||
|             iq.send() |  | ||||||
|         elif iq['type'] == 'result': |  | ||||||
|             log.debug("Received disco items result from" + \ |  | ||||||
|                       "%s to %s." % (iq['from'], iq['to'])) |  | ||||||
|             self.xmpp.event('disco_items', iq) |  | ||||||
|  |  | ||||||
|     def _fix_default_info(self, info): |  | ||||||
|         """ |  | ||||||
|         Disco#info results for a JID are required to include at least |  | ||||||
|         one identity and feature. As a default, if no other identity is |  | ||||||
|         provided, SleekXMPP will use either the generic component or the |  | ||||||
|         bot client identity. A the standard disco#info feature will also be |  | ||||||
|         added if no features are provided. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             info -- The disco#info quest (not the full Iq stanza) to modify. |  | ||||||
|         """ |  | ||||||
|         if not info['node']: |  | ||||||
|             if not info['identities']: |  | ||||||
|                 if self.xmpp.is_component: |  | ||||||
|                     log.debug("No identity found for this entity." + \ |  | ||||||
|                               "Using default component identity.") |  | ||||||
|                     info.add_identity('component', 'generic') |  | ||||||
|                 else: |  | ||||||
|                     log.debug("No identity found for this entity." + \ |  | ||||||
|                               "Using default client identity.") |  | ||||||
|                     info.add_identity('client', 'bot') |  | ||||||
|             if not info['features']: |  | ||||||
|                 log.debug("No features found for this entity." + \ |  | ||||||
|                           "Using default disco#info feature.") |  | ||||||
|                 info.add_feature(info.namespace) |  | ||||||
|         return info |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Retain some backwards compatibility |  | ||||||
| xep_0030.getInfo = xep_0030.get_info |  | ||||||
| xep_0030.getItems = xep_0030.get_items |  | ||||||
| xep_0030.make_static = xep_0030.restore_defaults |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0030.stanza.info import DiscoInfo |  | ||||||
| from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems |  | ||||||
| @@ -1,262 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DiscoInfo(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XMPP allows for users and agents to find the identities and features |  | ||||||
|     supported by other entities in the XMPP network through service discovery, |  | ||||||
|     or "disco". In particular, the "disco#info" query type for <iq> stanzas is |  | ||||||
|     used to request the list of identities and features offered by a JID. |  | ||||||
|  |  | ||||||
|     An identity is a combination of a category and type, such as the 'client' |  | ||||||
|     category with a type of 'pc' to indicate the agent is a human operated |  | ||||||
|     client with a GUI, or a category of 'gateway' with a type of 'aim' to |  | ||||||
|     identify the agent as a gateway for the legacy AIM protocol. See |  | ||||||
|     <http://xmpp.org/registrar/disco-categories.html> for a full list of |  | ||||||
|     accepted category and type combinations. |  | ||||||
|  |  | ||||||
|     Features are simply a set of the namespaces that identify the supported |  | ||||||
|     features. For example, a client that supports service discovery will |  | ||||||
|     include the feature 'http://jabber.org/protocol/disco#info'. |  | ||||||
|  |  | ||||||
|     Since clients and components may operate in several roles at once, identity |  | ||||||
|     and feature information may be grouped into "nodes". If one were to write |  | ||||||
|     all of the identities and features used by a client, then node names would |  | ||||||
|     be like section headings. |  | ||||||
|  |  | ||||||
|     Example disco#info stanzas: |  | ||||||
|         <iq type="get"> |  | ||||||
|           <query xmlns="http://jabber.org/protocol/disco#info" /> |  | ||||||
|         </iq> |  | ||||||
|  |  | ||||||
|         <iq type="result"> |  | ||||||
|           <query xmlns="http://jabber.org/protocol/disco#info"> |  | ||||||
|             <identity category="client" type="bot" name="SleekXMPP Bot" /> |  | ||||||
|             <feature var="http://jabber.org/protocol/disco#info" /> |  | ||||||
|             <feature var="jabber:x:data" /> |  | ||||||
|             <feature var="urn:xmpp:ping" /> |  | ||||||
|           </query> |  | ||||||
|         </iq> |  | ||||||
|  |  | ||||||
|     Stanza Interface: |  | ||||||
|         node       -- The name of the node to either |  | ||||||
|                       query or return info from. |  | ||||||
|         identities -- A set of 4-tuples, where each tuple contains |  | ||||||
|                       the category, type, xml:lang, and name |  | ||||||
|                       of an identity. |  | ||||||
|         features   -- A set of namespaces for features. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         add_identity   -- Add a new, single identity. |  | ||||||
|         del_identity   -- Remove a single identity. |  | ||||||
|         get_identities -- Return all identities in tuple form. |  | ||||||
|         set_identities -- Use multiple identities, each given in tuple form. |  | ||||||
|         del_identities -- Remove all identities. |  | ||||||
|         add_feature    -- Add a single feature. |  | ||||||
|         del_feature    -- Remove a single feature. |  | ||||||
|         get_features   -- Return a list of all features. |  | ||||||
|         set_features   -- Use a given list of features. |  | ||||||
|         del_features   -- Remove all features. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'query' |  | ||||||
|     namespace = 'http://jabber.org/protocol/disco#info' |  | ||||||
|     plugin_attrib = 'disco_info' |  | ||||||
|     interfaces = set(('node', 'features', 'identities')) |  | ||||||
|     lang_interfaces = set(('identities',)) |  | ||||||
|  |  | ||||||
|     # Cache identities and features |  | ||||||
|     _identities = set() |  | ||||||
|     _features = set() |  | ||||||
|  |  | ||||||
|     def setup(self, xml=None): |  | ||||||
|         """ |  | ||||||
|         Populate the stanza object using an optional XML object. |  | ||||||
|  |  | ||||||
|         Overrides ElementBase.setup |  | ||||||
|  |  | ||||||
|         Caches identity and feature information. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xml -- Use an existing XML object for the stanza's values. |  | ||||||
|         """ |  | ||||||
|         ElementBase.setup(self, xml) |  | ||||||
|  |  | ||||||
|         self._identities = set([id[0:3] for id in self['identities']]) |  | ||||||
|         self._features = self['features'] |  | ||||||
|  |  | ||||||
|     def add_identity(self, category, itype, name=None, lang=None): |  | ||||||
|         """ |  | ||||||
|         Add a new identity element. Each identity must be unique |  | ||||||
|         in terms of all four identity components. |  | ||||||
|  |  | ||||||
|         Multiple, identical category/type pairs are allowed only |  | ||||||
|         if the xml:lang values are different. Likewise, multiple |  | ||||||
|         category/type/xml:lang pairs are allowed so long as the names |  | ||||||
|         are different. In any case, a category and type are required. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             category -- The general category to which the agent belongs. |  | ||||||
|             itype    -- A more specific designation with the category. |  | ||||||
|             name     -- Optional human readable name for this identity. |  | ||||||
|             lang     -- Optional standard xml:lang value. |  | ||||||
|         """ |  | ||||||
|         identity = (category, itype, lang) |  | ||||||
|         if identity not in self._identities: |  | ||||||
|             self._identities.add(identity) |  | ||||||
|             id_xml = ET.Element('{%s}identity' % self.namespace) |  | ||||||
|             id_xml.attrib['category'] = category |  | ||||||
|             id_xml.attrib['type'] = itype |  | ||||||
|             if lang: |  | ||||||
|                 id_xml.attrib['{%s}lang' % self.xml_ns] = lang |  | ||||||
|             if name: |  | ||||||
|                 id_xml.attrib['name'] = name |  | ||||||
|             self.xml.append(id_xml) |  | ||||||
|             return True |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def del_identity(self, category, itype, name=None, lang=None): |  | ||||||
|         """ |  | ||||||
|         Remove a given identity. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             category -- The general category to which the agent belonged. |  | ||||||
|             itype    -- A more specific designation with the category. |  | ||||||
|             name     -- Optional human readable name for this identity. |  | ||||||
|             lang     -- Optional, standard xml:lang value. |  | ||||||
|         """ |  | ||||||
|         identity = (category, itype, lang) |  | ||||||
|         if identity in self._identities: |  | ||||||
|             self._identities.remove(identity) |  | ||||||
|             for id_xml in self.findall('{%s}identity' % self.namespace): |  | ||||||
|                 id = (id_xml.attrib['category'], |  | ||||||
|                       id_xml.attrib['type'], |  | ||||||
|                       id_xml.attrib.get('{%s}lang' % self.xml_ns, None)) |  | ||||||
|                 if id == identity: |  | ||||||
|                     self.xml.remove(id_xml) |  | ||||||
|                     return True |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def get_identities(self, lang=None): |  | ||||||
|         """ |  | ||||||
|         Return a set of all identities in tuple form as so: |  | ||||||
|             (category, type, lang, name) |  | ||||||
|  |  | ||||||
|         If a language was specified, only return identities using |  | ||||||
|         that language. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             lang -- Optional, standard xml:lang value. |  | ||||||
|         """ |  | ||||||
|         identities = set() |  | ||||||
|         for id_xml in self.findall('{%s}identity' % self.namespace): |  | ||||||
|             xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None) |  | ||||||
|             if lang is None or xml_lang == lang: |  | ||||||
|                 identities.add(( |  | ||||||
|                     id_xml.attrib['category'], |  | ||||||
|                     id_xml.attrib['type'], |  | ||||||
|                     id_xml.attrib.get('{%s}lang' % self.xml_ns, None), |  | ||||||
|                     id_xml.attrib.get('name', None))) |  | ||||||
|         return identities |  | ||||||
|  |  | ||||||
|     def set_identities(self, identities, lang=None): |  | ||||||
|         """ |  | ||||||
|         Add or replace all identities. The identities must be a in set |  | ||||||
|         where each identity is a tuple of the form: |  | ||||||
|             (category, type, lang, name) |  | ||||||
|  |  | ||||||
|         If a language is specifified, any identities using that language |  | ||||||
|         will be removed to be replaced with the given identities. |  | ||||||
|  |  | ||||||
|         NOTE: An identity's language will not be changed regardless of |  | ||||||
|               the value of lang. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             identities -- A set of identities in tuple form. |  | ||||||
|             lang       -- Optional, standard xml:lang value. |  | ||||||
|         """ |  | ||||||
|         self.del_identities(lang) |  | ||||||
|         for identity in identities: |  | ||||||
|             category, itype, lang, name = identity |  | ||||||
|             self.add_identity(category, itype, name, lang) |  | ||||||
|  |  | ||||||
|     def del_identities(self, lang=None): |  | ||||||
|         """ |  | ||||||
|         Remove all identities. If a language was specified, only |  | ||||||
|         remove identities using that language. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             lang -- Optional, standard xml:lang value. |  | ||||||
|         """ |  | ||||||
|         for id_xml in self.findall('{%s}identity' % self.namespace): |  | ||||||
|             if lang is None: |  | ||||||
|                 self.xml.remove(id_xml) |  | ||||||
|             elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang: |  | ||||||
|                 self._identities.remove(( |  | ||||||
|                     id_xml.attrib['category'], |  | ||||||
|                     id_xml.attrib['type'], |  | ||||||
|                     id_xml.attrib.get('{%s}lang' % self.xml_ns, None))) |  | ||||||
|                 self.xml.remove(id_xml) |  | ||||||
|  |  | ||||||
|     def add_feature(self, feature): |  | ||||||
|         """ |  | ||||||
|         Add a single, new feature. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             feature -- The namespace of the supported feature. |  | ||||||
|         """ |  | ||||||
|         if feature not in self._features: |  | ||||||
|             self._features.add(feature) |  | ||||||
|             feature_xml = ET.Element('{%s}feature' % self.namespace) |  | ||||||
|             feature_xml.attrib['var'] = feature |  | ||||||
|             self.xml.append(feature_xml) |  | ||||||
|             return True |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def del_feature(self, feature): |  | ||||||
|         """ |  | ||||||
|         Remove a single feature. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             feature -- The namespace of the removed feature. |  | ||||||
|         """ |  | ||||||
|         if feature in self._features: |  | ||||||
|             self._features.remove(feature) |  | ||||||
|             for feature_xml in self.findall('{%s}feature' % self.namespace): |  | ||||||
|                 if feature_xml.attrib['var'] == feature: |  | ||||||
|                     self.xml.remove(feature_xml) |  | ||||||
|                     return True |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def get_features(self): |  | ||||||
|         """Return the set of all supported features.""" |  | ||||||
|         features = set() |  | ||||||
|         for feature_xml in self.findall('{%s}feature' % self.namespace): |  | ||||||
|             features.add(feature_xml.attrib['var']) |  | ||||||
|         return features |  | ||||||
|  |  | ||||||
|     def set_features(self, features): |  | ||||||
|         """ |  | ||||||
|         Add or replace the set of supported features. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             features -- The new set of supported features. |  | ||||||
|         """ |  | ||||||
|         self.del_features() |  | ||||||
|         for feature in features: |  | ||||||
|             self.add_feature(feature) |  | ||||||
|  |  | ||||||
|     def del_features(self): |  | ||||||
|         """Remove all features.""" |  | ||||||
|         self._features = set() |  | ||||||
|         for feature_xml in self.findall('{%s}feature' % self.namespace): |  | ||||||
|             self.xml.remove(feature_xml) |  | ||||||
| @@ -1,136 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DiscoItems(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     Example disco#items stanzas: |  | ||||||
|         <iq type="get"> |  | ||||||
|           <query xmlns="http://jabber.org/protocol/disco#items" /> |  | ||||||
|         </iq> |  | ||||||
|  |  | ||||||
|         <iq type="result"> |  | ||||||
|           <query xmlns="http://jabber.org/protocol/disco#items"> |  | ||||||
|             <item jid="chat.example.com" |  | ||||||
|                   node="xmppdev" |  | ||||||
|                   name="XMPP Dev" /> |  | ||||||
|             <item jid="chat.example.com" |  | ||||||
|                   node="sleekdev" |  | ||||||
|                   name="SleekXMPP Dev" /> |  | ||||||
|           </query> |  | ||||||
|         </iq> |  | ||||||
|  |  | ||||||
|     Stanza Interface: |  | ||||||
|         node  -- The name of the node to either |  | ||||||
|                  query or return info from. |  | ||||||
|         items -- A list of 3-tuples, where each tuple contains |  | ||||||
|                  the JID, node, and name of an item. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         add_item  -- Add a single new item. |  | ||||||
|         del_item  -- Remove a single item. |  | ||||||
|         get_items -- Return all items. |  | ||||||
|         set_items -- Set or replace all items. |  | ||||||
|         del_items -- Remove all items. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'query' |  | ||||||
|     namespace = 'http://jabber.org/protocol/disco#items' |  | ||||||
|     plugin_attrib = 'disco_items' |  | ||||||
|     interfaces = set(('node', 'items')) |  | ||||||
|  |  | ||||||
|     # Cache items |  | ||||||
|     _items = set() |  | ||||||
|  |  | ||||||
|     def setup(self, xml=None): |  | ||||||
|         """ |  | ||||||
|         Populate the stanza object using an optional XML object. |  | ||||||
|  |  | ||||||
|         Overrides ElementBase.setup |  | ||||||
|  |  | ||||||
|         Caches item information. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xml -- Use an existing XML object for the stanza's values. |  | ||||||
|         """ |  | ||||||
|         ElementBase.setup(self, xml) |  | ||||||
|         self._items = set([item[0:2] for item in self['items']]) |  | ||||||
|  |  | ||||||
|     def add_item(self, jid, node=None, name=None): |  | ||||||
|         """ |  | ||||||
|         Add a new item element. Each item is required to have a |  | ||||||
|         JID, but may also specify a node value to reference |  | ||||||
|         non-addressable entitities. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid  -- The JID for the item. |  | ||||||
|             node -- Optional additional information to reference |  | ||||||
|                     non-addressable items. |  | ||||||
|             name -- Optional human readable name for the item. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) not in self._items: |  | ||||||
|             self._items.add((jid, node)) |  | ||||||
|             item_xml = ET.Element('{%s}item' % self.namespace) |  | ||||||
|             item_xml.attrib['jid'] = jid |  | ||||||
|             if name: |  | ||||||
|                 item_xml.attrib['name'] = name |  | ||||||
|             if node: |  | ||||||
|                 item_xml.attrib['node'] = node |  | ||||||
|             self.xml.append(item_xml) |  | ||||||
|             return True |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def del_item(self, jid, node=None): |  | ||||||
|         """ |  | ||||||
|         Remove a single item. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid  -- JID of the item to remove. |  | ||||||
|             node -- Optional extra identifying information. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) in self._items: |  | ||||||
|             for item_xml in self.findall('{%s}item' % self.namespace): |  | ||||||
|                 item = (item_xml.attrib['jid'], |  | ||||||
|                         item_xml.attrib.get('node', None)) |  | ||||||
|                 if item == (jid, node): |  | ||||||
|                     self.xml.remove(item_xml) |  | ||||||
|                     return True |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|     def get_items(self): |  | ||||||
|         """Return all items.""" |  | ||||||
|         items = set() |  | ||||||
|         for item_xml in self.findall('{%s}item' % self.namespace): |  | ||||||
|             item = (item_xml.attrib['jid'], |  | ||||||
|                     item_xml.attrib.get('node'), |  | ||||||
|                     item_xml.attrib.get('name')) |  | ||||||
|             items.add(item) |  | ||||||
|         return items |  | ||||||
|  |  | ||||||
|     def set_items(self, items): |  | ||||||
|         """ |  | ||||||
|         Set or replace all items. The given items must be in a |  | ||||||
|         list or set where each item is a tuple of the form: |  | ||||||
|             (jid, node, name) |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             items -- A series of items in tuple format. |  | ||||||
|         """ |  | ||||||
|         self.del_items() |  | ||||||
|         for item in items: |  | ||||||
|             jid, node, name = item |  | ||||||
|             self.add_item(jid, node, name) |  | ||||||
|  |  | ||||||
|     def del_items(self): |  | ||||||
|         """Remove all items.""" |  | ||||||
|         self._items = set() |  | ||||||
|         for item_xml in self.findall('{%s}item' % self.namespace): |  | ||||||
|             self.xml.remove(item_xml) |  | ||||||
| @@ -1,265 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp import Iq |  | ||||||
| from sleekxmpp.exceptions import XMPPError |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.xmlstream.handler import Callback |  | ||||||
| from sleekxmpp.xmlstream.matcher import StanzaPath |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID |  | ||||||
| from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StaticDisco(object): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     While components will likely require fully dynamic handling |  | ||||||
|     of service discovery information, most clients and simple bots |  | ||||||
|     only need to manage a few disco nodes that will remain mostly |  | ||||||
|     static. |  | ||||||
|  |  | ||||||
|     StaticDisco provides a set of node handlers that will store |  | ||||||
|     static sets of disco info and items in memory. |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|         nodes -- A dictionary mapping (JID, node) tuples to a dict |  | ||||||
|                  containing a disco#info and a disco#items stanza. |  | ||||||
|         xmpp  -- The main SleekXMPP object. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, xmpp): |  | ||||||
|         """ |  | ||||||
|         Create a static disco interface. Sets of disco#info and |  | ||||||
|         disco#items are maintained for every given JID and node |  | ||||||
|         combination. These stanzas are used to store disco |  | ||||||
|         information in memory without any additional processing. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xmpp -- The main SleekXMPP object. |  | ||||||
|         """ |  | ||||||
|         self.nodes = {} |  | ||||||
|         self.xmpp = xmpp |  | ||||||
|  |  | ||||||
|     def add_node(self, jid=None, node=None): |  | ||||||
|         """ |  | ||||||
|         Create a new set of stanzas for the provided |  | ||||||
|         JID and node combination. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid  -- The JID that will own the new stanzas. |  | ||||||
|             node -- The node that will own the new stanzas. |  | ||||||
|         """ |  | ||||||
|         if jid is None: |  | ||||||
|             jid = self.xmpp.boundjid.full |  | ||||||
|         if node is None: |  | ||||||
|             node = '' |  | ||||||
|         if (jid, node) not in self.nodes: |  | ||||||
|             self.nodes[(jid, node)] = {'info': DiscoInfo(), |  | ||||||
|                                        'items': DiscoItems()} |  | ||||||
|             self.nodes[(jid, node)]['info']['node'] = node |  | ||||||
|             self.nodes[(jid, node)]['items']['node'] = node |  | ||||||
|  |  | ||||||
|     # ================================================================= |  | ||||||
|     # Node Handlers |  | ||||||
|     # |  | ||||||
|     # Each handler accepts three arguments: jid, node, and data. |  | ||||||
|     # The jid and node parameters together determine the set of |  | ||||||
|     # info and items stanzas that will be retrieved or added. |  | ||||||
|     # The data parameter is a dictionary with additional paramters |  | ||||||
|     # that will be passed to other calls. |  | ||||||
|  |  | ||||||
|     def get_info(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Return the stored info data for the requested JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter is not used. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) not in self.nodes: |  | ||||||
|             if not node: |  | ||||||
|                 return DiscoInfo() |  | ||||||
|             else: |  | ||||||
|                 raise XMPPError(condition='item-not-found') |  | ||||||
|         else: |  | ||||||
|             return self.nodes[(jid, node)]['info'] |  | ||||||
|  |  | ||||||
|     def del_info(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Reset the info stanza for a given JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter is not used. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) in self.nodes: |  | ||||||
|             self.nodes[(jid, node)]['info'] = DiscoInfo() |  | ||||||
|  |  | ||||||
|     def get_items(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Return the stored items data for the requested JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter is not used. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) not in self.nodes: |  | ||||||
|             if not node: |  | ||||||
|                 return DiscoInfo() |  | ||||||
|             else: |  | ||||||
|                 raise XMPPError(condition='item-not-found') |  | ||||||
|         else: |  | ||||||
|             return self.nodes[(jid, node)]['items'] |  | ||||||
|  |  | ||||||
|     def set_items(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Replace the stored items data for a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter may provided: |  | ||||||
|             items -- A set of items in tuple format. |  | ||||||
|         """ |  | ||||||
|         items = data.get('items', set()) |  | ||||||
|         self.add_node(jid, node) |  | ||||||
|         self.nodes[(jid, node)]['items']['items'] = items |  | ||||||
|  |  | ||||||
|     def del_items(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Reset the items stanza for a given JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter is not used. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) in self.nodes: |  | ||||||
|             self.nodes[(jid, node)]['items'] = DiscoItems() |  | ||||||
|  |  | ||||||
|     def add_identity(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Add a new identity to te JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter may provide: |  | ||||||
|             category -- The general category to which the agent belongs. |  | ||||||
|             itype    -- A more specific designation with the category. |  | ||||||
|             name     -- Optional human readable name for this identity. |  | ||||||
|             lang     -- Optional standard xml:lang value. |  | ||||||
|         """ |  | ||||||
|         self.add_node(jid, node) |  | ||||||
|         self.nodes[(jid, node)]['info'].add_identity( |  | ||||||
|                 data.get('category', ''), |  | ||||||
|                 data.get('itype', ''), |  | ||||||
|                 data.get('name', None), |  | ||||||
|                 data.get('lang', None)) |  | ||||||
|  |  | ||||||
|     def set_identities(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Add or replace all identities for a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter should include: |  | ||||||
|             identities -- A list of identities in tuple form: |  | ||||||
|                             (category, type, name, lang) |  | ||||||
|         """ |  | ||||||
|         identities = data.get('identities', set()) |  | ||||||
|         self.add_node(jid, node) |  | ||||||
|         self.nodes[(jid, node)]['info']['identities'] = identities |  | ||||||
|  |  | ||||||
|     def del_identity(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Remove an identity from a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter may provide: |  | ||||||
|             category -- The general category to which the agent belonged. |  | ||||||
|             itype    -- A more specific designation with the category. |  | ||||||
|             name     -- Optional human readable name for this identity. |  | ||||||
|             lang     -- Optional, standard xml:lang value. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) not in self.nodes: |  | ||||||
|             return |  | ||||||
|         self.nodes[(jid, node)]['info'].del_identity( |  | ||||||
|                 data.get('category', ''), |  | ||||||
|                 data.get('itype', ''), |  | ||||||
|                 data.get('name', None), |  | ||||||
|                 data.get('lang', None)) |  | ||||||
|  |  | ||||||
|     def del_identities(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Remove all identities from a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter is not used. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) not in self.nodes: |  | ||||||
|             return |  | ||||||
|         del self.nodes[(jid, node)]['info']['identities'] |  | ||||||
|  |  | ||||||
|     def add_feature(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Add a feature to a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter should include: |  | ||||||
|             feature -- The namespace of the supported feature. |  | ||||||
|         """ |  | ||||||
|         self.add_node(jid, node) |  | ||||||
|         self.nodes[(jid, node)]['info'].add_feature(data.get('feature', '')) |  | ||||||
|  |  | ||||||
|     def set_features(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Add or replace all features for a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter should include: |  | ||||||
|             features -- The new set of supported features. |  | ||||||
|         """ |  | ||||||
|         features = data.get('features', set()) |  | ||||||
|         self.add_node(jid, node) |  | ||||||
|         self.nodes[(jid, node)]['info']['features'] = features |  | ||||||
|  |  | ||||||
|     def del_feature(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Remove a feature from a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter should include: |  | ||||||
|             feature -- The namespace of the removed feature. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) not in self.nodes: |  | ||||||
|             return |  | ||||||
|         self.nodes[(jid, node)]['info'].del_feature(data.get('feature', '')) |  | ||||||
|  |  | ||||||
|     def del_features(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Remove all features from a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter is not used. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) not in self.nodes: |  | ||||||
|             return |  | ||||||
|         del self.nodes[(jid, node)]['info']['features'] |  | ||||||
|  |  | ||||||
|     def add_item(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Add an item to a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter may include: |  | ||||||
|             ijid  -- The JID for the item. |  | ||||||
|             inode -- Optional additional information to reference |  | ||||||
|                      non-addressable items. |  | ||||||
|             name  -- Optional human readable name for the item. |  | ||||||
|         """ |  | ||||||
|         self.add_node(jid, node) |  | ||||||
|         self.nodes[(jid, node)]['items'].add_item( |  | ||||||
|                 data.get('ijid', ''), |  | ||||||
|                 node=data.get('inode', ''), |  | ||||||
|                 name=data.get('name', '')) |  | ||||||
|  |  | ||||||
|     def del_item(self, jid, node, data): |  | ||||||
|         """ |  | ||||||
|         Remove an item from a JID/node combination. |  | ||||||
|  |  | ||||||
|         The data parameter may include: |  | ||||||
|             ijid  -- JID of the item to remove. |  | ||||||
|             inode -- Optional extra identifying information. |  | ||||||
|         """ |  | ||||||
|         if (jid, node) in self.nodes: |  | ||||||
|             self.nodes[(jid, node)]['items'].del_item( |  | ||||||
|                     data.get('ijid', ''), |  | ||||||
|                     node=data.get('inode', None)) |  | ||||||
| @@ -1,161 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from . import base |  | ||||||
| from .. xmlstream.handler.callback import Callback |  | ||||||
| from .. xmlstream.matcher.xpath import MatchXPath |  | ||||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID |  | ||||||
| from .. stanza.message import Message |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Addresses(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/address' |  | ||||||
| 	name = 'addresses' |  | ||||||
| 	plugin_attrib = 'addresses' |  | ||||||
| 	interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) |  | ||||||
|  |  | ||||||
| 	def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False): |  | ||||||
| 		address = Address(parent=self) |  | ||||||
| 		address['type'] = atype |  | ||||||
| 		address['jid'] = jid |  | ||||||
| 		address['node'] = node |  | ||||||
| 		address['uri'] = uri |  | ||||||
| 		address['desc'] = desc |  | ||||||
| 		address['delivered'] = delivered |  | ||||||
| 		return address |  | ||||||
|  |  | ||||||
| 	def getAddresses(self, atype=None): |  | ||||||
| 		addresses = [] |  | ||||||
| 		for addrXML in self.xml.findall('{%s}address' % Address.namespace): |  | ||||||
| 			# ElementTree 1.2.6 does not support [@attr='value'] in findall |  | ||||||
| 			if atype is None or addrXML.attrib.get('type') == atype: |  | ||||||
| 				addresses.append(Address(xml=addrXML, parent=None)) |  | ||||||
| 		return addresses |  | ||||||
|  |  | ||||||
| 	def setAddresses(self, addresses, set_type=None): |  | ||||||
| 		self.delAddresses(set_type) |  | ||||||
| 		for addr in addresses: |  | ||||||
| 			addr = dict(addr) |  | ||||||
| 			# Remap 'type' to 'atype' to match the add method |  | ||||||
| 			if set_type is not None: |  | ||||||
| 				addr['type'] = set_type |  | ||||||
| 			curr_type = addr.get('type', None) |  | ||||||
| 			if curr_type is not None: |  | ||||||
| 				del addr['type'] |  | ||||||
| 				addr['atype'] = curr_type |  | ||||||
| 			self.addAddress(**addr) |  | ||||||
|  |  | ||||||
| 	def delAddresses(self, atype=None): |  | ||||||
| 		if atype is None: |  | ||||||
| 			return |  | ||||||
| 		for addrXML in self.xml.findall('{%s}address' % Address.namespace): |  | ||||||
| 			# ElementTree 1.2.6 does not support [@attr='value'] in findall |  | ||||||
| 			if addrXML.attrib.get('type') == atype: |  | ||||||
| 				self.xml.remove(addrXML) |  | ||||||
|  |  | ||||||
| 	# -------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| 	def delBcc(self): |  | ||||||
| 		self.delAddresses('bcc') |  | ||||||
|  |  | ||||||
| 	def delCc(self): |  | ||||||
| 		self.delAddresses('cc') |  | ||||||
|  |  | ||||||
| 	def delNoreply(self): |  | ||||||
| 		self.delAddresses('noreply') |  | ||||||
|  |  | ||||||
| 	def delReplyroom(self): |  | ||||||
| 		self.delAddresses('replyroom') |  | ||||||
|  |  | ||||||
| 	def delReplyto(self): |  | ||||||
| 		self.delAddresses('replyto') |  | ||||||
|  |  | ||||||
| 	def delTo(self): |  | ||||||
| 		self.delAddresses('to') |  | ||||||
|  |  | ||||||
| 	# -------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| 	def getBcc(self): |  | ||||||
| 		return self.getAddresses('bcc') |  | ||||||
|  |  | ||||||
| 	def getCc(self): |  | ||||||
| 		return self.getAddresses('cc') |  | ||||||
|  |  | ||||||
| 	def getNoreply(self): |  | ||||||
| 		return self.getAddresses('noreply') |  | ||||||
|  |  | ||||||
| 	def getReplyroom(self): |  | ||||||
| 		return self.getAddresses('replyroom') |  | ||||||
|  |  | ||||||
| 	def getReplyto(self): |  | ||||||
| 		return self.getAddresses('replyto') |  | ||||||
|  |  | ||||||
| 	def getTo(self): |  | ||||||
| 		return self.getAddresses('to') |  | ||||||
|  |  | ||||||
| 	# -------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| 	def setBcc(self, addresses): |  | ||||||
| 		self.setAddresses(addresses, 'bcc') |  | ||||||
|  |  | ||||||
| 	def setCc(self, addresses): |  | ||||||
| 		self.setAddresses(addresses, 'cc') |  | ||||||
|  |  | ||||||
| 	def setNoreply(self, addresses): |  | ||||||
| 		self.setAddresses(addresses, 'noreply') |  | ||||||
|  |  | ||||||
| 	def setReplyroom(self, addresses): |  | ||||||
| 		self.setAddresses(addresses, 'replyroom') |  | ||||||
|  |  | ||||||
| 	def setReplyto(self, addresses): |  | ||||||
| 		self.setAddresses(addresses, 'replyto') |  | ||||||
|  |  | ||||||
| 	def setTo(self, addresses): |  | ||||||
| 		self.setAddresses(addresses, 'to') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Address(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/address' |  | ||||||
| 	name = 'address' |  | ||||||
| 	plugin_attrib = 'address' |  | ||||||
| 	interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri')) |  | ||||||
| 	address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) |  | ||||||
|  |  | ||||||
| 	def getDelivered(self): |  | ||||||
| 		return self.xml.attrib.get('delivered', False) |  | ||||||
|  |  | ||||||
| 	def setDelivered(self, delivered): |  | ||||||
| 		if delivered: |  | ||||||
| 			self.xml.attrib['delivered'] = "true" |  | ||||||
| 		else: |  | ||||||
| 			del self['delivered'] |  | ||||||
|  |  | ||||||
| 	def setUri(self, uri): |  | ||||||
| 		if uri: |  | ||||||
| 			del self['jid'] |  | ||||||
| 			del self['node'] |  | ||||||
| 			self.xml.attrib['uri'] = uri |  | ||||||
| 		elif 'uri' in self.xml.attrib: |  | ||||||
| 			del self.xml.attrib['uri'] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0033(base.base_plugin): |  | ||||||
| 	""" |  | ||||||
| 	XEP-0033: Extended Stanza Addressing |  | ||||||
| 	""" |  | ||||||
|  |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = '0033' |  | ||||||
| 		self.description = 'Extended Stanza Addressing' |  | ||||||
|  |  | ||||||
| 		registerStanzaPlugin(Message, Addresses) |  | ||||||
|  |  | ||||||
| 	def post_init(self): |  | ||||||
| 		base.base_plugin.post_init(self) |  | ||||||
| 		self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace) |  | ||||||
| @@ -1,353 +1,316 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library | 	SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz | 	Copyright (C) 2007  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. | 	This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. | 	SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  | 	it under the terms of the GNU General Public License as published by | ||||||
|  | 	the Free Software Foundation; either version 2 of the License, or | ||||||
|  | 	(at your option) any later version. | ||||||
|  |  | ||||||
|  | 	SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  | 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | 	GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | 	You should have received a copy of the GNU General Public License | ||||||
|  | 	along with SleekXMPP; if not, write to the Free Software | ||||||
|  | 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
| """ | """ | ||||||
| from __future__ import with_statement | from __future__ import with_statement | ||||||
| from . import base | from . import base | ||||||
| import logging | import logging | ||||||
| from xml.etree import cElementTree as ET | from xml.etree import cElementTree as ET | ||||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID | from .. xmlstream.stanzabase import ElementBase, JID | ||||||
| from .. stanza.presence import Presence | from .. stanza.presence import Presence | ||||||
| from .. xmlstream.handler.callback import Callback | from .. xmlstream.handler.callback import Callback | ||||||
| from .. xmlstream.matcher.xpath import MatchXPath | from .. xmlstream.matcher.xpath import MatchXPath | ||||||
| from .. xmlstream.matcher.xmlmask import MatchXMLMask | from .. xmlstream.matcher.xmlmask import MatchXMLMask | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MUCPresence(ElementBase): | class MUCPresence(ElementBase): | ||||||
|     name = 'x' | 	name = 'x' | ||||||
|     namespace = 'http://jabber.org/protocol/muc#user' | 	namespace = 'http://jabber.org/protocol/muc#user' | ||||||
|     plugin_attrib = 'muc' | 	plugin_attrib = 'muc' | ||||||
|     interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room')) | 	interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room')) | ||||||
|     affiliations = set(('', )) | 	affiliations = set(('', )) | ||||||
|     roles = set(('', )) | 	roles = set(('', )) | ||||||
|  |  | ||||||
|     def getXMLItem(self): | 	def getXMLItem(self): | ||||||
|         item = self.xml.find('{http://jabber.org/protocol/muc#user}item') | 		item = self.xml.find('{http://jabber.org/protocol/muc#user}item') | ||||||
|         if item is None: | 		if item is None: | ||||||
|             item = ET.Element('{http://jabber.org/protocol/muc#user}item') | 			item = ET.Element('{http://jabber.org/protocol/muc#user}item') | ||||||
|             self.xml.append(item) | 			self.xml.append(item) | ||||||
|         return item | 		return item | ||||||
|  |  | ||||||
|     def getAffiliation(self): | 	def getAffiliation(self): | ||||||
|         #TODO if no affilation, set it to the default and return default | 		#TODO if no affilation, set it to the default and return default | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         return item.get('affiliation', '') | 		return item.get('affiliation', '') | ||||||
|  | 	 | ||||||
|     def setAffiliation(self, value): | 	def setAffiliation(self, value): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         #TODO check for valid affiliation | 		#TODO check for valid affiliation | ||||||
|         item.attrib['affiliation'] = value | 		item.attrib['affiliation'] = value | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def delAffiliation(self): | 	def delAffiliation(self): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         #TODO set default affiliation | 		#TODO set default affiliation | ||||||
|         if 'affiliation' in item.attrib: del item.attrib['affiliation'] | 		if 'affiliation' in item.attrib: del item.attrib['affiliation'] | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def getJid(self): | 	def getJid(self): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         return JID(item.get('jid', '')) | 		return JID(item.get('jid', '')) | ||||||
|  | 	 | ||||||
|     def setJid(self, value): | 	def setJid(self, value): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         if not isinstance(value, str): | 		if not isinstance(value, str): | ||||||
|             value = str(value) | 			value = str(value) | ||||||
|         item.attrib['jid'] = value | 		item.attrib['jid'] = value | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def delJid(self): | 	def delJid(self): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         if 'jid' in item.attrib: del item.attrib['jid'] | 		if 'jid' in item.attrib: del item.attrib['jid'] | ||||||
|         return self | 		return self | ||||||
|  | 		 | ||||||
|     def getRole(self): | 	def getRole(self): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         #TODO get default role, set default role if none | 		#TODO get default role, set default role if none | ||||||
|         return item.get('role', '') | 		return item.get('role', '') | ||||||
|  | 	 | ||||||
|     def setRole(self, value): | 	def setRole(self, value): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         #TODO check for valid role | 		#TODO check for valid role | ||||||
|         item.attrib['role'] = value | 		item.attrib['role'] = value | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def delRole(self): | 	def delRole(self): | ||||||
|         item = self.getXMLItem() | 		item = self.getXMLItem() | ||||||
|         #TODO set default role | 		#TODO set default role | ||||||
|         if 'role' in item.attrib: del item.attrib['role'] | 		if 'role' in item.attrib: del item.attrib['role'] | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def getNick(self): | 	def getNick(self): | ||||||
|         return self.parent()['from'].resource | 		return self.parent()['from'].resource | ||||||
|  | 	 | ||||||
|     def getRoom(self): | 	def getRoom(self): | ||||||
|         return self.parent()['from'].bare | 		return self.parent()['from'].bare | ||||||
|  | 	 | ||||||
|     def setNick(self, value): | 	def setNick(self, value): | ||||||
|         log.warning("Cannot set nick through mucpresence plugin.") | 		logging.warning("Cannot set nick through mucpresence plugin.") | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def setRoom(self, value): | 	def setRoom(self, value): | ||||||
|         log.warning("Cannot set room through mucpresence plugin.") | 		logging.warning("Cannot set room through mucpresence plugin.") | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def delNick(self): | 	def delNick(self): | ||||||
|         log.warning("Cannot delete nick through mucpresence plugin.") | 		logging.warning("Cannot delete nick through mucpresence plugin.") | ||||||
|         return self | 		return self | ||||||
|  | 	 | ||||||
|     def delRoom(self): | 	def delRoom(self): | ||||||
|         log.warning("Cannot delete room through mucpresence plugin.") | 		logging.warning("Cannot delete room through mucpresence plugin.") | ||||||
|         return self | 		return self | ||||||
|  |  | ||||||
| class xep_0045(base.base_plugin): | class xep_0045(base.base_plugin): | ||||||
|     """ | 	""" | ||||||
|     Implements XEP-0045 Multi User Chat | 	Impliments XEP-0045 Multi User Chat | ||||||
|     """ | 	""" | ||||||
|  | 	 | ||||||
|  | 	def plugin_init(self): | ||||||
|  | 		self.rooms = {} | ||||||
|  | 		self.ourNicks = {} | ||||||
|  | 		self.xep = '0045' | ||||||
|  | 		self.description = 'Multi User Chat' | ||||||
|  | 		# load MUC support in presence stanzas | ||||||
|  | 		self.xmpp.stanzaPlugin(Presence, MUCPresence) | ||||||
|  | 		self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) | ||||||
|  | 		self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) | ||||||
|  | 	 | ||||||
|  | 	def handle_groupchat_presence(self, pr): | ||||||
|  | 		""" Handle a presence in a muc. | ||||||
|  | 		""" | ||||||
|  | 		if pr['muc']['room'] not in self.rooms.keys(): | ||||||
|  | 			return | ||||||
|  | 		entry = pr['muc'].getValues() | ||||||
|  | 		if pr['type'] == 'unavailable': | ||||||
|  | 			del self.rooms[entry['room']][entry['nick']] | ||||||
|  | 		else: | ||||||
|  | 			self.rooms[entry['room']][entry['nick']] = entry | ||||||
|  | 		logging.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry)) | ||||||
|  | 		self.xmpp.event("groupchat_presence", pr) | ||||||
|  | 	 | ||||||
|  | 	def handle_groupchat_message(self, msg): | ||||||
|  | 		""" Handle a message event in a muc. | ||||||
|  | 		""" | ||||||
|  | 		self.xmpp.event('groupchat_message', msg) | ||||||
|  | 		        | ||||||
|  | 	def jidInRoom(self, room, jid): | ||||||
|  | 		for nick in self.rooms[room]: | ||||||
|  | 			entry = self.rooms[room][nick] | ||||||
|  | 			if entry is not None and entry['jid'].full == jid: | ||||||
|  | 				return True | ||||||
|  | 		return False | ||||||
|  |  | ||||||
|     def plugin_init(self): | 	def getRoomForm(self, room, ifrom=None): | ||||||
|         self.rooms = {} | 		iq = self.xmpp.makeIqGet() | ||||||
|         self.ourNicks = {} | 		iq['to'] = room | ||||||
|         self.xep = '0045' | 		if ifrom is not None: | ||||||
|         self.description = 'Multi User Chat' | 			iq['from'] = ifrom | ||||||
|         # load MUC support in presence stanzas | 		query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||||
|         registerStanzaPlugin(Presence, MUCPresence) | 		iq.append(query) | ||||||
|         self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) | 		result = iq.send() | ||||||
|         self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) | 		if result['type'] == 'error': | ||||||
|         self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject)) | 			return False | ||||||
|         self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{http://jabber.org/protocol/muc#user}x/invite" % self.xmpp.default_ns), self.handle_groupchat_invite)) | 		xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') | ||||||
|  | 		if xform is None: return False | ||||||
|  | 		form = self.xmpp.plugin['xep_0004'].buildForm(xform) | ||||||
|  | 		return form | ||||||
|  | 	 | ||||||
|  | 	def configureRoom(self, room, form=None, ifrom=None): | ||||||
|  | 		if form is None: | ||||||
|  | 			form = self.getRoomForm(room, ifrom=ifrom) | ||||||
|  | 			#form = self.xmpp.plugin['xep_0004'].makeForm(ftype='submit') | ||||||
|  | 			#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')	 | ||||||
|  | 		iq = self.xmpp.makeIqSet() | ||||||
|  | 		iq['to'] = room | ||||||
|  | 		if ifrom is not None: | ||||||
|  | 			iq['from'] = ifrom | ||||||
|  | 		query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||||
|  | 		form = form.getXML('submit') | ||||||
|  | 		query.append(form) | ||||||
|  | 		iq.append(query) | ||||||
|  | 		result = iq.send() | ||||||
|  | 		if result['type'] == 'error': | ||||||
|  | 			return False | ||||||
|  | 		return True | ||||||
|  | 	 | ||||||
|  | 	def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None): | ||||||
|  | 		""" Join the specified room, requesting 'maxhistory' lines of history. | ||||||
|  | 		""" | ||||||
|  | 		stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow) | ||||||
|  | 		x = ET.Element('{http://jabber.org/protocol/muc}x') | ||||||
|  | 		if password: | ||||||
|  | 			passelement = ET.Element('password') | ||||||
|  | 			passelement.text = password | ||||||
|  | 			x.append(passelement) | ||||||
|  | 		history = ET.Element('history') | ||||||
|  | 		history.attrib['maxstanzas'] = maxhistory | ||||||
|  | 		x.append(history) | ||||||
|  | 		stanza.append(x) | ||||||
|  | 		if not wait: | ||||||
|  | 			self.xmpp.send(stanza) | ||||||
|  | 		else: | ||||||
|  | 			#wait for our own room presence back | ||||||
|  | 			expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)}) | ||||||
|  | 			self.xmpp.send(stanza, expect) | ||||||
|  | 		self.rooms[room] = {} | ||||||
|  | 		self.ourNicks[room] = nick | ||||||
|  | 	 | ||||||
|  | 	def destroy(self, room, reason='', altroom = '', ifrom=None): | ||||||
|  | 		iq = self.xmpp.makeIqSet() | ||||||
|  | 		if ifrom is not None: | ||||||
|  | 			iq['from'] = ifrom | ||||||
|  | 		iq['to'] = room | ||||||
|  | 		query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||||
|  | 		destroy = ET.Element('destroy') | ||||||
|  | 		if altroom: | ||||||
|  | 			destroy.attrib['jid'] = altroom | ||||||
|  | 		xreason = ET.Element('reason') | ||||||
|  | 		xreason.text = reason | ||||||
|  | 		destroy.append(xreason) | ||||||
|  | 		query.append(destroy) | ||||||
|  | 		iq.append(query) | ||||||
|  | 		r = iq.send() | ||||||
|  | 		if r is False or r['type'] == 'error': | ||||||
|  | 			return False | ||||||
|  | 		return True | ||||||
|  |  | ||||||
|     def handle_groupchat_invite(self, inv): | 	def setAffiliation(self, room, jid=None, nick=None, affiliation='member'): | ||||||
|         """ Handle an invite into a muc. | 		""" Change room affiliation.""" | ||||||
|         """ | 		if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): | ||||||
|         logging.debug("MUC invite to %s from %s: %s" % (inv['from'], inv["from"], inv)) | 			raise TypeError | ||||||
|         if inv['from'] not in self.rooms.keys(): | 		query = ET.Element('{http://jabber.org/protocol/muc#admin}query') | ||||||
|             self.xmpp.event("groupchat_invite", inv) | 		if nick is not None: | ||||||
|  | 			item = ET.Element('item', {'affiliation':affiliation, 'nick':nick})	 | ||||||
|  | 		else: | ||||||
|  | 			item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})	 | ||||||
|  | 		query.append(item) | ||||||
|  | 		iq = self.xmpp.makeIqSet(query) | ||||||
|  | 		iq['to'] = room | ||||||
|  | 		result = iq.send() | ||||||
|  | 		if result is False or result['type'] != 'result': | ||||||
|  | 			raise ValueError | ||||||
|  | 		return True | ||||||
|  | 	 | ||||||
|  | 	def invite(self, room, jid, reason=''): | ||||||
|  | 		""" Invite a jid to a room.""" | ||||||
|  | 		msg = self.xmpp.makeMessage(room) | ||||||
|  | 		msg['from'] = self.xmpp.jid | ||||||
|  | 		x = ET.Element('{http://jabber.org/protocol/muc#user}x') | ||||||
|  | 		invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid}) | ||||||
|  | 		if reason: | ||||||
|  | 			rxml = ET.Element('reason') | ||||||
|  | 			rxml.text = reason | ||||||
|  | 			invite.append(rxml) | ||||||
|  | 		x.append(invite) | ||||||
|  | 		msg.append(x) | ||||||
|  | 		self.xmpp.send(msg) | ||||||
|  |  | ||||||
|     def handle_groupchat_presence(self, pr): | 	def leaveMUC(self, room, nick): | ||||||
|         """ Handle a presence in a muc. | 		""" Leave the specified room. | ||||||
|         """ | 		""" | ||||||
|         got_offline = False | 		self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick)) | ||||||
|         got_online = False | 		del self.rooms[room] | ||||||
|         if pr['muc']['room'] not in self.rooms.keys(): | 	 | ||||||
|             return | 	def getRoomConfig(self, room): | ||||||
|         entry = pr['muc'].getStanzaValues() | 		iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') | ||||||
|         entry['show'] = pr['show'] | 		iq['to'] = room | ||||||
|         entry['status'] = pr['status'] | 		result = iq.send() | ||||||
|         if pr['type'] == 'unavailable': | 		if result is None or result['type'] != 'result': | ||||||
|             if entry['nick'] in self.rooms[entry['room']]: | 			raise ValueError | ||||||
|                 del self.rooms[entry['room']][entry['nick']] | 		form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') | ||||||
|             got_offline = True | 		if form is None: | ||||||
|         else: | 			raise ValueError | ||||||
|             if entry['nick'] not in self.rooms[entry['room']]: | 		return self.xmpp.plugin['xep_0004'].buildForm(form) | ||||||
|                 got_online = True | 	 | ||||||
|             self.rooms[entry['room']][entry['nick']] = entry | 	def cancelConfig(self, room): | ||||||
|         log.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry)) | 		query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||||
|         self.xmpp.event("groupchat_presence", pr) | 		x = ET.Element('{jabber:x:data}x', type='cancel') | ||||||
|         self.xmpp.event("muc::%s::presence" % entry['room'], pr) | 		query.append(x) | ||||||
|         if got_offline: | 		iq = self.xmpp.makeIqSet(query) | ||||||
|             self.xmpp.event("muc::%s::got_offline" % entry['room'], pr) | 		iq.send() | ||||||
|         if got_online: | 	 | ||||||
|             self.xmpp.event("muc::%s::got_online" % entry['room'], pr) | 	def setRoomConfig(self, room, config): | ||||||
|  | 		query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||||
|     def handle_groupchat_message(self, msg): | 		x = config.getXML('submit') | ||||||
|         """ Handle a message event in a muc. | 		query.append(x) | ||||||
|         """ | 		iq = self.xmpp.makeIqSet(query) | ||||||
|         self.xmpp.event('groupchat_message', msg) | 		iq['to'] = room | ||||||
|         self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) | 		iq.send() | ||||||
|  | 		 | ||||||
|     def handle_groupchat_subject(self, msg): | 	def getJoinedRooms(self): | ||||||
|         """ Handle a message coming from a muc indicating | 		return self.rooms.keys() | ||||||
|         a change of subject (or announcing it when joining the room) | 		 | ||||||
|         """ | 	def getOurJidInRoom(self, roomJid): | ||||||
|         self.xmpp.event('groupchat_subject', msg) | 		""" Return the jid we're using in a room. | ||||||
|  | 		""" | ||||||
|     def jidInRoom(self, room, jid): | 		return "%s/%s" % (roomJid, self.ourNicks[roomJid]) | ||||||
|         for nick in self.rooms[room]: | 		 | ||||||
|             entry = self.rooms[room][nick] | 	def getJidProperty(self, room, nick, jidProperty): | ||||||
|             if entry is not None and entry['jid'].full == jid: | 		""" Get the property of a nick in a room, such as its 'jid' or 'affiliation' | ||||||
|                 return True | 			If not found, return None. | ||||||
|         return False | 		""" | ||||||
|  | 		if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]: | ||||||
|     def getNick(self, room, jid): | 			return self.rooms[room][nick][jidProperty] | ||||||
|         for nick in self.rooms[room]: | 		else: | ||||||
|             entry = self.rooms[room][nick] | 			return None | ||||||
|             if entry is not None and entry['jid'].full == jid: | 	 | ||||||
|                 return nick | 	def getRoster(self, room): | ||||||
|  | 		""" Get the list of nicks in a room. | ||||||
|     def getRoomForm(self, room, ifrom=None): | 		""" | ||||||
|         iq = self.xmpp.makeIqGet() | 		if room not in self.rooms.keys(): | ||||||
|         iq['to'] = room | 			return None | ||||||
|         if ifrom is not None: | 		return self.rooms[room].keys() | ||||||
|             iq['from'] = ifrom |  | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |  | ||||||
|         iq.append(query) |  | ||||||
|         result = iq.send() |  | ||||||
|         if result['type'] == 'error': |  | ||||||
|             return False |  | ||||||
|         xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') |  | ||||||
|         if xform is None: return False |  | ||||||
|         form = self.xmpp.plugin['old_0004'].buildForm(xform) |  | ||||||
|         return form |  | ||||||
|  |  | ||||||
|     def configureRoom(self, room, form=None, ifrom=None): |  | ||||||
|         if form is None: |  | ||||||
|             form = self.getRoomForm(room, ifrom=ifrom) |  | ||||||
|             #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') |  | ||||||
|             #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') |  | ||||||
|         iq = self.xmpp.makeIqSet() |  | ||||||
|         iq['to'] = room |  | ||||||
|         if ifrom is not None: |  | ||||||
|             iq['from'] = ifrom |  | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |  | ||||||
|         form = form.getXML('submit') |  | ||||||
|         query.append(form) |  | ||||||
|         iq.append(query) |  | ||||||
|         result = iq.send() |  | ||||||
|         if result['type'] == 'error': |  | ||||||
|             return False |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None): |  | ||||||
|         """ Join the specified room, requesting 'maxhistory' lines of history. |  | ||||||
|         """ |  | ||||||
|         stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow) |  | ||||||
|         x = ET.Element('{http://jabber.org/protocol/muc}x') |  | ||||||
|         if password: |  | ||||||
|             passelement = ET.Element('password') |  | ||||||
|             passelement.text = password |  | ||||||
|             x.append(passelement) |  | ||||||
|         if maxhistory: |  | ||||||
|             history = ET.Element('history') |  | ||||||
|             if maxhistory ==  "0": |  | ||||||
|                 history.attrib['maxchars'] = maxhistory |  | ||||||
|             else: |  | ||||||
|                 history.attrib['maxstanzas'] = maxhistory |  | ||||||
|             x.append(history) |  | ||||||
|         stanza.append(x) |  | ||||||
|         if not wait: |  | ||||||
|             self.xmpp.send(stanza) |  | ||||||
|         else: |  | ||||||
|             #wait for our own room presence back |  | ||||||
|             expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)}) |  | ||||||
|             self.xmpp.send(stanza, expect) |  | ||||||
|         self.rooms[room] = {} |  | ||||||
|         self.ourNicks[room] = nick |  | ||||||
|  |  | ||||||
|     def destroy(self, room, reason='', altroom = '', ifrom=None): |  | ||||||
|         iq = self.xmpp.makeIqSet() |  | ||||||
|         if ifrom is not None: |  | ||||||
|             iq['from'] = ifrom |  | ||||||
|         iq['to'] = room |  | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |  | ||||||
|         destroy = ET.Element('destroy') |  | ||||||
|         if altroom: |  | ||||||
|             destroy.attrib['jid'] = altroom |  | ||||||
|         xreason = ET.Element('reason') |  | ||||||
|         xreason.text = reason |  | ||||||
|         destroy.append(xreason) |  | ||||||
|         query.append(destroy) |  | ||||||
|         iq.append(query) |  | ||||||
|         r = iq.send() |  | ||||||
|         if r is False or r['type'] == 'error': |  | ||||||
|             return False |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def setAffiliation(self, room, jid=None, nick=None, affiliation='member'): |  | ||||||
|         """ Change room affiliation.""" |  | ||||||
|         if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): |  | ||||||
|             raise TypeError |  | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#admin}query') |  | ||||||
|         if nick is not None: |  | ||||||
|             item = ET.Element('item', {'affiliation':affiliation, 'nick':nick}) |  | ||||||
|         else: |  | ||||||
|             item = ET.Element('item', {'affiliation':affiliation, 'jid':jid}) |  | ||||||
|         query.append(item) |  | ||||||
|         iq = self.xmpp.makeIqSet(query) |  | ||||||
|         iq['to'] = room |  | ||||||
|         result = iq.send() |  | ||||||
|         if result is False or result['type'] != 'result': |  | ||||||
|             raise ValueError |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def invite(self, room, jid, reason='', mfrom=''): |  | ||||||
|         """ Invite a jid to a room.""" |  | ||||||
|         msg = self.xmpp.makeMessage(room) |  | ||||||
|         msg['from'] = mfrom |  | ||||||
|         x = ET.Element('{http://jabber.org/protocol/muc#user}x') |  | ||||||
|         invite = ET.Element('{http://jabber.org/protocol/muc#user}invite', {'to': jid}) |  | ||||||
|         if reason: |  | ||||||
|             rxml = ET.Element('reason') |  | ||||||
|             rxml.text = reason |  | ||||||
|             invite.append(rxml) |  | ||||||
|         x.append(invite) |  | ||||||
|         msg.append(x) |  | ||||||
|         self.xmpp.send(msg) |  | ||||||
|  |  | ||||||
|     def leaveMUC(self, room, nick, msg=''): |  | ||||||
|         """ Leave the specified room. |  | ||||||
|         """ |  | ||||||
|         if msg: |  | ||||||
|             self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg) |  | ||||||
|         else: |  | ||||||
|             self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick)) |  | ||||||
|         del self.rooms[room] |  | ||||||
|  |  | ||||||
|     def getRoomConfig(self, room, ifrom=''): |  | ||||||
|         iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') |  | ||||||
|         iq['to'] = room |  | ||||||
|         iq['from'] = ifrom |  | ||||||
|         result = iq.send() |  | ||||||
|         if result is None or result['type'] != 'result': |  | ||||||
|             raise ValueError |  | ||||||
|         form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') |  | ||||||
|         if form is None: |  | ||||||
|             raise ValueError |  | ||||||
|         return self.xmpp.plugin['xep_0004'].buildForm(form) |  | ||||||
|  |  | ||||||
|     def cancelConfig(self, room): |  | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |  | ||||||
|         x = ET.Element('{jabber:x:data}x', type='cancel') |  | ||||||
|         query.append(x) |  | ||||||
|         iq = self.xmpp.makeIqSet(query) |  | ||||||
|         iq['to'] = room |  | ||||||
|         iq.send() |  | ||||||
|  |  | ||||||
|     def setRoomConfig(self, room, config, ifrom=''): |  | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |  | ||||||
|         x = config.getXML('submit') |  | ||||||
|         query.append(x) |  | ||||||
|         iq = self.xmpp.makeIqSet(query) |  | ||||||
|         iq['to'] = room |  | ||||||
|         iq['from'] = ifrom |  | ||||||
|         iq.send() |  | ||||||
|  |  | ||||||
|     def getJoinedRooms(self): |  | ||||||
|         return self.rooms.keys() |  | ||||||
|  |  | ||||||
|     def getOurJidInRoom(self, roomJid): |  | ||||||
|         """ Return the jid we're using in a room. |  | ||||||
|         """ |  | ||||||
|         return "%s/%s" % (roomJid, self.ourNicks[roomJid]) |  | ||||||
|  |  | ||||||
|     def getJidProperty(self, room, nick, jidProperty): |  | ||||||
|         """ Get the property of a nick in a room, such as its 'jid' or 'affiliation' |  | ||||||
|             If not found, return None. |  | ||||||
|         """ |  | ||||||
|         if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]: |  | ||||||
|             return self.rooms[room][nick][jidProperty] |  | ||||||
|         else: |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|     def getRoster(self, room): |  | ||||||
|         """ Get the list of nicks in a room. |  | ||||||
|         """ |  | ||||||
|         if room not in self.rooms.keys(): |  | ||||||
|             return None |  | ||||||
|         return self.rooms[room].keys() |  | ||||||
|   | |||||||
| @@ -1,17 +1,30 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library | 	SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz | 	Copyright (C) 2007  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. | 	This file is part of SleekXMPP. | ||||||
|      | 
 | ||||||
|     See the file LICENSE for copying permission. | 	SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  | 	it under the terms of the GNU General Public License as published by | ||||||
|  | 	the Free Software Foundation; either version 2 of the License, or | ||||||
|  | 	(at your option) any later version. | ||||||
|  | 
 | ||||||
|  | 	SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  | 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | 	GNU General Public License for more details. | ||||||
|  | 
 | ||||||
|  | 	You should have received a copy of the GNU General Public License | ||||||
|  | 	along with SleekXMPP; if not, write to the Free Software | ||||||
|  | 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
| """ | """ | ||||||
| from __future__ import with_statement | from __future__ import with_statement | ||||||
| from . import base | from . import base | ||||||
| import logging | import logging | ||||||
| from xml.etree import cElementTree as ET | from xml.etree import cElementTree as ET | ||||||
|  | import traceback | ||||||
| import time | import time | ||||||
| 
 | 
 | ||||||
| class old_0050(base.base_plugin): | class xep_0050(base.base_plugin): | ||||||
| 	""" | 	""" | ||||||
| 	XEP-0050 Ad-Hoc Commands | 	XEP-0050 Ad-Hoc Commands | ||||||
| 	""" | 	""" | ||||||
| @@ -19,11 +32,11 @@ class old_0050(base.base_plugin): | |||||||
| 	def plugin_init(self): | 	def plugin_init(self): | ||||||
| 		self.xep = '0050' | 		self.xep = '0050' | ||||||
| 		self.description = 'Ad-Hoc Commands' | 		self.description = 'Ad-Hoc Commands' | ||||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None') | 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__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, name='Ad-Hoc Execute') | 		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, name='Ad-Hoc Next', threaded=True) | 		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, name='Ad-Hoc Cancel') | 		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, name='Ad-Hoc Complete') | 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete) | ||||||
| 		self.commands = {} | 		self.commands = {} | ||||||
| 		self.sessions = {} | 		self.sessions = {} | ||||||
| 		self.sd = self.xmpp.plugin['xep_0030'] | 		self.sd = self.xmpp.plugin['xep_0030'] | ||||||
| @@ -70,7 +83,7 @@ class old_0050(base.base_plugin): | |||||||
| 		in_command = xml.find('{http://jabber.org/protocol/commands}command') | 		in_command = xml.find('{http://jabber.org/protocol/commands}command') | ||||||
| 		sessionid = in_command.get('sessionid', None) | 		sessionid = in_command.get('sessionid', None) | ||||||
| 		pointer = self.sessions[sessionid]['next'] | 		pointer = self.sessions[sessionid]['next'] | ||||||
| 		results = self.xmpp.plugin['old_0004'].makeForm('result') | 		results = self.xmpp.plugin['xep_0004'].makeForm('result') | ||||||
| 		results.fromXML(in_command.find('{jabber:x:data}x')) | 		results.fromXML(in_command.find('{jabber:x:data}x')) | ||||||
| 		pointer(results,sessionid) | 		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=[])) | 		self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[])) | ||||||
| @@ -81,7 +94,7 @@ class old_0050(base.base_plugin): | |||||||
| 		in_command = xml.find('{http://jabber.org/protocol/commands}command') | 		in_command = xml.find('{http://jabber.org/protocol/commands}command') | ||||||
| 		sessionid = in_command.get('sessionid', None) | 		sessionid = in_command.get('sessionid', None) | ||||||
| 		pointer = self.sessions[sessionid]['next'] | 		pointer = self.sessions[sessionid]['next'] | ||||||
| 		results = self.xmpp.plugin['old_0004'].makeForm('result') | 		results = self.xmpp.plugin['xep_0004'].makeForm('result') | ||||||
| 		results.fromXML(in_command.find('{jabber:x:data}x')) | 		results.fromXML(in_command.find('{jabber:x:data}x')) | ||||||
| 		form, npointer, next = pointer(results,sessionid) | 		form, npointer, next = pointer(results,sessionid) | ||||||
| 		self.sessions[sessionid]['next'] = npointer | 		self.sessions[sessionid]['next'] = npointer | ||||||
| @@ -110,7 +123,7 @@ class old_0050(base.base_plugin): | |||||||
| 		if not id: | 		if not id: | ||||||
| 			id = self.xmpp.getNewId() | 			id = self.xmpp.getNewId() | ||||||
| 		iq = self.xmpp.makeIqResult(id) | 		iq = self.xmpp.makeIqResult(id) | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		iq.attrib['to'] = to | 		iq.attrib['to'] = to | ||||||
| 		command = ET.Element('{http://jabber.org/protocol/commands}command') | 		command = ET.Element('{http://jabber.org/protocol/commands}command') | ||||||
| 		command.attrib['node'] = node | 		command.attrib['node'] = node | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0050.stanza import Command |  | ||||||
| from sleekxmpp.plugins.xep_0050.adhoc import xep_0050 |  | ||||||
| @@ -1,593 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| from sleekxmpp import Iq |  | ||||||
| from sleekxmpp.xmlstream.handler import Callback |  | ||||||
| from sleekxmpp.xmlstream.matcher import StanzaPath |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin, JID |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.plugins.xep_0050 import stanza |  | ||||||
| from sleekxmpp.plugins.xep_0050 import Command |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0050(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0050: Ad-Hoc Commands |  | ||||||
|  |  | ||||||
|     XMPP's Adhoc Commands provides a generic workflow mechanism for |  | ||||||
|     interacting with applications. The result is similar to menu selections |  | ||||||
|     and multi-step dialogs in normal desktop applications. Clients do not |  | ||||||
|     need to know in advance what commands are provided by any particular |  | ||||||
|     application or agent. While adhoc commands provide similar functionality |  | ||||||
|     to Jabber-RPC, adhoc commands are used primarily for human interaction. |  | ||||||
|  |  | ||||||
|     Also see <http://xmpp.org/extensions/xep-0050.html> |  | ||||||
|  |  | ||||||
|     Configuration Values: |  | ||||||
|         threaded -- Indicates if command events should be threaded. |  | ||||||
|                     Defaults to True. |  | ||||||
|  |  | ||||||
|     Events: |  | ||||||
|         command_execute  -- Received a command with action="execute" |  | ||||||
|         command_next     -- Received a command with action="next" |  | ||||||
|         command_complete -- Received a command with action="complete" |  | ||||||
|         command_cancel   -- Received a command with action="cancel" |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|         threaded -- Indicates if command events should be threaded. |  | ||||||
|                     Defaults to True. |  | ||||||
|         commands -- A dictionary mapping JID/node pairs to command |  | ||||||
|                     names and handlers. |  | ||||||
|         sessions -- A dictionary or equivalent backend mapping |  | ||||||
|                     session IDs to dictionaries containing data |  | ||||||
|                     relevant to a command's session. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         plugin_init       -- Overrides base_plugin.plugin_init |  | ||||||
|         post_init         -- Overrides base_plugin.post_init |  | ||||||
|         new_session       -- Return a new session ID. |  | ||||||
|         prep_handlers     -- Placeholder. May call with a list of handlers |  | ||||||
|                              to prepare them for use with the session storage |  | ||||||
|                              backend, if needed. |  | ||||||
|         set_backend       -- Replace the default session storage with some |  | ||||||
|                              external storage mechanism, such as a database. |  | ||||||
|                              The provided backend wrapper must be able to |  | ||||||
|                              act using the same syntax as a dictionary. |  | ||||||
|         add_command       -- Add a command for use by external entitites. |  | ||||||
|         get_commands      -- Retrieve a list of commands provided by a |  | ||||||
|                              remote agent. |  | ||||||
|         send_command      -- Send a command request to a remote agent. |  | ||||||
|         start_command     -- Command user API: initiate a command session |  | ||||||
|         continue_command  -- Command user API: proceed to the next step |  | ||||||
|         cancel_command    -- Command user API: cancel a command |  | ||||||
|         complete_command  -- Command user API: finish a command |  | ||||||
|         terminate_command -- Command user API: delete a command's session |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         """Start the XEP-0050 plugin.""" |  | ||||||
|         self.xep = '0050' |  | ||||||
|         self.description = 'Ad-Hoc Commands' |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         self.threaded = self.config.get('threaded', True) |  | ||||||
|         self.commands = {} |  | ||||||
|         self.sessions = self.config.get('session_db', {}) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback("Ad-Hoc Execute", |  | ||||||
|                          StanzaPath('iq@type=set/command'), |  | ||||||
|                          self._handle_command)) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback("Ad-Hoc Result", |  | ||||||
|                          StanzaPath('iq@type=result/command'), |  | ||||||
|                          self._handle_command_result)) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback("Ad-Hoc Error", |  | ||||||
|                          StanzaPath('iq@type=error/command'), |  | ||||||
|                          self._handle_command_result)) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, stanza.Command) |  | ||||||
|  |  | ||||||
|         self.xmpp.add_event_handler('command_execute', |  | ||||||
|                                     self._handle_command_start, |  | ||||||
|                                     threaded=self.threaded) |  | ||||||
|         self.xmpp.add_event_handler('command_next', |  | ||||||
|                                     self._handle_command_next, |  | ||||||
|                                     threaded=self.threaded) |  | ||||||
|         self.xmpp.add_event_handler('command_cancel', |  | ||||||
|                                     self._handle_command_cancel, |  | ||||||
|                                     threaded=self.threaded) |  | ||||||
|         self.xmpp.add_event_handler('command_complete', |  | ||||||
|                                     self._handle_command_complete, |  | ||||||
|                                     threaded=self.threaded) |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         """Handle cross-plugin interactions.""" |  | ||||||
|         base_plugin.post_init(self) |  | ||||||
|         self.xmpp['xep_0030'].add_feature(Command.namespace) |  | ||||||
|  |  | ||||||
|     def set_backend(self, db): |  | ||||||
|         """ |  | ||||||
|         Replace the default session storage dictionary with |  | ||||||
|         a generic, external data storage mechanism. |  | ||||||
|  |  | ||||||
|         The replacement backend must be able to interact through |  | ||||||
|         the same syntax and interfaces as a normal dictionary. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             db -- The new session storage mechanism. |  | ||||||
|         """ |  | ||||||
|         self.sessions = db |  | ||||||
|  |  | ||||||
|     def prep_handlers(self, handlers, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Prepare a list of functions for use by the backend service. |  | ||||||
|  |  | ||||||
|         Intended to be replaced by the backend service as needed. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             handlers -- A list of function pointers |  | ||||||
|             **kwargs -- Any additional parameters required by the backend. |  | ||||||
|         """ |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     # ================================================================= |  | ||||||
|     # Server side (command provider) API |  | ||||||
|  |  | ||||||
|     def add_command(self, jid=None, node=None, name='', handler=None): |  | ||||||
|         """ |  | ||||||
|         Make a new command available to external entities. |  | ||||||
|  |  | ||||||
|         Access control may be implemented in the provided handler. |  | ||||||
|  |  | ||||||
|         Command workflow is done across a sequence of command handlers. The |  | ||||||
|         first handler is given the intial Iq stanza of the request in order |  | ||||||
|         to support access control. Subsequent handlers are given only the |  | ||||||
|         payload items of the command. All handlers will receive the command's |  | ||||||
|         session data. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid     -- The JID that will expose the command. |  | ||||||
|             node    -- The node associated with the command. |  | ||||||
|             name    -- A human readable name for the command. |  | ||||||
|             handler -- A function that will generate the response to the |  | ||||||
|                        initial command request, as well as enforcing any |  | ||||||
|                        access control policies. |  | ||||||
|         """ |  | ||||||
|         if jid is None: |  | ||||||
|             jid = self.xmpp.boundjid |  | ||||||
|         elif not isinstance(jid, JID): |  | ||||||
|             jid = JID(jid) |  | ||||||
|         item_jid = jid.full |  | ||||||
|  |  | ||||||
|         # Client disco uses only the bare JID |  | ||||||
|         if self.xmpp.is_component: |  | ||||||
|             jid = jid.full |  | ||||||
|         else: |  | ||||||
|             jid = jid.bare |  | ||||||
|  |  | ||||||
|         self.xmpp['xep_0030'].add_identity(category='automation', |  | ||||||
|                                            itype='command-list', |  | ||||||
|                                            name='Ad-Hoc commands', |  | ||||||
|                                            node=Command.namespace, |  | ||||||
|                                            jid=jid) |  | ||||||
|         self.xmpp['xep_0030'].add_item(jid=item_jid, |  | ||||||
|                                        name=name, |  | ||||||
|                                        node=Command.namespace, |  | ||||||
|                                        subnode=node, |  | ||||||
|                                        ijid=jid) |  | ||||||
|         self.xmpp['xep_0030'].add_identity(category='automation', |  | ||||||
|                                            itype='command-node', |  | ||||||
|                                            name=name, |  | ||||||
|                                            node=node, |  | ||||||
|                                            jid=jid) |  | ||||||
|         self.xmpp['xep_0030'].add_feature(Command.namespace, None, jid) |  | ||||||
|  |  | ||||||
|         self.commands[(item_jid, node)] = (name, handler) |  | ||||||
|  |  | ||||||
|     def new_session(self): |  | ||||||
|         """Return a new session ID.""" |  | ||||||
|         return str(time.time()) + '-' + self.xmpp.new_id() |  | ||||||
|  |  | ||||||
|     def _handle_command(self, iq): |  | ||||||
|         """Raise command events based on the command action.""" |  | ||||||
|         self.xmpp.event('command_%s' % iq['command']['action'], iq) |  | ||||||
|  |  | ||||||
|     def _handle_command_start(self, iq): |  | ||||||
|         """ |  | ||||||
|         Process an initial request to execute a command. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The command execution request. |  | ||||||
|         """ |  | ||||||
|         sessionid = self.new_session() |  | ||||||
|         node = iq['command']['node'] |  | ||||||
|         key = (iq['to'].full, node) |  | ||||||
|         name, handler = self.commands.get(key, ('Not found', None)) |  | ||||||
|         if not handler: |  | ||||||
|             log.debug('Command not found: %s, %s' % (key, self.commands)) |  | ||||||
|  |  | ||||||
|         initial_session = {'id': sessionid, |  | ||||||
|                            'from': iq['from'], |  | ||||||
|                            'to': iq['to'], |  | ||||||
|                            'node': node, |  | ||||||
|                            'payload': None, |  | ||||||
|                            'interfaces': '', |  | ||||||
|                            'payload_classes': None, |  | ||||||
|                            'notes': None, |  | ||||||
|                            'has_next': False, |  | ||||||
|                            'allow_complete': False, |  | ||||||
|                            'allow_prev': False, |  | ||||||
|                            'past': [], |  | ||||||
|                            'next': None, |  | ||||||
|                            'prev': None, |  | ||||||
|                            'cancel': None} |  | ||||||
|  |  | ||||||
|         session = handler(iq, initial_session) |  | ||||||
|  |  | ||||||
|         self._process_command_response(iq, session) |  | ||||||
|  |  | ||||||
|     def _handle_command_next(self, iq): |  | ||||||
|         """ |  | ||||||
|         Process a request for the next step in the workflow |  | ||||||
|         for a command with multiple steps. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The command continuation request. |  | ||||||
|         """ |  | ||||||
|         sessionid = iq['command']['sessionid'] |  | ||||||
|         session = self.sessions[sessionid] |  | ||||||
|  |  | ||||||
|         handler = session['next'] |  | ||||||
|         interfaces = session['interfaces'] |  | ||||||
|         results = [] |  | ||||||
|         for stanza in iq['command']['substanzas']: |  | ||||||
|             if stanza.plugin_attrib in interfaces: |  | ||||||
|                 results.append(stanza) |  | ||||||
|         if len(results) == 1: |  | ||||||
|             results = results[0] |  | ||||||
|  |  | ||||||
|         session = handler(results, session) |  | ||||||
|  |  | ||||||
|         self._process_command_response(iq, session) |  | ||||||
|  |  | ||||||
|     def _process_command_response(self, iq, session): |  | ||||||
|         """ |  | ||||||
|         Generate a command reply stanza based on the |  | ||||||
|         provided session data. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The command request stanza. |  | ||||||
|             session -- A dictionary of relevant session data. |  | ||||||
|         """ |  | ||||||
|         sessionid = session['id'] |  | ||||||
|  |  | ||||||
|         payload = session['payload'] |  | ||||||
|         if not isinstance(payload, list): |  | ||||||
|             payload = [payload] |  | ||||||
|  |  | ||||||
|         session['interfaces'] = [item.plugin_attrib for item in payload] |  | ||||||
|         session['payload_classes'] = [item.__class__ for item in payload] |  | ||||||
|  |  | ||||||
|         self.sessions[sessionid] = session |  | ||||||
|  |  | ||||||
|         for item in payload: |  | ||||||
|             register_stanza_plugin(Command, item.__class__, iterable=True) |  | ||||||
|  |  | ||||||
|         iq.reply() |  | ||||||
|         iq['command']['node'] = session['node'] |  | ||||||
|         iq['command']['sessionid'] = session['id'] |  | ||||||
|  |  | ||||||
|         if session['next'] is None: |  | ||||||
|             iq['command']['actions'] = [] |  | ||||||
|             iq['command']['status'] = 'completed' |  | ||||||
|         elif session['has_next']: |  | ||||||
|             actions = ['next'] |  | ||||||
|             if session['allow_complete']: |  | ||||||
|                 actions.append('complete') |  | ||||||
|             if session['allow_prev']: |  | ||||||
|                 actions.append('prev') |  | ||||||
|             iq['command']['actions'] = actions |  | ||||||
|             iq['command']['status'] = 'executing' |  | ||||||
|         else: |  | ||||||
|             iq['command']['actions'] = ['complete'] |  | ||||||
|             iq['command']['status'] = 'executing' |  | ||||||
|  |  | ||||||
|         iq['command']['notes'] = session['notes'] |  | ||||||
|  |  | ||||||
|         for item in payload: |  | ||||||
|             iq['command'].append(item) |  | ||||||
|  |  | ||||||
|         iq.send() |  | ||||||
|  |  | ||||||
|     def _handle_command_cancel(self, iq): |  | ||||||
|         """ |  | ||||||
|         Process a request to cancel a command's execution. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The command cancellation request. |  | ||||||
|         """ |  | ||||||
|         node = iq['command']['node'] |  | ||||||
|         sessionid = iq['command']['sessionid'] |  | ||||||
|         session = self.sessions[sessionid] |  | ||||||
|         handler = session['cancel'] |  | ||||||
|  |  | ||||||
|         if handler: |  | ||||||
|             handler(iq, session) |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             del self.sessions[sessionid] |  | ||||||
|         except: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|         iq.reply() |  | ||||||
|         iq['command']['node'] = node |  | ||||||
|         iq['command']['sessionid'] = sessionid |  | ||||||
|         iq['command']['status'] = 'canceled' |  | ||||||
|         iq['command']['notes'] = session['notes'] |  | ||||||
|         iq.send() |  | ||||||
|  |  | ||||||
|     def _handle_command_complete(self, iq): |  | ||||||
|         """ |  | ||||||
|         Process a request to finish the execution of command |  | ||||||
|         and terminate the workflow. |  | ||||||
|  |  | ||||||
|         All data related to the command session will be removed. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The command completion request. |  | ||||||
|         """ |  | ||||||
|         node = iq['command']['node'] |  | ||||||
|         sessionid = iq['command']['sessionid'] |  | ||||||
|         session = self.sessions[sessionid] |  | ||||||
|         handler = session['next'] |  | ||||||
|         interfaces = session['interfaces'] |  | ||||||
|         results = [] |  | ||||||
|         for stanza in iq['command']['substanzas']: |  | ||||||
|             if stanza.plugin_attrib in interfaces: |  | ||||||
|                 results.append(stanza) |  | ||||||
|         if len(results) == 1: |  | ||||||
|             results = results[0] |  | ||||||
|  |  | ||||||
|         if handler: |  | ||||||
|             handler(results, session) |  | ||||||
|  |  | ||||||
|         iq.reply() |  | ||||||
|         iq['command']['node'] = node |  | ||||||
|         iq['command']['sessionid'] = sessionid |  | ||||||
|         iq['command']['actions'] = [] |  | ||||||
|         iq['command']['status'] = 'completed' |  | ||||||
|         iq['command']['notes'] = session['notes'] |  | ||||||
|         iq.send() |  | ||||||
|  |  | ||||||
|         del self.sessions[sessionid] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # ================================================================= |  | ||||||
|     # Client side (command user) API |  | ||||||
|  |  | ||||||
|     def get_commands(self, jid, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Return a list of commands provided by a given JID. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid      -- The JID to query for commands. |  | ||||||
|             local    -- If true, then the query is for a JID/node |  | ||||||
|                         combination handled by this Sleek instance and |  | ||||||
|                         no stanzas need to be sent. |  | ||||||
|                         Otherwise, a disco stanza must be sent to the |  | ||||||
|                         remove JID to retrieve the items. |  | ||||||
|             ifrom    -- Specifiy the sender's JID. |  | ||||||
|             block    -- If true, block and wait for the stanzas' reply. |  | ||||||
|             timeout  -- The time in seconds to block while waiting for |  | ||||||
|                         a reply. If None, then wait indefinitely. |  | ||||||
|             callback -- Optional callback to execute when a reply is |  | ||||||
|                         received instead of blocking and waiting for |  | ||||||
|                         the reply. |  | ||||||
|             iterator -- If True, return a result set iterator using |  | ||||||
|                         the XEP-0059 plugin, if the plugin is loaded. |  | ||||||
|                         Otherwise the parameter is ignored. |  | ||||||
|         """ |  | ||||||
|         return self.xmpp['xep_0030'].get_items(jid=jid, |  | ||||||
|                                                node=Command.namespace, |  | ||||||
|                                                **kwargs) |  | ||||||
|  |  | ||||||
|     def send_command(self, jid, node, ifrom=None, action='execute', |  | ||||||
|                     payload=None, sessionid=None, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Create and send a command stanza, without using the provided |  | ||||||
|         workflow management APIs. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid       -- The JID to send the command request or result. |  | ||||||
|             node      -- The node for the command. |  | ||||||
|             ifrom     -- Specify the sender's JID. |  | ||||||
|             action    -- May be one of: execute, cancel, complete, |  | ||||||
|                          or cancel. |  | ||||||
|             payload   -- Either a list of payload items, or a single |  | ||||||
|                          payload item such as a data form. |  | ||||||
|             sessionid -- The current session's ID value. |  | ||||||
|             block     -- Specify if the send call will block until a |  | ||||||
|                          response is received, or a timeout occurs. |  | ||||||
|                          Defaults to True. |  | ||||||
|             timeout   -- The length of time (in seconds) to wait for a |  | ||||||
|                          response before exiting the send call |  | ||||||
|                          if blocking is used. Defaults to |  | ||||||
|                          sleekxmpp.xmlstream.RESPONSE_TIMEOUT |  | ||||||
|             callback  -- Optional reference to a stream handler |  | ||||||
|                          function. Will be executed when a reply |  | ||||||
|                          stanza is received. |  | ||||||
|         """ |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         iq['type'] = 'set' |  | ||||||
|         iq['to'] = jid |  | ||||||
|         if ifrom: |  | ||||||
|             iq['from'] = ifrom |  | ||||||
|         iq['command']['node'] = node |  | ||||||
|         iq['command']['action'] = action |  | ||||||
|         if sessionid is not None: |  | ||||||
|             iq['command']['sessionid'] = sessionid |  | ||||||
|         if payload is not None: |  | ||||||
|             if not isinstance(payload, list): |  | ||||||
|                 payload = [payload] |  | ||||||
|             for item in payload: |  | ||||||
|                 iq['command'].append(item) |  | ||||||
|         return iq.send(**kwargs) |  | ||||||
|  |  | ||||||
|     def start_command(self, jid, node, session, ifrom=None): |  | ||||||
|         """ |  | ||||||
|         Initiate executing a command provided by a remote agent. |  | ||||||
|  |  | ||||||
|         The workflow provided is always non-blocking. |  | ||||||
|  |  | ||||||
|         The provided session dictionary should contain: |  | ||||||
|             next  -- A handler for processing the command result. |  | ||||||
|             error -- A handler for processing any error stanzas |  | ||||||
|                      generated by the request. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid     -- The JID to send the command request. |  | ||||||
|             node    -- The node for the desired command. |  | ||||||
|             session -- A dictionary of relevant session data. |  | ||||||
|             ifrom   -- Optionally specify the sender's JID. |  | ||||||
|         """ |  | ||||||
|         session['jid'] = jid |  | ||||||
|         session['node'] = node |  | ||||||
|         session['timestamp'] = time.time() |  | ||||||
|         session['payload'] = None |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         iq['type'] = 'set' |  | ||||||
|         iq['to'] = jid |  | ||||||
|         if ifrom: |  | ||||||
|             iq['from'] = ifrom |  | ||||||
|             session['from'] = ifrom |  | ||||||
|         iq['command']['node'] = node |  | ||||||
|         iq['command']['action'] = 'execute' |  | ||||||
|         sessionid = 'client:pending_' + iq['id'] |  | ||||||
|         session['id'] = sessionid |  | ||||||
|         self.sessions[sessionid] = session |  | ||||||
|         iq.send(block=False) |  | ||||||
|  |  | ||||||
|     def continue_command(self, session): |  | ||||||
|         """ |  | ||||||
|         Execute the next action of the command. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             session -- All stored data relevant to the current |  | ||||||
|                        command session. |  | ||||||
|         """ |  | ||||||
|         sessionid = 'client:' + session['id'] |  | ||||||
|         self.sessions[sessionid] = session |  | ||||||
|  |  | ||||||
|         self.send_command(session['jid'], |  | ||||||
|                           session['node'], |  | ||||||
|                           ifrom=session.get('from', None), |  | ||||||
|                           action='next', |  | ||||||
|                           payload=session.get('payload', None), |  | ||||||
|                           sessionid=session['id']) |  | ||||||
|  |  | ||||||
|     def cancel_command(self, session): |  | ||||||
|         """ |  | ||||||
|         Cancel the execution of a command. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             session -- All stored data relevant to the current |  | ||||||
|                        command session. |  | ||||||
|         """ |  | ||||||
|         sessionid = 'client:' + session['id'] |  | ||||||
|         self.sessions[sessionid] = session |  | ||||||
|  |  | ||||||
|         self.send_command(session['jid'], |  | ||||||
|                           session['node'], |  | ||||||
|                           ifrom=session.get('from', None), |  | ||||||
|                           action='cancel', |  | ||||||
|                           payload=session.get('payload', None), |  | ||||||
|                           sessionid=session['id']) |  | ||||||
|  |  | ||||||
|     def complete_command(self, session): |  | ||||||
|         """ |  | ||||||
|         Finish the execution of a command workflow. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             session -- All stored data relevant to the current |  | ||||||
|                        command session. |  | ||||||
|         """ |  | ||||||
|         sessionid = 'client:' + session['id'] |  | ||||||
|         self.sessions[sessionid] = session |  | ||||||
|  |  | ||||||
|         self.send_command(session['jid'], |  | ||||||
|                           session['node'], |  | ||||||
|                           ifrom=session.get('from', None), |  | ||||||
|                           action='complete', |  | ||||||
|                           payload=session.get('payload', None), |  | ||||||
|                           sessionid=session['id']) |  | ||||||
|  |  | ||||||
|     def terminate_command(self, session): |  | ||||||
|         """ |  | ||||||
|         Delete a command's session after a command has completed |  | ||||||
|         or an error has occured. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             session -- All stored data relevant to the current |  | ||||||
|                        command session. |  | ||||||
|         """ |  | ||||||
|         try: |  | ||||||
|             del self.sessions[session['id']] |  | ||||||
|         except: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|     def _handle_command_result(self, iq): |  | ||||||
|         """ |  | ||||||
|         Process the results of a command request. |  | ||||||
|  |  | ||||||
|         Will execute the 'next' handler stored in the session |  | ||||||
|         data, or the 'error' handler depending on the Iq's type. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The command response. |  | ||||||
|         """ |  | ||||||
|         sessionid = 'client:' + iq['command']['sessionid'] |  | ||||||
|         pending = False |  | ||||||
|  |  | ||||||
|         if sessionid not in self.sessions: |  | ||||||
|             pending = True |  | ||||||
|             pendingid = 'client:pending_' + iq['id'] |  | ||||||
|             if pendingid not in self.sessions: |  | ||||||
|                 return |  | ||||||
|             sessionid = pendingid |  | ||||||
|  |  | ||||||
|         session = self.sessions[sessionid] |  | ||||||
|         sessionid = 'client:' + iq['command']['sessionid'] |  | ||||||
|         session['id'] = iq['command']['sessionid'] |  | ||||||
|  |  | ||||||
|         self.sessions[sessionid] = session |  | ||||||
|  |  | ||||||
|         if pending: |  | ||||||
|             del self.sessions[pendingid] |  | ||||||
|  |  | ||||||
|         handler_type = 'next' |  | ||||||
|         if iq['type'] == 'error': |  | ||||||
|             handler_type = 'error' |  | ||||||
|         handler = session.get(handler_type, None) |  | ||||||
|         if handler: |  | ||||||
|             handler(iq, session) |  | ||||||
|         elif iq['type'] == 'error': |  | ||||||
|             self.terminate_command(session) |  | ||||||
|  |  | ||||||
|         if iq['command']['status'] == 'completed': |  | ||||||
|             self.terminate_command(session) |  | ||||||
| @@ -1,185 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Command(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XMPP's Adhoc Commands provides a generic workflow mechanism for |  | ||||||
|     interacting with applications. The result is similar to menu selections |  | ||||||
|     and multi-step dialogs in normal desktop applications. Clients do not |  | ||||||
|     need to know in advance what commands are provided by any particular |  | ||||||
|     application or agent. While adhoc commands provide similar functionality |  | ||||||
|     to Jabber-RPC, adhoc commands are used primarily for human interaction. |  | ||||||
|  |  | ||||||
|     Also see <http://xmpp.org/extensions/xep-0050.html> |  | ||||||
|  |  | ||||||
|     Example command stanzas: |  | ||||||
|       <iq type="set"> |  | ||||||
|         <command xmlns="http://jabber.org/protocol/commands" |  | ||||||
|                  node="run_foo" |  | ||||||
|                  action="execute" /> |  | ||||||
|       </iq> |  | ||||||
|  |  | ||||||
|       <iq type="result"> |  | ||||||
|         <command xmlns="http://jabber.org/protocol/commands" |  | ||||||
|                  node="run_foo" |  | ||||||
|                  sessionid="12345" |  | ||||||
|                  status="executing"> |  | ||||||
|           <actions> |  | ||||||
|             <complete /> |  | ||||||
|           </actions> |  | ||||||
|           <note type="info">Information!</note> |  | ||||||
|           <x xmlns="jabber:x:data"> |  | ||||||
|             <field var="greeting" |  | ||||||
|                    type="text-single" |  | ||||||
|                    label="Greeting" /> |  | ||||||
|           </x> |  | ||||||
|         </command> |  | ||||||
|       </iq> |  | ||||||
|  |  | ||||||
|     Stanza Interface: |  | ||||||
|         action    -- The action to perform. |  | ||||||
|         actions   -- The set of allowable next actions. |  | ||||||
|         node      -- The node associated with the command. |  | ||||||
|         notes     -- A list of tuples for informative notes. |  | ||||||
|         sessionid -- A unique identifier for a command session. |  | ||||||
|         status    -- May be one of: canceled, completed, or executing. |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|         actions      -- A set of allowed action values. |  | ||||||
|         statuses     -- A set of allowed status values. |  | ||||||
|         next_actions -- A set of allowed next action names. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         get_action  -- Return the requested action. |  | ||||||
|         get_actions -- Return the allowable next actions. |  | ||||||
|         set_actions -- Set the allowable next actions. |  | ||||||
|         del_actions -- Remove the current set of next actions. |  | ||||||
|         get_notes   -- Return a list of informative note data. |  | ||||||
|         set_notes   -- Set informative notes. |  | ||||||
|         del_notes   -- Remove any note data. |  | ||||||
|         add_note    -- Add a single note. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'command' |  | ||||||
|     namespace = 'http://jabber.org/protocol/commands' |  | ||||||
|     plugin_attrib = 'command' |  | ||||||
|     interfaces = set(('action', 'sessionid', 'node', |  | ||||||
|                       'status', 'actions', 'notes')) |  | ||||||
|     actions = set(('cancel', 'complete', 'execute', 'next', 'prev')) |  | ||||||
|     statuses = set(('canceled', 'completed', 'executing')) |  | ||||||
|     next_actions = set(('prev', 'next', 'complete')) |  | ||||||
|  |  | ||||||
|     def get_action(self): |  | ||||||
|         """ |  | ||||||
|         Return the value of the action attribute. |  | ||||||
|  |  | ||||||
|         If the Iq stanza's type is "set" then use a default |  | ||||||
|         value of "execute". |  | ||||||
|         """ |  | ||||||
|         if self.parent()['type'] == 'set': |  | ||||||
|             return self._get_attr('action', default='execute') |  | ||||||
|         return self._get_attr('action') |  | ||||||
|  |  | ||||||
|     def set_actions(self, values): |  | ||||||
|         """ |  | ||||||
|         Assign the set of allowable next actions. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             values -- A list containing any combination of: |  | ||||||
|                         'prev', 'next', and 'complete' |  | ||||||
|         """ |  | ||||||
|         self.del_actions() |  | ||||||
|         if values: |  | ||||||
|             self._set_sub_text('{%s}actions' % self.namespace, '', True) |  | ||||||
|             actions = self.find('{%s}actions' % self.namespace) |  | ||||||
|             for val in values: |  | ||||||
|                 if val in self.next_actions: |  | ||||||
|                     action = ET.Element('{%s}%s' % (self.namespace, val)) |  | ||||||
|                     actions.append(action) |  | ||||||
|  |  | ||||||
|     def get_actions(self): |  | ||||||
|         """ |  | ||||||
|         Return the set of allowable next actions. |  | ||||||
|         """ |  | ||||||
|         actions = [] |  | ||||||
|         actions_xml = self.find('{%s}actions' % self.namespace) |  | ||||||
|         if actions_xml is not None: |  | ||||||
|             for action in self.next_actions: |  | ||||||
|                 action_xml = actions_xml.find('{%s}%s' % (self.namespace, |  | ||||||
|                                                           action)) |  | ||||||
|                 if action_xml is not None: |  | ||||||
|                     actions.append(action) |  | ||||||
|         return actions |  | ||||||
|  |  | ||||||
|     def del_actions(self): |  | ||||||
|         """ |  | ||||||
|         Remove all allowable next actions. |  | ||||||
|         """ |  | ||||||
|         self._del_sub('{%s}actions' % self.namespace) |  | ||||||
|  |  | ||||||
|     def get_notes(self): |  | ||||||
|         """ |  | ||||||
|         Return a list of note information. |  | ||||||
|  |  | ||||||
|         Example: |  | ||||||
|             [('info', 'Some informative data'), |  | ||||||
|              ('warning', 'Use caution'), |  | ||||||
|              ('error', 'The command ran, but had errors')] |  | ||||||
|         """ |  | ||||||
|         notes = [] |  | ||||||
|         notes_xml = self.findall('{%s}note' % self.namespace) |  | ||||||
|         for note in notes_xml: |  | ||||||
|             notes.append((note.attrib.get('type', 'info'), |  | ||||||
|                           note.text)) |  | ||||||
|         return notes |  | ||||||
|  |  | ||||||
|     def set_notes(self, notes): |  | ||||||
|         """ |  | ||||||
|         Add multiple notes to the command result. |  | ||||||
|  |  | ||||||
|         Each note is a tuple, with the first item being one of: |  | ||||||
|         'info', 'warning', or 'error', and the second item being |  | ||||||
|         any human readable message. |  | ||||||
|  |  | ||||||
|         Example: |  | ||||||
|             [('info', 'Some informative data'), |  | ||||||
|              ('warning', 'Use caution'), |  | ||||||
|              ('error', 'The command ran, but had errors')] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             notes -- A list of tuples of note information. |  | ||||||
|         """ |  | ||||||
|         self.del_notes() |  | ||||||
|         for note in notes: |  | ||||||
|             self.add_note(note[1], note[0]) |  | ||||||
|  |  | ||||||
|     def del_notes(self): |  | ||||||
|         """ |  | ||||||
|         Remove all notes associated with the command result. |  | ||||||
|         """ |  | ||||||
|         notes_xml = self.findall('{%s}note' % self.namespace) |  | ||||||
|         for note in notes_xml: |  | ||||||
|             self.xml.remove(note) |  | ||||||
|  |  | ||||||
|     def add_note(self, msg='', ntype='info'): |  | ||||||
|         """ |  | ||||||
|         Add a single note annotation to the command. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg   -- A human readable message. |  | ||||||
|             ntype -- One of: 'info', 'warning', 'error' |  | ||||||
|         """ |  | ||||||
|         xml = ET.Element('{%s}note' % self.namespace) |  | ||||||
|         xml.attrib['type'] = ntype |  | ||||||
|         xml.text = msg |  | ||||||
|         self.xml.append(xml) |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0059.stanza import Set |  | ||||||
| from sleekxmpp.plugins.xep_0059.rsm import ResultIterator, xep_0059 |  | ||||||
| @@ -1,119 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp import Iq |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
| from sleekxmpp.plugins.xep_0059 import Set |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ResultIterator(): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     An iterator for Result Set Managment |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, query, interface, amount=10, start=None, reverse=False): |  | ||||||
|         """ |  | ||||||
|         Arguments: |  | ||||||
|            query     -- The template query |  | ||||||
|            interface -- The substanza of the query, for example disco_items |  | ||||||
|            amount    -- The max amounts of items to request per iteration |  | ||||||
|            start     -- From which item id to start |  | ||||||
|            reverse   -- If True, page backwards through the results |  | ||||||
|  |  | ||||||
|         Example: |  | ||||||
|            q = Iq() |  | ||||||
|            q['to'] = 'pubsub.example.com' |  | ||||||
|            q['disco_items']['node'] = 'blog' |  | ||||||
|            for i in ResultIterator(q, 'disco_items', '10'): |  | ||||||
|                print i['disco_items']['items'] |  | ||||||
|  |  | ||||||
|         """ |  | ||||||
|         self.query = query |  | ||||||
|         self.amount = amount |  | ||||||
|         self.start = start |  | ||||||
|         self.interface = interface |  | ||||||
|         self.reverse = reverse |  | ||||||
|  |  | ||||||
|     def __iter__(self): |  | ||||||
|         return self |  | ||||||
|  |  | ||||||
|     def __next__(self): |  | ||||||
|         return self.next() |  | ||||||
|  |  | ||||||
|     def next(self): |  | ||||||
|         """ |  | ||||||
|         Return the next page of results from a query. |  | ||||||
|  |  | ||||||
|         Note: If using backwards paging, then the next page of |  | ||||||
|               results will be the items before the current page |  | ||||||
|               of items. |  | ||||||
|         """ |  | ||||||
|         self.query[self.interface]['rsm']['before'] = self.reverse |  | ||||||
|         self.query['id'] = self.query.stream.new_id() |  | ||||||
|         self.query[self.interface]['rsm']['max'] = str(self.amount) |  | ||||||
|  |  | ||||||
|         if self.start and self.reverse: |  | ||||||
|             self.query[self.interface]['rsm']['before'] = self.start |  | ||||||
|         elif self.start: |  | ||||||
|             self.query[self.interface]['rsm']['after'] = self.start |  | ||||||
|  |  | ||||||
|         r = self.query.send(block=True) |  | ||||||
|  |  | ||||||
|         if not r or not r[self.interface]['rsm']['first'] and \ |  | ||||||
|            not r[self.interface]['rsm']['last']: |  | ||||||
|             raise StopIteration |  | ||||||
|  |  | ||||||
|         if self.reverse: |  | ||||||
|             self.start = r[self.interface]['rsm']['first'] |  | ||||||
|         else: |  | ||||||
|             self.start = r[self.interface]['rsm']['last'] |  | ||||||
|  |  | ||||||
|         return r |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0059(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0050: Result Set Management |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         """ |  | ||||||
|         Start the XEP-0059 plugin. |  | ||||||
|         """ |  | ||||||
|         self.xep = '0059' |  | ||||||
|         self.description = 'Result Set Management' |  | ||||||
|         self.stanza = sleekxmpp.plugins.xep_0059.stanza |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         """Handle inter-plugin dependencies.""" |  | ||||||
|         base_plugin.post_init(self) |  | ||||||
|         self.xmpp['xep_0030'].add_feature(Set.namespace) |  | ||||||
|  |  | ||||||
|     def iterate(self, stanza, interface): |  | ||||||
|         """ |  | ||||||
|         Create a new result set iterator for a given stanza query. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             stanza    -- A stanza object to serve as a template for |  | ||||||
|                          queries made each iteration. For example, a |  | ||||||
|                          basic disco#items query. |  | ||||||
|             interface -- The name of the substanza to which the |  | ||||||
|                          result set management stanza should be |  | ||||||
|                          appended. For example, for disco#items queries |  | ||||||
|                          the interface 'disco_items' should be used. |  | ||||||
|         """ |  | ||||||
|         return ResultIterator(stanza, interface) |  | ||||||
| @@ -1,108 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Erik Reuterborg Larsson |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET |  | ||||||
| from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Set(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0059 (Result Set Managment) can be used to manage the |  | ||||||
|     results of queries. For example, limiting the number of items |  | ||||||
|     per response or starting at certain positions. |  | ||||||
|  |  | ||||||
|     Example set stanzas: |  | ||||||
|     <iq type="get"> |  | ||||||
|       <query xmlns="http://jabber.org/protocol/disco#items"> |  | ||||||
|         <set xmlns="http://jabber.org/protocol/rsm"> |  | ||||||
|           <max>2</max> |  | ||||||
|         </set> |  | ||||||
|       </query> |  | ||||||
|     </iq> |  | ||||||
|  |  | ||||||
|     <iq type="result"> |  | ||||||
|       <query xmlns="http://jabber.org/protocol/disco#items"> |  | ||||||
|         <item jid="conference.example.com" /> |  | ||||||
|         <item jid="pubsub.example.com" /> |  | ||||||
|         <set xmlns="http://jabber.org/protocol/rsm"> |  | ||||||
|           <first>conference.example.com</first> |  | ||||||
|           <last>pubsub.example.com</last> |  | ||||||
|         </set> |  | ||||||
|       </query> |  | ||||||
|     </iq> |  | ||||||
|  |  | ||||||
|     Stanza Interface: |  | ||||||
|         first_index -- The index attribute of <first> |  | ||||||
|         after       -- The id defining from which item to start |  | ||||||
|         before      -- The id defining from which item to |  | ||||||
|                        start when browsing backwards |  | ||||||
|         max         -- Max amount per response |  | ||||||
|         first       -- Id for the first item in the response |  | ||||||
|         last        -- Id for the last item in the response |  | ||||||
|         index       -- Used to set an index to start from |  | ||||||
|         count       -- The number of remote items available |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         set_first_index -- Sets the index attribute for <first> and |  | ||||||
|                            creates the element if it doesn't exist |  | ||||||
|         get_first_index -- Returns the value of the index |  | ||||||
|                            attribute for <first> |  | ||||||
|         del_first_index -- Removes the index attribute for <first> |  | ||||||
|                            but keeps the element |  | ||||||
|         set_before      -- Sets the value of <before>, if the value is True |  | ||||||
|                            then the element will be created without a value |  | ||||||
|         get_before      -- Returns the value of <before>, if it is |  | ||||||
|                            empty it will return True |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     namespace = 'http://jabber.org/protocol/rsm' |  | ||||||
|     name = 'set' |  | ||||||
|     plugin_attrib = 'rsm' |  | ||||||
|     sub_interfaces = set(('first', 'after', 'before', 'count', |  | ||||||
|                           'index', 'last', 'max')) |  | ||||||
|     interfaces = set(('first_index', 'first', 'after', 'before', |  | ||||||
|                       'count', 'index', 'last', 'max')) |  | ||||||
|  |  | ||||||
|     def set_first_index(self, val): |  | ||||||
|         fi = self.find("{%s}first" % (self.namespace)) |  | ||||||
|         if fi is not None: |  | ||||||
|             if val: |  | ||||||
|                 fi.attrib['index'] = val |  | ||||||
|             else: |  | ||||||
|                 del fi.attrib['index'] |  | ||||||
|         elif val: |  | ||||||
|             fi = ET.Element("{%s}first" % (self.namespace)) |  | ||||||
|             fi.attrib['index'] = val |  | ||||||
|             self.xml.append(fi) |  | ||||||
|  |  | ||||||
|     def get_first_index(self): |  | ||||||
|         fi = self.find("{%s}first" % (self.namespace)) |  | ||||||
|         if fi is not None: |  | ||||||
|             return fi.attrib.get('index', '') |  | ||||||
|  |  | ||||||
|     def del_first_index(self): |  | ||||||
|         fi = self.xml.find("{%s}first" % (self.namespace)) |  | ||||||
|         if fi is not None: |  | ||||||
|             del fi.attrib['index'] |  | ||||||
|  |  | ||||||
|     def set_before(self, val): |  | ||||||
|         b = self.xml.find("{%s}before" % (self.namespace)) |  | ||||||
|         if b is None and val == True: |  | ||||||
|             self._set_sub_text('{%s}before' % self.namespace, '', True) |  | ||||||
|         else: |  | ||||||
|             self._set_sub_text('{%s}before' % self.namespace, val) |  | ||||||
|  |  | ||||||
|     def get_before(self): |  | ||||||
|         b = self.xml.find("{%s}before" % (self.namespace)) |  | ||||||
|         if b is not None and not b.text: |  | ||||||
|             return True |  | ||||||
|         elif b is not None: |  | ||||||
|             return b.text |  | ||||||
|         else: |  | ||||||
|             return None |  | ||||||
| @@ -2,13 +2,8 @@ from __future__ import with_statement | |||||||
| from . import base | from . import base | ||||||
| import logging | import logging | ||||||
| #from xml.etree import cElementTree as ET | #from xml.etree import cElementTree as ET | ||||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET | from .. xmlstream.stanzabase import ElementBase, ET | ||||||
| from . import stanza_pubsub | from . import stanza_pubsub | ||||||
| from . xep_0004 import Form |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class xep_0060(base.base_plugin): | class xep_0060(base.base_plugin): | ||||||
| 	""" | 	""" | ||||||
| @@ -18,7 +13,7 @@ class xep_0060(base.base_plugin): | |||||||
| 	def plugin_init(self): | 	def plugin_init(self): | ||||||
| 		self.xep = '0060' | 		self.xep = '0060' | ||||||
| 		self.description = 'Publish-Subscribe' | 		self.description = 'Publish-Subscribe' | ||||||
| 
 | 	 | ||||||
| 	def create_node(self, jid, node, config=None, collection=False, ntype=None): | 	def create_node(self, jid, node, config=None, collection=False, ntype=None): | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | ||||||
| 		create = ET.Element('create') | 		create = ET.Element('create') | ||||||
| @@ -46,57 +41,56 @@ class xep_0060(base.base_plugin): | |||||||
| 					submitform.field['pubsub#node_type'].setValue('leaf') | 					submitform.field['pubsub#node_type'].setValue('leaf') | ||||||
| 				else: | 				else: | ||||||
| 					submitform.addField('pubsub#node_type', value='leaf') | 					submitform.addField('pubsub#node_type', value='leaf') | ||||||
| 			submitform['type'] = 'submit' | 			configure.append(submitform.getXML('submit')) | ||||||
| 			configure.append(submitform.xml) |  | ||||||
| 		pubsub.append(configure) | 		pubsub.append(configure) | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) | 		iq = self.xmpp.makeIqSet(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False | 		if result is False or result is None or result['type'] == 'error': return False | ||||||
| 		return True | 		return True | ||||||
| 
 | 	 | ||||||
| 	def subscribe(self, jid, node, bare=True, subscribee=None): | 	def subscribe(self, jid, node, bare=True, subscribee=None): | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | ||||||
| 		subscribe = ET.Element('subscribe') | 		subscribe = ET.Element('subscribe') | ||||||
| 		subscribe.attrib['node'] = node | 		subscribe.attrib['node'] = node | ||||||
| 		if subscribee is None: | 		if subscribee is None: | ||||||
| 			if bare: | 			if bare: | ||||||
| 				subscribe.attrib['jid'] = self.xmpp.boundjid.bare | 				subscribe.attrib['jid'] = self.xmpp.jid | ||||||
| 			else: | 			else: | ||||||
| 				subscribe.attrib['jid'] = self.xmpp.boundjid.full | 				subscribe.attrib['jid'] = self.xmpp.fulljid | ||||||
| 		else: | 		else: | ||||||
| 			subscribe.attrib['jid'] = subscribee | 			subscribe.attrib['jid'] = subscribee | ||||||
| 		pubsub.append(subscribe) | 		pubsub.append(subscribe) | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) | 		iq = self.xmpp.makeIqSet(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False | 		if result is False or result is None or result['type'] == 'error': return False | ||||||
| 		return True | 		return True | ||||||
| 
 | 	 | ||||||
| 	def unsubscribe(self, jid, node, bare=True, subscribee=None): | 	def unsubscribe(self, jid, node, bare=True, subscribee=None): | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | ||||||
| 		unsubscribe = ET.Element('unsubscribe') | 		unsubscribe = ET.Element('unsubscribe') | ||||||
| 		unsubscribe.attrib['node'] = node | 		unsubscribe.attrib['node'] = node | ||||||
| 		if subscribee is None: | 		if subscribee is None: | ||||||
| 			if bare: | 			if bare: | ||||||
| 				unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare | 				unsubscribe.attrib['jid'] = self.xmpp.jid | ||||||
| 			else: | 			else: | ||||||
| 				unsubscribe.attrib['jid'] = self.xmpp.boundjid.full | 				unsubscribe.attrib['jid'] = self.xmpp.fulljid | ||||||
| 		else: | 		else: | ||||||
| 			unsubscribe.attrib['jid'] = subscribee | 			unsubscribe.attrib['jid'] = subscribee | ||||||
| 		pubsub.append(unsubscribe) | 		pubsub.append(unsubscribe) | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) | 		iq = self.xmpp.makeIqSet(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False | 		if result is False or result is None or result['type'] == 'error': return False | ||||||
| 		return True | 		return True | ||||||
| 
 | 	 | ||||||
| 	def getNodeConfig(self, jid, node=None): # if no node, then grab default | 	def getNodeConfig(self, jid, node=None): # if no node, then grab default | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') | 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') | ||||||
| 		if node is not None: | 		if node is not None: | ||||||
| @@ -109,22 +103,22 @@ class xep_0060(base.base_plugin): | |||||||
| 		iq = self.xmpp.makeIqGet() | 		iq = self.xmpp.makeIqGet() | ||||||
| 		iq.append(pubsub) | 		iq.append(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse) | 		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse) | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or result == False or result['type'] == 'error': | 		if result is None or result == False or result['type'] == 'error': | ||||||
| 			log.warning("got error instead of config") | 			logging.warning("got error instead of config") | ||||||
| 			return False | 			return False | ||||||
| 		if node is not None: | 		if node is not None: | ||||||
| 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x') | 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x') | ||||||
| 		else: | 		else: | ||||||
| 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x') | 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x') | ||||||
| 		if not form or form is None: | 		if not form or form is None: | ||||||
| 			log.error("No form found.") | 			logging.error("No form found.") | ||||||
| 			return False | 			return False | ||||||
| 		return Form(xml=form) | 		return self.xmpp.plugin['xep_0004'].buildForm(form) | ||||||
| 
 | 	 | ||||||
| 	def getNodeSubscriptions(self, jid, node): | 	def getNodeSubscriptions(self, jid, node): | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') | 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') | ||||||
| 		subscriptions = ET.Element('subscriptions') | 		subscriptions = ET.Element('subscriptions') | ||||||
| @@ -133,11 +127,11 @@ class xep_0060(base.base_plugin): | |||||||
| 		iq = self.xmpp.makeIqGet() | 		iq = self.xmpp.makeIqGet() | ||||||
| 		iq.append(pubsub) | 		iq.append(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or result == False or result['type'] == 'error': | 		if result is None or result == False or result['type'] == 'error': | ||||||
| 			log.warning("got error instead of config") | 			logging.warning("got error instead of config") | ||||||
| 			return False | 			return False | ||||||
| 		else: | 		else: | ||||||
| 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription') | 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription') | ||||||
| @@ -156,11 +150,11 @@ class xep_0060(base.base_plugin): | |||||||
| 		iq = self.xmpp.makeIqGet() | 		iq = self.xmpp.makeIqGet() | ||||||
| 		iq.append(pubsub) | 		iq.append(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or result == False or result['type'] == 'error': | 		if result is None or result == False or result['type'] == 'error': | ||||||
| 			log.warning("got error instead of config") | 			logging.warning("got error instead of config") | ||||||
| 			return False | 			return False | ||||||
| 		else: | 		else: | ||||||
| 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation') | 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation') | ||||||
| @@ -179,14 +173,14 @@ class xep_0060(base.base_plugin): | |||||||
| 		pubsub.append(delete) | 		pubsub.append(delete) | ||||||
| 		iq.append(pubsub) | 		iq.append(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is not None and result is not False and result['type'] != 'error': | 		if result is not None and result is not False and result['type'] != 'error': | ||||||
| 			return True | 			return True | ||||||
| 		else: | 		else: | ||||||
| 			return False | 			return False | ||||||
| 
 | 		 | ||||||
| 
 | 	 | ||||||
| 	def setNodeConfig(self, jid, node, config): | 	def setNodeConfig(self, jid, node, config): | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') | 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') | ||||||
| 		configure = ET.Element('configure') | 		configure = ET.Element('configure') | ||||||
| @@ -196,13 +190,13 @@ class xep_0060(base.base_plugin): | |||||||
| 		pubsub.append(configure) | 		pubsub.append(configure) | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) | 		iq = self.xmpp.makeIqSet(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or result['type'] == 'error': | 		if result is None or result['type'] == 'error':  | ||||||
| 			return False | 			return False | ||||||
| 		return True | 		return True | ||||||
| 
 | 	 | ||||||
| 	def setItem(self, jid, node, items=[]): | 	def setItem(self, jid, node, items=[]): | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') | ||||||
| 		publish = ET.Element('publish') | 		publish = ET.Element('publish') | ||||||
| @@ -217,12 +211,12 @@ class xep_0060(base.base_plugin): | |||||||
| 		pubsub.append(publish) | 		pubsub.append(publish) | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) | 		iq = self.xmpp.makeIqSet(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or result is False or result['type'] == 'error': return False | 		if result is None or result is False or result['type'] == 'error': return False | ||||||
| 		return True | 		return True | ||||||
| 
 | 	 | ||||||
| 	def addItem(self, jid, node, items=[]): | 	def addItem(self, jid, node, items=[]): | ||||||
| 		return self.setItem(jid, node, items) | 		return self.setItem(jid, node, items) | ||||||
| 
 | 
 | ||||||
| @@ -236,12 +230,12 @@ class xep_0060(base.base_plugin): | |||||||
| 		pubsub.append(retract) | 		pubsub.append(retract) | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) | 		iq = self.xmpp.makeIqSet(pubsub) | ||||||
| 		iq.attrib['to'] = jid | 		iq.attrib['to'] = jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or result is False or result['type'] == 'error': return False | 		if result is None or result is False or result['type'] == 'error': return False | ||||||
| 		return True | 		return True | ||||||
| 
 | 	 | ||||||
| 	def getNodes(self, jid): | 	def getNodes(self, jid): | ||||||
| 		response = self.xmpp.plugin['xep_0030'].getItems(jid) | 		response = self.xmpp.plugin['xep_0030'].getItems(jid) | ||||||
| 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') | 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') | ||||||
| @@ -250,7 +244,7 @@ class xep_0060(base.base_plugin): | |||||||
| 			for item in items: | 			for item in items: | ||||||
| 				nodes[item.get('node')] = item.get('name') | 				nodes[item.get('node')] = item.get('name') | ||||||
| 		return nodes | 		return nodes | ||||||
| 
 | 	 | ||||||
| 	def getItems(self, jid, node): | 	def getItems(self, jid, node): | ||||||
| 		response = self.xmpp.plugin['xep_0030'].getItems(jid, node) | 		response = self.xmpp.plugin['xep_0030'].getItems(jid, node) | ||||||
| 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') | 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') | ||||||
| @@ -268,7 +262,7 @@ class xep_0060(base.base_plugin): | |||||||
| 		try: | 		try: | ||||||
| 			config.field['pubsub#collection'].setValue(parent) | 			config.field['pubsub#collection'].setValue(parent) | ||||||
| 		except KeyError: | 		except KeyError: | ||||||
| 			log.warning("pubsub#collection doesn't exist in config, trying to add it") | 			logging.warning("pubsub#collection doesn't exist in config, trying to add it") | ||||||
| 			config.addField('pubsub#collection', value=parent) | 			config.addField('pubsub#collection', value=parent) | ||||||
| 		if not self.setNodeConfig(jid, child, config): | 		if not self.setNodeConfig(jid, child, config): | ||||||
| 			return False | 			return False | ||||||
| @@ -287,7 +281,7 @@ class xep_0060(base.base_plugin): | |||||||
| 		pubsub.append(affs) | 		pubsub.append(affs) | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) | 		iq = self.xmpp.makeIqSet(pubsub) | ||||||
| 		iq.attrib['to'] = ps_jid | 		iq.attrib['to'] = ps_jid | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
| 		id = iq['id'] | 		id = iq['id'] | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or result is False or result['type'] == 'error': | 		if result is None or result is False or result['type'] == 'error': | ||||||
| @@ -302,7 +296,7 @@ class xep_0060(base.base_plugin): | |||||||
| 		try: | 		try: | ||||||
| 			config.field['pubsub#collection'].setValue(parent) | 			config.field['pubsub#collection'].setValue(parent) | ||||||
| 		except KeyError: | 		except KeyError: | ||||||
| 			log.warning("pubsub#collection doesn't exist in config, trying to add it") | 			logging.warning("pubsub#collection doesn't exist in config, trying to add it") | ||||||
| 			config.addField('pubsub#collection', value=parent) | 			config.addField('pubsub#collection', value=parent) | ||||||
| 		if not self.setNodeConfig(jid, child, config): | 		if not self.setNodeConfig(jid, child, config): | ||||||
| 			return False | 			return False | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| from sleekxmpp.plugins.xep_0060.pubsub import xep_0060 |  | ||||||
| from sleekxmpp.plugins.xep_0060 import stanza |  | ||||||
| @@ -1,313 +0,0 @@ | |||||||
| from __future__ import with_statement |  | ||||||
| from sleekxmpp.plugins import base |  | ||||||
| import logging |  | ||||||
| #from xml.etree import cElementTree as ET |  | ||||||
| from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET |  | ||||||
| from sleekxmpp.plugins.xep_0060 import stanza |  | ||||||
| from sleekxmpp.plugins.xep_0004 import Form |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0060(base.base_plugin): |  | ||||||
| 	""" |  | ||||||
| 	XEP-0060 Publish Subscribe |  | ||||||
| 	""" |  | ||||||
|  |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = '0060' |  | ||||||
| 		self.description = 'Publish-Subscribe' |  | ||||||
|  |  | ||||||
| 	def create_node(self, jid, node, config=None, collection=False, ntype=None): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		create = ET.Element('create') |  | ||||||
| 		create.set('node', node) |  | ||||||
| 		pubsub.append(create) |  | ||||||
| 		configure = ET.Element('configure') |  | ||||||
| 		if collection: |  | ||||||
| 			ntype = 'collection' |  | ||||||
| 		#if config is None: |  | ||||||
| 		#	submitform = self.xmpp.plugin['xep_0004'].makeForm('submit') |  | ||||||
| 		#else: |  | ||||||
| 		if config is not None: |  | ||||||
| 			submitform = config |  | ||||||
| 			if 'FORM_TYPE' in submitform.field: |  | ||||||
| 				submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config') |  | ||||||
| 			else: |  | ||||||
| 				submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') |  | ||||||
| 			if ntype: |  | ||||||
| 				if 'pubsub#node_type' in submitform.field: |  | ||||||
| 					submitform.field['pubsub#node_type'].setValue(ntype) |  | ||||||
| 				else: |  | ||||||
| 					submitform.addField('pubsub#node_type', value=ntype) |  | ||||||
| 			else: |  | ||||||
| 				if 'pubsub#node_type' in submitform.field: |  | ||||||
| 					submitform.field['pubsub#node_type'].setValue('leaf') |  | ||||||
| 				else: |  | ||||||
| 					submitform.addField('pubsub#node_type', value='leaf') |  | ||||||
| 			submitform['type'] = 'submit' |  | ||||||
| 			configure.append(submitform.xml) |  | ||||||
| 		pubsub.append(configure) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def subscribe(self, jid, node, bare=True, subscribee=None): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		subscribe = ET.Element('subscribe') |  | ||||||
| 		subscribe.attrib['node'] = node |  | ||||||
| 		if subscribee is None: |  | ||||||
| 			if bare: |  | ||||||
| 				subscribe.attrib['jid'] = self.xmpp.boundjid.bare |  | ||||||
| 			else: |  | ||||||
| 				subscribe.attrib['jid'] = self.xmpp.boundjid.full |  | ||||||
| 		else: |  | ||||||
| 			subscribe.attrib['jid'] = subscribee |  | ||||||
| 		pubsub.append(subscribe) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def unsubscribe(self, jid, node, bare=True, subscribee=None): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		unsubscribe = ET.Element('unsubscribe') |  | ||||||
| 		unsubscribe.attrib['node'] = node |  | ||||||
| 		if subscribee is None: |  | ||||||
| 			if bare: |  | ||||||
| 				unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare |  | ||||||
| 			else: |  | ||||||
| 				unsubscribe.attrib['jid'] = self.xmpp.boundjid.full |  | ||||||
| 		else: |  | ||||||
| 			unsubscribe.attrib['jid'] = subscribee |  | ||||||
| 		pubsub.append(unsubscribe) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def getNodeConfig(self, jid, node=None): # if no node, then grab default |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		if node is not None: |  | ||||||
| 			configure = ET.Element('configure') |  | ||||||
| 			configure.attrib['node'] = node |  | ||||||
| 		else: |  | ||||||
| 			configure = ET.Element('default') |  | ||||||
| 		pubsub.append(configure) |  | ||||||
| 		#TODO: Add configure support. |  | ||||||
| 		iq = self.xmpp.makeIqGet() |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse) |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result == False or result['type'] == 'error': |  | ||||||
| 			log.warning("got error instead of config") |  | ||||||
| 			return False |  | ||||||
| 		if node is not None: |  | ||||||
| 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x') |  | ||||||
| 		else: |  | ||||||
| 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x') |  | ||||||
| 		if not form or form is None: |  | ||||||
| 			log.error("No form found.") |  | ||||||
| 			return False |  | ||||||
| 		return Form(xml=form) |  | ||||||
|  |  | ||||||
| 	def getNodeSubscriptions(self, jid, node): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		subscriptions = ET.Element('subscriptions') |  | ||||||
| 		subscriptions.attrib['node'] = node |  | ||||||
| 		pubsub.append(subscriptions) |  | ||||||
| 		iq = self.xmpp.makeIqGet() |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result == False or result['type'] == 'error': |  | ||||||
| 			log.warning("got error instead of config") |  | ||||||
| 			return False |  | ||||||
| 		else: |  | ||||||
| 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription') |  | ||||||
| 			if results is None: |  | ||||||
| 				return False |  | ||||||
| 			subs = {} |  | ||||||
| 			for sub in results: |  | ||||||
| 				subs[sub.get('jid')] = sub.get('subscription') |  | ||||||
| 			return subs |  | ||||||
|  |  | ||||||
| 	def getNodeAffiliations(self, jid, node): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		affiliations = ET.Element('affiliations') |  | ||||||
| 		affiliations.attrib['node'] = node |  | ||||||
| 		pubsub.append(affiliations) |  | ||||||
| 		iq = self.xmpp.makeIqGet() |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result == False or result['type'] == 'error': |  | ||||||
| 			log.warning("got error instead of config") |  | ||||||
| 			return False |  | ||||||
| 		else: |  | ||||||
| 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation') |  | ||||||
| 			if results is None: |  | ||||||
| 				return False |  | ||||||
| 			subs = {} |  | ||||||
| 			for sub in results: |  | ||||||
| 				subs[sub.get('jid')] = sub.get('affiliation') |  | ||||||
| 			return subs |  | ||||||
|  |  | ||||||
| 	def deleteNode(self, jid, node): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		iq = self.xmpp.makeIqSet() |  | ||||||
| 		delete = ET.Element('delete') |  | ||||||
| 		delete.attrib['node'] = node |  | ||||||
| 		pubsub.append(delete) |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is not None and result is not False and result['type'] != 'error': |  | ||||||
| 			return True |  | ||||||
| 		else: |  | ||||||
| 			return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	def setNodeConfig(self, jid, node, config): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		configure = ET.Element('configure') |  | ||||||
| 		configure.attrib['node'] = node |  | ||||||
| 		config = config.getXML('submit') |  | ||||||
| 		configure.append(config) |  | ||||||
| 		pubsub.append(configure) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result['type'] == 'error': |  | ||||||
| 			return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def setItem(self, jid, node, items=[]): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		publish = ET.Element('publish') |  | ||||||
| 		publish.attrib['node'] = node |  | ||||||
| 		for pub_item in items: |  | ||||||
| 			id, payload = pub_item |  | ||||||
| 			item = ET.Element('item') |  | ||||||
| 			if id is not None: |  | ||||||
| 				item.attrib['id'] = id |  | ||||||
| 			item.append(payload) |  | ||||||
| 			publish.append(item) |  | ||||||
| 		pubsub.append(publish) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result is False or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def addItem(self, jid, node, items=[]): |  | ||||||
| 		return self.setItem(jid, node, items) |  | ||||||
|  |  | ||||||
| 	def deleteItem(self, jid, node, item): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		retract = ET.Element('retract') |  | ||||||
| 		retract.attrib['node'] = node |  | ||||||
| 		itemn = ET.Element('item') |  | ||||||
| 		itemn.attrib['id'] = item |  | ||||||
| 		retract.append(itemn) |  | ||||||
| 		pubsub.append(retract) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result is False or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def getNodes(self, jid): |  | ||||||
| 		response = self.xmpp.plugin['xep_0030'].getItems(jid) |  | ||||||
| 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') |  | ||||||
| 		nodes = {} |  | ||||||
| 		if items is not None and items is not False: |  | ||||||
| 			for item in items: |  | ||||||
| 				nodes[item.get('node')] = item.get('name') |  | ||||||
| 		return nodes |  | ||||||
|  |  | ||||||
| 	def getItems(self, jid, node): |  | ||||||
| 		response = self.xmpp.plugin['xep_0030'].getItems(jid, node) |  | ||||||
| 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') |  | ||||||
| 		nodeitems = [] |  | ||||||
| 		if items is not None and items is not False: |  | ||||||
| 			for item in items: |  | ||||||
| 				nodeitems.append(item.get('node')) |  | ||||||
| 		return nodeitems |  | ||||||
|  |  | ||||||
| 	def addNodeToCollection(self, jid, child, parent=''): |  | ||||||
| 		config = self.getNodeConfig(jid, child) |  | ||||||
| 		if not config or config is None: |  | ||||||
| 			self.lasterror = "Config Error" |  | ||||||
| 			return False |  | ||||||
| 		try: |  | ||||||
| 			config.field['pubsub#collection'].setValue(parent) |  | ||||||
| 		except KeyError: |  | ||||||
| 			log.warning("pubsub#collection doesn't exist in config, trying to add it") |  | ||||||
| 			config.addField('pubsub#collection', value=parent) |  | ||||||
| 		if not self.setNodeConfig(jid, child, config): |  | ||||||
| 			return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def modifyAffiliation(self, ps_jid, node, user_jid, affiliation): |  | ||||||
| 		if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'): |  | ||||||
| 			raise TypeError |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		affs = ET.Element('affiliations') |  | ||||||
| 		affs.attrib['node'] = node |  | ||||||
| 		aff = ET.Element('affiliation') |  | ||||||
| 		aff.attrib['jid'] = user_jid |  | ||||||
| 		aff.attrib['affiliation'] = affiliation |  | ||||||
| 		affs.append(aff) |  | ||||||
| 		pubsub.append(affs) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = ps_jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result is False or result['type'] == 'error': |  | ||||||
| 		    return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def addNodeToCollection(self, jid, child, parent=''): |  | ||||||
| 		config = self.getNodeConfig(jid, child) |  | ||||||
| 		if not config or config is None: |  | ||||||
| 			self.lasterror = "Config Error" |  | ||||||
| 			return False |  | ||||||
| 		try: |  | ||||||
| 			config.field['pubsub#collection'].setValue(parent) |  | ||||||
| 		except KeyError: |  | ||||||
| 			log.warning("pubsub#collection doesn't exist in config, trying to add it") |  | ||||||
| 			config.addField('pubsub#collection', value=parent) |  | ||||||
| 		if not self.setNodeConfig(jid, child, config): |  | ||||||
| 			return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def removeNodeFromCollection(self, jid, child): |  | ||||||
| 		self.addNodeToCollection(jid, child, '') |  | ||||||
|  |  | ||||||
| @@ -1,3 +0,0 @@ | |||||||
| from sleekxmpp.plugins.xep_0060.stanza.pubsub import Pubsub, Affiliation, Affiliations, Subscription, Subscriptions, SubscribeOptions, Item, Items, Create, Publish, Retract, Unsubscribe, Subscribe, Configure, Options, PubsubState, PubsubStateEvent |  | ||||||
| from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import PubsubOwner, DefaultConfig, OwnerAffiliations, OwnerAffiliation, OwnerConfigure, OwnerDefault, OwnerDelete, OwnerPurge, OwnerRedirect, OwnerSubscriptions, OwnerSubscription |  | ||||||
| from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import Event, EventItem, EventRetract, EventItems, EventCollection, EventAssociate, EventDisassociate, EventConfiguration, EventPurge, EventSubscription |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| from xml.etree import cElementTree as ET |  | ||||||
|  |  | ||||||
| class OptionalSetting(object): |  | ||||||
| 	interfaces = set(('required',)) |  | ||||||
|  |  | ||||||
| 	def setRequired(self, value): |  | ||||||
| 		value = bool(value) |  | ||||||
| 		if value and not self['required']: |  | ||||||
| 			self.xml.append(ET.Element("{%s}required" % self.namespace)) |  | ||||||
| 		elif not value and self['required']: |  | ||||||
| 			self.delRequired() |  | ||||||
| 	 |  | ||||||
| 	def getRequired(self): |  | ||||||
| 		required = self.xml.find("{%s}required" % self.namespace) |  | ||||||
| 		if required is not None: |  | ||||||
| 			return True |  | ||||||
| 		else: |  | ||||||
| 			return False |  | ||||||
| 	 |  | ||||||
| 	def delRequired(self): |  | ||||||
| 		required = self.xml.find("{%s}required" % self.namespace) |  | ||||||
| 		if required is not None: |  | ||||||
| 			self.xml.remove(required) |  | ||||||
|  |  | ||||||
| @@ -1,277 +0,0 @@ | |||||||
| from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID |  | ||||||
| from sleekxmpp.stanza.iq import Iq |  | ||||||
| from sleekxmpp.stanza.message import Message |  | ||||||
| from sleekxmpp.basexmpp import basexmpp |  | ||||||
| from sleekxmpp.xmlstream.xmlstream import XMLStream |  | ||||||
| import logging |  | ||||||
| from sleekxmpp.plugins import xep_0004 |  | ||||||
| from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Pubsub(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'pubsub' |  | ||||||
| 	plugin_attrib = 'pubsub' |  | ||||||
| 	interfaces = set(tuple()) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Iq, Pubsub) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Affiliation(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'affiliation' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node', 'affiliation')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| class Affiliations(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'affiliations' |  | ||||||
| 	plugin_attrib = 'affiliations' |  | ||||||
| 	interfaces = set(tuple()) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	subitem = (Affiliation,) |  | ||||||
|  |  | ||||||
| 	def append(self, affiliation): |  | ||||||
| 		if not isinstance(affiliation, Affiliation): |  | ||||||
| 			raise TypeError |  | ||||||
| 		self.xml.append(affiliation.xml) |  | ||||||
| 		return self.iterables.append(affiliation) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Affiliations) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Subscription(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'subscription' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('jid', 'node', 'subscription', 'subid')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def setjid(self, value): |  | ||||||
| 		self._setattr('jid', str(value)) |  | ||||||
|  |  | ||||||
| 	def getjid(self): |  | ||||||
| 		return jid(self._getattr('jid')) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Subscription) |  | ||||||
|  |  | ||||||
| class Subscriptions(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'subscriptions' |  | ||||||
| 	plugin_attrib = 'subscriptions' |  | ||||||
| 	interfaces = set(tuple()) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	subitem = (Subscription,) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Subscriptions) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SubscribeOptions(ElementBase, OptionalSetting): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'subscribe-options' |  | ||||||
| 	plugin_attrib = 'suboptions' |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	interfaces = set(('required',)) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Subscription, SubscribeOptions) |  | ||||||
|  |  | ||||||
| class Item(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'item' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('id', 'payload')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def setPayload(self, value): |  | ||||||
| 		self.xml.append(value) |  | ||||||
|  |  | ||||||
| 	def getPayload(self): |  | ||||||
| 		childs = self.xml.getchildren() |  | ||||||
| 		if len(childs) > 0: |  | ||||||
| 			return childs[0] |  | ||||||
|  |  | ||||||
| 	def delPayload(self): |  | ||||||
| 		for child in self.xml.getchildren(): |  | ||||||
| 			self.xml.remove(child) |  | ||||||
|  |  | ||||||
| class Items(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'items' |  | ||||||
| 	plugin_attrib = 'items' |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	subitem = (Item,) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Items) |  | ||||||
|  |  | ||||||
| class Create(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'create' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Create) |  | ||||||
|  |  | ||||||
| #class Default(ElementBase): |  | ||||||
| #	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| #	name = 'default' |  | ||||||
| #	plugin_attrib = name |  | ||||||
| #	interfaces = set(('node', 'type')) |  | ||||||
| #	plugin_attrib_map = {} |  | ||||||
| #	plugin_tag_map = {} |  | ||||||
| # |  | ||||||
| #	def getType(self): |  | ||||||
| #		t = self._getAttr('type') |  | ||||||
| #		if not t: t == 'leaf' |  | ||||||
| #		return t |  | ||||||
| # |  | ||||||
| #registerStanzaPlugin(Pubsub, Default) |  | ||||||
|  |  | ||||||
| class Publish(Items): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'publish' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	subitem = (Item,) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Publish) |  | ||||||
|  |  | ||||||
| class Retract(Items): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'retract' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node', 'notify')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Retract) |  | ||||||
|  |  | ||||||
| class Unsubscribe(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'unsubscribe' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node', 'jid')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def setJid(self, value): |  | ||||||
| 		self._setAttr('jid', str(value)) |  | ||||||
|  |  | ||||||
| 	def getJid(self): |  | ||||||
| 		return JID(self._getAttr('jid')) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Unsubscribe) |  | ||||||
|  |  | ||||||
| class Subscribe(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'subscribe' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node', 'jid')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def setJid(self, value): |  | ||||||
| 		self._setAttr('jid', str(value)) |  | ||||||
|  |  | ||||||
| 	def getJid(self): |  | ||||||
| 		return JID(self._getAttr('jid')) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Subscribe) |  | ||||||
|  |  | ||||||
| class Configure(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'configure' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node', 'type')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def getType(self): |  | ||||||
| 		t = self._getAttr('type') |  | ||||||
| 		if not t: t == 'leaf' |  | ||||||
| 		return t |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Configure) |  | ||||||
| registerStanzaPlugin(Configure, xep_0004.Form) |  | ||||||
|  |  | ||||||
| class Options(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub' |  | ||||||
| 	name = 'options' |  | ||||||
| 	plugin_attrib = 'options' |  | ||||||
| 	interfaces = set(('jid', 'node', 'options')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def __init__(self, *args, **kwargs): |  | ||||||
| 		ElementBase.__init__(self, *args, **kwargs) |  | ||||||
|  |  | ||||||
| 	def getOptions(self): |  | ||||||
| 		config = self.xml.find('{jabber:x:data}x') |  | ||||||
| 		form = xep_0004.Form() |  | ||||||
| 		if config is not None: |  | ||||||
| 			form.fromXML(config) |  | ||||||
| 		return form |  | ||||||
|  |  | ||||||
| 	def setOptions(self, value): |  | ||||||
| 		self.xml.append(value.getXML()) |  | ||||||
| 		return self |  | ||||||
|  |  | ||||||
| 	def delOptions(self): |  | ||||||
| 		config = self.xml.find('{jabber:x:data}x') |  | ||||||
| 		self.xml.remove(config) |  | ||||||
|  |  | ||||||
| 	def setJid(self, value): |  | ||||||
| 		self._setAttr('jid', str(value)) |  | ||||||
|  |  | ||||||
| 	def getJid(self): |  | ||||||
| 		return JID(self._getAttr('jid')) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Pubsub, Options) |  | ||||||
| registerStanzaPlugin(Subscribe, Options) |  | ||||||
|  |  | ||||||
| class PubsubState(ElementBase): |  | ||||||
|     namespace = 'http://jabber.org/protocol/psstate' |  | ||||||
|     name = 'state' |  | ||||||
|     plugin_attrib = 'psstate' |  | ||||||
|     interfaces = set(('node', 'item', 'payload')) |  | ||||||
|     plugin_attrib_map = {} |  | ||||||
|     plugin_tag_map = {} |  | ||||||
|  |  | ||||||
|     def setPayload(self, value): |  | ||||||
|         self.xml.append(value) |  | ||||||
|  |  | ||||||
|     def getPayload(self): |  | ||||||
|         childs = self.xml.getchildren() |  | ||||||
|         if len(childs) > 0: |  | ||||||
|             return childs[0] |  | ||||||
|  |  | ||||||
|     def delPayload(self): |  | ||||||
|         for child in self.xml.getchildren(): |  | ||||||
|             self.xml.remove(child) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Iq, PubsubState) |  | ||||||
|  |  | ||||||
| class PubsubStateEvent(ElementBase): |  | ||||||
|     namespace = 'http://jabber.org/protocol/psstate#event' |  | ||||||
|     name = 'event' |  | ||||||
|     plugin_attrib = 'psstate_event' |  | ||||||
|     intefaces = set(tuple()) |  | ||||||
|     plugin_attrib_map = {} |  | ||||||
|     plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Message, PubsubStateEvent) |  | ||||||
| registerStanzaPlugin(PubsubStateEvent, PubsubState) |  | ||||||
| @@ -1,124 +0,0 @@ | |||||||
| from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID |  | ||||||
| from sleekxmpp.stanza.iq import Iq |  | ||||||
| from sleekxmpp.stanza.message import Message |  | ||||||
| from sleekxmpp.basexmpp import basexmpp |  | ||||||
| from sleekxmpp.xmlstream.xmlstream import XMLStream |  | ||||||
| import logging |  | ||||||
| from sleekxmpp.plugins import xep_0004 |  | ||||||
|  |  | ||||||
| class Event(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'event' |  | ||||||
| 	plugin_attrib = 'pubsub_event' |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Message, Event) |  | ||||||
|  |  | ||||||
| class EventItem(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'item' |  | ||||||
| 	plugin_attrib = 'item' |  | ||||||
| 	interfaces = set(('id', 'payload')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def setPayload(self, value): |  | ||||||
| 		self.xml.append(value) |  | ||||||
| 	 |  | ||||||
| 	def getPayload(self): |  | ||||||
| 		childs = self.xml.getchildren() |  | ||||||
| 		if len(childs) > 0: |  | ||||||
| 			return childs[0] |  | ||||||
| 	 |  | ||||||
| 	def delPayload(self): |  | ||||||
| 		for child in self.xml.getchildren(): |  | ||||||
| 			self.xml.remove(child) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EventRetract(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'retract' |  | ||||||
| 	plugin_attrib = 'retract' |  | ||||||
| 	interfaces = set(('id',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| class EventItems(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'items' |  | ||||||
| 	plugin_attrib = 'items' |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	subitem = (EventItem, EventRetract) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Event, EventItems) |  | ||||||
|  |  | ||||||
| class EventCollection(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'collection' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Event, EventCollection) |  | ||||||
|  |  | ||||||
| class EventAssociate(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'associate' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(EventCollection, EventAssociate) |  | ||||||
|  |  | ||||||
| class EventDisassociate(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'disassociate' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(EventCollection, EventDisassociate) |  | ||||||
|  |  | ||||||
| class EventConfiguration(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'configuration' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node', 'config')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	 |  | ||||||
| registerStanzaPlugin(Event, EventConfiguration) |  | ||||||
| registerStanzaPlugin(EventConfiguration, xep_0004.Form) |  | ||||||
|  |  | ||||||
| class EventPurge(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'purge' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Event, EventPurge) |  | ||||||
|  |  | ||||||
| class EventSubscription(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' |  | ||||||
| 	name = 'subscription' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node','expiry', 'jid', 'subid', 'subscription')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	 |  | ||||||
| 	def setJid(self, value): |  | ||||||
| 		self._setAttr('jid', str(value)) |  | ||||||
| 	 |  | ||||||
| 	def getJid(self): |  | ||||||
| 		return JID(self._getAttr('jid')) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Event, EventSubscription) |  | ||||||
| @@ -1,152 +0,0 @@ | |||||||
| from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID |  | ||||||
| from sleekxmpp.stanza.iq import Iq |  | ||||||
| from sleekxmpp.stanza.message import Message |  | ||||||
| from sleekxmpp.basexmpp import basexmpp |  | ||||||
| from sleekxmpp.xmlstream.xmlstream import XMLStream |  | ||||||
| import logging |  | ||||||
| from sleekxmpp.plugins import xep_0004 |  | ||||||
| from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting |  | ||||||
| from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation, Configure, Subscriptions |  | ||||||
|  |  | ||||||
| class PubsubOwner(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	name = 'pubsub' |  | ||||||
| 	plugin_attrib = 'pubsub_owner' |  | ||||||
| 	interfaces = set(tuple()) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Iq, PubsubOwner) |  | ||||||
|  |  | ||||||
| class DefaultConfig(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	name = 'default' |  | ||||||
| 	plugin_attrib = 'default' |  | ||||||
| 	interfaces = set(('node', 'type', 'config')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def __init__(self, *args, **kwargs): |  | ||||||
| 		ElementBase.__init__(self, *args, **kwargs) |  | ||||||
|  |  | ||||||
| 	def getType(self): |  | ||||||
| 		t = self._getAttr('type') |  | ||||||
| 		if not t: t = 'leaf' |  | ||||||
| 		return t |  | ||||||
|  |  | ||||||
| 	def getConfig(self): |  | ||||||
| 		return self['form'] |  | ||||||
|  |  | ||||||
| 	def setConfig(self, value): |  | ||||||
| 		self['form'].setStanzaValues(value.getStanzaValues()) |  | ||||||
| 		return self |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(PubsubOwner, DefaultConfig) |  | ||||||
| registerStanzaPlugin(DefaultConfig, xep_0004.Form) |  | ||||||
|  |  | ||||||
| class OwnerAffiliations(Affiliations): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	interfaces = set(('node')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def append(self, affiliation): |  | ||||||
| 		if not isinstance(affiliation, OwnerAffiliation): |  | ||||||
| 			raise TypeError |  | ||||||
| 		self.xml.append(affiliation.xml) |  | ||||||
| 		return self.affiliations.append(affiliation) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(PubsubOwner, OwnerAffiliations) |  | ||||||
|  |  | ||||||
| class OwnerAffiliation(Affiliation): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	interfaces = set(('affiliation', 'jid')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| class OwnerConfigure(Configure): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	interfaces = set(('node', 'config')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(PubsubOwner, OwnerConfigure) |  | ||||||
|  |  | ||||||
| class OwnerDefault(OwnerConfigure): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	interfaces = set(('node', 'config')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def getConfig(self): |  | ||||||
| 		return self['form'] |  | ||||||
|  |  | ||||||
| 	def setConfig(self, value): |  | ||||||
| 		self['form'].setStanzaValues(value.getStanzaValues()) |  | ||||||
| 		return self |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(PubsubOwner, OwnerDefault) |  | ||||||
| registerStanzaPlugin(OwnerDefault, xep_0004.Form) |  | ||||||
|  |  | ||||||
| class OwnerDelete(ElementBase, OptionalSetting): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	name = 'delete' |  | ||||||
| 	plugin_attrib = 'delete' |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(PubsubOwner, OwnerDelete) |  | ||||||
|  |  | ||||||
| class OwnerPurge(ElementBase, OptionalSetting): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	name = 'purge' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(PubsubOwner, OwnerPurge) |  | ||||||
|  |  | ||||||
| class OwnerRedirect(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	name = 'redirect' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('node', 'jid')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def setJid(self, value): |  | ||||||
| 		self._setAttr('jid', str(value)) |  | ||||||
|  |  | ||||||
| 	def getJid(self): |  | ||||||
| 		return JID(self._getAttr('jid')) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(OwnerDelete, OwnerRedirect) |  | ||||||
|  |  | ||||||
| class OwnerSubscriptions(Subscriptions): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	interfaces = set(('node',)) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def append(self, subscription): |  | ||||||
| 		if not isinstance(subscription, OwnerSubscription): |  | ||||||
| 			raise TypeError |  | ||||||
| 		self.xml.append(subscription.xml) |  | ||||||
| 		return self.subscriptions.append(subscription) |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(PubsubOwner, OwnerSubscriptions) |  | ||||||
|  |  | ||||||
| class OwnerSubscription(ElementBase): |  | ||||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' |  | ||||||
| 	name = 'subscription' |  | ||||||
| 	plugin_attrib = name |  | ||||||
| 	interfaces = set(('jid', 'subscription')) |  | ||||||
| 	plugin_attrib_map = {} |  | ||||||
| 	plugin_tag_map = {} |  | ||||||
|  |  | ||||||
| 	def setJid(self, value): |  | ||||||
| 		self._setAttr('jid', str(value)) |  | ||||||
|  |  | ||||||
| 	def getJid(self): |  | ||||||
| 		return JID(self._getAttr('from')) |  | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0066 import stanza |  | ||||||
| from sleekxmpp.plugins.xep_0066.stanza import OOB, OOBTransfer |  | ||||||
| from sleekxmpp.plugins.xep_0066.oob import xep_0066 |  | ||||||
| @@ -1,154 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Message, Presence, Iq |  | ||||||
| from sleekxmpp.exceptions import XMPPError |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
| from sleekxmpp.xmlstream.handler import Callback |  | ||||||
| from sleekxmpp.xmlstream.matcher import StanzaPath |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.plugins.xep_0066 import stanza |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0066(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0066: Out-of-Band Data |  | ||||||
|  |  | ||||||
|     Out-of-Band Data is a basic method for transferring files between |  | ||||||
|     XMPP agents. The URL of the resource in question is sent to the receiving |  | ||||||
|     entity, which then downloads the resource before responding to the OOB |  | ||||||
|     request. OOB is also used as a generic means to transmit URLs in other |  | ||||||
|     stanzas to indicate where to find additional information. |  | ||||||
|  |  | ||||||
|     Also see <http://www.xmpp.org/extensions/xep-0066.html>. |  | ||||||
|  |  | ||||||
|     Events: |  | ||||||
|         oob_transfer -- Raised when a request to download a resource |  | ||||||
|                         has been received. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         send_oob -- Send a request to another entity to download a file |  | ||||||
|                     or other addressable resource. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         """Start the XEP-0066 plugin.""" |  | ||||||
|         self.xep = '0066' |  | ||||||
|         self.description = 'Out-of-Band Transfer' |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         self.url_handlers = {'global': self._default_handler, |  | ||||||
|                              'jid': {}} |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, stanza.OOBTransfer) |  | ||||||
|         register_stanza_plugin(Message, stanza.OOB) |  | ||||||
|         register_stanza_plugin(Presence, stanza.OOB) |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('OOB Transfer', |  | ||||||
|                          StanzaPath('iq@type=set/oob_transfer'), |  | ||||||
|                          self._handle_transfer)) |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         """Handle cross-plugin dependencies.""" |  | ||||||
|         base_plugin.post_init(self) |  | ||||||
|         self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace) |  | ||||||
|         self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace) |  | ||||||
|  |  | ||||||
|     def register_url_handler(self, jid=None, handler=None): |  | ||||||
|         """ |  | ||||||
|         Register a handler to process download requests, either for all |  | ||||||
|         JIDs or a single JID. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid     -- If None, then set the handler as a global default. |  | ||||||
|             handler -- If None, then remove the existing handler for the |  | ||||||
|                        given JID, or reset the global handler if the JID |  | ||||||
|                        is None. |  | ||||||
|         """ |  | ||||||
|         if jid is None: |  | ||||||
|             if handler is not None: |  | ||||||
|                 self.url_handlers['global'] = handler |  | ||||||
|             else: |  | ||||||
|                 self.url_handlers['global'] = self._default_handler |  | ||||||
|         else: |  | ||||||
|             if handler is not None: |  | ||||||
|                 self.url_handlers['jid'][jid] = handler |  | ||||||
|             else: |  | ||||||
|                 del self.url_handlers['jid'][jid] |  | ||||||
|  |  | ||||||
|     def send_oob(self, to, url, desc=None, ifrom=None, **iqargs): |  | ||||||
|         """ |  | ||||||
|         Initiate a basic file transfer by sending the URL of |  | ||||||
|         a file or other resource. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             url      -- The URL of the resource to transfer. |  | ||||||
|             desc     -- An optional human readable description of the item |  | ||||||
|                         that is to be transferred. |  | ||||||
|             ifrom    -- Specifiy the sender's JID. |  | ||||||
|             block    -- If true, block and wait for the stanzas' reply. |  | ||||||
|             timeout  -- The time in seconds to block while waiting for |  | ||||||
|                         a reply. If None, then wait indefinitely. |  | ||||||
|             callback -- Optional callback to execute when a reply is |  | ||||||
|                         received instead of blocking and waiting for |  | ||||||
|                         the reply. |  | ||||||
|         """ |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         iq['type'] = 'set' |  | ||||||
|         iq['to'] = to |  | ||||||
|         if ifrom: |  | ||||||
|             iq['from'] = ifrom |  | ||||||
|         iq['oob_transfer']['url'] = url |  | ||||||
|         iq['oob_transfer']['desc'] = desc |  | ||||||
|         return iq.send(**iqargs) |  | ||||||
|  |  | ||||||
|     def _run_url_handler(self, iq): |  | ||||||
|         """ |  | ||||||
|         Execute the appropriate handler for a transfer request. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The Iq stanza containing the OOB transfer request. |  | ||||||
|         """ |  | ||||||
|         if iq['to'] in self.url_handlers['jid']: |  | ||||||
|             return self.url_handlers['jid'][jid](iq) |  | ||||||
|         else: |  | ||||||
|             if self.url_handlers['global']: |  | ||||||
|                 self.url_handlers['global'](iq) |  | ||||||
|             else: |  | ||||||
|                 raise XMPPError('service-unavailable') |  | ||||||
|  |  | ||||||
|     def _default_handler(self, iq): |  | ||||||
|         """ |  | ||||||
|         As a safe default, don't actually download files. |  | ||||||
|  |  | ||||||
|         Register a new handler using self.register_url_handler to |  | ||||||
|         screen requests and download files. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The Iq stanza containing the OOB transfer request. |  | ||||||
|         """ |  | ||||||
|         raise XMPPError('service-unavailable') |  | ||||||
|  |  | ||||||
|     def _handle_transfer(self, iq): |  | ||||||
|         """ |  | ||||||
|         Handle receiving an out-of-band transfer request. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- An Iq stanza containing an OOB transfer request. |  | ||||||
|         """ |  | ||||||
|         log.debug('Received out-of-band data request for %s from %s:' % ( |  | ||||||
|             iq['oob_transfer']['url'], iq['from'])) |  | ||||||
|         self._run_url_handler(iq) |  | ||||||
|         iq.reply().send() |  | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.xmlstream import ElementBase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OOBTransfer(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'query' |  | ||||||
|     namespace = 'jabber:iq:oob' |  | ||||||
|     plugin_attrib = 'oob_transfer' |  | ||||||
|     interfaces = set(('url', 'desc', 'sid')) |  | ||||||
|     sub_interfaces = set(('url', 'desc')) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class OOB(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'x' |  | ||||||
|     namespace = 'jabber:x:oob' |  | ||||||
|     plugin_attrib = 'oob' |  | ||||||
|     interfaces = set(('url', 'desc')) |  | ||||||
|     sub_interfaces = interfaces |  | ||||||
| @@ -1,9 +1,21 @@ | |||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library | 	SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz | 	Copyright (C) 2007  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. | 	This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. | 	SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  | 	it under the terms of the GNU General Public License as published by | ||||||
|  | 	the Free Software Foundation; either version 2 of the License, or | ||||||
|  | 	(at your option) any later version. | ||||||
|  |  | ||||||
|  | 	SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  | 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | 	GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | 	You should have received a copy of the GNU General Public License | ||||||
|  | 	along with SleekXMPP; if not, write to the Free Software | ||||||
|  | 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
| """ | """ | ||||||
| from __future__ import with_statement | from __future__ import with_statement | ||||||
| from xml.etree import cElementTree as ET | from xml.etree import cElementTree as ET | ||||||
| @@ -12,9 +24,6 @@ import hashlib | |||||||
| from . import base | from . import base | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0078(base.base_plugin): | class xep_0078(base.base_plugin): | ||||||
| 	""" | 	""" | ||||||
| 	XEP-0078 NON-SASL Authentication | 	XEP-0078 NON-SASL Authentication | ||||||
| @@ -26,17 +35,17 @@ class xep_0078(base.base_plugin): | |||||||
| 		#disabling until I fix conflict with PLAIN | 		#disabling until I fix conflict with PLAIN | ||||||
| 		#self.xmpp.registerFeature("<auth xmlns='http://jabber.org/features/iq-auth'/>", self.auth) | 		#self.xmpp.registerFeature("<auth xmlns='http://jabber.org/features/iq-auth'/>", self.auth) | ||||||
| 		self.streamid = '' | 		self.streamid = '' | ||||||
|  | 	 | ||||||
| 	def check_stream(self, xml): | 	def check_stream(self, xml): | ||||||
| 		self.streamid = xml.attrib['id'] | 		self.streamid = xml.attrib['id'] | ||||||
| 		if xml.get('version', '0') != '1.0': | 		if xml.get('version', '0') != '1.0': | ||||||
| 			self.auth() | 			self.auth() | ||||||
|  | 	 | ||||||
| 	def auth(self, xml=None): | 	def auth(self, xml=None): | ||||||
| 		log.debug("Starting jabber:iq:auth Authentication") | 		logging.debug("Starting jabber:iq:auth Authentication") | ||||||
| 		auth_request = self.xmpp.makeIqGet() | 		auth_request = self.xmpp.makeIqGet() | ||||||
| 		auth_request_query = ET.Element('{jabber:iq:auth}query') | 		auth_request_query = ET.Element('{jabber:iq:auth}query') | ||||||
| 		auth_request.attrib['to'] = self.xmpp.boundjid.host | 		auth_request.attrib['to'] = self.xmpp.server | ||||||
| 		username = ET.Element('username') | 		username = ET.Element('username') | ||||||
| 		username.text = self.xmpp.username | 		username.text = self.xmpp.username | ||||||
| 		auth_request_query.append(username) | 		auth_request_query.append(username) | ||||||
| @@ -50,12 +59,12 @@ class xep_0078(base.base_plugin): | |||||||
| 		query.append(username) | 		query.append(username) | ||||||
| 		query.append(resource) | 		query.append(resource) | ||||||
| 		if rquery.find('{jabber:iq:auth}digest') is None: | 		if rquery.find('{jabber:iq:auth}digest') is None: | ||||||
| 			log.warning("Authenticating via jabber:iq:auth Plain.") | 			logging.warning("Authenticating via jabber:iq:auth Plain.") | ||||||
| 			password = ET.Element('password') | 			password = ET.Element('password') | ||||||
| 			password.text = self.xmpp.password | 			password.text = self.xmpp.password | ||||||
| 			query.append(password) | 			query.append(password) | ||||||
| 		else: | 		else: | ||||||
| 			log.debug("Authenticating via jabber:iq:auth Digest") | 			logging.debug("Authenticating via jabber:iq:auth Digest") | ||||||
| 			digest = ET.Element('digest') | 			digest = ET.Element('digest') | ||||||
| 			digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest() | 			digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest() | ||||||
| 			query.append(digest) | 			query.append(digest) | ||||||
| @@ -67,6 +76,6 @@ class xep_0078(base.base_plugin): | |||||||
| 				self.xmpp.sessionstarted = True | 				self.xmpp.sessionstarted = True | ||||||
| 			self.xmpp.event("session_start") | 			self.xmpp.event("session_start") | ||||||
| 		else: | 		else: | ||||||
| 			log.info("Authentication failed") | 			logging.info("Authentication failed") | ||||||
| 			self.xmpp.disconnect() | 			self.xmpp.disconnect() | ||||||
| 			self.xmpp.event("failed_auth") | 			self.xmpp.event("failed_auth") | ||||||
|   | |||||||
| @@ -1,206 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| import datetime as dt |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.thirdparty import tzutc, tzoffset, parse_iso |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # ===================================================================== |  | ||||||
| # To make it easier for stanzas without direct access to plugin objects |  | ||||||
| # to use the XEP-0082 utility methods, we will define them as top-level |  | ||||||
| # functions and then just reference them in the plugin itself. |  | ||||||
|  |  | ||||||
| def parse(time_str): |  | ||||||
|     """ |  | ||||||
|     Convert a string timestamp into a datetime object. |  | ||||||
|  |  | ||||||
|     Arguments: |  | ||||||
|         time_str -- A formatted timestamp string. |  | ||||||
|     """ |  | ||||||
|     return parse_iso(time_str) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def format_date(time_obj): |  | ||||||
|     """ |  | ||||||
|     Return a formatted string version of a date object. |  | ||||||
|  |  | ||||||
|     Format: |  | ||||||
|         YYYY-MM-DD |  | ||||||
|  |  | ||||||
|     Arguments: |  | ||||||
|         time_obj -- A date or datetime object. |  | ||||||
|     """ |  | ||||||
|     if isinstance(time_obj, dt.datetime): |  | ||||||
|         time_obj = time_obj.date() |  | ||||||
|     return time_obj.isoformat() |  | ||||||
|  |  | ||||||
| def format_time(time_obj): |  | ||||||
|     """ |  | ||||||
|     Return a formatted string version of a time object. |  | ||||||
|  |  | ||||||
|     format: |  | ||||||
|         hh:mm:ss[.sss][TZD] |  | ||||||
|  |  | ||||||
|     arguments: |  | ||||||
|         time_obj -- A time or datetime object. |  | ||||||
|     """ |  | ||||||
|     if isinstance(time_obj, dt.datetime): |  | ||||||
|         time_obj = time_obj.timetz() |  | ||||||
|     timestamp = time_obj.isoformat() |  | ||||||
|     if time_obj.tzinfo == tzutc(): |  | ||||||
|         timestamp = timestamp[:-6] |  | ||||||
|         return '%sZ' % timestamp |  | ||||||
|     return timestamp |  | ||||||
|  |  | ||||||
| def format_datetime(time_obj): |  | ||||||
|     """ |  | ||||||
|     Return a formatted string version of a datetime object. |  | ||||||
|  |  | ||||||
|     Format: |  | ||||||
|         YYYY-MM-DDThh:mm:ss[.sss]TZD |  | ||||||
|  |  | ||||||
|     arguments: |  | ||||||
|         time_obj -- A datetime object. |  | ||||||
|     """ |  | ||||||
|     timestamp = time_obj.isoformat('T') |  | ||||||
|     if time_obj.tzinfo == tzutc(): |  | ||||||
|         timestamp = timestamp[:-6] |  | ||||||
|         return '%sZ' % timestamp |  | ||||||
|     return timestamp |  | ||||||
|  |  | ||||||
| def date(year=None, month=None, day=None): |  | ||||||
|     """ |  | ||||||
|     Create a date only timestamp for the given instant. |  | ||||||
|  |  | ||||||
|     Unspecified components default to their current counterparts. |  | ||||||
|  |  | ||||||
|     Arguments: |  | ||||||
|         year   -- Integer value of the year (4 digits) |  | ||||||
|         month  -- Integer value of the month |  | ||||||
|         day    -- Integer value of the day of the month. |  | ||||||
|     """ |  | ||||||
|     today = dt.datetime.today() |  | ||||||
|     if year is None: |  | ||||||
|         year = today.year |  | ||||||
|     if month is None: |  | ||||||
|         month = today.month |  | ||||||
|     if day is None: |  | ||||||
|         day = today.day |  | ||||||
|     return format_date(dt.date(year, month, day)) |  | ||||||
|  |  | ||||||
| def time(hour=None, min=None, sec=None, micro=None, offset=None): |  | ||||||
|     """ |  | ||||||
|     Create a time only timestamp for the given instant. |  | ||||||
|  |  | ||||||
|     Unspecified components default to their current counterparts. |  | ||||||
|  |  | ||||||
|     Arguments: |  | ||||||
|         hour   -- Integer value of the hour. |  | ||||||
|         min    -- Integer value of the number of minutes. |  | ||||||
|         sec    -- Integer value of the number of seconds. |  | ||||||
|         micro  -- Integer value of the number of microseconds. |  | ||||||
|         offset -- Either a positive or negative number of seconds |  | ||||||
|                   to offset from UTC to match a desired timezone, |  | ||||||
|                   or a tzinfo object. |  | ||||||
|     """ |  | ||||||
|     now = dt.datetime.utcnow() |  | ||||||
|     if hour is None: |  | ||||||
|         hour = now.hour |  | ||||||
|     if min is None: |  | ||||||
|         min = now.minute |  | ||||||
|     if sec is None: |  | ||||||
|         sec = now.second |  | ||||||
|     if micro is None: |  | ||||||
|         micro = now.microsecond |  | ||||||
|     if offset is None: |  | ||||||
|         offset = tzutc() |  | ||||||
|     elif not isinstance(offset, dt.tzinfo): |  | ||||||
|         offset = tzoffset(None, offset) |  | ||||||
|     time = dt.time(hour, min, sec, micro, offset) |  | ||||||
|     return format_time(time) |  | ||||||
|  |  | ||||||
| def datetime(year=None, month=None, day=None, hour=None, |  | ||||||
|              min=None, sec=None, micro=None, offset=None, |  | ||||||
|              separators=True): |  | ||||||
|     """ |  | ||||||
|     Create a datetime timestamp for the given instant. |  | ||||||
|  |  | ||||||
|     Unspecified components default to their current counterparts. |  | ||||||
|  |  | ||||||
|     Arguments: |  | ||||||
|         year   -- Integer value of the year (4 digits) |  | ||||||
|         month  -- Integer value of the month |  | ||||||
|         day    -- Integer value of the day of the month. |  | ||||||
|         hour   -- Integer value of the hour. |  | ||||||
|         min    -- Integer value of the number of minutes. |  | ||||||
|         sec    -- Integer value of the number of seconds. |  | ||||||
|         micro  -- Integer value of the number of microseconds. |  | ||||||
|         offset -- Either a positive or negative number of seconds |  | ||||||
|                   to offset from UTC to match a desired timezone, |  | ||||||
|                   or a tzinfo object. |  | ||||||
|     """ |  | ||||||
|     now = dt.datetime.utcnow() |  | ||||||
|     if year is None: |  | ||||||
|         year = now.year |  | ||||||
|     if month is None: |  | ||||||
|         month = now.month |  | ||||||
|     if day is None: |  | ||||||
|         day = now.day |  | ||||||
|     if hour is None: |  | ||||||
|         hour = now.hour |  | ||||||
|     if min is None: |  | ||||||
|         min = now.minute |  | ||||||
|     if sec is None: |  | ||||||
|         sec = now.second |  | ||||||
|     if micro is None: |  | ||||||
|         micro = now.microsecond |  | ||||||
|     if offset is None: |  | ||||||
|         offset = tzutc() |  | ||||||
|     elif not isinstance(offset, dt.tzinfo): |  | ||||||
|         offset = tzoffset(None, offset) |  | ||||||
|  |  | ||||||
|     date = dt.datetime(year, month, day, hour, |  | ||||||
|                        min, sec, micro, offset) |  | ||||||
|     return format_datetime(date) |  | ||||||
|  |  | ||||||
| class xep_0082(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0082: XMPP Date and Time Profiles |  | ||||||
|  |  | ||||||
|     XMPP uses a subset of the formats allowed by ISO 8601 as a matter of |  | ||||||
|     pragmatism based on the relatively few formats historically used by |  | ||||||
|     the XMPP. |  | ||||||
|  |  | ||||||
|     Also see <http://www.xmpp.org/extensions/xep-0082.html>. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         date            -- Create a time stamp using the Date profile. |  | ||||||
|         datetime        -- Create a time stamp using the DateTime profile. |  | ||||||
|         time            -- Create a time stamp using the Time profile. |  | ||||||
|         format_date     -- Format an existing date object. |  | ||||||
|         format_datetime -- Format an existing datetime object. |  | ||||||
|         format_time     -- Format an existing time object. |  | ||||||
|         parse           -- Convert a time string into a Python datetime object. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         """Start the XEP-0082 plugin.""" |  | ||||||
|         self.xep = '0082' |  | ||||||
|         self.description = 'XMPP Date and Time Profiles' |  | ||||||
|  |  | ||||||
|         self.date = date |  | ||||||
|         self.datetime = datetime |  | ||||||
|         self.time = time |  | ||||||
|         self.format_date = format_date |  | ||||||
|         self.format_datetime = format_datetime |  | ||||||
|         self.format_time = format_time |  | ||||||
|         self.parse = parse |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permissio |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0085.stanza import ChatState |  | ||||||
| from sleekxmpp.plugins.xep_0085.chat_states import xep_0085 |  | ||||||
| @@ -1,49 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permissio |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp.stanza import Message |  | ||||||
| from sleekxmpp.xmlstream.handler import Callback |  | ||||||
| from sleekxmpp.xmlstream.matcher import StanzaPath |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.plugins.xep_0085 import stanza, ChatState |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0085(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0085 Chat State Notifications |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.xep = '0085' |  | ||||||
|         self.description = 'Chat State Notifications' |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         for state in ChatState.states: |  | ||||||
|             self.xmpp.register_handler( |  | ||||||
|                 Callback('Chat State: %s' % state, |  | ||||||
|                          StanzaPath('message@chat_state=%s' % state), |  | ||||||
|                          self._handle_chat_state)) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Message, ChatState) |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         base_plugin.post_init(self) |  | ||||||
|         self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace) |  | ||||||
|  |  | ||||||
|     def _handle_chat_state(self, msg): |  | ||||||
|         state = msg['chat_state'] |  | ||||||
|         log.debug("Chat State: %s, %s" % (state, msg['from'].jid)) |  | ||||||
|         self.xmpp.event('chatstate_%s' % state, msg) |  | ||||||
| @@ -1,73 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permissio |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ChatState(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     Example chat state stanzas: |  | ||||||
|         <message> |  | ||||||
|           <active xmlns="http://jabber.org/protocol/chatstates" /> |  | ||||||
|         </message> |  | ||||||
|  |  | ||||||
|         <message> |  | ||||||
|           <paused xmlns="http://jabber.org/protocol/chatstates" /> |  | ||||||
|         </message> |  | ||||||
|  |  | ||||||
|     Stanza Interfaces: |  | ||||||
|         chat_state |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|         states |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|         get_chat_state |  | ||||||
|         set_chat_state |  | ||||||
|         del_chat_state |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = '' |  | ||||||
|     namespace = 'http://jabber.org/protocol/chatstates' |  | ||||||
|     plugin_attrib = 'chat_state' |  | ||||||
|     interfaces = set(('chat_state',)) |  | ||||||
|     is_extension = True |  | ||||||
|  |  | ||||||
|     states = set(('active', 'composing', 'gone', 'inactive', 'paused')) |  | ||||||
|  |  | ||||||
|     def setup(self, xml=None): |  | ||||||
|         self.xml = ET.Element('') |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def get_chat_state(self): |  | ||||||
|         parent = self.parent() |  | ||||||
|         for state in self.states: |  | ||||||
|             state_xml = parent.find('{%s}%s' % (self.namespace, state)) |  | ||||||
|             if state_xml is not None: |  | ||||||
|                 self.xml = state_xml |  | ||||||
|                 return state |  | ||||||
|         return '' |  | ||||||
|  |  | ||||||
|     def set_chat_state(self, state): |  | ||||||
|         self.del_chat_state() |  | ||||||
|         parent = self.parent() |  | ||||||
|         if state in self.states: |  | ||||||
|             self.xml = ET.Element('{%s}%s' % (self.namespace, state)) |  | ||||||
|             parent.append(self.xml) |  | ||||||
|         elif state not in [None, '']: |  | ||||||
|             raise ValueError('Invalid chat state') |  | ||||||
|  |  | ||||||
|     def del_chat_state(self): |  | ||||||
|         parent = self.parent() |  | ||||||
|         for state in self.states: |  | ||||||
|             state_xml = parent.find('{%s}%s' % (self.namespace, state)) |  | ||||||
|             if state_xml is not None: |  | ||||||
|                 self.xml = ET.Element('') |  | ||||||
|                 parent.xml.remove(state_xml) |  | ||||||
							
								
								
									
										49
									
								
								sleekxmpp/plugins/xep_0086.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								sleekxmpp/plugins/xep_0086.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  |  | ||||||
|  | from __future__ import with_statement | ||||||
|  | from . import base | ||||||
|  | import logging | ||||||
|  | from xml.etree import cElementTree as ET | ||||||
|  | import copy | ||||||
|  |  | ||||||
|  | class xep_0086(base.base_plugin): | ||||||
|  | 	""" | ||||||
|  | 	XEP-0086 Error Condition Mappings | ||||||
|  | 	""" | ||||||
|  |  | ||||||
|  | 	def plugin_init(self): | ||||||
|  | 		self.xep = '0086' | ||||||
|  | 		self.description = 'Error Condition Mappings' | ||||||
|  | 		self.error_map = { | ||||||
|  | 			'bad-request':('modify','400'), | ||||||
|  | 			'conflict':('cancel','409'), | ||||||
|  | 			'feature-not-implemented':('cancel','501'), | ||||||
|  | 			'forbidden':('auth','403'), | ||||||
|  | 			'gone':('modify','302'), | ||||||
|  | 			'internal-server-error':('wait','500'), | ||||||
|  | 			'item-not-found':('cancel','404'), | ||||||
|  | 			'jid-malformed':('modify','400'), | ||||||
|  | 			'not-acceptable':('modify','406'), | ||||||
|  | 			'not-allowed':('cancel','405'), | ||||||
|  | 			'not-authorized':('auth','401'), | ||||||
|  | 			'payment-required':('auth','402'), | ||||||
|  | 			'recipient-unavailable':('wait','404'), | ||||||
|  | 			'redirect':('modify','302'), | ||||||
|  | 			'registration-required':('auth','407'), | ||||||
|  | 			'remote-server-not-found':('cancel','404'), | ||||||
|  | 			'remote-server-timeout':('wait','504'), | ||||||
|  | 			'resource-constraint':('wait','500'), | ||||||
|  | 			'service-unavailable':('cancel','503'), | ||||||
|  | 			'subscription-required':('auth','407'), | ||||||
|  | 			'undefined-condition':(None,'500'), | ||||||
|  | 			'unexpected-request':('wait','400') | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	def makeError(self, condition, cdata=None, errorType=None, text=None, customElem=None): | ||||||
|  | 		conditionElem = self.xmpp.makeStanzaErrorCondition(condition, cdata) | ||||||
|  | 		if errorType is None: | ||||||
|  | 			error = self.xmpp.makeStanzaError(conditionElem, self.error_map[condition][0], self.error_map[condition][1], text, customElem) | ||||||
|  | 		else: | ||||||
|  | 			error = self.xmpp.makeStanzaError(conditionElem, errorType, self.error_map[condition][1], text, customElem) | ||||||
|  | 		error.append(conditionElem) | ||||||
|  | 		return error | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0086.stanza import LegacyError |  | ||||||
| from sleekxmpp.plugins.xep_0086.legacy_error import xep_0086 |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Error |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.plugins.xep_0086 import stanza, LegacyError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0086(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0086: Error Condition Mappings |  | ||||||
|  |  | ||||||
|     Older XMPP implementations used code based error messages, similar |  | ||||||
|     to HTTP response codes. Since then, error condition elements have |  | ||||||
|     been introduced. XEP-0086 provides a mapping between the new |  | ||||||
|     condition elements and a combination of error types and the older |  | ||||||
|     response codes. |  | ||||||
|  |  | ||||||
|     Also see <http://xmpp.org/extensions/xep-0086.html>. |  | ||||||
|  |  | ||||||
|     Configuration Values: |  | ||||||
|         override -- Indicates if applying legacy error codes should |  | ||||||
|                     be done automatically. Defaults to True. |  | ||||||
|                     If False, then inserting legacy error codes can |  | ||||||
|                     be done using: |  | ||||||
|                         iq['error']['legacy']['condition'] = ... |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         self.xep = '0086' |  | ||||||
|         self.description = 'Error Condition Mappings' |  | ||||||
|         self.stanza = stanza |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Error, LegacyError, |  | ||||||
|                                overrides=self.config.get('override', True)) |  | ||||||
| @@ -1,91 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Error |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class LegacyError(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     Older XMPP implementations used code based error messages, similar |  | ||||||
|     to HTTP response codes. Since then, error condition elements have |  | ||||||
|     been introduced. XEP-0086 provides a mapping between the new |  | ||||||
|     condition elements and a combination of error types and the older |  | ||||||
|     response codes. |  | ||||||
|  |  | ||||||
|     Also see <http://xmpp.org/extensions/xep-0086.html>. |  | ||||||
|  |  | ||||||
|     Example legacy error stanzas: |  | ||||||
|         <error xmlns="jabber:client" code="501" type="cancel"> |  | ||||||
|           <feature-not-implemented |  | ||||||
|                 xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> |  | ||||||
|         </error> |  | ||||||
|  |  | ||||||
|         <error code="402" type="auth"> |  | ||||||
|           <payment-required |  | ||||||
|                 xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> |  | ||||||
|         </error> |  | ||||||
|  |  | ||||||
|     Attributes: |  | ||||||
|         error_map -- A map of error conditions to error types and |  | ||||||
|                      code values. |  | ||||||
|     Methods: |  | ||||||
|         setup         -- Overrides ElementBase.setup |  | ||||||
|         set_condition -- Remap the type and code interfaces when a |  | ||||||
|                          condition is set. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'legacy' |  | ||||||
|     namespace = Error.namespace |  | ||||||
|     plugin_attrib = name |  | ||||||
|     interfaces = set(('condition',)) |  | ||||||
|     overrides = ['set_condition'] |  | ||||||
|  |  | ||||||
|     error_map = {'bad-request': ('modify','400'), |  | ||||||
|                  'conflict': ('cancel','409'), |  | ||||||
|                  'feature-not-implemented': ('cancel','501'), |  | ||||||
|                  'forbidden': ('auth','403'), |  | ||||||
|                  'gone': ('modify','302'), |  | ||||||
|                  'internal-server-error': ('wait','500'), |  | ||||||
|                  'item-not-found': ('cancel','404'), |  | ||||||
|                  'jid-malformed': ('modify','400'), |  | ||||||
|                  'not-acceptable': ('modify','406'), |  | ||||||
|                  'not-allowed': ('cancel','405'), |  | ||||||
|                  'not-authorized': ('auth','401'), |  | ||||||
|                  'payment-required': ('auth','402'), |  | ||||||
|                  'recipient-unavailable': ('wait','404'), |  | ||||||
|                  'redirect': ('modify','302'), |  | ||||||
|                  'registration-required': ('auth','407'), |  | ||||||
|                  'remote-server-not-found': ('cancel','404'), |  | ||||||
|                  'remote-server-timeout': ('wait','504'), |  | ||||||
|                  'resource-constraint': ('wait','500'), |  | ||||||
|                  'service-unavailable': ('cancel','503'), |  | ||||||
|                  'subscription-required': ('auth','407'), |  | ||||||
|                  'undefined-condition': (None,'500'), |  | ||||||
|                  'unexpected-request': ('wait','400')} |  | ||||||
|  |  | ||||||
|     def setup(self, xml): |  | ||||||
|         """Don't create XML for the plugin.""" |  | ||||||
|         self.xml = ET.Element('') |  | ||||||
|  |  | ||||||
|     def set_condition(self, value): |  | ||||||
|         """ |  | ||||||
|         Set the error type and code based on the given error |  | ||||||
|         condition value. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             value -- The new error condition. |  | ||||||
|         """ |  | ||||||
|         self.parent().set_condition(value) |  | ||||||
|  |  | ||||||
|         error_data = self.error_map.get(value, None) |  | ||||||
|         if error_data is not None: |  | ||||||
|             if error_data[0] is not None: |  | ||||||
|                 self.parent()['type'] = error_data[0] |  | ||||||
|             self.parent()['code'] = error_data[1] |  | ||||||
							
								
								
									
										68
									
								
								sleekxmpp/plugins/xep_0092.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								sleekxmpp/plugins/xep_0092.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | """ | ||||||
|  | 	SleekXMPP: The Sleek XMPP Library | ||||||
|  | 	Copyright (C) 2007  Nathanael C. Fritz | ||||||
|  | 	This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  | 	SleekXMPP is free software; you can redistribute it and/or modify | ||||||
|  | 	it under the terms of the GNU General Public License as published by | ||||||
|  | 	the Free Software Foundation; either version 2 of the License, or | ||||||
|  | 	(at your option) any later version. | ||||||
|  |  | ||||||
|  | 	SleekXMPP is distributed in the hope that it will be useful, | ||||||
|  | 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | 	GNU General Public License for more details. | ||||||
|  |  | ||||||
|  | 	You should have received a copy of the GNU General Public License | ||||||
|  | 	along with SleekXMPP; if not, write to the Free Software | ||||||
|  | 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||||
|  | """ | ||||||
|  | from xml.etree import cElementTree as ET | ||||||
|  | from . import base | ||||||
|  | from .. xmlstream.handler.xmlwaiter import XMLWaiter | ||||||
|  |  | ||||||
|  | class xep_0092(base.base_plugin): | ||||||
|  | 	""" | ||||||
|  | 	XEP-0092 Software Version | ||||||
|  | 	""" | ||||||
|  | 	def plugin_init(self): | ||||||
|  | 		self.description = "Software Version" | ||||||
|  | 		self.xep = "0092" | ||||||
|  | 		self.name = self.config.get('name', 'SleekXMPP') | ||||||
|  | 		self.version = self.config.get('version', '0.1-dev') | ||||||
|  | 		self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version) | ||||||
|  | 	 | ||||||
|  | 	def post_init(self): | ||||||
|  | 		base.base_plugin.post_init(self) | ||||||
|  | 		self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') | ||||||
|  | 	 | ||||||
|  | 	def report_version(self, xml): | ||||||
|  | 		iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) | ||||||
|  | 		iq.attrib['to'] = xml.get('from', self.xmpp.server) | ||||||
|  | 		query = ET.Element('{jabber:iq:version}query') | ||||||
|  | 		name = ET.Element('name') | ||||||
|  | 		name.text = self.name | ||||||
|  | 		version = ET.Element('version') | ||||||
|  | 		version.text = self.version | ||||||
|  | 		query.append(name) | ||||||
|  | 		query.append(version) | ||||||
|  | 		iq.append(query) | ||||||
|  | 		self.xmpp.send(iq) | ||||||
|  | 	 | ||||||
|  | 	def getVersion(self, jid): | ||||||
|  | 		iq = self.xmpp.makeIqGet() | ||||||
|  | 		query = ET.Element('{jabber:iq:version}query') | ||||||
|  | 		iq.append(query) | ||||||
|  | 		iq.attrib['to'] = jid | ||||||
|  | 		iq.attrib['from'] = self.xmpp.fulljid | ||||||
|  | 		id = iq.get('id') | ||||||
|  | 		result = iq.send() | ||||||
|  | 		if result and result is not None and result.get('type', 'error') != 'error': | ||||||
|  | 			qry = result.find('{jabber:iq:version}query') | ||||||
|  | 			version = {} | ||||||
|  | 			for child in qry.getchildren(): | ||||||
|  | 				version[child.tag.split('}')[-1]] = child.text | ||||||
|  | 			return version | ||||||
|  | 		else: | ||||||
|  | 			return False | ||||||
|  |  | ||||||
| @@ -1,11 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0092 import stanza |  | ||||||
| from sleekxmpp.plugins.xep_0092.stanza import Version |  | ||||||
| from sleekxmpp.plugins.xep_0092.version import xep_0092 |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.xmlstream import ElementBase, ET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Version(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XMPP allows for an agent to advertise the name and version of the |  | ||||||
|     underlying software libraries, as well as the operating system |  | ||||||
|     that the agent is running on. |  | ||||||
|  |  | ||||||
|     Example version stanzas: |  | ||||||
|       <iq type="get"> |  | ||||||
|         <query xmlns="jabber:iq:version" /> |  | ||||||
|       </iq> |  | ||||||
|  |  | ||||||
|       <iq type="result"> |  | ||||||
|         <query xmlns="jabber:iq:version"> |  | ||||||
|           <name>SleekXMPP</name> |  | ||||||
|           <version>1.0</version> |  | ||||||
|           <os>Linux</os> |  | ||||||
|         </query> |  | ||||||
|       </iq> |  | ||||||
|  |  | ||||||
|     Stanza Interface: |  | ||||||
|         name    -- The human readable name of the software. |  | ||||||
|         version -- The specific version of the software. |  | ||||||
|         os      -- The name of the operating system running the program. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = 'query' |  | ||||||
|     namespace = 'jabber:iq:version' |  | ||||||
|     plugin_attrib = 'software_version' |  | ||||||
|     interfaces = set(('name', 'version', 'os')) |  | ||||||
|     sub_interfaces = interfaces |  | ||||||
| @@ -1,88 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp import Iq |  | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin |  | ||||||
| from sleekxmpp.xmlstream.handler import Callback |  | ||||||
| from sleekxmpp.xmlstream.matcher import StanzaPath |  | ||||||
| from sleekxmpp.plugins.base import base_plugin |  | ||||||
| from sleekxmpp.plugins.xep_0092 import Version |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0092(base_plugin): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     XEP-0092: Software Version |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def plugin_init(self): |  | ||||||
|         """ |  | ||||||
|         Start the XEP-0092 plugin. |  | ||||||
|         """ |  | ||||||
|         self.xep = "0092" |  | ||||||
|         self.description = "Software Version" |  | ||||||
|         self.stanza = sleekxmpp.plugins.xep_0092.stanza |  | ||||||
|  |  | ||||||
|         self.name = self.config.get('name', 'SleekXMPP') |  | ||||||
|         self.version = self.config.get('version', sleekxmpp.__version__) |  | ||||||
|         self.os = self.config.get('os', '') |  | ||||||
|  |  | ||||||
|         self.getVersion = self.get_version |  | ||||||
|  |  | ||||||
|         self.xmpp.register_handler( |  | ||||||
|                 Callback('Software Version', |  | ||||||
|                          StanzaPath('iq@type=get/software_version'), |  | ||||||
|                          self._handle_version)) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, Version) |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         """ |  | ||||||
|         Handle cross-plugin dependencies. |  | ||||||
|         """ |  | ||||||
|         base_plugin.post_init(self) |  | ||||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version') |  | ||||||
|  |  | ||||||
|     def _handle_version(self, iq): |  | ||||||
|         """ |  | ||||||
|         Respond to a software version query. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq -- The Iq stanza containing the software version query. |  | ||||||
|         """ |  | ||||||
|         iq.reply() |  | ||||||
|         iq['software_version']['name'] = self.name |  | ||||||
|         iq['software_version']['version'] = self.version |  | ||||||
|         iq['software_version']['os'] = self.os |  | ||||||
|         iq.send() |  | ||||||
|  |  | ||||||
|     def get_version(self, jid, ifrom=None): |  | ||||||
|         """ |  | ||||||
|         Retrieve the software version of a remote agent. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid -- The JID of the entity to query. |  | ||||||
|         """ |  | ||||||
|         iq = self.xmpp.Iq() |  | ||||||
|         iq['to'] = jid |  | ||||||
|         if ifrom: |  | ||||||
|             iq['from'] = ifrom |  | ||||||
|         iq['type'] = 'get' |  | ||||||
|         iq['query'] = Version.namespace |  | ||||||
|  |  | ||||||
|         result = iq.send() |  | ||||||
|  |  | ||||||
|         if result and result['type'] != 'error': |  | ||||||
|             return result['software_version'].values |  | ||||||
|         return False |  | ||||||
| @@ -1,10 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from sleekxmpp.plugins.xep_0128.static import StaticExtendedDisco |  | ||||||
| from sleekxmpp.plugins.xep_0128.extended_disco import xep_0128 |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user