Compare commits
	
		
			212 Commits
		
	
	
		
			1.0-Beta5
			...
			sleek-1.0-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2a80824076 | ||
|   | f92f96325a | ||
|   | b98555c512 | ||
|   | e02a42a008 | ||
|   | 3e51126e18 | ||
|   | a714fa82b2 | ||
|   | e86e6eae81 | ||
|   | 592c25f352 | ||
|   | 015f662249 | ||
|   | 8d998d71a3 | ||
|   | f75b6bf955 | ||
|   | fb78bf0996 | ||
|   | cd7cd30b4c | ||
|   | 4ea22ff69b | ||
|   | 3853898ab3 | ||
|   | 7d8aa4157b | ||
|   | 3fc20e10f5 | ||
|   | 004eabf809 | ||
|   | 62230fc970 | ||
|   | 961668d420 | ||
|   | 01061a0355 | ||
|   | 9fdd85d9f1 | ||
|   | 331db30f8f | ||
|   | 017d7ec62b | ||
|   | 76826b5495 | ||
|   | de315ff6d8 | ||
|   | c26b716164 | ||
|   | dcaddb8042 | ||
|   | 5ef197e5fd | ||
|   | 52ed02bd06 | ||
|   | bd427849fb | ||
|   | 127d7acb91 | ||
|   | 484efff156 | ||
|   | 8f1d0e7a79 | ||
|   | 88184ff955 | ||
|   | bd8c110f00 | ||
|   | 0050c51124 | ||
|   | 9b7ed73f95 | ||
|   | a189cb8333 | ||
|   | 0d4825d3ea | ||
|   | 156b3200e3 | ||
|   | 572becad44 | ||
|   | 75f23d1130 | ||
|   | e83fae3a6f | ||
|   | 5be5b8c02b | ||
|   | 6c4cb2bf00 | ||
|   | 148a23579c | ||
|   | ea95811c4c | ||
|   | 08cb5f42e7 | ||
|   | 9abf37bbd1 | ||
|   | 168203c94d | ||
|   | 47bc50d9fb | ||
|   | 93a4a3f8a0 | ||
|   | 940e3eba35 | ||
|   | b7cd119b0c | ||
|   | 7f90de887a | ||
|   | 6c8a135612 | ||
|   | caec2976d7 | ||
|   | 4d8933abdf | ||
|   | 6eac0606cf | ||
|   | 89cffd43f4 | ||
|   | b9764cc120 | ||
|   | 7cd39a6aad | ||
|   | a8f57d012f | ||
|   | a0767f6af6 | ||
|   | 9ffdba8643 | ||
|   | 9591cd3a7e | ||
|   | afeb8a679a | ||
|   | db92fa2330 | ||
|   | d94517d9ca | ||
|   | 0bec040cfe | ||
|   | 3918ddb075 | ||
|   | d4091dbde6 | ||
|   | d7fe724145 | ||
|   | ad978700fc | ||
|   | e022b2a36c | ||
|   | ad032e5ed7 | ||
|   | 45412fd404 | ||
|   | ccc6ab1281 | ||
|   | 712da4c46e | ||
|   | b2d42b1d6c | ||
|   | 1d22a04721 | ||
|   | 5efb170e1d | ||
|   | cccccdcc0a | ||
|   | 8d384ce44f | ||
|   | 4d6e7c7dbb | ||
|   | 9c5885c6b6 | ||
|   | ec3a14e6d9 | ||
|   | c98f5d4450 | ||
|   | 2e8e542bc9 | ||
|   | 7ccc67c06d | ||
|   | 20df6348a4 | ||
|   | 48fb7006f7 | ||
|   | 9a6eb333e6 | ||
|   | 086bf89d69 | ||
|   | 0224d028e7 | ||
|   | 540d749695 | ||
|   | 219df582da | ||
|   | b898b14b77 | ||
|   | fba235a801 | ||
|   | b0297af38d | ||
|   | 04def6d925 | ||
|   | 7057984831 | ||
|   | 2a2ac73845 | ||
|   | 634f5d691b | ||
|   | 754ac5092a | ||
|   | 9ed972ffeb | ||
|   | 3b1f3fddf0 | ||
|   | fa716457a5 | ||
|   | 847510c6b5 | ||
|   | 774e0f2022 | ||
|   | d1e12cd46f | ||
|   | adf6d49fd1 | ||
|   | 0826a44d4b | ||
|   | ce145b04ac | ||
|   | 29d775e675 | ||
|   | 251a47db8c | ||
|   | e4b3a191d6 | ||
|   | 393d702e77 | ||
|   | 4efd41f1ba | ||
|   | a3597d6deb | ||
|   | 8fada4d015 | ||
|   | 5d11ab269d | ||
|   | 6d59f55fd4 | ||
|   | 7a60e4b458 | ||
|   | 937dce8e65 | ||
|   | 5c1562f36b | ||
|   | 823c13707d | ||
|   | 8eb59072b4 | ||
|   | 20d053807d | ||
|   | 8aa4396e44 | ||
|   | 8f9100c762 | ||
|   | 788a5b73f9 | ||
|   | 83a73ac9b7 | ||
|   | 5ed27bf5f6 | ||
|   | ccb0eeefbd | ||
|   | 62bdaab7c7 | ||
|   | 9851a2a057 | ||
|   | bb2bc64d15 | ||
|   | baa1eaf73a | ||
|   | 4c7da3899e | ||
|   | 4d3593ac86 | ||
|   | c49a8e9114 | ||
|   | a269be485f | ||
|   | d3bd9cd31d | ||
|   | e694e4a791 | ||
|   | e2de82ac8d | ||
|   | e3b14bc5a9 | ||
|   | b1439df72a | ||
|   | 8e46aa7054 | ||
|   | 8a22597180 | ||
|   | e919906c8c | ||
|   | 46dc6eac88 | ||
|   | ea8c40c7b6 | ||
|   | b9bf30e095 | ||
|   | b60c51ef13 | ||
|   | ae5ce17035 | ||
|   | 6b05938573 | ||
|   | f125c11a81 | ||
|   | 77601f7262 | ||
|   | bf2f2782b7 | ||
|   | d9be51b2ef | ||
|   | 694673b9bd | ||
|   | 393259c24b | ||
|   | 306bdd8021 | ||
|   | 756c4c032f | ||
|   | a9685a00b3 | ||
|   | e1360ae049 | ||
|   | af45b51f4f | ||
|   | 566ec8a5f9 | ||
|   | b048f8d733 | ||
|   | dca8516cec | ||
|   | f2c99798a6 | ||
|   | f65f88325b | ||
|   | 42c8f6ae87 | ||
|   | c2161ca56b | ||
|   | d5b3a52827 | ||
|   | e4f3b777f9 | ||
|   | a278f79bdb | ||
|   | 606c369173 | ||
|   | 3c871920b1 | ||
|   | de6170a13d | ||
|   | 1a270dc05c | ||
|   | bd9bf3f1c7 | ||
|   | 65931bb384 | ||
|   | a5d53b3349 | ||
|   | 4487a90623 | ||
|   | 05da8cc3d1 | ||
|   | 23e499998f | ||
|   | c156a4f723 | ||
|   | 3657bf6636 | ||
|   | adade2e5ec | ||
|   | c16913c999 | ||
|   | 12b61365ad | ||
|   | 9214dc6610 | ||
|   | 58b95e4ae4 | ||
|   | debf909359 | ||
|   | 0d4d84b2fa | ||
|   | d2aca3e7e0 | ||
|   | 26aca2b789 | ||
|   | 5424ede413 | ||
|   | 20112f8e16 | ||
|   | 4260a754e5 | ||
|   | ce30f72738 | ||
|   | 69d430dd75 | ||
|   | 673545c7e4 | ||
|   | 4f69a03bb1 | ||
|   | c2c18acd6a | ||
|   | 12ead36f96 | ||
|   | 41a642e06c | ||
|   | c6ed4b8a1d | ||
|   | 65aa6573df | 
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,6 @@ | ||||
| *.pyc | ||||
| build/ | ||||
| dist/ | ||||
| MANIFEST | ||||
| docs/_build/ | ||||
| *.swp | ||||
|   | ||||
							
								
								
									
										121
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -17,3 +17,124 @@ 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. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| Licenses of Bundled Third Party 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 | ||||
| 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. | ||||
|   | ||||
							
								
								
									
										47
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								README
									
									
									
									
									
								
							| @@ -1,47 +0,0 @@ | ||||
| SleekXMPP is an XMPP library written for Python 3.1+ (with 2.6 compatibility). | ||||
| Hosted at http://wiki.github.com/fritzy/SleekXMPP/ | ||||
|  | ||||
| Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre | ||||
| If you're coming here from The Definitive Guide, please read http://wiki.github.com/fritzy/SleekXMPP/xmpp-the-definitive-guide | ||||
|  | ||||
| Requirements: | ||||
| We try to keep requirements to a minimum, but we suggest that you install http://dnspython.org although it isn't strictly required. | ||||
| If you do not install this library, you may need to specify the server/port for services that use SRV records (like GTalk). | ||||
| "sudo pip install dnspython" on a *nix system with pip installed. | ||||
|  | ||||
| SleekXMPP has several design goals/philosophies: | ||||
| - Low number of dependencies. | ||||
| - Every XEP as a plugin. | ||||
| - Rewarding to work with. | ||||
|  | ||||
| The goals for 1.0 include (and we're getting close): | ||||
| - Nearly Full test coverage of stanzas. | ||||
| - Wide range of functional tests. | ||||
| - Stanza objects for all interaction with the stream | ||||
| - Documentation on using and extending SleekXMPP. | ||||
| - Complete documentation on all implemented stanza objects | ||||
| - Documentation on all examples used in XMPP: The Definitive Guide | ||||
|  | ||||
| 1.1 will include: | ||||
| - More functional and unit tests | ||||
| - PEP-8 compliance | ||||
| - XEP-225 support | ||||
|  | ||||
| Since 0.2, here's the Changelog: | ||||
| - MANY bugfixes | ||||
| - Re-implementation of handlers/threading to greatly simplify and remove bugs (no more spawning threads in handlers) | ||||
| - Stanza objects for jabber:client and all implemented XEPs | ||||
| - Raising XMPPError for jabber:client and extended errors in handlers | ||||
| - Robust error handling and better insurance of iq responses | ||||
| - Stanza objects have made life a lot easier! | ||||
| - Massive audit/cleanup. | ||||
|  | ||||
| Credits | ||||
| ---------------- | ||||
| Main Author: Nathan Fritz fritz@netflint.net | ||||
| Contributors: Kevin Smith & Lance Stout | ||||
| Patches: Remko Tronçon | ||||
|  | ||||
| Feel free to add fritzy@netflint.net to your roster for direct support and comments. | ||||
| Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion. | ||||
| Join sleek@conference.jabber.org for groupchat discussion. | ||||
							
								
								
									
										169
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| SleekXMPP | ||||
| ######### | ||||
|  | ||||
| SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+, | ||||
| and is featured in examples in | ||||
| `XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_  | ||||
| by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived | ||||
| here from reading the Definitive Guide, please see the notes on updating | ||||
| the examples to the latest version of SleekXMPP. | ||||
|  | ||||
| SleekXMPP's design goals and philosphy are: | ||||
|  | ||||
| **Low number of dependencies** | ||||
|     Installing and using SleekXMPP should be as simple as possible, without | ||||
|     having to deal with long dependency chains. | ||||
|  | ||||
|     As part of reducing the number of dependencies, some third party | ||||
|     modules are included with SleekXMPP in the ``thirdparty`` directory. | ||||
|     Imports from this module first try to import an existing installed | ||||
|     version before loading the packaged version, when possible. | ||||
|  | ||||
| **Every XEP as a plugin** | ||||
|     Following Python's "batteries included" approach, the goal is to | ||||
|     provide support for all currently active XEPs (final and draft). Since | ||||
|     adding XEP support is done through easy to create plugins, the hope is | ||||
|     to also provide a solid base for implementing and creating experimental | ||||
|     XEPs. | ||||
|  | ||||
| **Rewarding to work with** | ||||
|     As much as possible, SleekXMPP should allow things to "just work" using | ||||
|     sensible defaults and appropriate abstractions. XML can be ugly to work | ||||
|     with, but it doesn't have to be that way. | ||||
|  | ||||
|  | ||||
| Get the Code | ||||
| ------------ | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install sleekxmpp | ||||
|  | ||||
| The latest source code for SleekXMPP may be found on `Github | ||||
| <http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the | ||||
| ``master`` branch, while the latest development version is in the | ||||
| ``develop`` branch. | ||||
|  | ||||
| **Stable Releases** | ||||
|     - `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_   | ||||
|     - `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_ | ||||
|     - `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_ | ||||
|     - `1.0 Beta3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta3>`_ | ||||
|     - `1.0 Beta2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta2>`_ | ||||
|     - `1.0 Beta1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta1>`_ | ||||
|  | ||||
| **Develop Releases** | ||||
|     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||
|  | ||||
|  | ||||
| Discussion | ||||
| ---------- | ||||
| A mailing list and XMPP chat room are available for discussing and getting | ||||
| help with SleekXMPP. | ||||
|  | ||||
| **Mailing List** | ||||
|     `SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_ | ||||
|  | ||||
| **Chat** | ||||
|     `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ | ||||
|  | ||||
| Documentation and Testing | ||||
| ------------------------- | ||||
|  | ||||
| Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``. | ||||
| To generate the Sphinx documentation, follow the commands below. The HTML output will | ||||
| be in ``docs/_build/html``:: | ||||
|  | ||||
|     cd docs | ||||
|     make html | ||||
|     open _build/html/index.html | ||||
|  | ||||
| To run the test suite for SleekXMPP:: | ||||
|  | ||||
|     python testall.py | ||||
|  | ||||
|  | ||||
| The SleekXMPP Boilerplate | ||||
| ------------------------- | ||||
|  | ||||
| Projects using SleekXMPP tend to follow a basic pattern for setting up client/component | ||||
| connections and configuration. Here is the gist of the boilerplate needed for a SleekXMPP | ||||
| based project. See the documetation or examples directory for more detailed archetypes for | ||||
| SleekXMPP projects:: | ||||
|  | ||||
|     import logging | ||||
|  | ||||
|     from sleekxmpp import ClientXMPP | ||||
|     from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
|     class EchoBot(ClientXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password): | ||||
|             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.send_presence() | ||||
|  | ||||
|             # Most get_* methods from plugins use Iq stanzas, which | ||||
|             # can generate IqError and IqTimeout exceptions | ||||
|             try: | ||||
|                 self.get_roster() | ||||
|             except IqError as err: | ||||
|                 logging.error('There was an error getting the roster') | ||||
|                 logging.error(err.iq['error']['condition']) | ||||
|                 self.disconnect() | ||||
|             except IqTimeout: | ||||
|                 logging.error('Server is taking too long to respond') | ||||
|                 self.disconnect() | ||||
|  | ||||
|         def message(self, msg): | ||||
|             if msg['type'] in ('chat', 'normal'): | ||||
|                 msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
|  | ||||
|     if __name__ == '__main__': | ||||
|         # Ideally use optparse or argparse to get JID,  | ||||
|         # password, and log level. | ||||
|  | ||||
|         logging.basicConfig(level=logging.DEBUG, | ||||
|                             format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|         xmpp = EchoBot('somejid@example.com', 'use_getpass') | ||||
|         xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|         xmpp.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|         # If you are working with an OpenFire server, you will need | ||||
|         # to useuterborg Larsson version: | ||||
|         # xmppissl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|         if xmpp.connect(): | ||||
|             xmpp.process(block=True) | ||||
|         else: | ||||
|             print("Unable to connect.") | ||||
|  | ||||
|  | ||||
| Credits | ||||
| ------- | ||||
| **Main Author:** Nathan Fritz | ||||
|     `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,  | ||||
|     `@fritzy <http://twitter.com/fritzy>`_ | ||||
|  | ||||
|     Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP | ||||
|     <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP | ||||
|     Council. | ||||
|  | ||||
| **Co-Author:** Lance Stout | ||||
|     `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,  | ||||
|     `@lancestout <http://twitter.com/lancestout>`_ | ||||
|  | ||||
| **Contributors:** | ||||
|     - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_) | ||||
|     - Dann Martens (`dannmartens <http://github.com/dannmartens>`_) | ||||
|     - Florent Le Coz (`louiz <http://github.com/louiz>`_) | ||||
|     - Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk) | ||||
|     - Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be) | ||||
|     - Te-jé Rogers (`te-je <http://github.com/te-je>`_) | ||||
|     - Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_) | ||||
							
								
								
									
										130
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| # Makefile for Sphinx documentation | ||||
| # | ||||
|  | ||||
| # You can set these variables from the command line. | ||||
| SPHINXOPTS    = | ||||
| SPHINXBUILD   = sphinx-build | ||||
| PAPER         = | ||||
| BUILDDIR      = _build | ||||
|  | ||||
| # Internal variables. | ||||
| PAPEROPT_a4     = -D latex_paper_size=a4 | ||||
| PAPEROPT_letter = -D latex_paper_size=letter | ||||
| ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | ||||
|  | ||||
| .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest | ||||
|  | ||||
| help: | ||||
| 	@echo "Please use \`make <target>' where <target> is one of" | ||||
| 	@echo "  html       to make standalone HTML files" | ||||
| 	@echo "  dirhtml    to make HTML files named index.html in directories" | ||||
| 	@echo "  singlehtml to make a single large HTML file" | ||||
| 	@echo "  pickle     to make pickle files" | ||||
| 	@echo "  json       to make JSON files" | ||||
| 	@echo "  htmlhelp   to make HTML files and a HTML help project" | ||||
| 	@echo "  qthelp     to make HTML files and a qthelp project" | ||||
| 	@echo "  devhelp    to make HTML files and a Devhelp project" | ||||
| 	@echo "  epub       to make an epub" | ||||
| 	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | ||||
| 	@echo "  latexpdf   to make LaTeX files and run them through pdflatex" | ||||
| 	@echo "  text       to make text files" | ||||
| 	@echo "  man        to make manual pages" | ||||
| 	@echo "  changes    to make an overview of all changed/added/deprecated items" | ||||
| 	@echo "  linkcheck  to check all external links for integrity" | ||||
| 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)" | ||||
|  | ||||
| clean: | ||||
| 	-rm -rf $(BUILDDIR)/* | ||||
|  | ||||
| html: | ||||
| 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | ||||
|  | ||||
| dirhtml: | ||||
| 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | ||||
|  | ||||
| singlehtml: | ||||
| 	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | ||||
| 	@echo | ||||
| 	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | ||||
|  | ||||
| pickle: | ||||
| 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can process the pickle files." | ||||
|  | ||||
| json: | ||||
| 	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can process the JSON files." | ||||
|  | ||||
| htmlhelp: | ||||
| 	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can run HTML Help Workshop with the" \ | ||||
| 	      ".hhp project file in $(BUILDDIR)/htmlhelp." | ||||
|  | ||||
| qthelp: | ||||
| 	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | ||||
| 	@echo | ||||
| 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \ | ||||
| 	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:" | ||||
| 	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SleekXMPP.qhcp" | ||||
| 	@echo "To view the help file:" | ||||
| 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SleekXMPP.qhc" | ||||
|  | ||||
| devhelp: | ||||
| 	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | ||||
| 	@echo | ||||
| 	@echo "Build finished." | ||||
| 	@echo "To view the help file:" | ||||
| 	@echo "# mkdir -p $$HOME/.local/share/devhelp/SleekXMPP" | ||||
| 	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SleekXMPP" | ||||
| 	@echo "# devhelp" | ||||
|  | ||||
| epub: | ||||
| 	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | ||||
| 	@echo | ||||
| 	@echo "Build finished. The epub file is in $(BUILDDIR)/epub." | ||||
|  | ||||
| latex: | ||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||
| 	@echo | ||||
| 	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | ||||
| 	@echo "Run \`make' in that directory to run these through (pdf)latex" \ | ||||
| 	      "(use \`make latexpdf' here to do that automatically)." | ||||
|  | ||||
| latexpdf: | ||||
| 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | ||||
| 	@echo "Running LaTeX files through pdflatex..." | ||||
| 	make -C $(BUILDDIR)/latex all-pdf | ||||
| 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | ||||
|  | ||||
| text: | ||||
| 	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | ||||
| 	@echo | ||||
| 	@echo "Build finished. The text files are in $(BUILDDIR)/text." | ||||
|  | ||||
| man: | ||||
| 	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | ||||
| 	@echo | ||||
| 	@echo "Build finished. The manual pages are in $(BUILDDIR)/man." | ||||
|  | ||||
| changes: | ||||
| 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | ||||
| 	@echo | ||||
| 	@echo "The overview file is in $(BUILDDIR)/changes." | ||||
|  | ||||
| linkcheck: | ||||
| 	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | ||||
| 	@echo | ||||
| 	@echo "Link check complete; look for any errors in the above output " \ | ||||
| 	      "or in $(BUILDDIR)/linkcheck/output.txt." | ||||
|  | ||||
| doctest: | ||||
| 	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | ||||
| 	@echo "Testing of doctests in the sources finished, look at the " \ | ||||
| 	      "results in $(BUILDDIR)/doctest/output.txt." | ||||
							
								
								
									
										452
									
								
								docs/_static/agogo.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										452
									
								
								docs/_static/agogo.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,452 @@ | ||||
| /* | ||||
|  * agogo.css_t | ||||
|  * ~~~~~~~~~~~ | ||||
|  * | ||||
|  * Sphinx stylesheet -- agogo theme. | ||||
|  * | ||||
|  * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|  * :license: BSD, see LICENSE for details. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| * { | ||||
|   margin: 0px; | ||||
|   padding: 0px; | ||||
| } | ||||
|  | ||||
| body { | ||||
|   font-family: "Verdana", Arial, sans-serif; | ||||
|   line-height: 1.4em; | ||||
|   color: black; | ||||
|   background-color: #eeeeec; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Page layout */ | ||||
|  | ||||
| div.header, div.content, div.footer { | ||||
|   width: 70em; | ||||
|   margin-left: auto; | ||||
|   margin-right: auto; | ||||
| } | ||||
|  | ||||
| div.header-wrapper { | ||||
|   background: url(bgtop.png) top left repeat-x; | ||||
|   border-bottom: 3px solid #2e3436; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Default body styles */ | ||||
| a { | ||||
|   color: #ce5c00; | ||||
| } | ||||
|  | ||||
| div.bodywrapper a, div.footer a { | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .clearer { | ||||
|   clear: both; | ||||
| } | ||||
|  | ||||
| .left { | ||||
|   float: left; | ||||
| } | ||||
|  | ||||
| .right { | ||||
|   float: right; | ||||
| } | ||||
|  | ||||
| .line-block { | ||||
|     display: block; | ||||
|     margin-top: 1em; | ||||
|     margin-bottom: 1em; | ||||
| } | ||||
|  | ||||
| .line-block .line-block { | ||||
|     margin-top: 0; | ||||
|     margin-bottom: 0; | ||||
|     margin-left: 1.5em; | ||||
| } | ||||
|  | ||||
| h1, h2, h3, h4 { | ||||
|   font-family: "Georgia", "Times New Roman", serif; | ||||
|   font-weight: normal; | ||||
|   color: #3465a4; | ||||
|   margin-bottom: .8em; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
|   color: #204a87; | ||||
| } | ||||
|  | ||||
| h2 { | ||||
|   padding-bottom: .5em; | ||||
|   border-bottom: 1px solid #3465a4; | ||||
| } | ||||
|  | ||||
| a.headerlink { | ||||
|   visibility: hidden; | ||||
|   color: #dddddd; | ||||
|   padding-left: .3em; | ||||
| } | ||||
|  | ||||
| h1:hover > a.headerlink, | ||||
| h2:hover > a.headerlink, | ||||
| h3:hover > a.headerlink, | ||||
| h4:hover > a.headerlink, | ||||
| h5:hover > a.headerlink, | ||||
| h6:hover > a.headerlink, | ||||
| dt:hover > a.headerlink { | ||||
|   visibility: visible; | ||||
| } | ||||
|  | ||||
| img { | ||||
|   border: 0; | ||||
| } | ||||
|  | ||||
| div.admonition { | ||||
|   margin-top: 10px; | ||||
|   margin-bottom: 10px; | ||||
|   padding: 2px 7px 1px 7px; | ||||
|   border-left: 0.2em solid black; | ||||
| } | ||||
|  | ||||
| p.admonition-title { | ||||
|   margin: 0px 10px 5px 0px; | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| dt:target, .highlighted { | ||||
|   background-color: #fbe54e; | ||||
| } | ||||
|  | ||||
| /* Header */ | ||||
|  | ||||
| div.header { | ||||
|   padding-top: 10px; | ||||
|   padding-bottom: 10px; | ||||
| } | ||||
|  | ||||
| div.header h1 { | ||||
|   font-family: "Georgia", "Times New Roman", serif; | ||||
|   font-weight: normal; | ||||
|   font-size: 180%; | ||||
|   letter-spacing: .08em; | ||||
| } | ||||
|  | ||||
| div.header h1 a { | ||||
|   color: white; | ||||
| } | ||||
|  | ||||
| div.header div.rel { | ||||
|   margin-top: 1em; | ||||
| } | ||||
|  | ||||
| div.header div.rel a { | ||||
|   color: #fcaf3e; | ||||
|   letter-spacing: .1em; | ||||
|   text-transform: uppercase; | ||||
| } | ||||
|  | ||||
| p.logo { | ||||
|     float: right; | ||||
| } | ||||
|  | ||||
| img.logo { | ||||
|     border: 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Content */ | ||||
| div.content-wrapper { | ||||
|   background-color: white; | ||||
|   padding-top: 20px; | ||||
|   padding-bottom: 20px; | ||||
| } | ||||
|  | ||||
| div.document { | ||||
|   width: 50em; | ||||
|   float: left; | ||||
| } | ||||
|  | ||||
| div.body { | ||||
|   padding-right: 2em; | ||||
|   text-align: justify; | ||||
| } | ||||
|  | ||||
| div.document ul { | ||||
|   margin: 1.5em; | ||||
|   list-style-type: square; | ||||
| } | ||||
|  | ||||
| div.document dd { | ||||
|   margin-left: 1.2em; | ||||
|   margin-top: .4em; | ||||
|   margin-bottom: 1em; | ||||
| } | ||||
|  | ||||
| div.document .section { | ||||
|   margin-top: 1.7em; | ||||
| } | ||||
| div.document .section:first-child { | ||||
|   margin-top: 0px; | ||||
| } | ||||
|  | ||||
| div.document div.highlight { | ||||
|   padding: 3px; | ||||
|   background-color: #eeeeec; | ||||
|   border-top: 2px solid #dddddd; | ||||
|   border-bottom: 2px solid #dddddd; | ||||
|   margin-top: .8em; | ||||
|   margin-bottom: .8em; | ||||
| } | ||||
|  | ||||
| div.document h2 { | ||||
|   margin-top: .7em; | ||||
| } | ||||
|  | ||||
| div.document p { | ||||
|   margin-bottom: .5em; | ||||
| } | ||||
|  | ||||
| div.document li.toctree-l1 { | ||||
|   margin-bottom: 1em; | ||||
| } | ||||
|  | ||||
| div.document .descname { | ||||
|   font-weight: bold; | ||||
| } | ||||
|  | ||||
| div.document .docutils.literal { | ||||
|   background-color: #eeeeec; | ||||
|   padding: 1px; | ||||
| } | ||||
|  | ||||
| div.document .docutils.xref.literal { | ||||
|   background-color: transparent; | ||||
|   padding: 0px; | ||||
| } | ||||
|  | ||||
| div.document blockquote { | ||||
|   margin: 1em; | ||||
| } | ||||
|  | ||||
| div.document ol { | ||||
|   margin: 1.5em; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Sidebar */ | ||||
|  | ||||
| div.sidebar { | ||||
|   width: 20em; | ||||
|   float: right; | ||||
|   font-size: .9em; | ||||
| } | ||||
|  | ||||
| div.sidebar a, div.header a { | ||||
|   text-decoration: none; | ||||
| } | ||||
|  | ||||
| div.sidebar a:hover, div.header a:hover { | ||||
|   text-decoration: underline; | ||||
| } | ||||
|  | ||||
| div.sidebar h3 { | ||||
|   color: #2e3436; | ||||
|   text-transform: uppercase; | ||||
|   font-size: 130%; | ||||
|   letter-spacing: .1em; | ||||
| } | ||||
|  | ||||
| div.sidebar ul { | ||||
|   list-style-type: none; | ||||
| } | ||||
|  | ||||
| div.sidebar li.toctree-l1 a { | ||||
|   display: block; | ||||
|   padding: 1px; | ||||
|   border: 1px solid #dddddd; | ||||
|   background-color: #eeeeec; | ||||
|   margin-bottom: .4em; | ||||
|   padding-left: 3px; | ||||
|   color: #2e3436; | ||||
| } | ||||
|  | ||||
| div.sidebar li.toctree-l2 a { | ||||
|   background-color: transparent; | ||||
|   border: none; | ||||
|   margin-left: 1em; | ||||
|   border-bottom: 1px solid #dddddd; | ||||
| } | ||||
|  | ||||
| div.sidebar li.toctree-l3 a { | ||||
|   background-color: transparent; | ||||
|   border: none; | ||||
|   margin-left: 2em; | ||||
|   border-bottom: 1px solid #dddddd; | ||||
| } | ||||
|  | ||||
| div.sidebar li.toctree-l2:last-child a { | ||||
|   border-bottom: none; | ||||
| } | ||||
|  | ||||
| div.sidebar li.toctree-l1.current a { | ||||
|   border-right: 5px solid #fcaf3e; | ||||
| } | ||||
|  | ||||
| div.sidebar li.toctree-l1.current li.toctree-l2 a { | ||||
|   border-right: none; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Footer */ | ||||
|  | ||||
| div.footer-wrapper { | ||||
|   background: url(bgfooter.png) top left repeat-x; | ||||
|   border-top: 4px solid #babdb6; | ||||
|   padding-top: 10px; | ||||
|   padding-bottom: 10px; | ||||
|   min-height: 80px; | ||||
| } | ||||
|  | ||||
| div.footer, div.footer a { | ||||
|   color: #888a85; | ||||
| } | ||||
|  | ||||
| div.footer .right { | ||||
|   text-align: right; | ||||
| } | ||||
|  | ||||
| div.footer .left { | ||||
|   text-transform: uppercase; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Styles copied from basic theme */ | ||||
|  | ||||
| img.align-left, .figure.align-left, object.align-left { | ||||
|     clear: left; | ||||
|     float: left; | ||||
|     margin-right: 1em; | ||||
| } | ||||
|  | ||||
| img.align-right, .figure.align-right, object.align-right { | ||||
|     clear: right; | ||||
|     float: right; | ||||
|     margin-left: 1em; | ||||
| } | ||||
|  | ||||
| img.align-center, .figure.align-center, object.align-center { | ||||
|   display: block; | ||||
|   margin-left: auto; | ||||
|   margin-right: auto; | ||||
| } | ||||
|  | ||||
| .align-left { | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| .align-center { | ||||
|     clear: both; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .align-right { | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| /* -- search page ----------------------------------------------------------- */ | ||||
|  | ||||
| ul.search { | ||||
|     margin: 10px 0 0 20px; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| ul.search li { | ||||
|     padding: 5px 0 5px 20px; | ||||
|     background-image: url(file.png); | ||||
|     background-repeat: no-repeat; | ||||
|     background-position: 0 7px; | ||||
| } | ||||
|  | ||||
| ul.search li a { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| ul.search li div.context { | ||||
|     color: #888; | ||||
|     margin: 2px 0 0 30px; | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| ul.keywordmatches li.goodmatch a { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| /* -- index page ------------------------------------------------------------ */ | ||||
|  | ||||
| table.contentstable { | ||||
|     width: 90%; | ||||
| } | ||||
|  | ||||
| table.contentstable p.biglink { | ||||
|     line-height: 150%; | ||||
| } | ||||
|  | ||||
| a.biglink { | ||||
|     font-size: 1.3em; | ||||
| } | ||||
|  | ||||
| span.linkdescr { | ||||
|     font-style: italic; | ||||
|     padding-top: 5px; | ||||
|     font-size: 90%; | ||||
| } | ||||
|  | ||||
| /* -- general index --------------------------------------------------------- */ | ||||
|  | ||||
| table.indextable td { | ||||
|     text-align: left; | ||||
|     vertical-align: top; | ||||
| } | ||||
|  | ||||
| table.indextable dl, table.indextable dd { | ||||
|     margin-top: 0; | ||||
|     margin-bottom: 0; | ||||
| } | ||||
|  | ||||
| table.indextable tr.pcap { | ||||
|     height: 10px; | ||||
| } | ||||
|  | ||||
| table.indextable tr.cap { | ||||
|     margin-top: 10px; | ||||
|     background-color: #f2f2f2; | ||||
| } | ||||
|  | ||||
| img.toggler { | ||||
|     margin-right: 3px; | ||||
|     margin-top: 3px; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| /* -- viewcode extension ---------------------------------------------------- */ | ||||
|  | ||||
| .viewcode-link { | ||||
|     float: right; | ||||
| } | ||||
|  | ||||
| .viewcode-back { | ||||
|     float: right; | ||||
|     font-family:: "Verdana", Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| div.viewcode-block:target { | ||||
|     margin: -1px -3px; | ||||
|     padding: 0 3px; | ||||
|     background-color: #f4debf; | ||||
|     border-top: 1px solid #ac9; | ||||
|     border-bottom: 1px solid #ac9; | ||||
| } | ||||
							
								
								
									
										532
									
								
								docs/_static/basic.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								docs/_static/basic.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,532 @@ | ||||
| /* | ||||
|  * basic.css | ||||
|  * ~~~~~~~~~ | ||||
|  * | ||||
|  * Sphinx stylesheet -- basic theme. | ||||
|  * | ||||
|  * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|  * :license: BSD, see LICENSE for details. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| /* -- main layout ----------------------------------------------------------- */ | ||||
|  | ||||
| div.clearer { | ||||
|     clear: both; | ||||
| } | ||||
|  | ||||
| /* -- relbar ---------------------------------------------------------------- */ | ||||
|  | ||||
| div.related { | ||||
|     width: 100%; | ||||
|     font-size: 90%; | ||||
| } | ||||
|  | ||||
| div.related h3 { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| div.related ul { | ||||
|     margin: 0; | ||||
|     padding: 0 0 0 10px; | ||||
|     list-style: none; | ||||
| } | ||||
|  | ||||
| div.related li { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| div.related li.right { | ||||
|     float: right; | ||||
|     margin-right: 5px; | ||||
| } | ||||
|  | ||||
| /* -- sidebar --------------------------------------------------------------- */ | ||||
|  | ||||
| div.sphinxsidebarwrapper { | ||||
|     padding: 10px 5px 0 10px; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar { | ||||
|     float: left; | ||||
|     width: 230px; | ||||
|     margin-left: -100%; | ||||
|     font-size: 90%; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar ul { | ||||
|     list-style: none; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar ul ul, | ||||
| div.sphinxsidebar ul.want-points { | ||||
|     margin-left: 20px; | ||||
|     list-style: square; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar ul ul { | ||||
|     margin-top: 0; | ||||
|     margin-bottom: 0; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar form { | ||||
|     margin-top: 10px; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar input { | ||||
|     border: 1px solid #98dbcc; | ||||
|     font-family: sans-serif; | ||||
|     font-size: 1em; | ||||
| } | ||||
|  | ||||
| img { | ||||
|     border: 0; | ||||
| } | ||||
|  | ||||
| /* -- search page ----------------------------------------------------------- */ | ||||
|  | ||||
| ul.search { | ||||
|     margin: 10px 0 0 20px; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| ul.search li { | ||||
|     padding: 5px 0 5px 20px; | ||||
|     background-image: url(file.png); | ||||
|     background-repeat: no-repeat; | ||||
|     background-position: 0 7px; | ||||
| } | ||||
|  | ||||
| ul.search li a { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| ul.search li div.context { | ||||
|     color: #888; | ||||
|     margin: 2px 0 0 30px; | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| ul.keywordmatches li.goodmatch a { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| /* -- index page ------------------------------------------------------------ */ | ||||
|  | ||||
| table.contentstable { | ||||
|     width: 90%; | ||||
| } | ||||
|  | ||||
| table.contentstable p.biglink { | ||||
|     line-height: 150%; | ||||
| } | ||||
|  | ||||
| a.biglink { | ||||
|     font-size: 1.3em; | ||||
| } | ||||
|  | ||||
| span.linkdescr { | ||||
|     font-style: italic; | ||||
|     padding-top: 5px; | ||||
|     font-size: 90%; | ||||
| } | ||||
|  | ||||
| /* -- general index --------------------------------------------------------- */ | ||||
|  | ||||
| table.indextable { | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| table.indextable td { | ||||
|     text-align: left; | ||||
|     vertical-align: top; | ||||
| } | ||||
|  | ||||
| table.indextable dl, table.indextable dd { | ||||
|     margin-top: 0; | ||||
|     margin-bottom: 0; | ||||
| } | ||||
|  | ||||
| table.indextable tr.pcap { | ||||
|     height: 10px; | ||||
| } | ||||
|  | ||||
| table.indextable tr.cap { | ||||
|     margin-top: 10px; | ||||
|     background-color: #f2f2f2; | ||||
| } | ||||
|  | ||||
| img.toggler { | ||||
|     margin-right: 3px; | ||||
|     margin-top: 3px; | ||||
|     cursor: pointer; | ||||
| } | ||||
|  | ||||
| div.modindex-jumpbox { | ||||
|     border-top: 1px solid #ddd; | ||||
|     border-bottom: 1px solid #ddd; | ||||
|     margin: 1em 0 1em 0; | ||||
|     padding: 0.4em; | ||||
| } | ||||
|  | ||||
| div.genindex-jumpbox { | ||||
|     border-top: 1px solid #ddd; | ||||
|     border-bottom: 1px solid #ddd; | ||||
|     margin: 1em 0 1em 0; | ||||
|     padding: 0.4em; | ||||
| } | ||||
|  | ||||
| /* -- general body styles --------------------------------------------------- */ | ||||
|  | ||||
| a.headerlink { | ||||
|     visibility: hidden; | ||||
| } | ||||
|  | ||||
| h1:hover > a.headerlink, | ||||
| h2:hover > a.headerlink, | ||||
| h3:hover > a.headerlink, | ||||
| h4:hover > a.headerlink, | ||||
| h5:hover > a.headerlink, | ||||
| h6:hover > a.headerlink, | ||||
| dt:hover > a.headerlink { | ||||
|     visibility: visible; | ||||
| } | ||||
|  | ||||
| div.body p.caption { | ||||
|     text-align: inherit; | ||||
| } | ||||
|  | ||||
| div.body td { | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| .field-list ul { | ||||
|     padding-left: 1em; | ||||
| } | ||||
|  | ||||
| .first { | ||||
|     margin-top: 0 !important; | ||||
| } | ||||
|  | ||||
| p.rubric { | ||||
|     margin-top: 30px; | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| img.align-left, .figure.align-left, object.align-left { | ||||
|     clear: left; | ||||
|     float: left; | ||||
|     margin-right: 1em; | ||||
| } | ||||
|  | ||||
| img.align-right, .figure.align-right, object.align-right { | ||||
|     clear: right; | ||||
|     float: right; | ||||
|     margin-left: 1em; | ||||
| } | ||||
|  | ||||
| img.align-center, .figure.align-center, object.align-center { | ||||
|   display: block; | ||||
|   margin-left: auto; | ||||
|   margin-right: auto; | ||||
| } | ||||
|  | ||||
| .align-left { | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| .align-center { | ||||
|     clear: both; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| .align-right { | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| /* -- sidebars -------------------------------------------------------------- */ | ||||
|  | ||||
| div.sidebar { | ||||
|     margin: 0 0 0.5em 1em; | ||||
|     border: 1px solid #ddb; | ||||
|     padding: 7px 7px 0 7px; | ||||
|     background-color: #efefef; | ||||
|     width: 40%; | ||||
|     float: right; | ||||
|     -mox-border-radius: 4px; | ||||
|     -webkit-border-radius: 4px; | ||||
|     border-radius: 4px; | ||||
| } | ||||
|  | ||||
| p.sidebar-title { | ||||
|     font-weight: bold; | ||||
|     text-transform: uppercase; | ||||
| } | ||||
|  | ||||
| /* -- topics ---------------------------------------------------------------- */ | ||||
|  | ||||
| div.topic { | ||||
|     border: 1px solid #ccc; | ||||
|     padding: 7px 7px 0 7px; | ||||
|     margin: 10px 0 10px 0; | ||||
| } | ||||
|  | ||||
| p.topic-title { | ||||
|     font-size: 1.1em; | ||||
|     font-weight: bold; | ||||
|     margin-top: 10px; | ||||
| } | ||||
|  | ||||
| /* -- admonitions ----------------------------------------------------------- */ | ||||
|  | ||||
| div.admonition { | ||||
|     margin-top: 10px; | ||||
|     margin-bottom: 10px; | ||||
|     padding: 7px; | ||||
| } | ||||
|  | ||||
| div.admonition dt { | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| div.admonition dl { | ||||
|     margin-bottom: 0; | ||||
| } | ||||
|  | ||||
| p.admonition-title { | ||||
|     margin: 0px 10px 5px 0px; | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| div.body p.centered { | ||||
|     text-align: center; | ||||
|     margin-top: 25px; | ||||
| } | ||||
|  | ||||
| /* -- tables ---------------------------------------------------------------- */ | ||||
|  | ||||
| table.docutils { | ||||
|     border: 0; | ||||
|     border-collapse: collapse; | ||||
| } | ||||
|  | ||||
| table.docutils td, table.docutils th { | ||||
|     padding: 1px 8px 1px 5px; | ||||
|     border-top: 0; | ||||
|     border-left: 0; | ||||
|     border-right: 0; | ||||
|     border-bottom: 1px solid #aaa; | ||||
| } | ||||
|  | ||||
| table.field-list td, table.field-list th { | ||||
|     border: 0 !important; | ||||
| } | ||||
|  | ||||
| table.footnote td, table.footnote th { | ||||
|     border: 0 !important; | ||||
| } | ||||
|  | ||||
| th { | ||||
|     text-align: left; | ||||
|     padding-right: 5px; | ||||
| } | ||||
|  | ||||
| table.citation { | ||||
|     border-left: solid 1px gray; | ||||
|     margin-left: 1px; | ||||
| } | ||||
|  | ||||
| table.citation td { | ||||
|     border-bottom: none; | ||||
| } | ||||
|  | ||||
| /* -- other body styles ----------------------------------------------------- */ | ||||
|  | ||||
| ol.arabic { | ||||
|     list-style: decimal; | ||||
| } | ||||
|  | ||||
| ol.loweralpha { | ||||
|     list-style: lower-alpha; | ||||
| } | ||||
|  | ||||
| ol.upperalpha { | ||||
|     list-style: upper-alpha; | ||||
| } | ||||
|  | ||||
| ol.lowerroman { | ||||
|     list-style: lower-roman; | ||||
| } | ||||
|  | ||||
| ol.upperroman { | ||||
|     list-style: upper-roman; | ||||
| } | ||||
|  | ||||
| dl { | ||||
|     margin-bottom: 15px; | ||||
| } | ||||
|  | ||||
| dd p { | ||||
|     margin-top: 0px; | ||||
| } | ||||
|  | ||||
| dd ul, dd table { | ||||
|     margin-bottom: 10px; | ||||
| } | ||||
|  | ||||
| dd { | ||||
|     margin-top: 3px; | ||||
|     margin-bottom: 10px; | ||||
|     margin-left: 30px; | ||||
| } | ||||
|  | ||||
| dt:target, .highlighted { | ||||
| } | ||||
|  | ||||
| dl.glossary dt { | ||||
|     font-weight: bold; | ||||
|     font-size: 1.1em; | ||||
| } | ||||
|  | ||||
| .field-list ul { | ||||
|     margin: 0; | ||||
|     padding-left: 1em; | ||||
| } | ||||
|  | ||||
| .field-list p { | ||||
|     margin: 0; | ||||
| } | ||||
|  | ||||
| .refcount { | ||||
|     color: #060; | ||||
| } | ||||
|  | ||||
| .optional { | ||||
|     font-size: 1.3em; | ||||
| } | ||||
|  | ||||
| .versionmodified { | ||||
|     font-style: italic; | ||||
| } | ||||
|  | ||||
| .system-message { | ||||
|     background-color: #fda; | ||||
|     padding: 5px; | ||||
|     border: 3px solid red; | ||||
| } | ||||
|  | ||||
| .footnote:target  { | ||||
|     background-color: #ffa; | ||||
| } | ||||
|  | ||||
| .line-block { | ||||
|     display: block; | ||||
|     margin-top: 1em; | ||||
|     margin-bottom: 1em; | ||||
| } | ||||
|  | ||||
| .line-block .line-block { | ||||
|     margin-top: 0; | ||||
|     margin-bottom: 0; | ||||
|     margin-left: 1.5em; | ||||
| } | ||||
|  | ||||
| .guilabel, .menuselection { | ||||
|     font-family: sans-serif; | ||||
| } | ||||
|  | ||||
| .accelerator { | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| .classifier { | ||||
|     font-style: oblique; | ||||
| } | ||||
|  | ||||
| /* -- code displays --------------------------------------------------------- */ | ||||
|  | ||||
| pre { | ||||
|     overflow: auto; | ||||
|     overflow-y: hidden;  /* fixes display issues on Chrome browsers */ | ||||
| } | ||||
|  | ||||
| td.linenos pre { | ||||
|     padding: 5px 0px; | ||||
|     border: 0; | ||||
|     background-color: transparent; | ||||
|     color: #aaa; | ||||
| } | ||||
|  | ||||
| table.highlighttable { | ||||
|     margin-left: 0.5em; | ||||
| } | ||||
|  | ||||
| table.highlighttable td { | ||||
|     padding: 0 0.5em 0 0.5em; | ||||
| } | ||||
|  | ||||
| tt.descname { | ||||
|     background-color: transparent; | ||||
|     font-weight: bold; | ||||
|     font-size: 1.2em; | ||||
| } | ||||
|  | ||||
| tt.descclassname { | ||||
|     background-color: transparent; | ||||
| } | ||||
|  | ||||
| tt.xref, a tt { | ||||
|     background-color: transparent; | ||||
|     font-weight: bold; | ||||
| } | ||||
|  | ||||
| h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { | ||||
|     background-color: transparent; | ||||
| } | ||||
|  | ||||
| .viewcode-link { | ||||
|     float: right; | ||||
| } | ||||
|  | ||||
| .viewcode-back { | ||||
|     float: right; | ||||
|     font-family: sans-serif; | ||||
| } | ||||
|  | ||||
| div.viewcode-block:target { | ||||
|     margin: -1px -10px; | ||||
|     padding: 0 10px; | ||||
| } | ||||
|  | ||||
| /* -- math display ---------------------------------------------------------- */ | ||||
|  | ||||
| img.math { | ||||
|     vertical-align: middle; | ||||
| } | ||||
|  | ||||
| div.body div.math p { | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| span.eqno { | ||||
|     float: right; | ||||
| } | ||||
|  | ||||
| /* -- printout stylesheet --------------------------------------------------- */ | ||||
|  | ||||
| @media print { | ||||
|     div.document, | ||||
|     div.documentwrapper, | ||||
|     div.bodywrapper { | ||||
|         margin: 0 !important; | ||||
|         width: 100%; | ||||
|     } | ||||
|  | ||||
|     div.sphinxsidebar, | ||||
|     div.related, | ||||
|     div.footer, | ||||
|     #top-link { | ||||
|         display: none; | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										256
									
								
								docs/_static/default.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								docs/_static/default.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,256 @@ | ||||
| /* | ||||
|  * default.css_t | ||||
|  * ~~~~~~~~~~~~~ | ||||
|  * | ||||
|  * Sphinx stylesheet -- default theme. | ||||
|  * | ||||
|  * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|  * :license: BSD, see LICENSE for details. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| @import url("basic.css"); | ||||
|  | ||||
| /* -- page layout ----------------------------------------------------------- */ | ||||
|  | ||||
| body { | ||||
|     font-family: sans-serif; | ||||
|     font-size: 100%; | ||||
|     background-color: #11303d; | ||||
|     color: #000; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.document { | ||||
|     background-color: #1c4e63; | ||||
| } | ||||
|  | ||||
| div.documentwrapper { | ||||
|     float: left; | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| div.bodywrapper { | ||||
|     margin: 0 0 0 230px; | ||||
| } | ||||
|  | ||||
| div.body { | ||||
|     background-color: #ffffff; | ||||
|     color: #000000; | ||||
|     padding: 0 20px 30px 20px; | ||||
| } | ||||
|  | ||||
| div.footer { | ||||
|     color: #ffffff; | ||||
|     width: 100%; | ||||
|     padding: 9px 0 9px 0; | ||||
|     text-align: center; | ||||
|     font-size: 75%; | ||||
| } | ||||
|  | ||||
| div.footer a { | ||||
|     color: #ffffff; | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| div.related { | ||||
|     background-color: #133f52; | ||||
|     line-height: 30px; | ||||
|     color: #ffffff; | ||||
| } | ||||
|  | ||||
| div.related a { | ||||
|     color: #ffffff; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar { | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar h3 { | ||||
|     font-family: 'Trebuchet MS', sans-serif; | ||||
|     color: #ffffff; | ||||
|     font-size: 1.4em; | ||||
|     font-weight: normal; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar h3 a { | ||||
|     color: #ffffff; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar h4 { | ||||
|     font-family: 'Trebuchet MS', sans-serif; | ||||
|     color: #ffffff; | ||||
|     font-size: 1.3em; | ||||
|     font-weight: normal; | ||||
|     margin: 5px 0 0 0; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar p { | ||||
|     color: #ffffff; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar p.topless { | ||||
|     margin: 5px 10px 10px 10px; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar ul { | ||||
|     margin: 10px; | ||||
|     padding: 0; | ||||
|     color: #ffffff; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar a { | ||||
|     color: #98dbcc; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar input { | ||||
|     border: 1px solid #98dbcc; | ||||
|     font-family: sans-serif; | ||||
|     font-size: 1em; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /* -- hyperlink styles ------------------------------------------------------ */ | ||||
|  | ||||
| a { | ||||
|     color: #355f7c; | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| a:visited { | ||||
|     color: #355f7c; | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| a:hover { | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /* -- body styles ----------------------------------------------------------- */ | ||||
|  | ||||
| div.body h1, | ||||
| div.body h2, | ||||
| div.body h3, | ||||
| div.body h4, | ||||
| div.body h5, | ||||
| div.body h6 { | ||||
|     font-family: 'Trebuchet MS', sans-serif; | ||||
|     background-color: #f2f2f2; | ||||
|     font-weight: normal; | ||||
|     color: #20435c; | ||||
|     border-bottom: 1px solid #ccc; | ||||
|     margin: 20px -20px 10px -20px; | ||||
|     padding: 3px 0 3px 10px; | ||||
| } | ||||
|  | ||||
| div.body h1 { margin-top: 0; font-size: 200%; } | ||||
| div.body h2 { font-size: 160%; } | ||||
| div.body h3 { font-size: 140%; } | ||||
| div.body h4 { font-size: 120%; } | ||||
| div.body h5 { font-size: 110%; } | ||||
| div.body h6 { font-size: 100%; } | ||||
|  | ||||
| a.headerlink { | ||||
|     color: #c60f0f; | ||||
|     font-size: 0.8em; | ||||
|     padding: 0 4px 0 4px; | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| a.headerlink:hover { | ||||
|     background-color: #c60f0f; | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| div.body p, div.body dd, div.body li { | ||||
|     text-align: justify; | ||||
|     line-height: 130%; | ||||
| } | ||||
|  | ||||
| div.admonition p.admonition-title + p { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| div.admonition p { | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| div.admonition pre { | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| div.admonition ul, div.admonition ol { | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| div.note { | ||||
|     background-color: #eee; | ||||
|     border: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| div.seealso { | ||||
|     background-color: #ffc; | ||||
|     border: 1px solid #ff6; | ||||
| } | ||||
|  | ||||
| div.topic { | ||||
|     background-color: #eee; | ||||
| } | ||||
|  | ||||
| div.warning { | ||||
|     background-color: #ffe4e4; | ||||
|     border: 1px solid #f66; | ||||
| } | ||||
|  | ||||
| p.admonition-title { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| p.admonition-title:after { | ||||
|     content: ":"; | ||||
| } | ||||
|  | ||||
| pre { | ||||
|     padding: 5px; | ||||
|     background-color: #eeffcc; | ||||
|     color: #333333; | ||||
|     line-height: 120%; | ||||
|     border: 1px solid #ac9; | ||||
|     border-left: none; | ||||
|     border-right: none; | ||||
| } | ||||
|  | ||||
| tt { | ||||
|     background-color: #ecf0f3; | ||||
|     padding: 0 1px 0 1px; | ||||
|     font-size: 0.95em; | ||||
| } | ||||
|  | ||||
| th { | ||||
|     background-color: #ede; | ||||
| } | ||||
|  | ||||
| .warning tt { | ||||
|     background: #efc2c2; | ||||
| } | ||||
|  | ||||
| .note tt { | ||||
|     background: #d6d6d6; | ||||
| } | ||||
|  | ||||
| .viewcode-back { | ||||
|     font-family: sans-serif; | ||||
| } | ||||
|  | ||||
| div.viewcode-block:target { | ||||
|     background-color: #f4debf; | ||||
|     border-top: 1px solid #ac9; | ||||
|     border-bottom: 1px solid #ac9; | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										406
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										406
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,406 @@ | ||||
| /* | ||||
|  * haiku.css_t | ||||
|  * ~~~~~~~~~~~ | ||||
|  * | ||||
|  * Sphinx stylesheet -- haiku theme. | ||||
|  * | ||||
|  * Adapted from http://haiku-os.org/docs/Haiku-doc.css. | ||||
|  * Original copyright message: | ||||
|  * | ||||
|  *     Copyright 2008-2009, Haiku. All rights reserved. | ||||
|  *     Distributed under the terms of the MIT License. | ||||
|  * | ||||
|  *     Authors: | ||||
|  *              Francois Revol <revol@free.fr> | ||||
|  *              Stephan Assmus <superstippi@gmx.de> | ||||
|  *              Braden Ewing <brewin@gmail.com> | ||||
|  *              Humdinger <humdingerb@gmail.com> | ||||
|  * | ||||
|  * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|  * :license: BSD, see LICENSE for details. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| @import url("basic.css"); | ||||
|  | ||||
|  | ||||
| @font-face { | ||||
|     font-family: "Museo Slab"; | ||||
|     font-weight: normal; | ||||
|     font-style: normal; | ||||
|     src: local("Museo Slab"), | ||||
|          url("fonts/Museo_Slab_500.otf") format("opentype"); | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|     font-family: "Yanone Kaffeesatz"; | ||||
|     font-weight: bold; | ||||
|     font-style: normal; | ||||
|     src: local("Yanone Kaffeesatz"), | ||||
|          url("fonts/YanoneKaffeesatz-Bold.ttf") format("truetype"); | ||||
| } | ||||
|  | ||||
| @font-face { | ||||
|     font-family: "Yanone Kaffeesatz"; | ||||
|     font-weight: lighter; | ||||
|     font-style: normal; | ||||
|     src: local("Yanone Kaffeesatz"), | ||||
|          url("fonts/YanoneKaffeesatz-Regular.ttf") format("truetype"); | ||||
| } | ||||
|  | ||||
| html { | ||||
|     margin: 0px; | ||||
|     padding: 0px; | ||||
|     background: #FFF url(header.png) top left repeat-x; | ||||
| } | ||||
|  | ||||
| body { | ||||
|     line-height: 1.5; | ||||
|     margin: auto; | ||||
|     padding: 0px; | ||||
|     font-family: "Helvetica Neueu", Helvetica, sans-serif; | ||||
|     min-width: 59em; | ||||
|     max-width: 70em; | ||||
|     color: #444; | ||||
| } | ||||
|  | ||||
| div.footer { | ||||
|     padding: 8px; | ||||
|     font-size: 11px; | ||||
|     text-align: center; | ||||
|     letter-spacing: 0.5px; | ||||
| } | ||||
|  | ||||
| /* link colors and text decoration */ | ||||
|  | ||||
| a:link { | ||||
|     font-weight: bold; | ||||
|     text-decoration: none; | ||||
|     color: #00ADEE; | ||||
| } | ||||
|  | ||||
| a:visited { | ||||
|     font-weight: bold; | ||||
|     text-decoration: none; | ||||
|     color: #00ADEE; | ||||
| } | ||||
|  | ||||
| a:hover, a:active { | ||||
|     text-decoration: underline; | ||||
|     color: #F46DBA; | ||||
| } | ||||
|  | ||||
| /* Some headers act as anchors, don't give them a hover effect */ | ||||
|  | ||||
| h1 a:hover, a:active { | ||||
|     text-decoration: none; | ||||
|     color: #CFCFCF; | ||||
| } | ||||
|  | ||||
| h2 a:hover, a:active { | ||||
|     text-decoration: none; | ||||
|     color: #CFCFCF; | ||||
| } | ||||
|  | ||||
| h3 a:hover, a:active { | ||||
|     text-decoration: none; | ||||
|     color: #CFCFCF; | ||||
| } | ||||
|  | ||||
| h4 a:hover, a:active { | ||||
|     text-decoration: none; | ||||
|     color: #CFCFCF; | ||||
| } | ||||
|  | ||||
| a.headerlink { | ||||
|     color: #a7ce38; | ||||
|     padding-left: 5px; | ||||
| } | ||||
|  | ||||
| a.headerlink:hover { | ||||
|     color: #a7ce38; | ||||
| } | ||||
|  | ||||
| /* basic text elements */ | ||||
|  | ||||
| div.content { | ||||
|     margin-top: 20px; | ||||
|     margin-left: 40px; | ||||
|     margin-right: 40px; | ||||
|     margin-bottom: 50px; | ||||
|     font-size: 0.9em; | ||||
| } | ||||
|  | ||||
| /* heading and navigation */ | ||||
|  | ||||
| div.header { | ||||
|     position: relative; | ||||
|     margin-top: 125px; | ||||
|     height: 85px; | ||||
|     padding: 0 40px; | ||||
|     font-family: "Yanone Kaffeesatz"; | ||||
| } | ||||
| div.header h1 { | ||||
|     font-size: 2.6em; | ||||
|     font-weight: normal; | ||||
|     letter-spacing: 1px; | ||||
|     color: #CFCFCF; | ||||
|     border: 0; | ||||
|     margin: 0; | ||||
|     padding-top: 15px; | ||||
|     font-family: "Yanone Kaffeesatz"; | ||||
|     text-shadow: 1px 1px 1px rgba(175, 175, 175, .8); | ||||
|     font-variant: small-caps; | ||||
| } | ||||
| div.header h1 a { | ||||
|     font-weight: normal; | ||||
|     color: #00ADEE; | ||||
| } | ||||
| div.header h2 { | ||||
|     font-size: 1.3em; | ||||
|     font-weight: normal; | ||||
|     letter-spacing: 1px; | ||||
|     text-transform: uppercase; | ||||
|     color: #aaa; | ||||
|     border: 0; | ||||
|     margin-top: -3px; | ||||
|     padding: 0; | ||||
|     font-family: "Yanone Kaffeesatz"; | ||||
| } | ||||
|  | ||||
| div.header img.rightlogo { | ||||
|     float: right; | ||||
| } | ||||
|  | ||||
|  | ||||
| div.title { | ||||
|     font-size: 1.3em; | ||||
|     font-weight: bold; | ||||
|     color: #CFCFCF; | ||||
|     border-bottom: dotted thin #e0e0e0; | ||||
|     margin-bottom: 25px; | ||||
| } | ||||
| div.topnav { | ||||
|     position: relative; | ||||
|     z-index: 0; | ||||
| } | ||||
| div.topnav p { | ||||
|     margin-top: 0; | ||||
|     margin-left: 40px; | ||||
|     margin-right: 40px; | ||||
|     margin-bottom: 0px; | ||||
|     text-align: right; | ||||
|     font-size: 0.8em; | ||||
| } | ||||
| div.bottomnav { | ||||
|     background: #eeeeee; | ||||
| } | ||||
| div.bottomnav p { | ||||
|     margin-right: 40px; | ||||
|     text-align: right; | ||||
|     font-size: 0.8em; | ||||
| } | ||||
|  | ||||
| a.uplink { | ||||
|     font-weight: normal; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* contents box */ | ||||
|  | ||||
| table.index { | ||||
|     margin: 0px 0px 30px 30px; | ||||
|     padding: 1px; | ||||
|     border-width: 1px; | ||||
|     border-style: dotted; | ||||
|     border-color: #e0e0e0; | ||||
| } | ||||
| table.index tr.heading { | ||||
|     background-color: #e0e0e0; | ||||
|     text-align: center; | ||||
|     font-weight: bold; | ||||
|     font-size: 1.1em; | ||||
| } | ||||
| table.index tr.index { | ||||
|     background-color: #eeeeee; | ||||
| } | ||||
| table.index td { | ||||
|     padding: 5px 20px; | ||||
| } | ||||
|  | ||||
| table.index a:link, table.index a:visited { | ||||
|     font-weight: normal; | ||||
|     text-decoration: none; | ||||
|     color: #4A7389; | ||||
| } | ||||
| table.index a:hover, table.index a:active { | ||||
|     text-decoration: underline; | ||||
|     color: #ff4500; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Haiku User Guide styles and layout */ | ||||
|  | ||||
| /* Rounded corner boxes */ | ||||
| /* Common declarations */ | ||||
| div.admonition { | ||||
|     -webkit-border-radius: 10px; | ||||
|     -khtml-border-radius: 10px; | ||||
|     -moz-border-radius: 10px; | ||||
|     border-radius: 10px; | ||||
|     border-style: dotted; | ||||
|     border-width: thin; | ||||
|     border-color: #dcdcdc; | ||||
|     padding: 10px 15px 10px 15px; | ||||
|     margin-bottom: 15px; | ||||
|     margin-top: 15px; | ||||
| } | ||||
| div.note { | ||||
|     padding: 10px 15px 10px 15px; | ||||
|     background-color: #e4ffde; | ||||
|     /*background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;*/ | ||||
|     min-height: 42px; | ||||
| } | ||||
| div.warning { | ||||
|     padding: 10px 15px 10px 15px; | ||||
|     background-color: #fffbc6; | ||||
|     /*background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;*/ | ||||
|     min-height: 42px; | ||||
| } | ||||
| div.seealso { | ||||
|     background: #e4ffde; | ||||
| } | ||||
|  | ||||
| /* More layout and styles */ | ||||
| h1 { | ||||
|     font-size: 1.6em; | ||||
|     color: #aaa; | ||||
|     border-bottom: dotted thin #e0e0e0; | ||||
|     margin-top: 30px; | ||||
|     font-family: "Museo Slab"; | ||||
|     text-shadow: 1px 1px 1px rgba(175, 175, 175, .25); | ||||
| } | ||||
|  | ||||
| h2 { | ||||
|     font-size: 1.5em; | ||||
|     font-weight: normal; | ||||
|     color: #aaa; | ||||
|     border-bottom: dotted thin #e0e0e0; | ||||
|     margin-top: 30px; | ||||
|     font-family: "Museo Slab"; | ||||
|     text-shadow: 1px 1px 1px rgba(175, 175, 175, .25); | ||||
| } | ||||
|  | ||||
| h3 { | ||||
|     font-size: 1.4em; | ||||
|     font-weight: normal; | ||||
|     color: #aaa; | ||||
|     margin-top: 30px; | ||||
|     font-family: "Museo Slab"; | ||||
|     text-shadow: 1px 1px 1px rgba(175, 175, 175, .25); | ||||
| } | ||||
|  | ||||
| h4 { | ||||
|     font-size: 1.3em; | ||||
|     font-weight: normal; | ||||
|     color: #CFCFCF; | ||||
|     margin-top: 30px; | ||||
| } | ||||
|  | ||||
| p { | ||||
|     text-align: justify; | ||||
| } | ||||
|  | ||||
| p.last { | ||||
|     margin-bottom: 0; | ||||
| } | ||||
|  | ||||
| ol { | ||||
|     padding-left: 20px; | ||||
| } | ||||
|  | ||||
| ul { | ||||
|     padding-left: 5px; | ||||
|     margin-top: 3px; | ||||
| } | ||||
|  | ||||
| li { | ||||
|     line-height: 1.3; | ||||
| } | ||||
|  | ||||
| div.content ul > li { | ||||
|     -moz-background-clip:border; | ||||
|     -moz-background-inline-policy:continuous; | ||||
|     -moz-background-origin:padding; | ||||
|     background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; | ||||
|     list-style-image: none; | ||||
|     list-style-type: none; | ||||
|     padding: 0 0 0 1.666em; | ||||
|     margin-bottom: 3px; | ||||
| } | ||||
|  | ||||
| td { | ||||
|     vertical-align: top; | ||||
| } | ||||
|  | ||||
| tt { | ||||
|     background-color: #e2e2e2; | ||||
|     font-size: 1.0em; | ||||
|     font-family: monospace; | ||||
| } | ||||
|  | ||||
| pre { | ||||
|     font-size: 1.1em; | ||||
|     margin: 0 0 12px 0; | ||||
|     padding: 0.8em; | ||||
|     background-image: url(noise_dk.png); | ||||
|     background-color: #222; | ||||
| } | ||||
|  | ||||
| hr { | ||||
|     border-top: 1px solid #ccc; | ||||
|     border-bottom: 0; | ||||
|     border-right: 0; | ||||
|     border-left: 0; | ||||
|     margin-bottom: 10px; | ||||
|     margin-top: 20px; | ||||
| } | ||||
|  | ||||
| /* printer only pretty stuff */ | ||||
| @media print { | ||||
|     .noprint { | ||||
|         display: none; | ||||
|     } | ||||
|     /* for acronyms we want their definitions inlined at print time */ | ||||
|     acronym[title]:after { | ||||
|         font-size: small; | ||||
|         content: " (" attr(title) ")"; | ||||
|         font-style: italic; | ||||
|     } | ||||
|     /* and not have mozilla dotted underline */ | ||||
|     acronym { | ||||
|         border: none; | ||||
|     } | ||||
|     div.topnav, div.bottomnav, div.header, table.index { | ||||
|         display: none; | ||||
|     } | ||||
|     div.content { | ||||
|         margin: 0px; | ||||
|         padding: 0px; | ||||
|     } | ||||
|     html { | ||||
|         background: #FFF; | ||||
|     } | ||||
| } | ||||
|  | ||||
| .viewcode-back { | ||||
|     font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; | ||||
| } | ||||
|  | ||||
| div.viewcode-block:target { | ||||
|     border-top: 1px solid #ac9; | ||||
|     border-bottom: 1px solid #ac9; | ||||
|     margin: -1px -12px; | ||||
|     padding: 0 12px; | ||||
| } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 27 KiB | 
							
								
								
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| .highlight .hll { background-color: #ffffcc } | ||||
| .highlight  { background: #000000; color: #f6f3e8; } | ||||
| .highlight .c { color: #7C7C7C; } /* Comment */ | ||||
| .highlight .err { color: #f6f3e8; } /* Error */ | ||||
| .highlight .g { color: #f6f3e8; } /* Generic */ | ||||
| .highlight .k { color: #00ADEE; } /* Keyword */ | ||||
| .highlight .l { color: #f6f3e8; } /* Literal */ | ||||
| .highlight .n { color: #f6f3e8; } /* Name */ | ||||
| .highlight .o { color: #f6f3e8; } /* Operator */ | ||||
| .highlight .x { color: #f6f3e8; } /* Other */ | ||||
| .highlight .p { color: #f6f3e8; } /* Punctuation */ | ||||
| .highlight .cm { color: #7C7C7C; } /* Comment.Multiline */ | ||||
| .highlight .cp { color: #96CBFE; } /* Comment.Preproc */ | ||||
| .highlight .c1 { color: #7C7C7C; } /* Comment.Single */ | ||||
| .highlight .cs { color: #7C7C7C; } /* Comment.Special */ | ||||
| .highlight .gd { color: #f6f3e8; } /* Generic.Deleted */ | ||||
| .highlight .ge { color: #f6f3e8; } /* Generic.Emph */ | ||||
| .highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */ | ||||
| .highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */ | ||||
| .highlight .gi { color: #f6f3e8; } /* Generic.Inserted */ | ||||
| .highlight .go { color: #070707; } /* Generic.Output */ | ||||
| .highlight .gp { color: #f6f3e8; } /* Generic.Prompt */ | ||||
| .highlight .gs { color: #f6f3e8; } /* Generic.Strong */ | ||||
| .highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */ | ||||
| .highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */ | ||||
| .highlight .kc { color: #6699CC; } /* Keyword.Constant */ | ||||
| .highlight .kd { color: #6699CC; } /* Keyword.Declaration */ | ||||
| .highlight .kn { color: #6699CC; } /* Keyword.Namespace */ | ||||
| .highlight .kp { color: #6699CC; } /* Keyword.Pseudo */ | ||||
| .highlight .kr { color: #6699CC; } /* Keyword.Reserved */ | ||||
| .highlight .kt { color: #FFFFB6; } /* Keyword.Type */ | ||||
| .highlight .ld { color: #f6f3e8; } /* Literal.Date */ | ||||
| .highlight .m { color: #FF73FD; } /* Literal.Number */ | ||||
| .highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */ | ||||
| .highlight .na { color: #f6f3e8; } /* Name.Attribute */ | ||||
| .highlight .nb { color: #f6f3e8; } /* Name.Builtin */ | ||||
| .highlight .nc { color: #f6f3e8; } /* Name.Class */ | ||||
| .highlight .no { color: #99CC99; } /* Name.Constant */ | ||||
| .highlight .nd { color: #f6f3e8; } /* Name.Decorator */ | ||||
| .highlight .ni { color: #E18964; } /* Name.Entity */ | ||||
| .highlight .ne { color: #f6f3e8; } /* Name.Exception */ | ||||
| .highlight .nf { color: #F64DBA; } /* Name.Function */ | ||||
| .highlight .nl { color: #f6f3e8; } /* Name.Label */ | ||||
| .highlight .nn { color: #f6f3e8; } /* Name.Namespace */ | ||||
| .highlight .nx { color: #f6f3e8; } /* Name.Other */ | ||||
| .highlight .py { color: #f6f3e8; } /* Name.Property */ | ||||
| .highlight .nt { color: #00ADEE; } /* Name.Tag */ | ||||
| .highlight .nv { color: #C6C5FE; } /* Name.Variable */ | ||||
| .highlight .ow { color: #ffffff; } /* Operator.Word */ | ||||
| .highlight .w { color: #f6f3e8; } /* Text.Whitespace */ | ||||
| .highlight .mf { color: #FF73FD; } /* Literal.Number.Float */ | ||||
| .highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */ | ||||
| .highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */ | ||||
| .highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */ | ||||
| .highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */ | ||||
| .highlight .sc { color: #A8FF60; } /* Literal.String.Char */ | ||||
| .highlight .sd { color: #A8FF60; } /* Literal.String.Doc */ | ||||
| .highlight .s2 { color: #A8FF60; } /* Literal.String.Double */ | ||||
| .highlight .se { color: #A8FF60; } /* Literal.String.Escape */ | ||||
| .highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */ | ||||
| .highlight .si { color: #A8FF60; } /* Literal.String.Interpol */ | ||||
| .highlight .sx { color: #A8FF60; } /* Literal.String.Other */ | ||||
| .highlight .sr { color: #A8FF60; } /* Literal.String.Regex */ | ||||
| .highlight .s1 { color: #A8FF60; } /* Literal.String.Single */ | ||||
| .highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */ | ||||
| .highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */ | ||||
| .highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */ | ||||
| .highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */ | ||||
| .highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */ | ||||
| .highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */ | ||||
							
								
								
									
										245
									
								
								docs/_static/nature.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								docs/_static/nature.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,245 @@ | ||||
| /* | ||||
|  * nature.css_t | ||||
|  * ~~~~~~~~~~~~ | ||||
|  * | ||||
|  * Sphinx stylesheet -- nature theme. | ||||
|  * | ||||
|  * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|  * :license: BSD, see LICENSE for details. | ||||
|  * | ||||
|  */ | ||||
|   | ||||
| @import url("basic.css"); | ||||
|   | ||||
| /* -- page layout ----------------------------------------------------------- */ | ||||
|   | ||||
| body { | ||||
|     font-family: Arial, sans-serif; | ||||
|     font-size: 100%; | ||||
|     background-color: #111; | ||||
|     color: #555; | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.documentwrapper { | ||||
|     float: left; | ||||
|     width: 100%; | ||||
| } | ||||
|  | ||||
| div.bodywrapper { | ||||
|     margin: 0 0 0 230px; | ||||
| } | ||||
|  | ||||
| hr { | ||||
|     border: 1px solid #B1B4B6; | ||||
| } | ||||
|   | ||||
| div.document { | ||||
|     background-color: #eee; | ||||
| } | ||||
|   | ||||
| div.body { | ||||
|     background-color: #ffffff; | ||||
|     color: #3E4349; | ||||
|     padding: 0 30px 30px 30px; | ||||
|     font-size: 0.9em; | ||||
| } | ||||
|   | ||||
| div.footer { | ||||
|     color: #555; | ||||
|     width: 100%; | ||||
|     padding: 13px 0; | ||||
|     text-align: center; | ||||
|     font-size: 75%; | ||||
| } | ||||
|   | ||||
| div.footer a { | ||||
|     color: #444; | ||||
|     text-decoration: underline; | ||||
| } | ||||
|   | ||||
| div.related { | ||||
|     background-color: #6BA81E; | ||||
|     line-height: 32px; | ||||
|     color: #fff; | ||||
|     text-shadow: 0px 1px 0 #444; | ||||
|     font-size: 0.9em; | ||||
| } | ||||
|   | ||||
| div.related a { | ||||
|     color: #E2F3CC; | ||||
| } | ||||
|   | ||||
| div.sphinxsidebar { | ||||
|     font-size: 0.75em; | ||||
|     line-height: 1.5em; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebarwrapper{ | ||||
|     padding: 20px 0; | ||||
| } | ||||
|   | ||||
| div.sphinxsidebar h3, | ||||
| div.sphinxsidebar h4 { | ||||
|     font-family: Arial, sans-serif; | ||||
|     color: #222; | ||||
|     font-size: 1.2em; | ||||
|     font-weight: normal; | ||||
|     margin: 0; | ||||
|     padding: 5px 10px; | ||||
|     background-color: #ddd; | ||||
|     text-shadow: 1px 1px 0 white | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar h4{ | ||||
|     font-size: 1.1em; | ||||
| } | ||||
|   | ||||
| div.sphinxsidebar h3 a { | ||||
|     color: #444; | ||||
| } | ||||
|   | ||||
|   | ||||
| div.sphinxsidebar p { | ||||
|     color: #888; | ||||
|     padding: 5px 20px; | ||||
| } | ||||
|   | ||||
| div.sphinxsidebar p.topless { | ||||
| } | ||||
|   | ||||
| div.sphinxsidebar ul { | ||||
|     margin: 10px 20px; | ||||
|     padding: 0; | ||||
|     color: #000; | ||||
| } | ||||
|   | ||||
| div.sphinxsidebar a { | ||||
|     color: #444; | ||||
| } | ||||
|   | ||||
| div.sphinxsidebar input { | ||||
|     border: 1px solid #ccc; | ||||
|     font-family: sans-serif; | ||||
|     font-size: 1em; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar input[type=text]{ | ||||
|     margin-left: 20px; | ||||
| } | ||||
|   | ||||
| /* -- body styles ----------------------------------------------------------- */ | ||||
|   | ||||
| a { | ||||
|     color: #005B81; | ||||
|     text-decoration: none; | ||||
| } | ||||
|   | ||||
| a:hover { | ||||
|     color: #E32E00; | ||||
|     text-decoration: underline; | ||||
| } | ||||
|   | ||||
| div.body h1, | ||||
| div.body h2, | ||||
| div.body h3, | ||||
| div.body h4, | ||||
| div.body h5, | ||||
| div.body h6 { | ||||
|     font-family: Arial, sans-serif; | ||||
|     background-color: #BED4EB; | ||||
|     font-weight: normal; | ||||
|     color: #212224; | ||||
|     margin: 30px 0px 10px 0px; | ||||
|     padding: 5px 0 5px 10px; | ||||
|     text-shadow: 0px 1px 0 white | ||||
| } | ||||
|   | ||||
| div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } | ||||
| div.body h2 { font-size: 150%; background-color: #C8D5E3; } | ||||
| div.body h3 { font-size: 120%; background-color: #D8DEE3; } | ||||
| div.body h4 { font-size: 110%; background-color: #D8DEE3; } | ||||
| div.body h5 { font-size: 100%; background-color: #D8DEE3; } | ||||
| div.body h6 { font-size: 100%; background-color: #D8DEE3; } | ||||
|   | ||||
| a.headerlink { | ||||
|     color: #c60f0f; | ||||
|     font-size: 0.8em; | ||||
|     padding: 0 4px 0 4px; | ||||
|     text-decoration: none; | ||||
| } | ||||
|   | ||||
| a.headerlink:hover { | ||||
|     background-color: #c60f0f; | ||||
|     color: white; | ||||
| } | ||||
|   | ||||
| div.body p, div.body dd, div.body li { | ||||
|     line-height: 1.5em; | ||||
| } | ||||
|   | ||||
| div.admonition p.admonition-title + p { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| div.highlight{ | ||||
|     background-color: white; | ||||
| } | ||||
|  | ||||
| div.note { | ||||
|     background-color: #eee; | ||||
|     border: 1px solid #ccc; | ||||
| } | ||||
|   | ||||
| div.seealso { | ||||
|     background-color: #ffc; | ||||
|     border: 1px solid #ff6; | ||||
| } | ||||
|   | ||||
| div.topic { | ||||
|     background-color: #eee; | ||||
| } | ||||
|   | ||||
| div.warning { | ||||
|     background-color: #ffe4e4; | ||||
|     border: 1px solid #f66; | ||||
| } | ||||
|   | ||||
| p.admonition-title { | ||||
|     display: inline; | ||||
| } | ||||
|   | ||||
| p.admonition-title:after { | ||||
|     content: ":"; | ||||
| } | ||||
|   | ||||
| pre { | ||||
|     padding: 10px; | ||||
|     background-color: White; | ||||
|     color: #222; | ||||
|     line-height: 1.2em; | ||||
|     border: 1px solid #C6C9CB; | ||||
|     font-size: 1.1em; | ||||
|     margin: 1.5em 0 1.5em 0; | ||||
|     -webkit-box-shadow: 1px 1px 1px #d8d8d8; | ||||
|     -moz-box-shadow: 1px 1px 1px #d8d8d8; | ||||
| } | ||||
|   | ||||
| tt { | ||||
|     background-color: #ecf0f3; | ||||
|     color: #222; | ||||
|     /* padding: 1px 2px; */ | ||||
|     font-size: 1.1em; | ||||
|     font-family: monospace; | ||||
| } | ||||
|  | ||||
| .viewcode-back { | ||||
|     font-family: Arial, sans-serif; | ||||
| } | ||||
|  | ||||
| div.viewcode-block:target { | ||||
|     background-color: #f4debf; | ||||
|     border-top: 1px solid #ac9; | ||||
|     border-bottom: 1px solid #ac9; | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/noise_dk.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/noise_dk.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 22 KiB | 
							
								
								
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| .highlight .hll { background-color: #ffffcc } | ||||
| .highlight  { background: #000000; color: #f6f3e8; } | ||||
| .highlight .c { color: #7C7C7C; } /* Comment */ | ||||
| .highlight .err { color: #f6f3e8; } /* Error */ | ||||
| .highlight .g { color: #f6f3e8; } /* Generic */ | ||||
| .highlight .k { color: #00ADEE; } /* Keyword */ | ||||
| .highlight .l { color: #f6f3e8; } /* Literal */ | ||||
| .highlight .n { color: #f6f3e8; } /* Name */ | ||||
| .highlight .o { color: #f6f3e8; } /* Operator */ | ||||
| .highlight .x { color: #f6f3e8; } /* Other */ | ||||
| .highlight .p { color: #f6f3e8; } /* Punctuation */ | ||||
| .highlight .cm { color: #7C7C7C; } /* Comment.Multiline */ | ||||
| .highlight .cp { color: #96CBFE; } /* Comment.Preproc */ | ||||
| .highlight .c1 { color: #7C7C7C; } /* Comment.Single */ | ||||
| .highlight .cs { color: #7C7C7C; } /* Comment.Special */ | ||||
| .highlight .gd { color: #f6f3e8; } /* Generic.Deleted */ | ||||
| .highlight .ge { color: #f6f3e8; } /* Generic.Emph */ | ||||
| .highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */ | ||||
| .highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */ | ||||
| .highlight .gi { color: #f6f3e8; } /* Generic.Inserted */ | ||||
| .highlight .go { color: #070707; } /* Generic.Output */ | ||||
| .highlight .gp { color: #f6f3e8; } /* Generic.Prompt */ | ||||
| .highlight .gs { color: #f6f3e8; } /* Generic.Strong */ | ||||
| .highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */ | ||||
| .highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */ | ||||
| .highlight .kc { color: #6699CC; } /* Keyword.Constant */ | ||||
| .highlight .kd { color: #6699CC; } /* Keyword.Declaration */ | ||||
| .highlight .kn { color: #6699CC; } /* Keyword.Namespace */ | ||||
| .highlight .kp { color: #6699CC; } /* Keyword.Pseudo */ | ||||
| .highlight .kr { color: #6699CC; } /* Keyword.Reserved */ | ||||
| .highlight .kt { color: #FFFFB6; } /* Keyword.Type */ | ||||
| .highlight .ld { color: #f6f3e8; } /* Literal.Date */ | ||||
| .highlight .m { color: #FF73FD; } /* Literal.Number */ | ||||
| .highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */ | ||||
| .highlight .na { color: #f6f3e8; } /* Name.Attribute */ | ||||
| .highlight .nb { color: #f6f3e8; } /* Name.Builtin */ | ||||
| .highlight .nc { color: #f6f3e8; } /* Name.Class */ | ||||
| .highlight .no { color: #99CC99; } /* Name.Constant */ | ||||
| .highlight .nd { color: #f6f3e8; } /* Name.Decorator */ | ||||
| .highlight .ni { color: #E18964; } /* Name.Entity */ | ||||
| .highlight .ne { color: #f6f3e8; } /* Name.Exception */ | ||||
| .highlight .nf { color: #F64DBA; } /* Name.Function */ | ||||
| .highlight .nl { color: #f6f3e8; } /* Name.Label */ | ||||
| .highlight .nn { color: #f6f3e8; } /* Name.Namespace */ | ||||
| .highlight .nx { color: #f6f3e8; } /* Name.Other */ | ||||
| .highlight .py { color: #f6f3e8; } /* Name.Property */ | ||||
| .highlight .nt { color: #00ADEE; } /* Name.Tag */ | ||||
| .highlight .nv { color: #C6C5FE; } /* Name.Variable */ | ||||
| .highlight .ow { color: #ffffff; } /* Operator.Word */ | ||||
| .highlight .w { color: #f6f3e8; } /* Text.Whitespace */ | ||||
| .highlight .mf { color: #FF73FD; } /* Literal.Number.Float */ | ||||
| .highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */ | ||||
| .highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */ | ||||
| .highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */ | ||||
| .highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */ | ||||
| .highlight .sc { color: #A8FF60; } /* Literal.String.Char */ | ||||
| .highlight .sd { color: #A8FF60; } /* Literal.String.Doc */ | ||||
| .highlight .s2 { color: #A8FF60; } /* Literal.String.Double */ | ||||
| .highlight .se { color: #A8FF60; } /* Literal.String.Escape */ | ||||
| .highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */ | ||||
| .highlight .si { color: #A8FF60; } /* Literal.String.Interpol */ | ||||
| .highlight .sx { color: #A8FF60; } /* Literal.String.Other */ | ||||
| .highlight .sr { color: #A8FF60; } /* Literal.String.Regex */ | ||||
| .highlight .s1 { color: #A8FF60; } /* Literal.String.Single */ | ||||
| .highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */ | ||||
| .highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */ | ||||
| .highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */ | ||||
| .highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */ | ||||
| .highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */ | ||||
| .highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */ | ||||
							
								
								
									
										339
									
								
								docs/_static/sphinxdoc.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								docs/_static/sphinxdoc.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,339 @@ | ||||
| /* | ||||
|  * sphinxdoc.css_t | ||||
|  * ~~~~~~~~~~~~~~~ | ||||
|  * | ||||
|  * Sphinx stylesheet -- sphinxdoc theme.  Originally created by | ||||
|  * Armin Ronacher for Werkzeug. | ||||
|  * | ||||
|  * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|  * :license: BSD, see LICENSE for details. | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| @import url("basic.css"); | ||||
|  | ||||
| /* -- page layout ----------------------------------------------------------- */ | ||||
|  | ||||
| body { | ||||
|     font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', | ||||
|                  'Verdana', sans-serif; | ||||
|     font-size: 14px; | ||||
|     letter-spacing: -0.01em; | ||||
|     line-height: 150%; | ||||
|     text-align: center; | ||||
|     background-color: #BFD1D4; | ||||
|     color: black; | ||||
|     padding: 0; | ||||
|     border: 1px solid #aaa; | ||||
|  | ||||
|     margin: 0px 80px 0px 80px; | ||||
|     min-width: 740px; | ||||
| } | ||||
|  | ||||
| div.document { | ||||
|     background-color: white; | ||||
|     text-align: left; | ||||
|     background-image: url(contents.png); | ||||
|     background-repeat: repeat-x; | ||||
| } | ||||
|  | ||||
| div.bodywrapper { | ||||
|     margin: 0 240px 0 0; | ||||
|     border-right: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| div.body { | ||||
|     margin: 0; | ||||
|     padding: 0.5em 20px 20px 20px; | ||||
| } | ||||
|  | ||||
| div.related { | ||||
|     font-size: 1em; | ||||
| } | ||||
|  | ||||
| div.related ul { | ||||
|     background-image: url(navigation.png); | ||||
|     height: 2em; | ||||
|     border-top: 1px solid #ddd; | ||||
|     border-bottom: 1px solid #ddd; | ||||
| } | ||||
|  | ||||
| div.related ul li { | ||||
|     margin: 0; | ||||
|     padding: 0; | ||||
|     height: 2em; | ||||
|     float: left; | ||||
| } | ||||
|  | ||||
| div.related ul li.right { | ||||
|     float: right; | ||||
|     margin-right: 5px; | ||||
| } | ||||
|  | ||||
| div.related ul li a { | ||||
|     margin: 0; | ||||
|     padding: 0 5px 0 5px; | ||||
|     line-height: 1.75em; | ||||
|     color: #EE9816; | ||||
| } | ||||
|  | ||||
| div.related ul li a:hover { | ||||
|     color: #3CA8E7; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebarwrapper { | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar { | ||||
|     margin: 0; | ||||
|     padding: 0.5em 15px 15px 0; | ||||
|     width: 210px; | ||||
|     float: right; | ||||
|     font-size: 1em; | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar h3, div.sphinxsidebar h4 { | ||||
|     margin: 1em 0 0.5em 0; | ||||
|     font-size: 1em; | ||||
|     padding: 0.1em 0 0.1em 0.5em; | ||||
|     color: white; | ||||
|     border: 1px solid #86989B; | ||||
|     background-color: #AFC1C4; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar h3 a { | ||||
|     color: white; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar ul { | ||||
|     padding-left: 1.5em; | ||||
|     margin-top: 7px; | ||||
|     padding: 0; | ||||
|     line-height: 130%; | ||||
| } | ||||
|  | ||||
| div.sphinxsidebar ul ul { | ||||
|     margin-left: 20px; | ||||
| } | ||||
|  | ||||
| div.footer { | ||||
|     background-color: #E3EFF1; | ||||
|     color: #86989B; | ||||
|     padding: 3px 8px 3px 0; | ||||
|     clear: both; | ||||
|     font-size: 0.8em; | ||||
|     text-align: right; | ||||
| } | ||||
|  | ||||
| div.footer a { | ||||
|     color: #86989B; | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| /* -- body styles ----------------------------------------------------------- */ | ||||
|  | ||||
| p {     | ||||
|     margin: 0.8em 0 0.5em 0; | ||||
| } | ||||
|  | ||||
| a { | ||||
|     color: #CA7900; | ||||
|     text-decoration: none; | ||||
| } | ||||
|  | ||||
| a:hover { | ||||
|     color: #2491CF; | ||||
| } | ||||
|  | ||||
| div.body a { | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| h1 { | ||||
|     margin: 0; | ||||
|     padding: 0.7em 0 0.3em 0; | ||||
|     font-size: 1.5em; | ||||
|     color: #11557C; | ||||
| } | ||||
|  | ||||
| h2 { | ||||
|     margin: 1.3em 0 0.2em 0; | ||||
|     font-size: 1.35em; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| h3 { | ||||
|     margin: 1em 0 -0.3em 0; | ||||
|     font-size: 1.2em; | ||||
| } | ||||
|  | ||||
| div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { | ||||
|     color: black!important; | ||||
| } | ||||
|  | ||||
| h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { | ||||
|     display: none; | ||||
|     margin: 0 0 0 0.3em; | ||||
|     padding: 0 0.2em 0 0.2em; | ||||
|     color: #aaa!important; | ||||
| } | ||||
|  | ||||
| h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, | ||||
| h5:hover a.anchor, h6:hover a.anchor { | ||||
|     display: inline; | ||||
| } | ||||
|  | ||||
| h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, | ||||
| h5 a.anchor:hover, h6 a.anchor:hover { | ||||
|     color: #777; | ||||
|     background-color: #eee; | ||||
| } | ||||
|  | ||||
| a.headerlink { | ||||
|     color: #c60f0f!important; | ||||
|     font-size: 1em; | ||||
|     margin-left: 6px; | ||||
|     padding: 0 4px 0 4px; | ||||
|     text-decoration: none!important; | ||||
| } | ||||
|  | ||||
| a.headerlink:hover { | ||||
|     background-color: #ccc; | ||||
|     color: white!important; | ||||
| } | ||||
|  | ||||
| cite, code, tt { | ||||
|     font-family: 'Consolas', 'Deja Vu Sans Mono', | ||||
|                  'Bitstream Vera Sans Mono', monospace; | ||||
|     font-size: 0.95em; | ||||
|     letter-spacing: 0.01em; | ||||
| } | ||||
|  | ||||
| tt { | ||||
|     background-color: #f2f2f2; | ||||
|     border-bottom: 1px solid #ddd; | ||||
|     color: #333; | ||||
| } | ||||
|  | ||||
| tt.descname, tt.descclassname, tt.xref { | ||||
|     border: 0; | ||||
| } | ||||
|  | ||||
| hr { | ||||
|     border: 1px solid #abc; | ||||
|     margin: 2em; | ||||
| } | ||||
|  | ||||
| a tt { | ||||
|     border: 0; | ||||
|     color: #CA7900; | ||||
| } | ||||
|  | ||||
| a tt:hover { | ||||
|     color: #2491CF; | ||||
| } | ||||
|  | ||||
| pre { | ||||
|     font-family: 'Consolas', 'Deja Vu Sans Mono', | ||||
|                  'Bitstream Vera Sans Mono', monospace; | ||||
|     font-size: 0.95em; | ||||
|     letter-spacing: 0.015em; | ||||
|     line-height: 120%; | ||||
|     padding: 0.5em; | ||||
|     border: 1px solid #ccc; | ||||
|     background-color: #f8f8f8; | ||||
| } | ||||
|  | ||||
| pre a { | ||||
|     color: inherit; | ||||
|     text-decoration: underline; | ||||
| } | ||||
|  | ||||
| td.linenos pre { | ||||
|     padding: 0.5em 0; | ||||
| } | ||||
|  | ||||
| div.quotebar { | ||||
|     background-color: #f8f8f8; | ||||
|     max-width: 250px; | ||||
|     float: right; | ||||
|     padding: 2px 7px; | ||||
|     border: 1px solid #ccc; | ||||
| } | ||||
|  | ||||
| div.topic { | ||||
|     background-color: #f8f8f8; | ||||
| } | ||||
|  | ||||
| table { | ||||
|     border-collapse: collapse; | ||||
|     margin: 0 -0.5em 0 -0.5em; | ||||
| } | ||||
|  | ||||
| table td, table th { | ||||
|     padding: 0.2em 0.5em 0.2em 0.5em; | ||||
| } | ||||
|  | ||||
| div.admonition, div.warning { | ||||
|     font-size: 0.9em; | ||||
|     margin: 1em 0 1em 0; | ||||
|     border: 1px solid #86989B; | ||||
|     background-color: #f7f7f7; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.admonition p, div.warning p { | ||||
|     margin: 0.5em 1em 0.5em 1em; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.admonition pre, div.warning pre { | ||||
|     margin: 0.4em 1em 0.4em 1em; | ||||
| } | ||||
|  | ||||
| div.admonition p.admonition-title, | ||||
| div.warning p.admonition-title { | ||||
|     margin: 0; | ||||
|     padding: 0.1em 0 0.1em 0.5em; | ||||
|     color: white; | ||||
|     border-bottom: 1px solid #86989B; | ||||
|     font-weight: bold; | ||||
|     background-color: #AFC1C4; | ||||
| } | ||||
|  | ||||
| div.warning { | ||||
|     border: 1px solid #940000; | ||||
| } | ||||
|  | ||||
| div.warning p.admonition-title { | ||||
|     background-color: #CF0000; | ||||
|     border-bottom-color: #940000; | ||||
| } | ||||
|  | ||||
| div.admonition ul, div.admonition ol, | ||||
| div.warning ul, div.warning ol { | ||||
|     margin: 0.1em 0.5em 0.5em 3em; | ||||
|     padding: 0; | ||||
| } | ||||
|  | ||||
| div.versioninfo { | ||||
|     margin: 1em 0 0 0; | ||||
|     border: 1px solid #ccc; | ||||
|     background-color: #DDEAF0; | ||||
|     padding: 8px; | ||||
|     line-height: 1.3em; | ||||
|     font-size: 0.9em; | ||||
| } | ||||
|  | ||||
| .viewcode-back { | ||||
|     font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', | ||||
|                  'Verdana', sans-serif; | ||||
| } | ||||
|  | ||||
| div.viewcode-block:target { | ||||
|     background-color: #f4debf; | ||||
|     border-top: 1px solid #ac9; | ||||
|     border-bottom: 1px solid #ac9; | ||||
| } | ||||
							
								
								
									
										35
									
								
								docs/_templates/defindex.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/_templates/defindex.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| {# | ||||
|     basic/defindex.html | ||||
|     ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|     Default template for the "index" page. | ||||
|  | ||||
|     :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|     :license: BSD, see LICENSE for details. | ||||
| #} | ||||
| {% extends "layout.html" %} | ||||
| {% set title = _('Overview') %} | ||||
| {% block body %} | ||||
|   <h1>{{ docstitle|e }}</h1> | ||||
|   <p> | ||||
|     Welcome! This is | ||||
|     {% block description %}the documentation for {{ project|e }} | ||||
|     {{ release|e }}{% if last_updated %}, last updated {{ last_updated|e }}{% endif %}{% endblock %}. | ||||
|   </p> | ||||
|   {% block tables %} | ||||
|   <p><strong>{{ _('Indices and tables:') }}</strong></p> | ||||
|   <table class="contentstable" align="center"><tr> | ||||
|     <td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">{{ _('Complete Table of Contents') }}</a><br> | ||||
|          <span class="linkdescr">{{ _('lists all sections and subsections') }}</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">{{ _('Search Page') }}</a><br> | ||||
|          <span class="linkdescr">{{ _('search this documentation') }}</span></p> | ||||
|     </td><td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("modindex") }}">{{ _('Global Module Index') }}</a><br> | ||||
|          <span class="linkdescr">{{ _('quick access to all modules') }}</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">{{ _('General Index') }}</a><br> | ||||
|          <span class="linkdescr">{{ _('all functions, classes, terms') }}</span></p> | ||||
|     </td></tr> | ||||
|   </table> | ||||
|   {% endblock %} | ||||
| {% endblock %} | ||||
							
								
								
									
										61
									
								
								docs/_templates/indexcontent.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								docs/_templates/indexcontent.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| {% extends "defindex.html" %} | ||||
| {% block tables %} | ||||
|   <p><strong>Parts of the documentation:</strong></p> | ||||
|   <table class="contentstable" align="center"><tr> | ||||
|     <td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("whatsnew/" + version) }}">What's new in Python {{ version }}?</a><br/> | ||||
|          <span class="linkdescr">or <a href="{{ pathto("whatsnew/index") }}">all "What's new" documents</a> since 2.0</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("tutorial/index") }}">Tutorial</a><br/> | ||||
|          <span class="linkdescr">start here</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("library/index") }}">Library Reference</a><br/> | ||||
|          <span class="linkdescr">keep this under your pillow</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("reference/index") }}">Language Reference</a><br/> | ||||
|          <span class="linkdescr">describes syntax and language elements</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("using/index") }}">Python Setup and Usage</a><br/> | ||||
|          <span class="linkdescr">how to use Python on different platforms</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("howto/index") }}">Python HOWTOs</a><br/> | ||||
|          <span class="linkdescr">in-depth documents on specific topics</span></p> | ||||
|     </td><td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("extending/index") }}">Extending and Embedding</a><br/> | ||||
|          <span class="linkdescr">tutorial for C/C++ programmers</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("c-api/index") }}">Python/C API</a><br/> | ||||
|          <span class="linkdescr">reference for C/C++ programmers</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("install/index") }}">Installing Python Modules</a><br/> | ||||
|          <span class="linkdescr">information for installers & sys-admins</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("distutils/index") }}">Distributing Python Modules</a><br/> | ||||
|          <span class="linkdescr">sharing modules with others</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("documenting/index") }}">Documenting Python</a><br/> | ||||
|          <span class="linkdescr">guide for documentation authors</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("faq/index") }}">FAQs</a><br/> | ||||
|          <span class="linkdescr">frequently asked questions (with answers!)</span></p> | ||||
|     </td></tr> | ||||
|   </table> | ||||
|  | ||||
|   <p><strong>Indices and tables:</strong></p> | ||||
|   <table class="contentstable" align="center"><tr> | ||||
|     <td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("py-modindex") }}">Global Module Index</a><br/> | ||||
|          <span class="linkdescr">quick access to all modules</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">General Index</a><br/> | ||||
|          <span class="linkdescr">all functions, classes, terms</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("glossary") }}">Glossary</a><br/> | ||||
|          <span class="linkdescr">the most important terms explained</span></p> | ||||
|     </td><td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">Search page</a><br/> | ||||
|          <span class="linkdescr">search this documentation</span></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br/> | ||||
|          <span class="linkdescr">lists all sections and subsections</span></p> | ||||
|     </td></tr> | ||||
|   </table> | ||||
|  | ||||
|   <p><strong>Meta information:</strong></p> | ||||
|   <table class="contentstable" align="center"><tr> | ||||
|     <td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("bugs") }}">Reporting bugs</a></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("about") }}">About the documentation</a></p> | ||||
|     </td><td width="50%"> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("license") }}">History and License of Python</a></p> | ||||
|       <p class="biglink"><a class="biglink" href="{{ pathto("copyright") }}">Copyright</a></p> | ||||
|     </td></tr> | ||||
|   </table> | ||||
| {% endblock %} | ||||
							
								
								
									
										69
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| {# | ||||
|     haiku/layout.html | ||||
|     ~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|     Sphinx layout template for the haiku theme. | ||||
|  | ||||
|     :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. | ||||
|     :license: BSD, see LICENSE for details. | ||||
| #} | ||||
| {% extends "basic/layout.html" %} | ||||
| {% set script_files = script_files + ['_static/theme_extras.js'] %} | ||||
| {% set css_files = css_files + ['_static/print.css'] %} | ||||
|  | ||||
| {# do not display relbars #} | ||||
| {% block relbar1 %}{% endblock %} | ||||
| {% block relbar2 %}{% endblock %} | ||||
|  | ||||
| {% macro nav() %} | ||||
|         <p> | ||||
|         {%- block haikurel1 %} | ||||
|         {%- endblock %} | ||||
|         {%- if prev %} | ||||
|         «  <a href="{{ prev.link|e }}">{{ prev.title }}</a> | ||||
|           ::   | ||||
|         {%- endif %} | ||||
|         <a class="uplink" href="{{ pathto(master_doc) }}">{{ _('Contents') }}</a> | ||||
|         {%- if next %} | ||||
|           ::   | ||||
|         <a href="{{ next.link|e }}">{{ next.title }}</a>  » | ||||
|         {%- endif %} | ||||
|         {%- block haikurel2 %} | ||||
|         {%- endblock %} | ||||
|         </p> | ||||
| {% endmacro %} | ||||
|  | ||||
| {% block content %} | ||||
|       <div class="header"> | ||||
|         {%- block haikuheader %} | ||||
|         {%- if theme_full_logo != "false" %} | ||||
|         <a href="{{ pathto('index') }}"> | ||||
|           <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/> | ||||
|         </a> | ||||
|         {%- else %} | ||||
|         {%- if logo -%} | ||||
|           <img class="rightlogo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/> | ||||
|         {%- endif -%} | ||||
|         <h1 class="heading"> | ||||
|             <a href="{{ pathto('index') }}"><span>{{ project|e }}</span></a> | ||||
|         </h1> | ||||
|         <h2 class="heading"><span>{{ shorttitle|e }}</span></h2> | ||||
|         {%- endif %} | ||||
|         {%- endblock %} | ||||
|       </div> | ||||
|       <div class="topnav"> | ||||
|       {{ nav() }} | ||||
|       </div> | ||||
|       <div class="content"> | ||||
|         {#{%- if display_toc %} | ||||
|         <div id="toc"> | ||||
|           <h3>Table Of Contents</h3> | ||||
|           {{ toc }} | ||||
|         </div> | ||||
|         {%- endif %}#} | ||||
|         {% block body %}{% endblock %} | ||||
|       </div> | ||||
|       <div class="bottomnav"> | ||||
|       {{ nav() }} | ||||
|       </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										8
									
								
								docs/api/basexmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/basexmpp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| ======== | ||||
| basexmpp | ||||
| ======== | ||||
|  | ||||
| .. module:: sleekxmpp.basexmpp | ||||
|  | ||||
| .. autoclass:: BaseXMPP | ||||
|     :members: | ||||
							
								
								
									
										19
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| ========== | ||||
| clientxmpp | ||||
| ========== | ||||
|  | ||||
| .. module:: sleekxmpp.clientxmpp | ||||
|  | ||||
| .. autodata:: SRV_SUPPORT | ||||
|  | ||||
| .. autoclass:: ClientXMPP | ||||
|      | ||||
|     .. automethod:: connect | ||||
|  | ||||
|     .. automethod:: register_feature | ||||
|  | ||||
|     .. automethod:: get_roster | ||||
|  | ||||
|     .. automethod:: update_roster | ||||
|  | ||||
|     .. automethod:: del_roster_item | ||||
							
								
								
									
										8
									
								
								docs/api/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| ========= | ||||
| xmlstream | ||||
| ========= | ||||
|  | ||||
| .. module:: sleekxmpp.xmlstream | ||||
|  | ||||
| .. autoclass:: XMLStream | ||||
|     :members: | ||||
							
								
								
									
										269
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,269 @@ | ||||
| .. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP | ||||
|  | ||||
| SleekXMPP Architecture | ||||
| ====================== | ||||
|  | ||||
| The core of SleekXMPP is contained in four classes: ``XMLStream``, | ||||
| ``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this | ||||
| stack is a library for working with XML objects that eliminates most | ||||
| of the tedium of creating/manipulating XML. | ||||
|  | ||||
| .. image:: _static/images/arch_layers.png | ||||
|     :height: 300px | ||||
|     :align: center | ||||
|  | ||||
|  | ||||
| .. index:: XMLStream | ||||
|  | ||||
| The Foundation: XMLStream | ||||
| ------------------------- | ||||
| ``XMLStream`` is a mostly XMPP-agnostic class whose purpose is to read | ||||
| and write from a bi-directional XML stream. It also allows for callback | ||||
| functions to execute when XML matching given patterns is received; these | ||||
| callbacks are also referred to as :term:`stream handlers <stream handler>`. | ||||
| The class also provides a basic eventing system which can be triggered | ||||
| either manually or on a timed schedule. | ||||
|  | ||||
| The Main Threads | ||||
| ~~~~~~~~~~~~~~~~ | ||||
| ``XMLStream`` instances run using at least three background threads: the | ||||
| send thread, the read thread, and the scheduler thread. The send thread is | ||||
| in charge of monitoring the send queue and writing text to the outgoing | ||||
| XML stream. The read thread pulls text off of the incoming XML stream and | ||||
| stores the results in an event queue. The scheduler thread is used to emit | ||||
| events after a given period of time. | ||||
|  | ||||
| Additionally, the main event processing loop may be executed in its | ||||
| own thread if SleekXMPP is being used in the background for another | ||||
| application. | ||||
|  | ||||
| Short-lived threads may also be spawned as requested for threaded | ||||
| :term:`event handlers <event handler>`. | ||||
|  | ||||
| How XML Text is Turned into Action | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| To demonstrate the flow of information, let's consider what happens | ||||
| when this bit of XML is received (with an assumed namespace of | ||||
| ``jabber:client``): | ||||
|  | ||||
| .. code-block:: xml | ||||
|  | ||||
|     <message to="user@example.com" from="friend@example.net"> | ||||
|       <body>Hej!</body> | ||||
|     </message> | ||||
|  | ||||
|  | ||||
| 1. **Convert XML strings into objects.** | ||||
|  | ||||
|    Incoming text is parsed and converted into XML objects (using | ||||
|    ElementTree) which are then wrapped into what are referred to as | ||||
|    :term:`Stanza objects <stanza object>`. The appropriate class for the | ||||
|    new object is determined using a map of namespaced element names to | ||||
|    classes. | ||||
|  | ||||
|    Our incoming XML is thus turned into a ``Message`` :term:`stanza object` | ||||
|    because the namespaced element name ``{jabber:client}message`` is | ||||
|    associated with the class ``sleekxmpp.stanza.Message``. | ||||
|  | ||||
| 2. **Match stanza objects to callbacks.** | ||||
|  | ||||
|    These objects are then compared against the stored patterns associated | ||||
|    with the registered callback handlers. For each match, a copy of the | ||||
|    :term:`stanza object` is paired with a reference to the handler and | ||||
|    placed into the event queue. | ||||
|  | ||||
|    Our ``Message`` object is thus paired with the message stanza handler | ||||
|    ``BaseXMPP._handle_message`` to create the tuple:: | ||||
|  | ||||
|        ('stanza', stanza_obj, handler) | ||||
|  | ||||
| 3. **Process the event queue.** | ||||
|  | ||||
|    The event queue is the heart of SleekXMPP. Nearly every action that | ||||
|    takes place is first inserted into this queue, whether that be received | ||||
|    stanzas, custom events, or scheduled events. | ||||
|  | ||||
|    When the stanza is pulled out of the event queue with an associated | ||||
|    callback, the callback function is executed with the stanza as its only | ||||
|    parameter. | ||||
|  | ||||
|    .. warning::  | ||||
|        The callback, aka :term:`stream handler`, is executed in the main | ||||
|        processing thread. If the handler blocks, event processing will also | ||||
|        block. | ||||
|  | ||||
| 4. **Raise Custom Events** | ||||
|  | ||||
|    Since a :term:`stream handler` shouldn't block, if extensive processing | ||||
|    for a stanza is required (such as needing to send and receive an | ||||
|    ``Iq`` stanza), then custom events must be used. These events are not | ||||
|    explicitly tied to the incoming XML stream and may be raised at any | ||||
|    time. Importantly, these events may be handled in their own thread. | ||||
|  | ||||
|    When the event is raised, a copy of the stanza is created for each | ||||
|    handler registered for the event. In contrast to :term:`stream handlers <stream handler>`,  | ||||
|    these functions are referred to as :term:`event handlers <event handler>`.  | ||||
|    Each stanza/handler pair is then put into the event queue. | ||||
|  | ||||
|    .. note:: | ||||
|        It is possible to skip the event queue and process an event immediately | ||||
|        by using ``direct=True`` when raising the event. | ||||
|  | ||||
|    The code for ``BaseXMPP._handle_message`` follows this pattern, and | ||||
|    raises a ``'message'`` event:: | ||||
|  | ||||
|        self.event('message', msg) | ||||
|  | ||||
|    The event call then places the message object back into the event queue | ||||
|    paired with an :term:`event handler`:: | ||||
|  | ||||
|        ('event', 'message', msg_copy1, custom_event_handler_1) | ||||
|        ('event', 'message', msg_copy2, custom_evetn_handler_2)  | ||||
|  | ||||
| 5. **Process Custom Events** | ||||
|  | ||||
|    The stanza and :term:`event handler` are then pulled from the event | ||||
|    queue, and the handler is executed, passing the stanza as its only | ||||
|    argument. If the handler was registered as threaded, then a new thread | ||||
|    will be spawned for it. | ||||
|  | ||||
|    .. note:: | ||||
|        Events may be raised without needing :term:`stanza objects <stanza object>`.  | ||||
|        For example, you could use ``self.event('custom', {'a': 'b'})``.  | ||||
|        You don't even need any arguments: ``self.event('no_parameters')``.  | ||||
|        However, every event handler MUST accept at least one argument. | ||||
|  | ||||
|    Finally, after a long trek, our message is handed off to the user's | ||||
|    custom handler in order to do awesome stuff:: | ||||
|  | ||||
|        msg.reply() | ||||
|        msg['body'] = "Hey! This is awesome!" | ||||
|        msg.send() | ||||
|  | ||||
|  | ||||
| .. index:: BaseXMPP, XMLStream | ||||
|  | ||||
| Raising XMPP Awareness: BaseXMPP | ||||
| -------------------------------- | ||||
| While ``XMLStream`` attempts to shy away from anything too XMPP specific, | ||||
| ``BaseXMPP``'s sole purpose is to provide foundational support for sending | ||||
| and receiving XMPP stanzas. This support includes registering the basic | ||||
| message, presence, and iq stanzas, methods for creating and sending | ||||
| stanzas, and default handlers for incoming messages and keeping track of | ||||
| presence notifications. | ||||
|  | ||||
| The plugin system for adding new XEP support is also maintained by | ||||
| ``BaseXMPP``. | ||||
|  | ||||
| .. index:: ClientXMPP, BaseXMPP | ||||
|  | ||||
| ClientXMPP | ||||
| ---------- | ||||
| ``ClientXMPP`` extends ``BaseXMPP`` with additional logic for connecting to | ||||
| an XMPP server by performing DNS lookups. It also adds support for stream | ||||
| features such as STARTTLS and SASL. | ||||
|  | ||||
| .. index:: ComponentXMPP, BaseXMPP | ||||
|  | ||||
| ComponentXMPP | ||||
| ------------- | ||||
| ``ComponentXMPP`` is only a thin layer on top of ``BaseXMPP`` that | ||||
| implements the component handshake protocol. | ||||
|  | ||||
| .. index:: | ||||
|     double: object; stanza | ||||
|  | ||||
| Stanza Objects: A Brief Look | ||||
| ---------------------------- | ||||
| .. seealso:: | ||||
|     See :ref:`api-stanza-objects` for a more detailed overview. | ||||
|  | ||||
| Almost worthy of their own standalone library, :term:`stanza objects <stanza object>` | ||||
| are wrappers for XML objects which expose dictionary like interfaces | ||||
| for manipulating their XML content. For example, consider the XML: | ||||
|  | ||||
| .. code-block:: xml | ||||
|  | ||||
|     <message /> | ||||
|  | ||||
| A very plain element to start with, but we can create a :term:`stanza object` | ||||
| using ``sleekxmpp.stanza.Message`` as so:: | ||||
|  | ||||
|     msg = Message(xml=ET.fromstring("<message />")) | ||||
|  | ||||
| The ``Message`` stanza class defines interfaces such as ``'body'`` and | ||||
| ``'to'``, so we can assign values to those interfaces to include new XML | ||||
| content:: | ||||
|  | ||||
|     msg['body'] = "Following so far?" | ||||
|     msg['to'] = 'user@example.com' | ||||
|  | ||||
| Dumping the XML content of ``msg`` (using ``msg.xml``), we find: | ||||
|  | ||||
| .. code-block:: xml | ||||
|  | ||||
|     <message to="user@example.com"> | ||||
|       <body>Following so far?</body> | ||||
|     </message> | ||||
|  | ||||
| The process is similar for reading from interfaces and deleting interface | ||||
| contents. A :term:`stanza object` behaves very similarly to a regular | ||||
| ``dict`` object: you may assign to keys, read from keys, and ``del`` keys. | ||||
|  | ||||
| Stanza interfaces come with built-in behaviours such as adding/removing | ||||
| attribute and sub element values. However, a lot of the time more custom | ||||
| logic is needed. This can be provided by defining methods of the form | ||||
| ``get_*``, ``set_*``, and ``del_*`` for any interface which requires custom | ||||
| behaviour. | ||||
|  | ||||
| Stanza Plugins | ||||
| ~~~~~~~~~~~~~~ | ||||
| Since it is generally possible to embed one XML element inside another, | ||||
| :term:`stanza objects <stanza object>` may be nested. Nested  | ||||
| :term:`stanza objects <stanza object>` are referred to as :term:`stanza plugins <stanza plugin>` | ||||
| or :term:`substanzas <substanza>`. | ||||
|  | ||||
| A :term:`stanza plugin` exposes its own interfaces by adding a new | ||||
| interface to its parent stanza. To demonstrate, consider these two stanza | ||||
| class definitions using ``sleekxmpp.xmlstream.ElementBase``: | ||||
|  | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     class Parent(ElementBase): | ||||
|         name = "the-parent-xml-element-name" | ||||
|         namespace = "the-parent-namespace" | ||||
|         interfaces = set(('foo', 'bar')) | ||||
|  | ||||
|     class Child(ElementBase): | ||||
|         name = "the-child-xml-element-name" | ||||
|         namespace = "the-child-namespace" | ||||
|         plugin_attrib = 'child' | ||||
|         interfaces = set(('baz',)) | ||||
|  | ||||
|  | ||||
| If we register the ``Child`` stanza as a plugin of the ``Parent`` stanza as | ||||
| so, using ``sleekxmpp.xmlstream.register_stanza_plugin``:: | ||||
|  | ||||
|     register_stanza_plugin(Parent, Child) | ||||
|  | ||||
| Then we can access content in the child stanza through the parent. | ||||
| Note that the interface used to access the child stanza is the same as | ||||
| ``Child.plugin_attrib``:: | ||||
|  | ||||
|     parent = Parent() | ||||
|     parent['foo'] = 'a' | ||||
|     parent['child']['baz'] = 'b' | ||||
|  | ||||
| The above code would produce: | ||||
|  | ||||
| .. code-block:: xml | ||||
|  | ||||
|     <the-parent-xml-element xmlns="the-parent-namespace" foo="a"> | ||||
|       <the-child-xml-element xmlsn="the-child-namespace" baz="b" /> | ||||
|     </the-parent-xml-element> | ||||
|  | ||||
| It is also possible to allow a :term:`substanza` to appear multiple times | ||||
| by using ``iterable=True`` in the ``register_stanza_plugin`` call. All | ||||
| iterable :term:`substanzas <substanza>` can be accessed using a standard | ||||
| ``substanzas`` interface. | ||||
							
								
								
									
										220
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,220 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # SleekXMPP documentation build configuration file, created by | ||||
| # sphinx-quickstart on Tue Aug  9 22:27:06 2011. | ||||
| # | ||||
| # This file is execfile()d with the current directory set to its containing dir. | ||||
| # | ||||
| # Note that not all possible configuration values are present in this | ||||
| # autogenerated file. | ||||
| # | ||||
| # All configuration values have a default; values that are commented out | ||||
| # serve to show the default. | ||||
|  | ||||
| import sys, os | ||||
|  | ||||
| # If extensions (or modules to document with autodoc) are in another directory, | ||||
| # add these directories to sys.path here. If the directory is relative to the | ||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. | ||||
| #sys.path.insert(0, os.path.abspath('.')) | ||||
|  | ||||
| # -- General configuration ----------------------------------------------------- | ||||
|  | ||||
| # If your documentation needs a minimal Sphinx version, state it here. | ||||
| #needs_sphinx = '1.0' | ||||
|  | ||||
| # Add any Sphinx extension module names here, as strings. They can be extensions | ||||
| # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. | ||||
| extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] | ||||
|  | ||||
| # Add any paths that contain templates here, relative to this directory. | ||||
| templates_path = ['_templates'] | ||||
|  | ||||
| # The suffix of source filenames. | ||||
| source_suffix = '.rst' | ||||
|  | ||||
| # The encoding of source files. | ||||
| #source_encoding = 'utf-8-sig' | ||||
|  | ||||
| # The master toctree document. | ||||
| master_doc = 'index' | ||||
|  | ||||
| # General information about the project. | ||||
| project = u'SleekXMPP' | ||||
| copyright = u'2011, Nathan Fritz, Lance Stout' | ||||
|  | ||||
| # The version info for the project you're documenting, acts as replacement for | ||||
| # |version| and |release|, also used in various other places throughout the | ||||
| # built documents. | ||||
| # | ||||
| # The short X.Y version. | ||||
| version = '1.0' | ||||
| # The full version, including alpha/beta/rc tags. | ||||
| release = '1.0RC1' | ||||
|  | ||||
| # The language for content autogenerated by Sphinx. Refer to documentation | ||||
| # for a list of supported languages. | ||||
| #language = None | ||||
|  | ||||
| # There are two options for replacing |today|: either, you set today to some | ||||
| # non-false value, then it is used: | ||||
| #today = '' | ||||
| # Else, today_fmt is used as the format for a strftime call. | ||||
| #today_fmt = '%B %d, %Y' | ||||
|  | ||||
| # List of patterns, relative to source directory, that match files and | ||||
| # directories to ignore when looking for source files. | ||||
| exclude_patterns = ['_build'] | ||||
|  | ||||
| # The reST default role (used for this markup: `text`) to use for all documents. | ||||
| #default_role = None | ||||
|  | ||||
| # If true, '()' will be appended to :func: etc. cross-reference text. | ||||
| #add_function_parentheses = True | ||||
|  | ||||
| # If true, the current module name will be prepended to all description | ||||
| # unit titles (such as .. function::). | ||||
| #add_module_names = True | ||||
|  | ||||
| # If true, sectionauthor and moduleauthor directives will be shown in the | ||||
| # output. They are ignored by default. | ||||
| #show_authors = False | ||||
|  | ||||
| # The name of the Pygments (syntax highlighting) style to use. | ||||
| pygments_style = 'default' | ||||
|  | ||||
| # A list of ignored prefixes for module index sorting. | ||||
| #modindex_common_prefix = [] | ||||
|  | ||||
|  | ||||
| # -- Options for HTML output --------------------------------------------------- | ||||
|  | ||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for | ||||
| # a list of builtin themes. | ||||
| html_theme = 'haiku' | ||||
|  | ||||
| # Theme options are theme-specific and customize the look and feel of a theme | ||||
| # further.  For a list of options available for each theme, see the | ||||
| # documentation. | ||||
| #html_theme_options = {'headingcolor': '#CFCFCF', 'linkcolor': '#4A7389'} | ||||
|  | ||||
| # 00ADEE | ||||
|  | ||||
| # Add any paths that contain custom themes here, relative to this directory. | ||||
| #html_theme_path = [] | ||||
|  | ||||
| # The name for this set of Sphinx documents.  If None, it defaults to | ||||
| # "<project> v<release> documentation". | ||||
| html_title = 'SleekXMPP' | ||||
|  | ||||
| # A shorter title for the navigation bar.  Default is the same as html_title. | ||||
| html_short_title = '%s Documentation' % release | ||||
|  | ||||
| # The name of an image file (relative to this directory) to place at the top | ||||
| # of the sidebar. | ||||
| #html_logo = None | ||||
|  | ||||
| # The name of an image file (within the static path) to use as favicon of the | ||||
| # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32 | ||||
| # pixels large. | ||||
| #html_favicon = None | ||||
|  | ||||
| # Add any paths that contain custom static files (such as style sheets) here, | ||||
| # relative to this directory. They are copied after the builtin static files, | ||||
| # so a file named "default.css" will overwrite the builtin "default.css". | ||||
| html_static_path = ['_static'] | ||||
|  | ||||
| # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | ||||
| # using the given strftime format. | ||||
| #html_last_updated_fmt = '%b %d, %Y' | ||||
|  | ||||
| # If true, SmartyPants will be used to convert quotes and dashes to | ||||
| # typographically correct entities. | ||||
| #html_use_smartypants = True | ||||
|  | ||||
| # Custom sidebar templates, maps document names to template names. | ||||
| #html_sidebars = {} | ||||
|  | ||||
| # Additional templates that should be rendered to pages, maps page names to | ||||
| # template names. | ||||
| html_additional_pages = { | ||||
| } | ||||
|  | ||||
|  | ||||
| # If false, no module index is generated. | ||||
| #html_domain_indices = True | ||||
|  | ||||
| # If false, no index is generated. | ||||
| #html_use_index = True | ||||
|  | ||||
| # If true, the index is split into individual pages for each letter. | ||||
| #html_split_index = False | ||||
|  | ||||
| # If true, links to the reST sources are added to the pages. | ||||
| #html_show_sourcelink = True | ||||
|  | ||||
| # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | ||||
| #html_show_sphinx = True | ||||
|  | ||||
| # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | ||||
| #html_show_copyright = True | ||||
|  | ||||
| # If true, an OpenSearch description file will be output, and all pages will | ||||
| # contain a <link> tag referring to it.  The value of this option must be the | ||||
| # base URL from which the finished HTML is served. | ||||
| #html_use_opensearch = '' | ||||
|  | ||||
| # This is the file name suffix for HTML files (e.g. ".xhtml"). | ||||
| #html_file_suffix = None | ||||
|  | ||||
| # Output file base name for HTML help builder. | ||||
| htmlhelp_basename = 'SleekXMPPdoc' | ||||
|  | ||||
|  | ||||
| # -- Options for LaTeX output -------------------------------------------------- | ||||
|  | ||||
| # The paper size ('letter' or 'a4'). | ||||
| #latex_paper_size = 'letter' | ||||
|  | ||||
| # The font size ('10pt', '11pt' or '12pt'). | ||||
| #latex_font_size = '10pt' | ||||
|  | ||||
| # Grouping the document tree into LaTeX files. List of tuples | ||||
| # (source start file, target name, title, author, documentclass [howto/manual]). | ||||
| latex_documents = [ | ||||
|   ('index', 'SleekXMPP.tex', u'SleekXMPP Documentation', | ||||
|    u'Nathan Fritz, Lance Stout', 'manual'), | ||||
| ] | ||||
|  | ||||
| # The name of an image file (relative to this directory) to place at the top of | ||||
| # the title page. | ||||
| #latex_logo = None | ||||
|  | ||||
| # For "manual" documents, if this is true, then toplevel headings are parts, | ||||
| # not chapters. | ||||
| #latex_use_parts = False | ||||
|  | ||||
| # If true, show page references after internal links. | ||||
| #latex_show_pagerefs = False | ||||
|  | ||||
| # If true, show URL addresses after external links. | ||||
| #latex_show_urls = False | ||||
|  | ||||
| # Additional stuff for the LaTeX preamble. | ||||
| #latex_preamble = '' | ||||
|  | ||||
| # Documents to append as an appendix to all manuals. | ||||
| #latex_appendices = [] | ||||
|  | ||||
| # If false, no module index is generated. | ||||
| #latex_domain_indices = True | ||||
|  | ||||
|  | ||||
| # -- Options for manual page output -------------------------------------------- | ||||
|  | ||||
| # One entry per manual page. List of tuples | ||||
| # (source start file, name, description, authors, manual section). | ||||
| man_pages = [ | ||||
|     ('index', 'sleekxmpp', u'SleekXMPP Documentation', | ||||
|      [u'Nathan Fritz, Lance Stout'], 1) | ||||
| ] | ||||
							
								
								
									
										677
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										677
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,677 @@ | ||||
| Creating a SleekXMPP Plugin | ||||
| =========================== | ||||
|  | ||||
| One of the goals of SleekXMPP is to provide support for every draft or final | ||||
| XMPP extension (`XEP <http://xmpp.org/extensions/>`_). To do this, SleekXMPP has a | ||||
| plugin mechanism for adding the functionalities required by each XEP. But even | ||||
| though plugins were made to quickly implement and prototype the official XMPP | ||||
| extensions, there is no reason you can't create your own plugin to implement | ||||
| your own custom XMPP-based protocol. | ||||
|  | ||||
| This guide will help walk you through the steps to | ||||
| implement a rudimentary version of `XEP-0077 In-band | ||||
| Registration <http://xmpp.org/extensions/xep-0077.html>`_. In-band registration | ||||
| was implemented in example 14-6 (page 223) of `XMPP: The Definitive | ||||
| Guide <http://oreilly.com/catalog/9780596521271>`_ because there was no SleekXMPP | ||||
| plugin for XEP-0077 at the time of writing. We will partially fix that issue | ||||
| here by turning the example implementation from *XMPP: The Definitive Guide* | ||||
| into a plugin. Again, note that this will not a complete implementation, and a | ||||
| different, more robust, official plugin for XEP-0077 may be added to SleekXMPP | ||||
| in the future. | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     The example plugin created in this guide is for the server side of the | ||||
|     registration process only. It will **NOT** be able to register new accounts | ||||
|     on an XMPP server. | ||||
|  | ||||
| First Steps | ||||
| ----------- | ||||
| Every plugin inherits from the class :mod:`base_plugin <sleekxmpp.plugins.base.base_plugin>`, | ||||
| and must include a ``plugin_init`` method. While the | ||||
| plugins distributed with SleekXMPP must be placed in the plugins directory | ||||
| ``sleekxmpp/plugins`` to be loaded, custom plugins may be loaded from any | ||||
| module. To do so, use the following form when registering the plugin: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     self.register_plugin('myplugin', module=mod_containing_my_plugin) | ||||
|  | ||||
| The plugin name must be the same as the plugin's class name. | ||||
|   | ||||
| Now, we can open our favorite text editors and create ``xep_0077.py`` in | ||||
| ``SleekXMPP/sleekxmpp/plugins``. We want to do some basic house-keeping and | ||||
| declare the name and description of the XEP we are implementing. If you | ||||
| are creating your own custom plugin, you don't need to include the ``xep`` | ||||
| attribute. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     """ | ||||
|     Creating a SleekXMPP Plugin | ||||
|  | ||||
|     This is a minimal implementation of XEP-0077 to serve | ||||
|     as a tutorial for creating SleekXMPP plugins. | ||||
|     """ | ||||
|  | ||||
|     from sleekxmpp.plugins.base import base_plugin | ||||
|  | ||||
|     class xep_0077(base_plugin): | ||||
|         """ | ||||
|         XEP-0077 In-Band Registration | ||||
|         """ | ||||
|  | ||||
|         def plugin_init(self): | ||||
|             self.description = "In-Band Registration" | ||||
|             self.xep = "0077" | ||||
|  | ||||
| Now that we have a basic plugin, we need to edit | ||||
| ``sleekxmpp/plugins/__init__.py`` to include our new plugin by adding | ||||
| ``'xep_0077'`` to the ``__all__`` declaration. | ||||
|  | ||||
| Interacting with Other Plugins | ||||
| ------------------------------ | ||||
|  | ||||
| In-band registration is a feature that should be advertised through `Service | ||||
| Discovery <http://xmpp.org/extensions/xep-0030.html>`_. To do that, we tell the | ||||
| ``xep_0030`` plugin to add the ``"jabber:iq:register"`` feature. We put this | ||||
| call in a method named ``post_init`` which will be called once the plugin has | ||||
| been loaded; by doing so we advertise that we can do registrations only after we | ||||
| finish activating the plugin. | ||||
|  | ||||
| The ``post_init`` method needs to call ``base_plugin.post_init(self)`` | ||||
| which will mark that ``post_init`` has been called for the plugin. Once the | ||||
| SleekXMPP object begins processing, ``post_init`` will be called on any plugins | ||||
| that have not already run ``post_init``. This allows you to register plugins and | ||||
| their dependencies without needing to worry about the order in which you do so. | ||||
|  | ||||
| **Note:** by adding this call we have introduced a dependency on the XEP-0030 | ||||
| plugin. Be sure to register ``'xep_0030'`` as well as ``'xep_0077'``. SleekXMPP | ||||
| does not automatically load plugin dependencies for you. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def post_init(self): | ||||
|         base_plugin.post_init(self) | ||||
|         self.xmpp['xep_0030'].add_feature("jabber:iq:register") | ||||
|  | ||||
| Creating Custom Stanza Objects | ||||
| ------------------------------ | ||||
|  | ||||
| Now, the IQ stanzas needed to implement our version of XEP-0077 are not very | ||||
| complex, and we could just interact with the XML objects directly just like | ||||
| in the *XMPP: The Definitive Guide* example. However, creating custom stanza | ||||
| objects is good practice. | ||||
|  | ||||
| We will create a new ``Registration`` stanza. Following the *XMPP: The | ||||
| Definitive Guide* example, we will add support for a username and password | ||||
| field. We also need two flags: ``registered`` and ``remove``. The ``registered`` | ||||
| flag is sent when an already registered user attempts to register, along with | ||||
| their registration data. The ``remove`` flag is a request to unregister a user's | ||||
| account. | ||||
|  | ||||
| Adding additional `fields specified in | ||||
| XEP-0077 <http://xmpp.org/extensions/xep-0077.html#registrar-formtypes-register>`_ | ||||
| will not be difficult and is left as an exercise for the reader. | ||||
|  | ||||
| Our ``Registration`` class needs to start with a few descriptions of its | ||||
| behaviour: | ||||
|  | ||||
| * ``namespace`` | ||||
|     The namespace our stanza object lives in. In this case, | ||||
|     ``"jabber:iq:register"``. | ||||
|  | ||||
| * ``name`` | ||||
|     The name of the root XML element. In this case, the ``query`` element. | ||||
|  | ||||
| * ``plugin_attrib`` | ||||
|     The name to access this type of stanza. In particular, given a | ||||
|     registration stanza, the ``Registration`` object can be found using: | ||||
|     ``iq_object['register']``. | ||||
|  | ||||
| * ``interfaces`` | ||||
|     A list of dictionary-like keys that can be used with the stanza object. | ||||
|     When using ``"key"``, if there exists a method of the form ``getKey``, | ||||
|     ``setKey``, or``delKey`` (depending on context) then the result of calling | ||||
|     that method will be returned. Otherwise, the value of the attribute ``key`` | ||||
|     of the main stanza element is returned if one exists. | ||||
|  | ||||
|     **Note:** The accessor methods currently use title case, and not camel case. | ||||
|     Thus if you need to access an item named ``"methodName"`` you will need to | ||||
|     use ``getMethodname``. This naming convention might change to full camel | ||||
|     case in a future version of SleekXMPP. | ||||
|  | ||||
| * ``sub_interfaces`` | ||||
|     A subset of ``interfaces``, but these keys map to the text of any | ||||
|     subelements that are direct children of the main stanza element. Thus, | ||||
|     referencing ``iq_object['register']['username']`` will either execute | ||||
|     ``getUsername`` or return the value in the ``username`` element of the | ||||
|     query. | ||||
|  | ||||
|     If you need to access an element, say ``elem``, that is not a direct child | ||||
|     of the main stanza element, you will need to add ``getElem``, ``setElem``, | ||||
|     and ``delElem``. See the note above about naming conventions. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin | ||||
|     from sleekxmpp import Iq | ||||
|  | ||||
|     class Registration(ElementBase): | ||||
|         namespace = 'jabber:iq:register' | ||||
|         name = 'query' | ||||
|         plugin_attrib = 'register' | ||||
|         interfaces = set(('username', 'password', 'registered', 'remove')) | ||||
|         sub_interfaces = interfaces | ||||
|  | ||||
|         def getRegistered(self): | ||||
|             present = self.xml.find('{%s}registered' % self.namespace) | ||||
|             return present is not None | ||||
|  | ||||
|         def getRemove(self): | ||||
|             present = self.xml.find('{%s}remove' % self.namespace) | ||||
|             return present is not None | ||||
|  | ||||
|         def setRegistered(self, registered): | ||||
|             if registered: | ||||
|                 self.addField('registered') | ||||
|             else: | ||||
|                 del self['registered'] | ||||
|  | ||||
|         def setRemove(self, remove): | ||||
|             if remove: | ||||
|                 self.addField('remove') | ||||
|             else: | ||||
|                 del self['remove'] | ||||
|  | ||||
|         def addField(self, name): | ||||
|             itemXML = ET.Element('{%s}%s' % (self.namespace, name)) | ||||
|             self.xml.append(itemXML) | ||||
|  | ||||
| Setting a ``sub_interface`` attribute to ``""`` will remove that subelement. | ||||
| Since we want to include empty registration fields in our form, we need the | ||||
| ``addField`` method to add the empty elements. | ||||
|  | ||||
| Since the ``registered`` and ``remove`` elements are just flags, we need to add | ||||
| custom logic to enforce the binary behavior. | ||||
|  | ||||
| Extracting Stanzas from the XML Stream | ||||
| -------------------------------------- | ||||
|  | ||||
| Now that we have a custom stanza object, we need to be able to detect when we | ||||
| receive one. To do this, we register a stream handler that will pattern match | ||||
| stanzas off of the XML stream against our stanza object's element name and | ||||
| namespace. To do so, we need to create a ``Callback`` object which contains | ||||
| an XML fragment that can identify our stanza type. We can add this handler | ||||
| registration to our ``plugin_init`` method. | ||||
|  | ||||
| Also, we need to associate our ``Registration`` class with IQ stanzas; | ||||
| that requires the use of the ``register_stanza_plugin`` function (in | ||||
| ``sleekxmpp.xmlstream.stanzabase``) which takes the class of a parent stanza | ||||
| type followed by the substanza type. In our case, the parent stanza is an IQ | ||||
| stanza, and the substanza is our registration query. | ||||
|  | ||||
| The ``__handleRegistration`` method referenced in the callback will be our | ||||
| handler function to process registration requests. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.description = "In-Band Registration" | ||||
|         self.xep = "0077" | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|           Callback('In-Band Registration', | ||||
|             MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), | ||||
|             self.__handleRegistration)) | ||||
|         register_stanza_plugin(Iq, Registration) | ||||
|  | ||||
| Handling Incoming Stanzas and Triggering Events | ||||
| ----------------------------------------------- | ||||
| There are six situations that we need to handle to finish our implementation of | ||||
| XEP-0077. | ||||
|  | ||||
| **Registration Form Request from a New User:** | ||||
|  | ||||
|     .. code-block:: xml | ||||
|  | ||||
|         <iq type="result"> | ||||
|          <query xmlns="jabber:iq:register"> | ||||
|           <username /> | ||||
|           <password /> | ||||
|          </query> | ||||
|         </iq> | ||||
|  | ||||
| **Registration Form Request from an Existing User:** | ||||
|  | ||||
|     .. code-block:: xml | ||||
|  | ||||
|         <iq type="result"> | ||||
|          <query xmlns="jabber:iq:register"> | ||||
|           <registered /> | ||||
|           <username>Foo</username> | ||||
|           <password>hunter2</password> | ||||
|          </query> | ||||
|         </iq> | ||||
|  | ||||
| **Unregister Account:** | ||||
|  | ||||
|     .. code-block:: xml | ||||
|  | ||||
|         <iq type="result"> | ||||
|          <query xmlns="jabber:iq:register" /> | ||||
|         </iq> | ||||
|  | ||||
| **Incomplete Registration:** | ||||
|  | ||||
|     .. code-block:: xml | ||||
|  | ||||
|         <iq type="error"> | ||||
|           <query xmlns="jabber:iq:register"> | ||||
|             <username>Foo</username> | ||||
|           </query> | ||||
|          <error code="406" type="modify"> | ||||
|           <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|          </error> | ||||
|         </iq> | ||||
|  | ||||
| **Conflicting Registrations:** | ||||
|  | ||||
|     .. code-block:: xml | ||||
|  | ||||
|         <iq type="error"> | ||||
|          <query xmlns="jabber:iq:register"> | ||||
|           <username>Foo</username> | ||||
|           <password>hunter2</password> | ||||
|          </query> | ||||
|          <error code="409" type="cancel"> | ||||
|           <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> | ||||
|          </error> | ||||
|         </iq> | ||||
|  | ||||
| **Successful Registration:** | ||||
|  | ||||
|     .. code-block:: xml | ||||
|  | ||||
|         <iq type="result"> | ||||
|          <query xmlns="jabber:iq:register" /> | ||||
|         </iq> | ||||
|  | ||||
| Cases 1 and 2: Registration Requests | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| Responding to registration requests depends on if the requesting user already | ||||
| has an account. If there is an account, the response should include the | ||||
| ``registered`` flag and the user's current registration information. Otherwise, | ||||
| we just send the fields for our registration form. | ||||
|  | ||||
| We will handle both cases by creating a ``sendRegistrationForm`` method that | ||||
| will create either an empty of full form depending on if we provide it with | ||||
| user data. Since we need to know which form fields to include (especially if we | ||||
| add support for the other fields specified in XEP-0077), we will also create a | ||||
| method ``setForm`` which will take the names of the fields we wish to include. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.description = "In-Band Registration" | ||||
|         self.xep = "0077" | ||||
|         self.form_fields = ('username', 'password') | ||||
|         ... remainder of plugin_init | ||||
|  | ||||
|     ... | ||||
|  | ||||
|     def __handleRegistration(self, iq): | ||||
|         if iq['type'] == 'get': | ||||
|             # Registration form requested | ||||
|             userData = self.backend[iq['from'].bare] | ||||
|             self.sendRegistrationForm(iq, userData) | ||||
|  | ||||
|     def setForm(self, *fields): | ||||
|         self.form_fields = fields | ||||
|  | ||||
|     def sendRegistrationForm(self, iq, userData=None): | ||||
|         reg = iq['register'] | ||||
|         if userData is None: | ||||
|             userData = {} | ||||
|         else: | ||||
|             reg['registered'] = True | ||||
|  | ||||
|         for field in self.form_fields: | ||||
|             data = userData.get(field, '') | ||||
|             if data: | ||||
|                 # Add field with existing data | ||||
|                 reg[field] = data | ||||
|             else: | ||||
|                 # Add a blank field | ||||
|                 reg.addField(field) | ||||
|  | ||||
|         iq.reply().setPayload(reg.xml) | ||||
|         iq.send() | ||||
|  | ||||
| Note how we are able to access our ``Registration`` stanza object with | ||||
| ``iq['register']``. | ||||
|  | ||||
| A User Backend | ||||
| ++++++++++++++ | ||||
| You might have noticed the reference to ``self.backend``, which is an object | ||||
| that abstracts away storing and retrieving user information. Since it is not | ||||
| much more than a dictionary, we will leave the implementation details to the | ||||
| final, full source code example. | ||||
|  | ||||
| Case 3: Unregister an Account | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| The next simplest case to consider is responding to a request to remove | ||||
| an account. If we receive a ``remove`` flag, we instruct the backend to | ||||
| remove the user's account. Since your application may need to know about | ||||
| when users are registered or unregistered, we trigger an event using | ||||
| ``self.xmpp.event('unregister_user', iq)``. See the component examples below for | ||||
| how to respond to that event. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|      def __handleRegistration(self, iq): | ||||
|         if iq['type'] == 'get': | ||||
|             # Registration form requested | ||||
|             userData = self.backend[iq['from'].bare] | ||||
|             self.sendRegistrationForm(iq, userData) | ||||
|         elif iq['type'] == 'set': | ||||
|             # Remove an account | ||||
|             if iq['register']['remove']: | ||||
|                 self.backend.unregister(iq['from'].bare) | ||||
|                 self.xmpp.event('unregistered_user', iq) | ||||
|                 iq.reply().send() | ||||
|                 return | ||||
|  | ||||
| Case 4: Incomplete Registration | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| For the next case we need to check the user's registration to ensure it has all | ||||
| of the fields we wanted. The simple option that we will use is to loop over the | ||||
| field names and check each one; however, this means that all fields we send to | ||||
| the user are required. Adding optional fields is left to the reader. | ||||
|  | ||||
| Since we have received an incomplete form, we need to send an error message back | ||||
| to the user. We have to send a few different types of errors, so we will also | ||||
| create a ``_sendError`` method that will add the appropriate ``error`` element | ||||
| to the IQ reply. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def __handleRegistration(self, iq): | ||||
|         if iq['type'] == 'get': | ||||
|             # Registration form requested | ||||
|             userData = self.backend[iq['from'].bare] | ||||
|             self.sendRegistrationForm(iq, userData) | ||||
|         elif iq['type'] == 'set': | ||||
|             if iq['register']['remove']: | ||||
|                 # Remove an account | ||||
|                 self.backend.unregister(iq['from'].bare) | ||||
|                 self.xmpp.event('unregistered_user', iq) | ||||
|                 iq.reply().send() | ||||
|                 return | ||||
|  | ||||
|             for field in self.form_fields: | ||||
|                 if not iq['register'][field]: | ||||
|                     # Incomplete Registration | ||||
|                     self._sendError(iq, '406', 'modify', 'not-acceptable' | ||||
|                                     "Please fill in all fields.") | ||||
|                     return | ||||
|  | ||||
|     ... | ||||
|  | ||||
|     def _sendError(self, iq, code, error_type, name, text=''): | ||||
|         iq.reply().setPayload(iq['register'].xml) | ||||
|         iq.error() | ||||
|         iq['error']['code'] = code | ||||
|         iq['error']['type'] = error_type | ||||
|         iq['error']['condition'] = name | ||||
|         iq['error']['text'] = text | ||||
|         iq.send() | ||||
|  | ||||
| Cases 5 and 6: Conflicting and Successful Registration | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| We are down to the final decision on if we have a successful registration. We | ||||
| send the user's data to the backend with the ``self.backend.register`` method. | ||||
| If it returns ``True``, then registration has been successful. Otherwise, | ||||
| there has been a conflict with usernames and registration has failed. Like | ||||
| with unregistering an account, we trigger an event indicating that a user has | ||||
| been registered by using ``self.xmpp.event('registered_user', iq)``. See the | ||||
| component examples below for how to respond to this event. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def __handleRegistration(self, iq): | ||||
|         if iq['type'] == 'get': | ||||
|             # Registration form requested | ||||
|             userData = self.backend[iq['from'].bare] | ||||
|             self.sendRegistrationForm(iq, userData) | ||||
|         elif iq['type'] == 'set': | ||||
|             if iq['register']['remove']: | ||||
|                 # Remove an account | ||||
|                 self.backend.unregister(iq['from'].bare) | ||||
|                 self.xmpp.event('unregistered_user', iq) | ||||
|                 iq.reply().send() | ||||
|                 return | ||||
|  | ||||
|             for field in self.form_fields: | ||||
|                 if not iq['register'][field]: | ||||
|                     # Incomplete Registration | ||||
|                     self._sendError(iq, '406', 'modify', 'not-acceptable', | ||||
|                                     "Please fill in all fields.") | ||||
|                     return | ||||
|  | ||||
|             if self.backend.register(iq['from'].bare, iq['register']): | ||||
|                 # Successful registration | ||||
|                 self.xmpp.event('registered_user', iq) | ||||
|                 iq.reply().setPayload(iq['register'].xml) | ||||
|                 iq.send() | ||||
|             else: | ||||
|                 # Conflicting registration | ||||
|                 self._sendError(iq, '409', 'cancel', 'conflict', | ||||
|                                 "That username is already taken.") | ||||
|  | ||||
| Example Component Using the XEP-0077 Plugin | ||||
| ------------------------------------------- | ||||
| Alright, the moment we've been working towards - actually using our plugin to | ||||
| simplify our other applications. Here is a basic component that simply manages | ||||
| user registrations and sends the user a welcoming message when they register, | ||||
| and a farewell message when they delete their account. | ||||
|  | ||||
| Note that we have to register the ``'xep_0030'`` plugin first, | ||||
| and that we specified the form fields we wish to use with | ||||
| ``self.xmpp.plugin['xep_0077'].setForm('username', 'password')``. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import sleekxmpp.componentxmpp | ||||
|  | ||||
|     class Example(sleekxmpp.componentxmpp.ComponentXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password): | ||||
|             sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888) | ||||
|  | ||||
|             self.registerPlugin('xep_0030') | ||||
|             self.registerPlugin('xep_0077') | ||||
|             self.plugin['xep_0077'].setForm('username', 'password') | ||||
|  | ||||
|             self.add_event_handler("registered_user", self.reg) | ||||
|             self.add_event_handler("unregistered_user", self.unreg) | ||||
|  | ||||
|         def reg(self, iq): | ||||
|             msg = "Welcome! %s" % iq['register']['username'] | ||||
|             self.sendMessage(iq['from'], msg, mfrom=self.fulljid) | ||||
|  | ||||
|         def unreg(self, iq): | ||||
|             msg = "Bye! %s" % iq['register']['username'] | ||||
|             self.sendMessage(iq['from'], msg, mfrom=self.fulljid) | ||||
|  | ||||
| **Congratulations!** We now have a basic, functioning implementation of | ||||
| XEP-0077. | ||||
|  | ||||
| Complete Source Code for XEP-0077 Plugin | ||||
| ---------------------------------------- | ||||
| Here is a copy of a more complete implementation of the plugin we created, but | ||||
| with some additional registration fields implemented. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     """ | ||||
|     Creating a SleekXMPP Plugin | ||||
|  | ||||
|     This is a minimal implementation of XEP-0077 to serve | ||||
|     as a tutorial for creating SleekXMPP plugins. | ||||
|     """ | ||||
|  | ||||
|     from sleekxmpp.plugins.base import base_plugin | ||||
|     from sleekxmpp.xmlstream.handler.callback import Callback | ||||
|     from sleekxmpp.xmlstream.matcher.xpath import MatchXPath | ||||
|     from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin | ||||
|     from sleekxmpp import Iq | ||||
|     import copy | ||||
|  | ||||
|  | ||||
|     class Registration(ElementBase): | ||||
|         namespace = 'jabber:iq:register' | ||||
|         name = 'query' | ||||
|         plugin_attrib = 'register' | ||||
|         interfaces = set(('username', 'password', 'email', 'nick', 'name',  | ||||
|                           'first', 'last', 'address', 'city', 'state', 'zip',  | ||||
|                           'phone', 'url', 'date', 'misc', 'text', 'key',  | ||||
|                           'registered', 'remove', 'instructions')) | ||||
|         sub_interfaces = interfaces | ||||
|  | ||||
|         def getRegistered(self): | ||||
|             present = self.xml.find('{%s}registered' % self.namespace) | ||||
|             return present is not None | ||||
|  | ||||
|         def getRemove(self): | ||||
|             present = self.xml.find('{%s}remove' % self.namespace) | ||||
|             return present is not None | ||||
|  | ||||
|         def setRegistered(self, registered): | ||||
|             if registered: | ||||
|                 self.addField('registered') | ||||
|             else: | ||||
|                 del self['registered'] | ||||
|  | ||||
|         def setRemove(self, remove): | ||||
|             if remove: | ||||
|                 self.addField('remove') | ||||
|             else: | ||||
|                 del self['remove'] | ||||
|  | ||||
|         def addField(self, name): | ||||
|             itemXML = ET.Element('{%s}%s' % (self.namespace, name)) | ||||
|             self.xml.append(itemXML) | ||||
|  | ||||
|  | ||||
|     class UserStore(object): | ||||
|         def __init__(self): | ||||
|             self.users = {} | ||||
|  | ||||
|         def __getitem__(self, jid): | ||||
|             return self.users.get(jid, None) | ||||
|  | ||||
|         def register(self, jid, registration): | ||||
|             username = registration['username'] | ||||
|  | ||||
|             def filter_usernames(user): | ||||
|                 return user != jid and self.users[user]['username'] == username | ||||
|  | ||||
|             conflicts = filter(filter_usernames, self.users.keys()) | ||||
|             if conflicts: | ||||
|                 return False | ||||
|  | ||||
|             self.users[jid] = registration | ||||
|             return True | ||||
|  | ||||
|         def unregister(self, jid): | ||||
|             del self.users[jid] | ||||
|  | ||||
|     class xep_0077(base_plugin): | ||||
|         """ | ||||
|         XEP-0077 In-Band Registration | ||||
|         """ | ||||
|  | ||||
|         def plugin_init(self): | ||||
|             self.description = "In-Band Registration" | ||||
|             self.xep = "0077" | ||||
|             self.form_fields = ('username', 'password') | ||||
|             self.form_instructions = "" | ||||
|             self.backend = UserStore() | ||||
|  | ||||
|             self.xmpp.registerHandler( | ||||
|                 Callback('In-Band Registration', | ||||
|                          MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), | ||||
|                          self.__handleRegistration)) | ||||
|             register_stanza_plugin(Iq, Registration) | ||||
|  | ||||
|         def post_init(self): | ||||
|             base_plugin.post_init(self) | ||||
|             self.xmpp['xep_0030'].add_feature("jabber:iq:register") | ||||
|  | ||||
|         def __handleRegistration(self, iq): | ||||
|             if iq['type'] == 'get': | ||||
|                 # Registration form requested | ||||
|                 userData = self.backend[iq['from'].bare] | ||||
|                 self.sendRegistrationForm(iq, userData) | ||||
|             elif iq['type'] == 'set': | ||||
|                 if iq['register']['remove']: | ||||
|                     # Remove an account | ||||
|                     self.backend.unregister(iq['from'].bare) | ||||
|                     self.xmpp.event('unregistered_user', iq) | ||||
|                     iq.reply().send() | ||||
|                     return | ||||
|  | ||||
|                 for field in self.form_fields: | ||||
|                     if not iq['register'][field]: | ||||
|                         # Incomplete Registration | ||||
|                         self._sendError(iq, '406', 'modify', 'not-acceptable', | ||||
|                                         "Please fill in all fields.") | ||||
|                         return | ||||
|  | ||||
|                 if self.backend.register(iq['from'].bare, iq['register']): | ||||
|                     # Successful registration | ||||
|                     self.xmpp.event('registered_user', iq) | ||||
|                     iq.reply().setPayload(iq['register'].xml) | ||||
|                     iq.send() | ||||
|                 else: | ||||
|                     # Conflicting registration | ||||
|                     self._sendError(iq, '409', 'cancel', 'conflict', | ||||
|                                     "That username is already taken.") | ||||
|  | ||||
|         def setForm(self, *fields): | ||||
|             self.form_fields = fields | ||||
|  | ||||
|         def setInstructions(self, instructions): | ||||
|             self.form_instructions = instructions | ||||
|  | ||||
|         def sendRegistrationForm(self, iq, userData=None): | ||||
|             reg = iq['register'] | ||||
|             if userData is None: | ||||
|                 userData = {} | ||||
|             else: | ||||
|                 reg['registered'] = True | ||||
|  | ||||
|             if self.form_instructions: | ||||
|                 reg['instructions'] = self.form_instructions | ||||
|  | ||||
|             for field in self.form_fields: | ||||
|                 data = userData.get(field, '') | ||||
|                 if data: | ||||
|                     # Add field with existing data | ||||
|                     reg[field] = data | ||||
|                 else: | ||||
|                     # Add a blank field | ||||
|                     reg.addField(field) | ||||
|  | ||||
|             iq.reply().setPayload(reg.xml) | ||||
|             iq.send() | ||||
|  | ||||
|         def _sendError(self, iq, code, error_type, name, text=''): | ||||
|             iq.reply().setPayload(iq['register'].xml) | ||||
|             iq.error() | ||||
|             iq['error']['code'] = code | ||||
|             iq['error']['type'] = error_type | ||||
|             iq['error']['condition'] = name | ||||
|             iq['error']['text'] = text | ||||
|             iq.send() | ||||
							
								
								
									
										271
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,271 @@ | ||||
| Event Index | ||||
| =========== | ||||
|  | ||||
| .. glossary:: | ||||
|     :sorted: | ||||
|  | ||||
|     connected | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP` | ||||
|  | ||||
|         Signal that a connection has been made with the XMPP server, but a session | ||||
|         has not yet been established. | ||||
|  | ||||
|     changed_status | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|  | ||||
|         Triggered when a presence stanza is received from a JID with a show type | ||||
|         different than the last presence stanza from the same JID. | ||||
|  | ||||
|     changed_subscription | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|  | ||||
|         Triggered whenever a presence stanza with a type of ``subscribe``, | ||||
|         ``subscribed``, ``unsubscribe``, or ``unsubscribed`` is received. | ||||
|  | ||||
|         Note that if the values ``xmpp.auto_authorize`` and ``xmpp.auto_subscribe`` | ||||
|         are set to ``True`` or ``False``, and not ``None``, then SleekXMPP will | ||||
|         either accept or reject all subscription requests before your event handlers | ||||
|         are called. Set these values to ``None`` if you wish to make more complex | ||||
|         subscription decisions. | ||||
|  | ||||
|     chatstate_active | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     chatstate_composing | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     chatstate_gone | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     chatstate_inactive | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     chatstate_paused | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     disco_info | ||||
|         - **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoInfo` | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030` | ||||
|          | ||||
|         Triggered whenever a ``disco#info`` result stanza is received. | ||||
|  | ||||
|     disco_items | ||||
|         - **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoItems` | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030` | ||||
|          | ||||
|         Triggered whenever a ``disco#items`` result stanza is received. | ||||
|  | ||||
|     disconnected | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`~sleekxmpp.ClientXMPP` | ||||
|          | ||||
|         Signal that the connection with the XMPP server has been lost. | ||||
|  | ||||
|     entity_time | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     failed_auth | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`~sleekxmpp.ClientXMPP`, :py:class:`~sleekxmpp.plugins.xep_0078.xep_0078` | ||||
|  | ||||
|         Signal that the server has rejected the provided login credentials. | ||||
|  | ||||
|     gmail_notify | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify` | ||||
|          | ||||
|         Signal that there are unread emails for the Gmail account associated with the current XMPP account. | ||||
|  | ||||
|     gmail_messages | ||||
|         - **Data:** :py:class:`~sleekxmpp.Iq` | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify` | ||||
|          | ||||
|         Signal that there are unread emails for the Gmail account associated with the current XMPP account. | ||||
|  | ||||
|     got_online | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         If a presence stanza is received from a JID which was previously marked as | ||||
|         offline, and the presence has a show type of '``chat``', '``dnd``', '``away``', | ||||
|         or '``xa``', then this event is triggered as well. | ||||
|  | ||||
|     got_offline | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         Signal that an unavailable presence stanza has been received from a JID. | ||||
|  | ||||
|     groupchat_invite | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     groupchat_direct_invite | ||||
|         - **Data:** :py:class:`~sleekxmpp.Message`  | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct` | ||||
|  | ||||
|     groupchat_message | ||||
|         - **Data:** :py:class:`~sleekxmpp.Message` | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045` | ||||
|          | ||||
|         Triggered whenever a message is received from a multi-user chat room. | ||||
|  | ||||
|     groupchat_presence | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045` | ||||
|          | ||||
|         Triggered whenever a presence stanza is received from a user in a multi-user chat room. | ||||
|  | ||||
|     groupchat_subject | ||||
|         - **Data:** :py:class:`~sleekxmpp.Message` | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045` | ||||
|          | ||||
|         Triggered whenever the subject of a multi-user chat room is changed, or announced when joining a room. | ||||
|  | ||||
|     killed | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     last_activity | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     message | ||||
|         - **Data:** :py:class:`~sleekxmpp.Message` | ||||
|         - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>` | ||||
|          | ||||
|         Makes the contents of message stanzas available whenever one is received. Be | ||||
|         sure to check the message type in order to handle error messages. | ||||
|  | ||||
|     message_form | ||||
|         - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`  | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`  | ||||
|          | ||||
|         Currently the same as :term:`message_xform`. | ||||
|  | ||||
|     message_xform | ||||
|         - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`  | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`  | ||||
|          | ||||
|         Triggered whenever a data form is received inside a message. | ||||
|  | ||||
|     mucc::[room]::got_offline | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     muc::[room]::got_online | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     muc::[room]::message | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     muc::[room]::presence | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
|     presence_available | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``available``' is received. | ||||
|  | ||||
|     presence_error | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``error``' is received. | ||||
|  | ||||
|     presence_form | ||||
|         - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`  | ||||
|         - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`  | ||||
|          | ||||
|         This event is present in the XEP-0004 plugin code, but is currently not used. | ||||
|  | ||||
|     presence_probe | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``probe``' is received. | ||||
|  | ||||
|     presence_subscribe | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``subscribe``' is received. | ||||
|  | ||||
|     presence_subscribed | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``subscribed``' is received. | ||||
|  | ||||
|     presence_unavailable | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``unavailable``' is received. | ||||
|  | ||||
|     presence_unsubscribe | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``unsubscribe``' is received. | ||||
|  | ||||
|     presence_unsubscribed | ||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
|          | ||||
|         A presence stanza with a type of '``unsubscribed``' is received. | ||||
|  | ||||
|     roster_update | ||||
|         - **Data:** :py:class:`~sleekxmpp.stanza.Roster`  | ||||
|         - **Source:** :py:class:`~sleekxmpp.ClientXMPP`  | ||||
|          | ||||
|         An IQ result containing roster entries is received. | ||||
|  | ||||
|     sent_presence | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>` | ||||
|          | ||||
|         Signal that an initial presence stanza has been written to the XML stream. | ||||
|  | ||||
|     session_end | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,  | ||||
|           :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>` | ||||
|           :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>` | ||||
|  | ||||
|         Signal that a connection to the XMPP server has been lost and the current | ||||
|         stream session has ended. Currently equivalent to :term:`disconnected`, but | ||||
|         future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ | ||||
|         will distinguish the two events. | ||||
|  | ||||
|         Plugins that maintain session-based state should clear themselves when | ||||
|         this event is fired. | ||||
|  | ||||
|     session_start | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,  | ||||
|           :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>` | ||||
|           :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>` | ||||
|  | ||||
|         Signal that a connection to the XMPP server has been made and a session has been established. | ||||
|  | ||||
|     socket_error | ||||
|         - **Data:** ``Socket`` exception object  | ||||
|         - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` | ||||
|  | ||||
|     stream_error | ||||
|         - **Data:** :py:class:`~sleekxmpp.stanza.StreamError` | ||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` | ||||
							
								
								
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| How to Use Stream Features | ||||
| ========================== | ||||
							
								
								
									
										2
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Create and Run a Server Component | ||||
| ================================= | ||||
							
								
								
									
										390
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,390 @@ | ||||
| .. _echobot: | ||||
|  | ||||
| =============================== | ||||
| SleekXMPP Quickstart - Echo Bot | ||||
| =============================== | ||||
|  | ||||
| .. note:: | ||||
|      | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|  | ||||
| If you have not yet installed SleekXMPP, do so now by either checking out a version | ||||
| from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install sleekxmpp  # Or: easy_install sleekxmpp | ||||
|  | ||||
|  | ||||
| As a basic starting project, we will create an echo bot which will reply to any | ||||
| messages sent to it. We will also go through adding some basic command line configuration | ||||
| for enabling or disabling debug log outputs and setting the username and password | ||||
| for the bot. | ||||
|  | ||||
| For the command line options processing, we will use the built-in ``optparse`` | ||||
| module and the ``getpass`` module for reading in passwords. | ||||
|  | ||||
| TL;DR Just Give Me the Code | ||||
| --------------------------- | ||||
| As you wish: :ref:`the completed example <echobot_complete>`. | ||||
|  | ||||
| Overview | ||||
| -------- | ||||
|  | ||||
| To get started, here is a brief outline of the structure that the final project will have: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     #!/usr/bin/env python | ||||
|     # -*- coding: utf-8 -*- | ||||
|  | ||||
|     import sys | ||||
|     import logging | ||||
|     import getpass | ||||
|     from optparse import OptionParser | ||||
|  | ||||
|     import sleekxmpp | ||||
|  | ||||
|     '''Here we will create out echo bot class''' | ||||
|  | ||||
|     if __name__ == '__main__': | ||||
|         '''Here we will configure and read command line options''' | ||||
|  | ||||
|         '''Here we will instantiate our echo bot''' | ||||
|  | ||||
|         '''Finally, we connect the bot and start listening for messages''' | ||||
|  | ||||
| Default Encoding | ||||
| ---------------- | ||||
| XMPP requires support for UTF-8 and so SleekXMPP must use UTF-8 as well. In | ||||
| Python3 this is simple because Unicode is the default string type. For Python2.6+ | ||||
| the situation is not as easy because standard strings are simply byte arrays and | ||||
| use ASCII. We can get Python to use UTF-8 as the default encoding by including: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     if sys.version_info < (3, 0): | ||||
|         reload(sys) | ||||
|         sys.setdefaultencoding('utf8') | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     Until we are able to ensure that SleekXMPP will always use Unicode in Python2.6+, this | ||||
|     may cause issues embedding SleekXMPP into other applications which assume ASCII encoding. | ||||
|  | ||||
| Creating the EchoBot Class | ||||
| -------------------------- | ||||
|  | ||||
| There are three main types of entities within XMPP — servers, components, and | ||||
| clients. Since our echo bot will only be responding to a few people, and won't need | ||||
| to remember thousands of users, we will use a client connection. A client connection | ||||
| is the same type that you use with your standard IM client such as Pidgin or Psi. | ||||
|  | ||||
| SleekXMPP comes with a :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` class | ||||
| which we can extend to add our message echoing feature. :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` | ||||
| requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those | ||||
| as well. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     class EchoBot(sleekxmpp.ClientXMPP): | ||||
|          | ||||
|         def __init__(self, jid, password): | ||||
|             super(EchoBot, self).__init__(jid, password) | ||||
|  | ||||
| Handling Session Start | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
| The XMPP spec requires clients to broadcast its presence and retrieve its roster (buddy list) once | ||||
| it connects and establishes a session with the XMPP server. Until these two tasks are completed, | ||||
| some servers may not deliver or send messages or presence notifications to the client. So we now | ||||
| need to be sure that we retrieve our roster and send an initial presence once the session has  | ||||
| started. To do that, we will register an event handler for the :term:`session_start` event. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|      def __init__(self, jid, password): | ||||
|         super(EchoBot, self).__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|  | ||||
|  | ||||
| Since we want the method ``self.start`` to execute when the :term:`session_start` event is triggered, | ||||
| we also need to define the ``self.start`` handler. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     Not sending an initial presence and retrieving the roster when using a client instance can | ||||
|     prevent your program from receiving presence notifications or messages depending on the | ||||
|     XMPP server you have chosen. | ||||
|  | ||||
| Our event handler, like every event handler, accepts a single parameter which typically is the stanza | ||||
| that was received that caused the event. In this case, ``event`` will just be an empty dictionary since | ||||
| there is no associated data. | ||||
|  | ||||
| Our first task of sending an initial presence is done using :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>`. | ||||
| Calling :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>` without any arguments will send the simplest | ||||
| stanza allowed in XMPP: | ||||
|  | ||||
| .. code-block:: xml | ||||
|  | ||||
|     <presence /> | ||||
|  | ||||
|  | ||||
| The second requirement is fulfilled using :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>`, which | ||||
| will send an IQ stanza requesting the roster to the server and then wait for the response. You may be wondering | ||||
| what :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>` returns since we are not saving any return | ||||
| value. The roster data is saved by an internal handler to ``self.roster``, and in the case of a :class:`ClientXMPP | ||||
| <sleekxmpp.clientxmpp.ClientXMPP>` instance to ``self.client_roster``. (The difference between ``self.roster`` and | ||||
| ``self.client_roster`` is that ``self.roster`` supports storing roster information for multiple JIDs, which is useful | ||||
| for components, whereas ``self.client_roster`` stores roster data for just the client's JID.) | ||||
|  | ||||
| It is possible for a timeout to occur while waiting for the server to respond, which can happen if the | ||||
| network is excessively slow or the server is no longer responding. In that case, an :class:`IQTimeout | ||||
| <sleekxmpp.exceptions.IQTimeout>` is raised. Similarly, an :class:`IQError <sleekxmpp.exceptions.IQError>` exception can | ||||
| be raised if the request contained bad data or requested the roster for the wrong user. In either case, you can wrap the | ||||
| ``get_roster()`` call in a ``try``/``except`` block to retry the roster retrieval process. | ||||
|  | ||||
| The XMPP stanzas from the roster retrieval process could look like this: | ||||
|  | ||||
| .. code-block:: xml | ||||
|  | ||||
|     <iq type="get"> | ||||
|       <query xmlns="jabber:iq:roster" /> | ||||
|     </iq> | ||||
|  | ||||
|     <iq type="result" to="echobot@example.com" from="example.com"> | ||||
|       <query xmlns="jabber:iq:roster"> | ||||
|         <item jid="friend@example.com" subscription="both" /> | ||||
|       </query> | ||||
|     </iq> | ||||
|  | ||||
| Responding to Messages | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
| Now that an ``EchoBot`` instance handles :term:`session_start`, we can begin receiving and | ||||
| responding to messages. Now we can register a handler for the :term:`message` event that is raised | ||||
| whenever a messsage is received. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|      def __init__(self, jid, password): | ||||
|         super(EchoBot, self).__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|         self.add_event_handler('message', self.message) | ||||
|  | ||||
|  | ||||
| The :term:`message` event is fired whenever a ``<message />`` stanza is received, including for | ||||
| group chat messages, errors, etc. Properly responding to messages thus requires checking the | ||||
| ``'type'`` interface of the message :term:`stanza object`. For responding to only messages | ||||
| addressed to our bot (and not from a chat room), we check that the type is either ``normal`` | ||||
| or ``chat``. (Other potential types are ``error``, ``headline``, and ``groupchat``.) | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def message(self, msg): | ||||
|         if msg['type'] in ('normal', 'chat'): | ||||
|             msg.reply("Thanks for sending:\n%s" % msg['body']).send() | ||||
|  | ||||
| Let's take a closer look at the ``.reply()`` method used above. For message stanzas, | ||||
| ``.reply()`` accepts the parameter ``body`` (also as the first positional argument), | ||||
| which is then used as the value of the ``<body />`` element of the message.  | ||||
| Setting the appropriate ``to`` JID is also handled by ``.reply()``. | ||||
|  | ||||
| Another way to have sent the reply message would be to use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`, | ||||
| which is a convenience method for generating and sending a message based on the values passed to it. If we were to use | ||||
| this method, the above code would look as so: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def message(self, msg): | ||||
|         if msg['type'] in ('normal', 'chat'): | ||||
|             self.send_message(mto=msg['from'], | ||||
|                               mbody='Thanks for sending:\n%s' % msg['body']) | ||||
|  | ||||
| Whichever method you choose to use, the results in action will look like this: | ||||
|  | ||||
| .. code-block:: xml | ||||
|  | ||||
|     <message to="echobot@example.com" from="someuser@example.net" type="chat"> | ||||
|       <body>Hej!</body> | ||||
|     </message> | ||||
|  | ||||
|     <message to="someuser@example.net" type="chat"> | ||||
|       <body>Thanks for sending: | ||||
|       Hej!</body> | ||||
|     </message> | ||||
|  | ||||
| .. note:: | ||||
|     XMPP does not require stanzas sent by a client to include a ``from`` attribute, and | ||||
|     leaves that responsibility to the XMPP server. However, if a sent stanza does | ||||
|     include a ``from`` attribute, it must match the full JID of the client or some | ||||
|     servers will reject it. SleekXMPP thus leaves out the ``from`` attribute when replying | ||||
|     using a client connection. | ||||
|  | ||||
| Command Line Arguments and Logging | ||||
| ---------------------------------- | ||||
|  | ||||
| While this isn't part of SleekXMPP itself, we do want our echo bot program to be able | ||||
| to accept a JID and password from the command line instead of hard coding them. We will | ||||
| use the ``optparse`` module for this, though there are several alternative methods, including | ||||
| the newer ``argparse`` module. | ||||
|  | ||||
| We want to accept three parameters: the JID for the echo bot, its password, and a flag for | ||||
| displaying the debugging logs. We also want these to be optional parameters, since passing | ||||
| a password directly through the command line can be a security risk.  | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     if __name__ == '__main__': | ||||
|         optp = OptionParser() | ||||
|  | ||||
|         optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||
|                         action='store_const', dest='loglevel', | ||||
|                         const=logging.DEBUG, default=logging.INFO) | ||||
|         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() | ||||
|  | ||||
|         if opts.jid is None: | ||||
|             opts.jid = raw_input("Username: ") | ||||
|         if opts.password is None: | ||||
|             opts.password = getpass.getpass("Password: ") | ||||
|  | ||||
| Since we included a flag for enabling debugging logs, we need to configure the | ||||
| ``logging`` module to behave accordingly. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     if __name__ == '__main__': | ||||
|  | ||||
|         # .. option parsing from above .. | ||||
|  | ||||
|         logging.basicConfig(level=opts.loglevel, | ||||
|                             format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|  | ||||
| Connecting to the Server and Processing | ||||
| --------------------------------------- | ||||
| There are three steps remaining until our echo bot is complete: | ||||
|     1. We need to instantiate the bot. | ||||
|     2. The bot needs to connect to an XMPP server. | ||||
|     3. We have to instruct the bot to start running and processing messages. | ||||
|  | ||||
| Creating the bot is straightforward, but we can also perform some configuration | ||||
| at this stage. For example, let's say we want our bot to support `service discovery | ||||
| <http://xmpp.org/extensions/xep-0030.html>`_ and `pings <http://xmpp.org/extensions/xep-0199.html>`_: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     if __name__ == '__main__': | ||||
|  | ||||
|         # .. option parsing and logging steps from above | ||||
|  | ||||
|         xmpp = EchoBot(opts.jid, opts.password) | ||||
|         xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|         xmpp.register_plugin('xep_0199') # Ping | ||||
|  | ||||
| If the ``EchoBot`` class had a hard dependency on a plugin, we could register that plugin in | ||||
| the ``EchoBot.__init__`` method instead. | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     If you are using the OpenFire server, you will need to include an additional  | ||||
|     configuration step. OpenFire supports a different version of SSL than what | ||||
|     most servers and SleekXMPP support. | ||||
|  | ||||
|     .. code-block:: python | ||||
|      | ||||
|         import ssl | ||||
|         xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
| Now we're ready to connect and begin echoing messages. If you have the package | ||||
| ``dnspython`` installed, then the :meth:`sleekxmpp.clientxmpp.ClientXMPP` method | ||||
| will perform a DNS query to find the appropriate server to connect to for the | ||||
| given JID. If you do not have ``dnspython``, then SleekXMPP will attempt to  | ||||
| connect to the hostname used by the JID, unless an address tuple is supplied | ||||
| to :meth:`sleekxmpp.clientxmpp.ClientXMPP`.  | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     if __name__ == '__main__': | ||||
|  | ||||
|         # .. option parsing & echo bot configuration | ||||
|  | ||||
|         if xmpp.connect(): | ||||
|             xmpp.process(block=True) | ||||
|         else: | ||||
|             print('Unable to connect') | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     For Google Talk users withouth ``dnspython`` installed, the above code | ||||
|     should look like: | ||||
|  | ||||
|     .. code-block:: python | ||||
|  | ||||
|         if __name__ == '__main__': | ||||
|  | ||||
|             # .. option parsing & echo bot configuration | ||||
|  | ||||
|             if xmpp.connect(('talk.google.com', 5222)): | ||||
|                 xmpp.process(block=True) | ||||
|             else: | ||||
|                 print('Unable to connect') | ||||
|  | ||||
| To begin responding to messages, you'll see we called :meth:`sleekxmpp.basexmpp.BaseXMPP.process` | ||||
| which will start the event handling, send queue, and XML reader threads. It will also call | ||||
| the :meth:`sleekxmpp.plugins.base.base_plugin.post_init` method on all registered plugins. By | ||||
| passing ``block=True`` to :meth:`sleekxmpp.basexmpp.BaseXMPP.process` we are running the  | ||||
| main processing loop in the main thread of execution. The :meth:`sleekxmpp.basexmpp.BaseXMPP.process` | ||||
| call will not return until after SleekXMPP disconnects. If you need to run the client in the background | ||||
| for another program, use ``block=False`` to spawn the processing loop in its own thread. | ||||
|  | ||||
| .. note::  | ||||
|  | ||||
|     Before 1.0, controlling the blocking behaviour of :meth:`sleekxmpp.basexmpp.BaseXMPP.process` was | ||||
|     done via the ``threaded`` argument. This arrangement was a source of confusion because some users | ||||
|     interpreted that as controlling whether or not SleekXMPP used threads at all, instead of how | ||||
|     the processing loop itself was spawned. | ||||
|  | ||||
|     The statements ``xmpp.process(threaded=False)`` and ``xmpp.process(block=True)`` are equivalent. | ||||
|  | ||||
|  | ||||
| .. _echobot_complete: | ||||
|  | ||||
| The Final Product | ||||
| ----------------- | ||||
|  | ||||
| Here then is what the final result should look like after working through the guide above. The code | ||||
| can also be found in the SleekXMPP `examples directory <http://github.com/fritzy/SleekXMPP/tree/master/examples>`_. | ||||
|  | ||||
| .. compound:: | ||||
|  | ||||
|     You can run the code using: | ||||
|  | ||||
|     .. code-block:: sh | ||||
|  | ||||
|         python echobot.py -d -j echobot@example.com | ||||
|  | ||||
|     which will prompt for the password and then begin echoing messages. To test, open | ||||
|     your regular IM client and start a chat with the echo bot. Messages you send to it should | ||||
|     be mirrored back to you. Be careful if you are using the same JID for the echo bot that | ||||
|     you also have logged in with another IM client. Messages could be routed to your IM client instead | ||||
|     of the bot. | ||||
|  | ||||
| .. include:: ../../examples/echo_client.py | ||||
|     :literal: | ||||
							
								
								
									
										2
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Send/Receive IQ Stanzas | ||||
| ======================= | ||||
							
								
								
									
										2
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Mulit-User Chat (MUC) Bot | ||||
| ========================= | ||||
							
								
								
									
										2
									
								
								docs/getting_started/presence.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/presence.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Manage Presence Subscriptions | ||||
| ============================= | ||||
							
								
								
									
										2
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Enable HTTP Proxy Support | ||||
| ========================= | ||||
							
								
								
									
										2
									
								
								docs/getting_started/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Send a Message Every 5 Minutes | ||||
| ============================== | ||||
							
								
								
									
										94
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| Sign in, Send a Message, and Disconnect | ||||
| ======================================= | ||||
|  | ||||
| .. note:: | ||||
|      | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|  | ||||
| A common use case for SleekXMPP is to send one-off messages from | ||||
| time to time. For example, one use case could be sending out a notice when  | ||||
| a shell script finishes a task. | ||||
|  | ||||
| We will create our one-shot bot based on the pattern explained in :ref:`echobot`. To | ||||
| start, we create a client class based on :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` and | ||||
| register a handler for the :term:`session_start` event. We will also accept parameters | ||||
| for the JID that will receive our message, and the string content of the message. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import sleekxmpp | ||||
|  | ||||
|  | ||||
|     class SendMsgBot(sleekxmpp.ClientXMPP): | ||||
|          | ||||
|         def __init__(self, jid, password, recipient, msg): | ||||
|             super(SendMsgBot, self).__init__(jid, password) | ||||
|  | ||||
|             self.recipient = recipient | ||||
|             self.msg = msg | ||||
|  | ||||
|             self.add_event_handler('session_start', self.start) | ||||
|  | ||||
|         def start(self, event): | ||||
|             self.send_presence() | ||||
|             self.get_roster() | ||||
|  | ||||
| Note that as in :ref:`echobot`, we need to include send an initial presence and request | ||||
| the roster. Next, we want to send our message, and to do that we will use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         self.send_message(mto=self.recipient, mbody=self.msg) | ||||
|  | ||||
| Finally, we need to disconnect the client using :meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>`. | ||||
| Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call | ||||
| :meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible | ||||
| for the client to disconnect before the send queue is processed and the message is actually | ||||
| sent on the wire. To ensure that our message is processed, we use  | ||||
| :meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         self.send_message(mto=self.recipient, mbody=self.msg) | ||||
|  | ||||
|         self.disconnect(wait=True) | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     If you happen to be adding stanzas to the send queue faster than the send thread | ||||
|     can process them, then :meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>` | ||||
|     will block and not disconnect. | ||||
|  | ||||
| Final Product | ||||
| ------------- | ||||
|  | ||||
| .. compound:: | ||||
|  | ||||
|     The final step is to create a small runner script for initialising our ``SendMsgBot`` class and adding some | ||||
|     basic configuration options. By following the basic boilerplate pattern in :ref:`echobot`, we arrive | ||||
|     at the code below. To experiment with this example, you can use: | ||||
|  | ||||
|     .. code-block:: sh | ||||
|  | ||||
|             python send_client.py -d -j oneshot@example.com -t someone@example.net -m "This is a message" | ||||
|  | ||||
|     which will prompt for the password and then log in, send your message, and then disconnect. To test, open | ||||
|     your regular IM client with the account you wish to send messages to. When you run the ``send_client.py`` | ||||
|     example and instruct it to send your IM client account a message, you should receive the message you | ||||
|     gave. If the two JIDs you use also have a mutual presence subscription (they're on each other's buddy lists) | ||||
|     then you will also see the ``SendMsgBot`` client come online and then go offline. | ||||
|  | ||||
| .. include:: ../../examples/send_client.py | ||||
|     :literal: | ||||
							
								
								
									
										35
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| .. _glossary: | ||||
|  | ||||
| Glossary | ||||
| ======== | ||||
|  | ||||
| .. glossary:: | ||||
|     :sorted: | ||||
|  | ||||
|     stream handler | ||||
|         A callback function that accepts stanza objects pulled directly | ||||
|         from the XML stream. A stream handler is encapsulated in a | ||||
|         object that includes a :term:`Matcher` object, and which provides | ||||
|         additional semantics. For example, the ``Waiter`` handler wrapper | ||||
|         blocks thread execution until a matching stanza is received. | ||||
|  | ||||
|     event handler | ||||
|         A callback function that responds to events raised by | ||||
|         ``XMLStream.event``. An event handler may be marked as | ||||
|         threaded, allowing it to execute outside of the main processing | ||||
|         loop. | ||||
|  | ||||
|     stanza object | ||||
|         Informally may refer both to classes which extend ``ElementBase`` | ||||
|         or ``StanzaBase``, and to objects of such classes. | ||||
|  | ||||
|         A stanza object is a wrapper for an XML object which exposes ``dict`` | ||||
|         like interfaces which may be assigned to, read from, or deleted. | ||||
|  | ||||
|     stanza plugin | ||||
|         A :term:`stanza object` which has been registered as a potential child | ||||
|         of another stanza object. The plugin stanza may accessed through the | ||||
|         parent stanza using the plugin's ``plugin_attrib`` as an interface. | ||||
|  | ||||
|     substanza | ||||
|         See :term:`stanza plugin` | ||||
							
								
								
									
										201
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | ||||
| XEP-0030: Working with Service Discovery | ||||
| ======================================== | ||||
|  | ||||
| XMPP networks can be composed of many individual clients, components, | ||||
| and servers. Determining the JIDs for these entities and the various | ||||
| features they may support is the role of `XEP-0030, Service | ||||
| Discovery <http://xmpp.org/extensions/xep-0030.html>`_, or "disco" for short. | ||||
|  | ||||
| Every XMPP entity may possess what are called nodes. A node is just a name for | ||||
| some aspect of an XMPP entity. For example, if an XMPP entity provides `Ad-Hoc | ||||
| Commands <http://xmpp.org/extensions/xep-0050.html>`_, then it will have a node | ||||
| named ``http://jabber.org/protocol/commands`` which will contain information | ||||
| about the commands provided. Other agents using these ad-hoc commands will | ||||
| interact with the information provided by this node. Note that the node name is | ||||
| just an identifier; there is no inherent meaning. | ||||
|  | ||||
| Working with service discovery is about creating and querying these nodes. | ||||
| According to XEP-0030, a node may contain three types of information: | ||||
| identities, features, and items. (Further, extensible, information types are | ||||
| defined in `XEP-0128 <http://xmpp.org/extensions/xep-0128.html>`_, but they are | ||||
| not yet implemented by SleekXMPP.) SleekXMPP provides methods to configure each | ||||
| of these node attributes. | ||||
|  | ||||
| Configuring Service Discovery | ||||
| ----------------------------- | ||||
| The design focus for the XEP-0030 plug-in is handling info and items requests | ||||
| in a dynamic fashion, allowing for complex policy decisions of who may receive | ||||
| information and how much, or use alternate backend storage mechanisms for all | ||||
| of the disco data. To do this, each action that the XEP-0030 plug-in performs | ||||
| is handed off to what is called a "node handler," which is just a callback | ||||
| function. These handlers are arranged in a hierarchy that allows for a single | ||||
| handler to manage an entire domain of JIDs (say for a component), while allowing | ||||
| other handler functions to override that global behaviour for certain JIDs, or | ||||
| even further limited to only certain JID and node combinations. | ||||
|  | ||||
| The Dynamic Handler Hierarchy | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| * ``global``: (JID is None, node is None) | ||||
|  | ||||
|   Handlers assigned at this level for an action (such as ``add_feature``) provide a global default | ||||
|   behaviour when the action is performed. | ||||
|  | ||||
| * ``jid``: (JID assigned, node is None) | ||||
|  | ||||
|   At this level, handlers provide a default behaviour for actions affecting any node owned by the | ||||
|   JID in question. This level is most useful for component connections; there is effectively no | ||||
|   difference between this and the global level when using a client connection. | ||||
|  | ||||
| * ``node``: (JID assigned, node assigned) | ||||
|  | ||||
|   A handler for this level is responsible for carrying out an action for only one node, and is the | ||||
|   most specific handler type available. These types of handlers will be most useful for "special" | ||||
|   nodes that require special processing different than others provided by the JID, such as using | ||||
|   access control lists, or consolidating data from other nodes. | ||||
|  | ||||
| Default Static Handlers | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| The XEP-0030 plug-in provides a default set of handlers that work using in-memory | ||||
| disco stanzas. Each handler simply performs the appropriate lookup or storage | ||||
| operation using these stanzas without doing any complex operations such as | ||||
| checking an ACL, etc. | ||||
|  | ||||
| You may find it necessary at some point to revert a particular node or JID to | ||||
| using the default, static handlers. To do so, use the method ``make_static()``. | ||||
| You may also elect to only convert a given set of actions instead. | ||||
|  | ||||
| Creating a Node Handler | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| Every node handler receives three arguments: the JID, the node, and a data | ||||
| parameter that will contain the relevant information for carrying out the | ||||
| handler's action, typically a dictionary. | ||||
|  | ||||
| The JID will always have a value, defaulting to ``xmpp.boundjid.full`` for | ||||
| components or ``xmpp.boundjid.bare`` for clients. The node value may be None or | ||||
| a string. | ||||
|  | ||||
| Only handlers for the actions ``get_info`` and ``get_items`` need to have return | ||||
| values. For these actions, DiscoInfo or DiscoItems stanzas are exepected as | ||||
| output. It is also acceptable for handlers for these actions to generate an | ||||
| XMPPError exception when necessary. | ||||
|  | ||||
| Example Node Handler: | ||||
| +++++++++++++++++++++ | ||||
| Here is one of the built-in default handlers as an example: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def add_identity(self, jid, node, data): | ||||
|         """ | ||||
|         Add a new identity to the 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)) | ||||
|  | ||||
| Adding Identities, Features, and Items | ||||
| -------------------------------------- | ||||
| In order to maintain some backwards compatibility, the methods ``add_identity``, | ||||
| ``add_feature``, and ``add_item`` do not follow the method signature pattern of | ||||
| the other API methods (i.e. jid, node, then other options), but rather retain | ||||
| the parameter orders from previous plug-in versions. | ||||
|  | ||||
| Adding an Identity | ||||
| ~~~~~~~~~~~~~~~~~~ | ||||
| Adding an identity may be done using either the older positional notation, or | ||||
| with keyword parameters. The example below uses the keyword arguments, but in | ||||
| the same order as expected using positional arguments. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     xmpp['xep_0030'].add_identity(category='client', | ||||
|                                   itype='bot', | ||||
|                                   name='Sleek', | ||||
|                                   node='foo', | ||||
|                                   jid=xmpp.boundjid.full, | ||||
|                                   lang='no') | ||||
|  | ||||
| The JID and node values determine which handler will be used to perform the | ||||
| ``add_identity`` action. | ||||
|  | ||||
| The ``lang`` parameter allows for adding localized versions of identities using | ||||
| the ``xml:lang`` attribute. | ||||
|  | ||||
| Adding a Feature | ||||
| ~~~~~~~~~~~~~~~~ | ||||
| The position ordering for ``add_feature()`` is to include the feature, then | ||||
| specify the node and then the JID. The JID and node values determine which | ||||
| handler will be used to perform the ``add_feature`` action. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     xmpp['xep_0030'].add_feature(feature='jabber:x:data', | ||||
|                                  node='foo', | ||||
|                                  jid=xmpp.boundjid.full) | ||||
|  | ||||
| Adding an Item | ||||
| ~~~~~~~~~~~~~~ | ||||
| The parameters to ``add_item()`` are potentially confusing due to the fact that | ||||
| adding an item requires two JID and node combinations: the JID and node of the | ||||
| item itself, and the JID and node that will own the item. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     xmpp['xep_0030'].add_item(jid='myitemjid@example.com', | ||||
|                               name='An Item!', | ||||
|                               node='owner_node', | ||||
|                               subnode='item_node', | ||||
|                               ijid=xmpp.boundjid.full) | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     In this case, the owning JID and node are provided with the | ||||
|     parameters ``ijid`` and ``node``.  | ||||
|  | ||||
| Peforming Disco Queries | ||||
| ----------------------- | ||||
| The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs | ||||
| and their nodes for disco information. Since these methods are wrappers for | ||||
| sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()`` | ||||
| method. The ``get_items()`` method may also accept the boolean parameter | ||||
| ``iterator``, which when set to ``True`` will return an iterator object using | ||||
| the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     info = self['xep_0030'].get_info(jid='foo@example.com', | ||||
|                                      node='bar', | ||||
|                                      ifrom='baz@mycomponent.example.com', | ||||
|                                      block=True, | ||||
|                                      timeout=30) | ||||
|  | ||||
|     items = self['xep_0030'].get_info(jid='foo@example.com', | ||||
|                                       node='bar', | ||||
|                                       iterator=True) | ||||
|  | ||||
| For more examples on how to use basic disco queries, check the ``disco_browser.py`` | ||||
| example in the ``examples`` directory. | ||||
|  | ||||
| Local Queries | ||||
| ~~~~~~~~~~~~~ | ||||
| In some cases, it may be necessary to query the contents of a node owned by the | ||||
| client itself, or one of a component's many JIDs. The same method is used as for | ||||
| normal queries, with two differences. First, the parameter ``local=True`` must | ||||
| be used. Second, the return value will be a DiscoInfo or DiscoItems stanza, not | ||||
| a full Iq stanza. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     info = self['xep_0030'].get_info(node='foo', local=True) | ||||
|     items = self['xep_0030'].get_items(jid='somejid@mycomponent.example.com', | ||||
|                                        node='bar',  | ||||
|                                        local=True) | ||||
							
								
								
									
										2
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Using Stream Handlers and Matchers | ||||
| ================================== | ||||
							
								
								
									
										155
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | ||||
| SleekXMPP | ||||
| ######### | ||||
|  | ||||
| .. sidebar:: Get the Code | ||||
|  | ||||
|     .. code-block:: sh | ||||
|  | ||||
|         pip install sleekxmpp | ||||
|  | ||||
|     The latest source code for SleekXMPP may be found on `Github | ||||
|     <http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the | ||||
|     ``master`` branch, while the latest development version is in the | ||||
|     ``develop`` branch. | ||||
|  | ||||
|     **Stable Releases** | ||||
|         - `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_   | ||||
|         - `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_ | ||||
|         - `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_ | ||||
|         - `1.0 Beta3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta3>`_ | ||||
|         - `1.0 Beta2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta2>`_ | ||||
|         - `1.0 Beta1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta1>`_ | ||||
|  | ||||
|     **Develop Releases** | ||||
|         - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||
|  | ||||
|  | ||||
|     A mailing list and XMPP chat room are available for discussing and getting | ||||
|     help with SleekXMPP. | ||||
|  | ||||
|     **Mailing List** | ||||
|         `SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_ | ||||
|  | ||||
|     **Chat** | ||||
|         `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ | ||||
|  | ||||
|  | ||||
| SleekXMPP is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+, | ||||
| and is featured in examples in | ||||
| `XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_  | ||||
| by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived | ||||
| here from reading the Definitive Guide, please see the notes on updating | ||||
| the examples to the latest version of SleekXMPP. | ||||
|  | ||||
| SleekXMPP's design goals and philosphy are: | ||||
|  | ||||
| **Low number of dependencies** | ||||
|     Installing and using SleekXMPP should be as simple as possible, without | ||||
|     having to deal with long dependency chains. | ||||
|  | ||||
|     As part of reducing the number of dependencies, some third party | ||||
|     modules are included with SleekXMPP in the ``thirdparty`` directory. | ||||
|     Imports from this module first try to import an existing installed | ||||
|     version before loading the packaged version, when possible. | ||||
|  | ||||
| **Every XEP as a plugin** | ||||
|     Following Python's "batteries included" approach, the goal is to | ||||
|     provide support for all currently active XEPs (final and draft). Since | ||||
|     adding XEP support is done through easy to create plugins, the hope is | ||||
|     to also provide a solid base for implementing and creating experimental | ||||
|     XEPs. | ||||
|  | ||||
| **Rewarding to work with** | ||||
|     As much as possible, SleekXMPP should allow things to "just work" using | ||||
|     sensible defaults and appropriate abstractions. XML can be ugly to work | ||||
|     with, but it doesn't have to be that way. | ||||
|  | ||||
| Getting Started (with Examples) | ||||
| ------------------------------- | ||||
| .. toctree:: | ||||
|     :maxdepth: 1 | ||||
|      | ||||
|     getting_started/echobot | ||||
|     getting_started/sendlogout | ||||
|     getting_started/component | ||||
|     getting_started/presence | ||||
|     getting_started/muc | ||||
|     getting_started/proxy | ||||
|     getting_started/scheduler | ||||
|     getting_started/iq | ||||
|  | ||||
|  | ||||
| Tutorials, FAQs, and How To Guides | ||||
| ---------------------------------- | ||||
| .. toctree:: | ||||
|     :maxdepth: 1 | ||||
|     | ||||
|     xeps | ||||
|     xmpp_tdg | ||||
|     create_plugin | ||||
|     features | ||||
|     sasl | ||||
|     handlersmatchers | ||||
|  | ||||
| Plugin Guides | ||||
| ~~~~~~~~~~~~~ | ||||
| .. toctree::  | ||||
|     :maxdepth: 1 | ||||
|  | ||||
|     guide_xep_0030 | ||||
|  | ||||
| SleekXMPP Architecture and Design | ||||
| --------------------------------- | ||||
| .. toctree:: | ||||
|     :maxdepth: 3 | ||||
|  | ||||
|     architecture | ||||
|     plugin_arch | ||||
|  | ||||
| API Reference | ||||
| ------------- | ||||
| .. toctree:: | ||||
|     :maxdepth: 2 | ||||
|      | ||||
|     event_index | ||||
|     api/clientxmpp | ||||
|     api/basexmpp | ||||
|     api/xmlstream | ||||
|  | ||||
| Additional Info | ||||
| --------------- | ||||
| .. toctree:: | ||||
|     :hidden: | ||||
|  | ||||
|     glossary | ||||
|     license | ||||
|  | ||||
| * :ref:`license` | ||||
| * :ref:`glossary` | ||||
| * :ref:`genindex` | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
|  | ||||
| Credits | ||||
| ------- | ||||
| **Main Author:** Nathan Fritz | ||||
|     `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,  | ||||
|     `@fritzy <http://twitter.com/fritzy>`_ | ||||
|  | ||||
|     Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP | ||||
|     <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP | ||||
|     Council. | ||||
|  | ||||
| **Co-Author:** Lance Stout | ||||
|     `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,  | ||||
|     `@lancestout <http://twitter.com/lancestout>`_ | ||||
|  | ||||
| **Contributors:** | ||||
|     - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_) | ||||
|     - Dann Martens (`dannmartens <http://github.com/dannmartens>`_) | ||||
|     - Florent Le Coz (`louiz <http://github.com/louiz>`_) | ||||
|     - Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk) | ||||
|     - Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be) | ||||
|     - Te-jé Rogers (`te-je <http://github.com/te-je>`_) | ||||
|     - Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_) | ||||
|  | ||||
							
								
								
									
										5
									
								
								docs/license.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/license.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| .. _license:  | ||||
|  | ||||
| License (MIT) | ||||
| ============= | ||||
| .. include:: ../LICENSE | ||||
							
								
								
									
										170
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| @ECHO OFF | ||||
|  | ||||
| REM Command file for Sphinx documentation | ||||
|  | ||||
| if "%SPHINXBUILD%" == "" ( | ||||
| 	set SPHINXBUILD=sphinx-build | ||||
| ) | ||||
| set BUILDDIR=_build | ||||
| set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . | ||||
| if NOT "%PAPER%" == "" ( | ||||
| 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% | ||||
| ) | ||||
|  | ||||
| if "%1" == "" goto help | ||||
|  | ||||
| if "%1" == "help" ( | ||||
| 	:help | ||||
| 	echo.Please use `make ^<target^>` where ^<target^> is one of | ||||
| 	echo.  html       to make standalone HTML files | ||||
| 	echo.  dirhtml    to make HTML files named index.html in directories | ||||
| 	echo.  singlehtml to make a single large HTML file | ||||
| 	echo.  pickle     to make pickle files | ||||
| 	echo.  json       to make JSON files | ||||
| 	echo.  htmlhelp   to make HTML files and a HTML help project | ||||
| 	echo.  qthelp     to make HTML files and a qthelp project | ||||
| 	echo.  devhelp    to make HTML files and a Devhelp project | ||||
| 	echo.  epub       to make an epub | ||||
| 	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter | ||||
| 	echo.  text       to make text files | ||||
| 	echo.  man        to make manual pages | ||||
| 	echo.  changes    to make an overview over all changed/added/deprecated items | ||||
| 	echo.  linkcheck  to check all external links for integrity | ||||
| 	echo.  doctest    to run all doctests embedded in the documentation if enabled | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "clean" ( | ||||
| 	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i | ||||
| 	del /q /s %BUILDDIR%\* | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "html" ( | ||||
| 	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/html. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "dirhtml" ( | ||||
| 	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "singlehtml" ( | ||||
| 	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "pickle" ( | ||||
| 	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can process the pickle files. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "json" ( | ||||
| 	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can process the JSON files. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "htmlhelp" ( | ||||
| 	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can run HTML Help Workshop with the ^ | ||||
| .hhp project file in %BUILDDIR%/htmlhelp. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "qthelp" ( | ||||
| 	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; now you can run "qcollectiongenerator" with the ^ | ||||
| .qhcp project file in %BUILDDIR%/qthelp, like this: | ||||
| 	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SleekXMPP.qhcp | ||||
| 	echo.To view the help file: | ||||
| 	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SleekXMPP.ghc | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "devhelp" ( | ||||
| 	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "epub" ( | ||||
| 	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The epub file is in %BUILDDIR%/epub. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "latex" ( | ||||
| 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "text" ( | ||||
| 	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The text files are in %BUILDDIR%/text. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "man" ( | ||||
| 	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Build finished. The manual pages are in %BUILDDIR%/man. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "changes" ( | ||||
| 	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.The overview file is in %BUILDDIR%/changes. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "linkcheck" ( | ||||
| 	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Link check complete; look for any errors in the above output ^ | ||||
| or in %BUILDDIR%/linkcheck/output.txt. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| if "%1" == "doctest" ( | ||||
| 	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest | ||||
| 	if errorlevel 1 exit /b 1 | ||||
| 	echo. | ||||
| 	echo.Testing of doctests in the sources finished, look at the ^ | ||||
| results in %BUILDDIR%/doctest/output.txt. | ||||
| 	goto end | ||||
| ) | ||||
|  | ||||
| :end | ||||
							
								
								
									
										2
									
								
								docs/plugin_arch.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/plugin_arch.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Plugin Architecture | ||||
| =================== | ||||
							
								
								
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| How SASL Authentication Works | ||||
| ============================= | ||||
							
								
								
									
										2
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Supported XEPS | ||||
| ============== | ||||
							
								
								
									
										249
									
								
								docs/xmpp_tdg.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								docs/xmpp_tdg.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,249 @@ | ||||
| Following *XMPP: The Definitive Guide* | ||||
| ====================================== | ||||
|  | ||||
| SleekXMPP was featured in the first edition of the O'Reilly book  | ||||
| `XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271/>`_ | ||||
| by Peter Saint-Andre, Kevin Smith, and Remko Tronçon. The original source code | ||||
| for the book's examples can be found at http://github.com/remko/xmpp-tdg. An | ||||
| updated version of the source code, maintained to stay current with the latest | ||||
| SleekXMPP release, is available at http://github.com/legastero/xmpp-tdg. | ||||
|  | ||||
| However, since publication, SleekXMPP has advanced from version 0.2.1 to version | ||||
| 1.0 and there have been several major API changes. The most notable is the | ||||
| introduction of :term:`stanza objects <stanza object>` which have simplified and | ||||
| standardized interactions with the XMPP XML stream. | ||||
|  | ||||
| What follows is a walk-through of *The Definitive Guide* highlighting the | ||||
| changes needed to make the code examples work with version 1.0 of SleekXMPP. | ||||
| These changes have been kept to a minimum to preserve the correlation with | ||||
| the book's explanations, so be aware that some code may not use current best | ||||
| practices. | ||||
|  | ||||
| Example 2-2. (Page 26) | ||||
| ---------------------- | ||||
|  | ||||
| **Implementation of a basic bot that echoes all incoming messages back to its sender.** | ||||
|  | ||||
| The echo bot example requires a change to the ``handleIncomingMessage`` method | ||||
| to reflect the use of the ``Message`` :term:`stanza object`. The | ||||
| ``"jid"`` field of the message object should now be ``"from"`` to match the | ||||
| ``from`` attribute of the actual XML message stanza. Likewise, ``"message"`` | ||||
| changes to ``"body"`` to match the ``body`` element of the message stanza. | ||||
|  | ||||
| Updated Code | ||||
| ~~~~~~~~~~~~ | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def handleIncomingMessage(self, message): | ||||
|         self.xmpp.sendMessage(message["from"], message["body"]) | ||||
|  | ||||
| `View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_ | | ||||
| `View original code <http://github.com/remko/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_ | ||||
|  | ||||
| Example 14-1. (Page 215) | ||||
| ------------------------ | ||||
|  | ||||
| **CheshiR IM bot implementation.** | ||||
|  | ||||
| The main event handling method in the Bot class is meant to process both message | ||||
| events and presence update events. With the new changes in SleekXMPP 1.0, | ||||
| extracting a CheshiR status "message" from both types of stanzas | ||||
| requires accessing different attributes. In the case of a message stanza, the | ||||
| ``"body"`` attribute would contain the CheshiR message. For a presence event, | ||||
| the information is stored in the ``"status"`` attribute. To handle both cases, | ||||
| we can test the type of the given event object and look up the proper attribute | ||||
| based on the type. | ||||
|  | ||||
| Like in the EchoBot example, the expression ``event["jid"]`` needs to change | ||||
| to ``event["from"]`` in order to get a JID object for the stanza's sender. | ||||
| Because other functions in CheshiR assume that the JID is a string, the ``jid`` | ||||
| attribute is used to access the string version of the JID. A check is also added | ||||
| in case ``user`` is ``None``, but the check could (and probably should) be | ||||
| placed in ``addMessageFromUser``. | ||||
|  | ||||
| Another change is needed in ``handleMessageAddedToBackend`` where | ||||
| an HTML-IM response is created. The HTML content should be enclosed in a single | ||||
| element, such as a ``<p>`` tag. | ||||
|  | ||||
| Updated Code | ||||
| ~~~~~~~~~~~~ | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|   def handleIncomingXMPPEvent(self, event): | ||||
|     msgLocations = {sleekxmpp.stanza.presence.Presence: "status", | ||||
|                     sleekxmpp.stanza.message.Message: "body"} | ||||
|  | ||||
|     message = event[msgLocations[type(event)]] | ||||
|     user = self.backend.getUserFromJID(event["from"].jid) | ||||
|     if user is not None: | ||||
|       self.backend.addMessageFromUser(message, user) | ||||
|    | ||||
|   def handleMessageAddedToBackend(self, message) : | ||||
|     body = message.user + ": " + message.text | ||||
|     htmlBody = "<p><a href='%(uri)s'>%(user)s</a>: %(message)s</p>" % { | ||||
|       "uri": self.url + "/" + message.user, | ||||
|       "user" : message.user, "message" : message.text } | ||||
|     for subscriberJID in self.backend.getSubscriberJIDs(message.user) : | ||||
|       self.xmpp.sendMessage(subscriberJID, body, mhtml=htmlBody) | ||||
|  | ||||
| `View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_ | | ||||
| `View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_ | ||||
|  | ||||
|  | ||||
| Example 14-3. (Page 217) | ||||
| ------------------------ | ||||
| **Configurable CheshiR IM bot implementation.** | ||||
|  | ||||
| .. note:: | ||||
|     Since the CheshiR examples build on each other, see previous sections for | ||||
|     corrections to code that is not marked as new in the book example. | ||||
|  | ||||
| The main difference for the configurable IM bot is the handling for the | ||||
| data form in ``handleConfigurationCommand``. The test for equality | ||||
| with the string ``"1"`` is no longer required; SleekXMPP converts | ||||
| boolean data form fields to the values ``True`` and ``False`` | ||||
| automatically. | ||||
|  | ||||
| For the method ``handleIncomingXMPPPresence``, the attribute | ||||
| ``"jid"`` is again converted to ``"from"`` to get a JID | ||||
| object for the presence stanza's sender, and the ``jid`` attribute is | ||||
| used to access the string version of that JID object. A check is also added in | ||||
| case ``user`` is ``None``, but the check could (and probably | ||||
| should) be placed in ``getShouldMonitorPresenceFromUser``. | ||||
|  | ||||
| Updated Code | ||||
| ~~~~~~~~~~~~ | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|   def handleConfigurationCommand(self, form, sessionId): | ||||
|     values = form.getValues() | ||||
|     monitorPresence =values["monitorPresence"] | ||||
|     jid = self.xmpp.plugin["xep_0050"].sessions[sessionId]["jid"] | ||||
|     user = self.backend.getUserFromJID(jid) | ||||
|     self.backend.setShouldMonitorPresenceFromUser(user, monitorPresence) | ||||
|  | ||||
|   def handleIncomingXMPPPresence(self, event): | ||||
|     user = self.backend.getUserFromJID(event["from"].jid) | ||||
|     if user is not None: | ||||
|       if self.backend.getShouldMonitorPresenceFromUser(user): | ||||
|         self.handleIncomingXMPPEvent(event) | ||||
|  | ||||
| `View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_ | | ||||
| `View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_ | ||||
|  | ||||
|  | ||||
| Example 14-4. (Page 220) | ||||
| ------------------------ | ||||
| **CheshiR IM server component implementation.** | ||||
|  | ||||
| .. note:: | ||||
|     Since the CheshiR examples build on each other, see previous sections for | ||||
|     corrections to code that is not marked as new in the book example. | ||||
|  | ||||
| Like several previous examples, a needed change is to replace | ||||
| ``subscription["from"]`` with ``subscription["from"].jid`` because the | ||||
| ``BaseXMPP`` method ``makePresence`` requires the JID to be a string. | ||||
|  | ||||
| A correction needs to be made in ``handleXMPPPresenceProbe`` because a line was | ||||
| left out of the original implementation; the variable ``user`` is undefined. The | ||||
| JID of the user can be extracted from the presence stanza's ``from`` attribute. | ||||
|  | ||||
| Since this implementation of CheshiR uses an XMPP component, it must | ||||
| include a ``from`` attribute in all messages that it sends. Adding the | ||||
| ``from`` attribute is done by including ``mfrom=self.xmpp.jid`` in calls to | ||||
| ``self.xmpp.sendMessage``. | ||||
|  | ||||
| Updated Code | ||||
| ~~~~~~~~~~~~ | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|   def handleXMPPPresenceProbe(self, event) : | ||||
|     self.xmpp.sendPresence(pto = event["from"]) | ||||
|  | ||||
|   def handleXMPPPresenceSubscription(self, subscription) : | ||||
|     if subscription["type"] == "subscribe" : | ||||
|       userJID = subscription["from"].jid | ||||
|       self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribed") | ||||
|       self.xmpp.sendPresence(pto = userJID) | ||||
|       self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribe") | ||||
|  | ||||
|   def handleMessageAddedToBackend(self, message) : | ||||
|     body = message.user + ": " + message.text | ||||
|     for subscriberJID in self.backend.getSubscriberJIDs(message.user) : | ||||
|       self.xmpp.sendMessage(subscriberJID, body, mfrom=self.xmpp.jid) | ||||
|  | ||||
| `View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_ | | ||||
| `View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_ | ||||
|  | ||||
|  | ||||
| Example 14-6. (Page 223) | ||||
| ------------------------ | ||||
| **CheshiR IM server component with in-band registration support.** | ||||
|  | ||||
| .. note:: | ||||
|     Since the CheshiR examples build on each other, see previous sections for | ||||
|     corrections to code that is not marked as new in the book example. | ||||
|  | ||||
| After applying the changes from Example 14-4 above, the registrable component | ||||
| implementation should work correctly. | ||||
|  | ||||
| .. tip:: | ||||
|     To see how to implement in-band registration as a SleekXMPP plugin, | ||||
|     see the tutorial :ref:`tutorial-create-plugin`. | ||||
|  | ||||
| `View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_ | | ||||
| `View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_ | ||||
|  | ||||
| Example 14-7. (Page 225) | ||||
| ------------------------ | ||||
| **Extended CheshiR IM server component implementation.** | ||||
|  | ||||
| .. note:: | ||||
|     Since the CheshiR examples build on each other, see previous  | ||||
|     sections for corrections to code that is not marked as new in the book | ||||
|     example. | ||||
|  | ||||
| While the final code example can look daunting with all of the changes | ||||
| made, it requires very few modifications to work with the latest version of | ||||
| SleekXMPP. Most differences are the result of CheshiR's backend functions | ||||
| expecting JIDs to be strings so that they can be stripped to bare JIDs. To | ||||
| resolve these, use the ``jid`` attribute of the JID objects. Also, | ||||
| references to ``"message"`` and ``"jid"`` attributes need to | ||||
| be changed to either ``"body"`` or ``"status"``, and either | ||||
| ``"from"`` or ``"to"`` depending on if the object is a message | ||||
| or presence stanza and which of the JIDs from the stanza is needed. | ||||
|  | ||||
| Updated Code | ||||
| ~~~~~~~~~~~~ | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|   def handleIncomingXMPPMessage(self, event) : | ||||
|     message = self.addRecipientToMessage(event["body"], event["to"].jid) | ||||
|     user = self.backend.getUserFromJID(event["from"].jid) | ||||
|     self.backend.addMessageFromUser(message, user) | ||||
|  | ||||
|   def handleIncomingXMPPPresence(self, event) : | ||||
|     if event["to"].jid == self.componentDomain : | ||||
|       user = self.backend.getUserFromJID(event["from"].jid) | ||||
|       self.backend.addMessageFromUser(event["status"], user) | ||||
|  | ||||
|   ... | ||||
|  | ||||
|   def handleXMPPPresenceSubscription(self, subscription) : | ||||
|     if subscription["type"] == "subscribe" : | ||||
|       userJID = subscription["from"].jid | ||||
|       user = self.backend.getUserFromJID(userJID) | ||||
|       contactJID = subscription["to"] | ||||
|       self.xmpp.sendPresenceSubscription( | ||||
|           pfrom=contactJID, pto=userJID, ptype="subscribed", pnick=user) | ||||
|       self.sendPresenceOfContactToUser(contactJID=contactJID, userJID=userJID) | ||||
|       if contactJID == self.componentDomain : | ||||
|         self.sendAllContactSubscriptionRequestsToUser(userJID) | ||||
|  | ||||
| `View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_ | | ||||
| `View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_  | ||||
| @@ -24,6 +24,8 @@ import sleekxmpp | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class CommandBot(sleekxmpp.ClientXMPP): | ||||
| @@ -79,6 +81,7 @@ class CommandBot(sleekxmpp.ClientXMPP): | ||||
|                        here to persist across handler callbacks. | ||||
|         """ | ||||
|         form = self['xep_0004'].makeForm('form', 'Greeting') | ||||
|         form['instructions'] = 'Send a custom greeting to a JID' | ||||
|         form.addField(var='greeting', | ||||
|                       ftype='text-single', | ||||
|                       label='Your greeting') | ||||
| @@ -123,8 +126,10 @@ class CommandBot(sleekxmpp.ClientXMPP): | ||||
|         form = payload | ||||
|  | ||||
|         greeting = form['values']['greeting'] | ||||
|  | ||||
|         self.send_message(mto=session['from'], | ||||
|                           mbody="%s, World!" % greeting) | ||||
|                           mbody="%s, World!" % greeting, | ||||
|                           mtype='chat') | ||||
|  | ||||
|         # Having no return statement is the same as unsetting the 'payload' | ||||
|         # and 'next' session values and returning the session. | ||||
| @@ -176,6 +181,7 @@ if __name__ == '__main__': | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands | ||||
|     xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency':15}) | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|   | ||||
| @@ -24,6 +24,8 @@ import sleekxmpp | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class CommandUserBot(sleekxmpp.ClientXMPP): | ||||
| @@ -136,6 +138,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP): | ||||
|         # The session will automatically be cleared if no error | ||||
|         # handler is provided. | ||||
|         self['xep_0050'].terminate_command(session) | ||||
|         self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| @@ -176,7 +179,7 @@ if __name__ == '__main__': | ||||
|     if opts.other is None: | ||||
|         opts.other = raw_input("JID Providing Commands: ") | ||||
|     if opts.greeting is None: | ||||
|         opts.other = raw_input("Greeting: ") | ||||
|         opts.greeting = raw_input("Greeting: ") | ||||
|  | ||||
|     # Setup the CommandBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|   | ||||
| @@ -27,6 +27,8 @@ from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class Config(ElementBase): | ||||
|   | ||||
| @@ -25,6 +25,8 @@ import sleekxmpp | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class Disco(sleekxmpp.ClientXMPP): | ||||
|   | ||||
| @@ -24,6 +24,8 @@ import sleekxmpp | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class EchoBot(sleekxmpp.ClientXMPP): | ||||
| @@ -76,7 +78,8 @@ class EchoBot(sleekxmpp.ClientXMPP): | ||||
|                    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 msg['type'] in ('chat', 'normal'): | ||||
|             msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   | ||||
| @@ -23,6 +23,8 @@ import sleekxmpp | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class MUCBot(sleekxmpp.ClientXMPP): | ||||
| @@ -59,7 +61,7 @@ class MUCBot(sleekxmpp.ClientXMPP): | ||||
|         # 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): | ||||
|         """ | ||||
| @@ -76,15 +78,15 @@ class MUCBot(sleekxmpp.ClientXMPP): | ||||
|         """ | ||||
|         self.getRoster() | ||||
|         self.sendPresence() | ||||
|         self.plugin['xep_0045'].joinMUC(self.room,  | ||||
|                                         self.nick,  | ||||
|         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  | ||||
|         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. | ||||
| @@ -96,7 +98,7 @@ class MUCBot(sleekxmpp.ClientXMPP): | ||||
|                    otherwise you will create an infinite loop responding | ||||
|                    to your own messages. | ||||
|  | ||||
|         This handler will reply to messages that mention  | ||||
|         This handler will reply to messages that mention | ||||
|         the bot's nickname. | ||||
|  | ||||
|         Arguments: | ||||
| @@ -112,12 +114,12 @@ class MUCBot(sleekxmpp.ClientXMPP): | ||||
|     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  | ||||
|         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  | ||||
|             presence -- The received presence stanza. See the | ||||
|                         documentation for the Presence stanza | ||||
|                         to see how else it may be used. | ||||
|         """ | ||||
|   | ||||
| @@ -24,6 +24,8 @@ import sleekxmpp | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class PingTest(sleekxmpp.ClientXMPP): | ||||
|   | ||||
							
								
								
									
										169
									
								
								examples/proxy_echo_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										169
									
								
								examples/proxy_echo_client.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| #!/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') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| 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.") | ||||
							
								
								
									
										173
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     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 sys | ||||
| import time | ||||
| import logging | ||||
| import getpass | ||||
| import threading | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
| # 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') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class RosterBrowser(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic script for dumping a client's roster to | ||||
|     the command line. | ||||
|     """ | ||||
|  | ||||
|     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. We need threaded=True so that the | ||||
|         # session_start handler doesn't block event processing | ||||
|         # while we wait for presence stanzas to arrive. | ||||
|         self.add_event_handler("session_start", self.start, threaded=True) | ||||
|         self.add_event_handler("changed_status", self.wait_for_presences) | ||||
|  | ||||
|         self.received = set() | ||||
|         self.presences_received = threading.Event() | ||||
|  | ||||
|     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. | ||||
|         """ | ||||
|         try: | ||||
|             self.get_roster() | ||||
|         except IqError as err: | ||||
|             print('Error: %' % err.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
|             print('Error: Request timed out') | ||||
|         self.send_presence() | ||||
|  | ||||
|  | ||||
|         print('Waiting for presence updates...\n') | ||||
|         self.presences_received.wait(5) | ||||
|  | ||||
|         print('Roster for %s' % self.boundjid.bare) | ||||
|         groups = self.client_roster.groups() | ||||
|         for group in groups: | ||||
|             print('\n%s' % group) | ||||
|             print('-' * 72) | ||||
|             for jid in groups[group]: | ||||
|                 sub = self.client_roster[jid]['subscription'] | ||||
|                 name = self.client_roster[jid]['name'] | ||||
|                 if self.client_roster[jid]['name']: | ||||
|                     print(' %s (%s) [%s]' % (name, jid, sub)) | ||||
|                 else: | ||||
|                     print(' %s [%s]' % (jid, sub)) | ||||
|  | ||||
|                 connections = self.client_roster.presence(jid) | ||||
|                 for res, pres in connections.items(): | ||||
|                     show = 'available' | ||||
|                     if pres['show']: | ||||
|                         show = pres['show'] | ||||
|                     print('   - %s (%s)' % (res, show)) | ||||
|                     if pres['status']: | ||||
|                         print('       %s' % pres['status']) | ||||
|  | ||||
|         self.disconnect() | ||||
|  | ||||
|     def wait_for_presences(self, pres): | ||||
|         """ | ||||
|         Track how many roster entries have received presence updates. | ||||
|         """ | ||||
|         self.received.add(pres['from'].bare) | ||||
|         if len(self.received) >= len(self.client_roster.keys()): | ||||
|             self.presences_received.set() | ||||
|         else: | ||||
|             self.presences_received.clear() | ||||
|  | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|     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 opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|  | ||||
|     xmpp = RosterBrowser(opts.jid, opts.password) | ||||
|  | ||||
|     # 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.") | ||||
|  | ||||
							
								
								
									
										144
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										144
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| #!/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') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class SendMsgBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic SleekXMPP bot that will log in, send a message, | ||||
|     and then log out. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, recipient, message): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         # The message we wish to send, and the JID that | ||||
|         # will receive it. | ||||
|         self.recipient = recipient | ||||
|         self.msg = message | ||||
|  | ||||
|         # 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() | ||||
|  | ||||
|         self.send_message(mto=self.recipient, | ||||
|                           mbody=self.msg, | ||||
|                           mtype='chat') | ||||
|  | ||||
|         # Using wait=True ensures that the send queue will be | ||||
|         # emptied before ending the session. | ||||
|         self.disconnect(wait=True) | ||||
|  | ||||
|  | ||||
| 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("-t", "--to", dest="to", | ||||
|                     help="JID to send the message to") | ||||
|     optp.add_option("-m", "--message", dest="message", | ||||
|                     help="message to send") | ||||
|  | ||||
|     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.to is None: | ||||
|         opts.to = raw_input("Send To: ") | ||||
|     if opts.message is None: | ||||
|         opts.message = raw_input("Message: ") | ||||
|  | ||||
|     # Setup the EchoBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = SendMsgBot(opts.jid, opts.password, opts.to, opts.message) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     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.") | ||||
							
								
								
									
										179
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										179
									
								
								setup.py
									
									
									
									
									
								
							| @@ -1,82 +1,97 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2007-2008 Nathanael C. Fritz | ||||
| # All Rights Reserved | ||||
| # | ||||
| # This software is licensed as described in the README file, | ||||
| # which you should have received as part of this distribution. | ||||
| # | ||||
|  | ||||
| # from ez_setup import use_setuptools | ||||
| from distutils.core import setup | ||||
| import sys | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| # if 'cygwin' in sys.platform.lower(): | ||||
| #     min_version = '0.6c6' | ||||
| # else: | ||||
| #     min_version = '0.6a9' | ||||
| # | ||||
| # try: | ||||
| #     use_setuptools(min_version=min_version) | ||||
| # except TypeError: | ||||
| #     # locally installed ez_setup won't have min_version | ||||
| #     use_setuptools() | ||||
| # | ||||
| # from setuptools import setup, find_packages, Extension, Feature | ||||
|  | ||||
| VERSION          = sleekxmpp.__version__ | ||||
| DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' | ||||
| LONG_DESCRIPTION = """ | ||||
| SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc). | ||||
| """ | ||||
|  | ||||
| CLASSIFIERS      = [ 'Intended Audience :: Developers', | ||||
|                      'License :: OSI Approved :: MIT', | ||||
|                      'Programming Language :: Python', | ||||
|                      'Topic :: Software Development :: Libraries :: Python Modules', | ||||
|                    ] | ||||
|  | ||||
| packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/stanza', | ||||
|                  'sleekxmpp/test', | ||||
|                  'sleekxmpp/xmlstream', | ||||
|                  'sleekxmpp/xmlstream/matcher', | ||||
|                  'sleekxmpp/xmlstream/handler', | ||||
|                  'sleekxmpp/thirdparty', | ||||
|                  'sleekxmpp/plugins', | ||||
|                  'sleekxmpp/plugins/xep_0009', | ||||
|                  'sleekxmpp/plugins/xep_0009/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0030', | ||||
|                  'sleekxmpp/plugins/xep_0030/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0050', | ||||
|                  'sleekxmpp/plugins/xep_0059', | ||||
|                  'sleekxmpp/plugins/xep_0085', | ||||
|                  'sleekxmpp/plugins/xep_0086', | ||||
|                  'sleekxmpp/plugins/xep_0092', | ||||
|                  'sleekxmpp/plugins/xep_0128', | ||||
|                  'sleekxmpp/plugins/xep_0199', | ||||
|                  ] | ||||
|  | ||||
| if sys.version_info < (3, 0): | ||||
|     py_modules = ['sleekxmpp.xmlstream.tostring.tostring26'] | ||||
| else: | ||||
|     py_modules = ['sleekxmpp.xmlstream.tostring.tostring'] | ||||
|  | ||||
| setup( | ||||
|     name             = "sleekxmpp", | ||||
|     version          = VERSION, | ||||
|     description      = DESCRIPTION, | ||||
|     long_description = LONG_DESCRIPTION, | ||||
|     author       = 'Nathanael Fritz', | ||||
|     author_email = 'fritzy [at] netflint.net', | ||||
|     url          = 'http://code.google.com/p/sleekxmpp', | ||||
|     license      = 'MIT', | ||||
|     platforms    = [ 'any' ], | ||||
|     packages     = packages, | ||||
|     py_modules   = py_modules, | ||||
|     requires     = [ 'tlslite', 'pythondns' ], | ||||
|     ) | ||||
|  | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # Copyright (C) 2007-2011 Nathanael C. Fritz | ||||
| # All Rights Reserved | ||||
| # | ||||
| # This software is licensed as described in the README file, | ||||
| # which you should have received as part of this distribution. | ||||
| # | ||||
|  | ||||
| # from ez_setup import use_setuptools | ||||
| from distutils.core import setup | ||||
| import sys | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| # if 'cygwin' in sys.platform.lower(): | ||||
| #     min_version = '0.6c6' | ||||
| # else: | ||||
| #     min_version = '0.6a9' | ||||
| # | ||||
| # try: | ||||
| #     use_setuptools(min_version=min_version) | ||||
| # except TypeError: | ||||
| #     # locally installed ez_setup won't have min_version | ||||
| #     use_setuptools() | ||||
| # | ||||
| # from setuptools import setup, find_packages, Extension, Feature | ||||
|  | ||||
| VERSION          = sleekxmpp.__version__ | ||||
| DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' | ||||
| with open('README.rst') as readme: | ||||
|     LONG_DESCRIPTION = '\n'.join(readme) | ||||
|  | ||||
| CLASSIFIERS      = [ 'Intended Audience :: Developers', | ||||
|                      'License :: OSI Approved :: MIT License', | ||||
|                      'Programming Language :: Python', | ||||
|                      'Programming Language :: Python 2.6', | ||||
|                      'Programming Language :: Python 2.7', | ||||
|                      'Programming Language :: Python 3.1', | ||||
|                      'Programming Language :: Python 3.2', | ||||
|                      'Topic :: Software Development :: Libraries :: Python Modules', | ||||
|                    ] | ||||
|  | ||||
| packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/stanza', | ||||
|                  'sleekxmpp/test', | ||||
|                  'sleekxmpp/roster', | ||||
|                  'sleekxmpp/xmlstream', | ||||
|                  'sleekxmpp/xmlstream/matcher', | ||||
|                  'sleekxmpp/xmlstream/handler', | ||||
|                  'sleekxmpp/plugins', | ||||
|                  'sleekxmpp/plugins/xep_0004', | ||||
|                  'sleekxmpp/plugins/xep_0004/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0009', | ||||
|                  'sleekxmpp/plugins/xep_0009/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0030', | ||||
|                  'sleekxmpp/plugins/xep_0030/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0050', | ||||
|                  'sleekxmpp/plugins/xep_0059', | ||||
|                  'sleekxmpp/plugins/xep_0060', | ||||
|                  'sleekxmpp/plugins/xep_0060/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0066', | ||||
|                  'sleekxmpp/plugins/xep_0078', | ||||
|                  '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( | ||||
|     name             = "sleekxmpp", | ||||
|     version          = VERSION, | ||||
|     description      = DESCRIPTION, | ||||
|     long_description = LONG_DESCRIPTION, | ||||
|     author       = 'Nathanael Fritz', | ||||
|     author_email = 'fritzy [at] netflint.net', | ||||
|     url          = 'http://github.com/fritzy/SleekXMPP', | ||||
|     license      = 'MIT', | ||||
|     platforms    = [ 'any' ], | ||||
|     packages     = packages, | ||||
|     requires     = [ 'tlslite', 'pythondns' ], | ||||
| ) | ||||
|   | ||||
| @@ -15,5 +15,5 @@ from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET | ||||
|  | ||||
| __version__ = '1.0beta5' | ||||
| __version_info__ = (1, 0, 0, 'beta5', 0) | ||||
| __version__ = '1.0rc1' | ||||
| __version_info__ = (1, 0, 0, 'rc1', 0) | ||||
|   | ||||
| @@ -13,7 +13,8 @@ import copy | ||||
| import logging | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp import plugins | ||||
| from sleekxmpp import plugins, roster | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
| from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError | ||||
| from sleekxmpp.stanza.roster import Roster | ||||
| @@ -78,7 +79,7 @@ class BaseXMPP(XMLStream): | ||||
|        send_presence_subscribe -- Send a subscription request. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, default_ns='jabber:client'): | ||||
|     def __init__(self, jid='', default_ns='jabber:client'): | ||||
|         """ | ||||
|         Adapt an XML stream for use with XMPP. | ||||
|  | ||||
| @@ -92,19 +93,26 @@ class BaseXMPP(XMLStream): | ||||
|         # Deprecated method names are re-mapped for backwards compatibility. | ||||
|         self.default_ns = default_ns | ||||
|         self.stream_ns = 'http://etherx.jabber.org/streams' | ||||
|         self.namespace_map[self.stream_ns] = 'stream' | ||||
|  | ||||
|         self.boundjid = JID("") | ||||
|         self.boundjid = JID(jid) | ||||
|  | ||||
|         self.plugin = {} | ||||
|         self.plugin_config = {} | ||||
|         self.plugin_whitelist = [] | ||||
|         self.roster = {} | ||||
|  | ||||
|         self.roster = roster.Roster(self) | ||||
|         self.roster.add(self.boundjid.bare) | ||||
|         self.client_roster = self.roster[self.boundjid.bare] | ||||
|  | ||||
|         self.is_component = False | ||||
|         self.auto_authorize = True | ||||
|         self.auto_subscribe = True | ||||
|  | ||||
|         self.sentpresence = False | ||||
|  | ||||
|         self.stanza = sleekxmpp.stanza | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('IM', | ||||
|                      MatchXPath('{%s}message/{%s}body' % (self.default_ns, | ||||
| @@ -119,10 +127,30 @@ class BaseXMPP(XMLStream): | ||||
|                      MatchXPath("{%s}error" % self.stream_ns), | ||||
|                      self._handle_stream_error)) | ||||
|  | ||||
|         self.add_event_handler('presence_subscribe', | ||||
|                                self._handle_subscribe) | ||||
|         self.add_event_handler('disconnected', | ||||
|                                self._handle_disconnected) | ||||
|         self.add_event_handler('presence_available', | ||||
|                                self._handle_available) | ||||
|         self.add_event_handler('presence_dnd', | ||||
|                                self._handle_available) | ||||
|         self.add_event_handler('presence_xa', | ||||
|                                self._handle_available) | ||||
|         self.add_event_handler('presence_chat', | ||||
|                                self._handle_available) | ||||
|         self.add_event_handler('presence_away', | ||||
|                                self._handle_available) | ||||
|         self.add_event_handler('presence_unavailable', | ||||
|                                self._handle_unavailable) | ||||
|         self.add_event_handler('presence_subscribe', | ||||
|                                self._handle_subscribe) | ||||
|         self.add_event_handler('presence_subscribed', | ||||
|                                self._handle_subscribed) | ||||
|         self.add_event_handler('presence_unsubscribe', | ||||
|                                self._handle_unsubscribe) | ||||
|         self.add_event_handler('presence_unsubscribed', | ||||
|                                self._handle_unsubscribed) | ||||
|         self.add_event_handler('roster_subscription_request', | ||||
|                                self._handle_new_subscription) | ||||
|  | ||||
|         # Set up the XML stream with XMPP's root stanzas. | ||||
|         self.register_stanza(Message) | ||||
| @@ -135,12 +163,41 @@ class BaseXMPP(XMLStream): | ||||
|         register_stanza_plugin(Message, Nick) | ||||
|         register_stanza_plugin(Message, HTMLIM) | ||||
|  | ||||
|     def start_stream_handler(self, xml): | ||||
|         """ | ||||
|         Save the stream ID once the streams have been established. | ||||
|  | ||||
|         Overrides XMLStream.start_stream_handler. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The incoming stream's root element. | ||||
|         """ | ||||
|         self.stream_id = xml.get('id', '') | ||||
|  | ||||
|     def process(self, *args, **kwargs): | ||||
|         """ | ||||
|         Ensure that plugin inter-dependencies are handled before starting | ||||
|         event processing. | ||||
|  | ||||
|         Overrides XMLStream.process. | ||||
|  | ||||
|         Initialize the XML streams and begin processing events. | ||||
|  | ||||
|         The number of threads used for processing stream events is determined | ||||
|         by HANDLER_THREADS. | ||||
|  | ||||
|         Arguments: | ||||
|             block -- If block=False then event dispatcher will run | ||||
|                      in a separate thread, allowing for the stream to be | ||||
|                      used in the background for another application. | ||||
|                      Otherwise, process(block=True) blocks the current thread. | ||||
|                      Defaults to False. | ||||
|  | ||||
|             **threaded is deprecated and included for API compatibility** | ||||
|             threaded -- If threaded=True then event dispatcher will run | ||||
|                         in a separate thread, allowing for the stream to be | ||||
|                         used in the background for another application. | ||||
|                         Defaults to True. | ||||
|  | ||||
|             Event handlers and the send queue will be threaded | ||||
|             regardless of these parameters. | ||||
|         """ | ||||
|         for name in self.plugin: | ||||
|             if not self.plugin[name].post_inited: | ||||
| @@ -162,23 +219,36 @@ class BaseXMPP(XMLStream): | ||||
|         try: | ||||
|             # Import the given module that contains the plugin. | ||||
|             if not module: | ||||
|                 module = sleekxmpp.plugins | ||||
|                 module = __import__("%s.%s" % (module.__name__, plugin), | ||||
|                                     globals(), locals(), [plugin]) | ||||
|                 try: | ||||
|                     module = sleekxmpp.plugins | ||||
|                     module = __import__( | ||||
|                             str("%s.%s" % (module.__name__, plugin)), | ||||
|                             globals(), locals(), [str(plugin)]) | ||||
|                 except ImportError: | ||||
|                     module = sleekxmpp.features | ||||
|                     module = __import__( | ||||
|                             str("%s.%s" % (module.__name__, plugin)), | ||||
|                             globals(), locals(), [str(plugin)]) | ||||
|             if isinstance(module, str): | ||||
|                 # We probably want to load a module from outside | ||||
|                 # the sleekxmpp package, so leave out the globals(). | ||||
|                 module = __import__(module, fromlist=[plugin]) | ||||
|  | ||||
|             # Use the global plugin config cache, if applicable | ||||
|             if not pconfig: | ||||
|                 pconfig = self.plugin_config.get(plugin, {}) | ||||
|  | ||||
|             # Load the plugin class from the module. | ||||
|             self.plugin[plugin] = getattr(module, plugin)(self, pconfig) | ||||
|  | ||||
|             # Let XEP implementing plugins have some extra logging info. | ||||
|             xep = '' | ||||
|             if hasattr(self.plugin[plugin], 'xep'): | ||||
|                 xep = "(XEP-%s) " % self.plugin[plugin].xep | ||||
|             # Let XEP/RFC implementing plugins have some extra logging info. | ||||
|             spec = '(CUSTOM) ' | ||||
|             if self.plugin[plugin].xep: | ||||
|                 spec = "(XEP-%s) " % self.plugin[plugin].xep | ||||
|             elif self.plugin[plugin].rfc: | ||||
|                 spec = "(RFC-%s) " % self.plugin[plugin].rfc | ||||
|  | ||||
|             desc = (xep, self.plugin[plugin].description) | ||||
|             desc = (spec, self.plugin[plugin].description) | ||||
|             log.debug("Loaded Plugin %s%s" % desc) | ||||
|         except: | ||||
|             log.exception("Unable to load plugin: %s", plugin) | ||||
| @@ -401,7 +471,7 @@ class BaseXMPP(XMLStream): | ||||
|             msubject -- Optional subject for the message. | ||||
|             mtype    -- The message's type, such as 'chat' or 'groupchat'. | ||||
|             mhtml    -- Optional HTML body content. | ||||
|             mfrom    -- The sender of the message. If sending from a client, | ||||
|             mfrom    -- The sender of the message. if sending from a client, | ||||
|                         be aware that some servers require that the full JID | ||||
|                         of the sender be used. | ||||
|             mnick    -- Optional nickname of the sender. | ||||
| @@ -416,7 +486,7 @@ class BaseXMPP(XMLStream): | ||||
|         return message | ||||
|  | ||||
|     def make_presence(self, pshow=None, pstatus=None, ppriority=None, | ||||
|                       pto=None, ptype=None, pfrom=None): | ||||
|                       pto=None, ptype=None, pfrom=None, pnick=None): | ||||
|         """ | ||||
|         Create and initialize a new Presence stanza. | ||||
|  | ||||
| @@ -427,14 +497,16 @@ class BaseXMPP(XMLStream): | ||||
|             pto       -- The recipient of a directed presence. | ||||
|             ptype     -- The type of presence, such as 'subscribe'. | ||||
|             pfrom     -- The sender of the presence. | ||||
|             pnick     -- Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) | ||||
|         if pshow is not None: | ||||
|             presence['type'] = pshow | ||||
|         if pfrom is None: | ||||
|         if pfrom is None and self.is_component: | ||||
|             presence['from'] = self.boundjid.full | ||||
|         presence['priority'] = ppriority | ||||
|         presence['status'] = pstatus | ||||
|         presence['nick'] = pnick | ||||
|         return presence | ||||
|  | ||||
|     def send_message(self, mto, mbody, msubject=None, mtype=None, | ||||
| @@ -442,13 +514,22 @@ class BaseXMPP(XMLStream): | ||||
|         """ | ||||
|         Create, initialize, and send a Message stanza. | ||||
|  | ||||
|  | ||||
|         Arguments: | ||||
|             mto      -- The recipient of the message. | ||||
|             mbody    -- The main contents of the message. | ||||
|             msubject -- Optional subject for the message. | ||||
|             mtype    -- The message's type, such as 'chat' or 'groupchat'. | ||||
|             mhtml    -- Optional HTML body content. | ||||
|             mfrom    -- The sender of the message. if sending from a client, | ||||
|                         be aware that some servers require that the full JID | ||||
|                         of the sender be used. | ||||
|             mnick    -- Optional nickname of the sender. | ||||
|         """ | ||||
|         self.makeMessage(mto, mbody, msubject, mtype, | ||||
|                          mhtml, mfrom, mnick).send() | ||||
|         self.make_message(mto, mbody, msubject, mtype, | ||||
|                           mhtml, mfrom, mnick).send() | ||||
|  | ||||
|     def send_presence(self, pshow=None, pstatus=None, ppriority=None, | ||||
|                       pto=None, pfrom=None, ptype=None): | ||||
|                       pto=None, pfrom=None, ptype=None, pnick=None): | ||||
|         """ | ||||
|         Create, initialize, and send a Presence stanza. | ||||
|  | ||||
| @@ -459,13 +540,20 @@ class BaseXMPP(XMLStream): | ||||
|             pto       -- The recipient of a directed presence. | ||||
|             ptype     -- The type of presence, such as 'subscribe'. | ||||
|             pfrom     -- The sender of the presence. | ||||
|             pnick     -- Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         self.makePresence(pshow, pstatus, ppriority, pto, | ||||
|                           ptype=ptype, pfrom=pfrom).send() | ||||
|         # Unexpected errors may occur if | ||||
|         if not self.sentpresence: | ||||
|             self.event('sent_presence') | ||||
|             self.sentpresence = True | ||||
|         # Python2.6 chokes on Unicode strings for dict keys. | ||||
|         args = {str('pto'): pto, | ||||
|                 str('ptype'): ptype, | ||||
|                 str('pshow'): pshow, | ||||
|                 str('pstatus'): pstatus, | ||||
|                 str('ppriority'): ppriority, | ||||
|                 str('pnick'): pnick} | ||||
|  | ||||
|         if self.is_component: | ||||
|             self.roster[pfrom].send_presence(**args) | ||||
|         else: | ||||
|             self.client_roster.send_presence(**args) | ||||
|  | ||||
|     def send_presence_subscription(self, pto, pfrom=None, | ||||
|                                    ptype='subscribe', pnick=None): | ||||
| @@ -568,7 +656,7 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def _handle_disconnected(self, event): | ||||
|         """When disconnected, reset the roster""" | ||||
|         self.roster = {} | ||||
|         self.roster.reset() | ||||
|  | ||||
|     def _handle_stream_error(self, error): | ||||
|         self.event('stream_error', error) | ||||
| @@ -577,6 +665,65 @@ class BaseXMPP(XMLStream): | ||||
|         """Process incoming message stanzas.""" | ||||
|         self.event('message', msg) | ||||
|  | ||||
|     def _handle_available(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_available(presence) | ||||
|  | ||||
|     def _handle_unavailable(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unavailable(presence) | ||||
|  | ||||
|     def _handle_new_subscription(self, stanza): | ||||
|         """ | ||||
|         Attempt to automatically handle subscription requests. | ||||
|  | ||||
|         Subscriptions will be approved if the request is from | ||||
|         a whitelisted JID, of self.auto_authorize is True. They | ||||
|         will be rejected if self.auto_authorize is False. Setting | ||||
|         self.auto_authorize to None will disable automatic | ||||
|         subscription handling (except for whitelisted JIDs). | ||||
|  | ||||
|         If a subscription is accepted, a request for a mutual | ||||
|         subscription will be sent if self.auto_subscribe is True. | ||||
|         """ | ||||
|         roster = self.roster[stanza['to'].bare] | ||||
|         item = self.roster[stanza['to'].bare][stanza['from'].bare] | ||||
|         if item['whitelisted']: | ||||
|             item.authorize() | ||||
|         elif roster.auto_authorize: | ||||
|             item.authorize() | ||||
|             if roster.auto_subscribe: | ||||
|                 item.subscribe() | ||||
|         elif roster.auto_authorize == False: | ||||
|             item.unauthorize() | ||||
|  | ||||
|     def _handle_removed_subscription(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].unauthorize() | ||||
|  | ||||
|     def _handle_subscribe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_subscribe(presence) | ||||
|  | ||||
|     def _handle_subscribed(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_subscribed(presence) | ||||
|  | ||||
|     def _handle_unsubscribe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unsubscribe(presence) | ||||
|  | ||||
|     def _handle_unsubscribed(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unsubscribed(presence) | ||||
|  | ||||
|     def _handle_presence(self, presence): | ||||
|         """ | ||||
|         Process incoming presence stanzas. | ||||
| @@ -594,97 +741,30 @@ class BaseXMPP(XMLStream): | ||||
|              not presence['type'] in presence.showtypes: | ||||
|             return | ||||
|  | ||||
|         # Strip the information from the stanza. | ||||
|         jid = presence['from'].bare | ||||
|         resource = presence['from'].resource | ||||
|         show = presence['type'] | ||||
|         status = presence['status'] | ||||
|         priority = presence['priority'] | ||||
|  | ||||
|         was_offline = False | ||||
|         got_online = False | ||||
|         old_roster = self.roster.get(jid, {}).get(resource, {}) | ||||
|  | ||||
|         # Create a new roster entry if needed. | ||||
|         if not jid in self.roster: | ||||
|             self.roster[jid] = {'groups': [], | ||||
|                                 'name': '', | ||||
|                                 'subscription': 'none', | ||||
|                                 'presence': {}, | ||||
|                                 'in_roster': False} | ||||
|  | ||||
|         # Alias to simplify some references. | ||||
|         connections = self.roster[jid].get('presence', {}) | ||||
|  | ||||
|         # Determine if the user has just come online. | ||||
|         if not resource in connections: | ||||
|             if show == 'available' or show in presence.showtypes: | ||||
|                 got_online = True | ||||
|             was_offline = True | ||||
|             connections[resource] = {} | ||||
|  | ||||
|         if connections[resource].get('show', 'unavailable') == 'unavailable': | ||||
|             was_offline = True | ||||
|  | ||||
|         # Update the roster's state for this JID's resource. | ||||
|         connections[resource] = {'show': show, | ||||
|                                 'status': status, | ||||
|                                 'priority': priority} | ||||
|  | ||||
|         name = self.roster[jid].get('name', '') | ||||
|  | ||||
|         # Remove unneeded state information after a resource | ||||
|         # disconnects. Determine if this was the last connection | ||||
|         # for the JID. | ||||
|         if show == 'unavailable': | ||||
|             log.debug("%s %s got offline" % (jid, resource)) | ||||
|             del connections[resource] | ||||
|  | ||||
|             if not connections and \ | ||||
|                not self.roster[jid].get('in_roster', False): | ||||
|                 del self.roster[jid] | ||||
|             if not was_offline: | ||||
|                 self.event("got_offline", presence) | ||||
|             else: | ||||
|                 return False | ||||
|  | ||||
|         name = '(%s) ' % name if name else '' | ||||
|  | ||||
|         # Presence state has changed. | ||||
|         self.event("changed_status", presence) | ||||
|         if got_online: | ||||
|             self.event("got_online", presence) | ||||
|         log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, | ||||
|                                                    show, status)) | ||||
|  | ||||
|     def _handle_subscribe(self, presence): | ||||
|     def exception(self, exception): | ||||
|         """ | ||||
|         Automatically managage subscription requests. | ||||
|         Process any uncaught exceptions, notably IqError and | ||||
|         IqTimeout exceptions. | ||||
|  | ||||
|         Subscription behavior is controlled by the settings | ||||
|         self.auto_authorize and self.auto_subscribe. | ||||
|         Overrides XMLStream.exception. | ||||
|  | ||||
|         auto_auth  auto_sub   Result: | ||||
|         True       True       Create bi-directional subsriptions. | ||||
|         True       False      Create only directed subscriptions. | ||||
|         False      *          Decline all subscriptions. | ||||
|         None       *          Disable automatic handling and use | ||||
|                               a custom handler. | ||||
|         Arguments: | ||||
|             exception -- An unhandled exception object. | ||||
|         """ | ||||
|         presence.reply() | ||||
|         presence['to'] = presence['to'].bare | ||||
|         if isinstance(exception, IqError): | ||||
|             iq = exception.iq | ||||
|             log.error('%s: %s' % (iq['error']['condition'], | ||||
|                                   iq['error']['text'])) | ||||
|             log.warning('You should catch IqError exceptions') | ||||
|         elif isinstance(exception, IqTimeout): | ||||
|             iq = exception.iq | ||||
|             log.error('Request timed out: %s' % iq) | ||||
|             log.warning('You should catch IqTimeout exceptions') | ||||
|         else: | ||||
|             log.exception(exception) | ||||
|  | ||||
|         # We are using trinary logic, so conditions have to be | ||||
|         # more explicit than usual. | ||||
|         if self.auto_authorize == True: | ||||
|             presence['type'] = 'subscribed' | ||||
|             presence.send() | ||||
|             if self.auto_subscribe: | ||||
|                 presence['type'] = 'subscribe' | ||||
|                 presence.send() | ||||
|         elif self.auto_authorize == False: | ||||
|             presence['type'] = 'unsubscribed' | ||||
|             presence.send() | ||||
|  | ||||
| # Restore the old, lowercased name for backwards compatibility. | ||||
| basexmpp = BaseXMPP | ||||
|   | ||||
| @@ -15,21 +15,24 @@ 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 Message, Presence, Iq | ||||
| from sleekxmpp.stanza import * | ||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET | ||||
| 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 | ||||
| except ImportError: | ||||
|     DNSPYTHON = False | ||||
| else: | ||||
|     DNSPYTHON = True | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
| @@ -38,9 +41,12 @@ log = logging.getLogger(__name__) | ||||
| class ClientXMPP(BaseXMPP): | ||||
|  | ||||
|     """ | ||||
|     SleekXMPP's client class. | ||||
|     SleekXMPP's client class. ( Use only for good, not for evil.) | ||||
|  | ||||
|     Use only for good, not for evil. | ||||
|     Typical Use: | ||||
|     xmpp = ClientXMPP('user@server.tld/resource', 'password') | ||||
|     xmpp.process(block=False) // when block is True, it blocks the current | ||||
|     //                           thread. False by default. | ||||
|  | ||||
|     Attributes: | ||||
|  | ||||
| @@ -66,14 +72,14 @@ class ClientXMPP(BaseXMPP): | ||||
|                                 when calling register_plugins. | ||||
|             escape_quotes    -- Deprecated. | ||||
|         """ | ||||
|         BaseXMPP.__init__(self, 'jabber:client') | ||||
|         BaseXMPP.__init__(self, jid, '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.default_port = 5222 | ||||
|  | ||||
|         self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( | ||||
|                 self.boundjid.host, | ||||
| @@ -81,15 +87,19 @@ class ClientXMPP(BaseXMPP): | ||||
|                 "xmlns='%s'" % self.default_ns) | ||||
|         self.stream_footer = "</stream:stream>" | ||||
|  | ||||
|         self.features = [] | ||||
|         self.registered_features = [] | ||||
|         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.add_event_handler('connected', self._handle_connected) | ||||
|  | ||||
|         self.register_stanza(StreamFeatures) | ||||
|  | ||||
|         self.register_handler( | ||||
|                 Callback('Stream Features', | ||||
| @@ -102,32 +112,11 @@ class ClientXMPP(BaseXMPP): | ||||
|                              'jabber:iq:roster')), | ||||
|                          self._handle_roster)) | ||||
|  | ||||
|         self.register_feature( | ||||
|             "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", | ||||
|             self._handle_starttls, True) | ||||
|         self.register_feature( | ||||
|             "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", | ||||
|             self._handle_sasl_auth, True) | ||||
|         self.register_feature( | ||||
|             "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", | ||||
|             self._handle_bind_resource) | ||||
|         self.register_feature( | ||||
|             "<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", | ||||
|             self._handle_start_session) | ||||
|  | ||||
|     def handle_connected(self, event=None): | ||||
|         #TODO: Use stream state here | ||||
|         self.authenticated = False | ||||
|         self.sessionstarted = False | ||||
|         self.bound = False | ||||
|         self.bindfail = False | ||||
|         self.schedule("session timeout checker", 15, | ||||
|                       self._session_timeout_check) | ||||
|  | ||||
|     def _session_timeout_check(self): | ||||
|         if not self.session_started_event.isSet(): | ||||
|             log.debug("Session start has taken more than 15 seconds") | ||||
|             self.disconnect(reconnect=self.auto_reconnect) | ||||
|         # 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): | ||||
|         """ | ||||
| @@ -145,63 +134,59 @@ class ClientXMPP(BaseXMPP): | ||||
|                          connection. Defaults to True. | ||||
|         """ | ||||
|         self.session_started_event.clear() | ||||
|         if not address or len(address) < 2: | ||||
|             if not self.srv_support: | ||||
|                 log.debug("Did not supply (address, port) to connect" + \ | ||||
|                               " to and no SRV support is installed" + \ | ||||
|                               " (http://www.dnspython.org)." + \ | ||||
|                               " Continuing to attempt connection, using" + \ | ||||
|                               " server hostname from JID.") | ||||
|             else: | ||||
|                 log.debug("Since no address is supplied," + \ | ||||
|                               "attempting SRV lookup.") | ||||
|                 try: | ||||
|                     xmpp_srv = "_xmpp-client._tcp.%s" % self.boundjid.host | ||||
|                     answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) | ||||
|                 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): | ||||
|                     log.debug("No appropriate SRV record found." + \ | ||||
|                                   " Using JID server name.") | ||||
|                 except (dns.exception.Timeout,): | ||||
|                     log.debug("DNS resolution timed out.") | ||||
|                 else: | ||||
|                     # Pick a random server, weighted by priority. | ||||
|  | ||||
|                     addresses = {} | ||||
|                     intmax = 0 | ||||
|                     for answer in answers: | ||||
|                         intmax += answer.priority | ||||
|                         addresses[intmax] = (answer.target.to_text()[:-1], | ||||
|                                              answer.port) | ||||
|                     #python3 returns a generator for dictionary keys | ||||
|                     priorities = [x for x in addresses.keys()] | ||||
|                     priorities.sort() | ||||
|  | ||||
|                     picked = random.randint(0, intmax) | ||||
|                     for priority in priorities: | ||||
|                         if picked <= priority: | ||||
|                             address = addresses[priority] | ||||
|                             break | ||||
|  | ||||
|         if not address: | ||||
|             # If all else fails, use the server from the JID. | ||||
|             address = (self.boundjid.host, 5222) | ||||
|  | ||||
|         return XMLStream.connect(self, address[0], address[1], | ||||
|                                  use_tls=use_tls, reattempt=reattempt) | ||||
|  | ||||
|     def register_feature(self, mask, pointer, breaker=False): | ||||
|     def get_dns_records(self, domain, port=None): | ||||
|         """ | ||||
|         Get the DNS records for a domain. | ||||
|         Overriddes XMLStream.get_dns_records to use SRV. | ||||
|  | ||||
|         Arguments: | ||||
|             domain -- The domain in question. | ||||
|             port   -- If the results don't include a port, use this one. | ||||
|         """ | ||||
|         if port is None: | ||||
|             port = self.default_port | ||||
|         if DNSPYTHON: | ||||
|             try: | ||||
|                 record = "_xmpp-client._tcp.%s" % domain | ||||
|                 answers = [] | ||||
|                 for answer in dns.resolver.query(record, dns.rdatatype.SRV): | ||||
|                     address = (answer.target.to_text()[:-1], answer.port) | ||||
|                     answers.append((address, answer.priority, answer.weight)) | ||||
|             except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): | ||||
|                 log.warning("No SRV records for %s" % domain) | ||||
|                 answers = super(ClientXMPP, self).get_dns_records(domain, port) | ||||
|             except dns.exception.Timeout: | ||||
|                 log.warning("DNS resolution timed out " + \ | ||||
|                             "for SRV record of %s" % domain) | ||||
|                 answers = super(ClientXMPP, self).get_dns_records(domain, port) | ||||
|             return answers | ||||
|         else: | ||||
|             log.warning("dnspython is not installed -- " + \ | ||||
|                         "relying on OS A record resolution") | ||||
|             return [((domain, port), 0, 0)] | ||||
|  | ||||
|     def register_feature(self, name, handler, restart=False, order=5000): | ||||
|         """ | ||||
|         Register a stream feature. | ||||
|  | ||||
|         Arguments: | ||||
|             mask    -- An XML string matching the feature's element. | ||||
|             pointer -- The function to execute if the feature is received. | ||||
|             breaker -- Indicates if feature processing should halt with | ||||
|             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.registered_features.append((MatchXMLMask(mask), | ||||
|                                          pointer, | ||||
|                                          breaker)) | ||||
|         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): | ||||
| @@ -225,15 +210,8 @@ class ClientXMPP(BaseXMPP): | ||||
|                             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' | ||||
|         return self.client_roster.updtae(jid, name, subscription, groups, | ||||
|                                          block, timeout, callback) | ||||
|  | ||||
|     def del_roster_item(self, jid): | ||||
|         """ | ||||
| @@ -243,7 +221,7 @@ class ClientXMPP(BaseXMPP): | ||||
|         Arguments: | ||||
|             jid -- The JID of the item to remove. | ||||
|         """ | ||||
|         return self.update_roster(jid, subscription='remove') | ||||
|         return self.client_roster.remove(jid) | ||||
|  | ||||
|     def get_roster(self, block=True, timeout=None, callback=None): | ||||
|         """ | ||||
| @@ -265,14 +243,24 @@ class ClientXMPP(BaseXMPP): | ||||
|         iq.enable('roster') | ||||
|         response = iq.send(block, timeout, callback) | ||||
|  | ||||
|         if response == False: | ||||
|             self.event('roster_timeout') | ||||
|  | ||||
|         if response in [False, None] or not isinstance(response, Iq): | ||||
|             return response | ||||
|         else: | ||||
|         if callback is None: | ||||
|             return self._handle_roster(response, request=True) | ||||
|  | ||||
|     def _handle_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. | ||||
| @@ -280,172 +268,13 @@ class ClientXMPP(BaseXMPP): | ||||
|         Arguments: | ||||
|             features -- The features stanza. | ||||
|         """ | ||||
|         # Record all of the features. | ||||
|         self.features = [] | ||||
|         for sub in features.xml: | ||||
|             self.features.append(sub.tag) | ||||
|  | ||||
|         # Process the features. | ||||
|         for sub in features.xml: | ||||
|             for feature in self.registered_features: | ||||
|                 mask, handler, halt = feature | ||||
|                 if mask.match(sub): | ||||
|                     if handler(sub) and halt: | ||||
|                         # Don't continue if the feature was | ||||
|                         # marked as a breaker. | ||||
|                         return True | ||||
|  | ||||
|     def _handle_starttls(self, xml): | ||||
|         """ | ||||
|         Handle notification that the server supports TLS. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The STARTLS proceed element. | ||||
|         """ | ||||
|         if not self.use_tls: | ||||
|             return False | ||||
|         elif not self.authenticated and self.ssl_support: | ||||
|             tls_ns = 'urn:ietf:params:xml:ns:xmpp-tls' | ||||
|             self.add_handler("<proceed xmlns='%s' />" % tls_ns, | ||||
|                              self._handle_tls_start, | ||||
|                              name='TLS Proceed', | ||||
|                              instream=True) | ||||
|             self.send_xml(xml, now=True) | ||||
|             return True | ||||
|         else: | ||||
|             log.warning("The module tlslite is required to log in" +\ | ||||
|                             " to some servers, and has not been found.") | ||||
|             return False | ||||
|  | ||||
|     def _handle_tls_start(self, xml): | ||||
|         """ | ||||
|         Handle encrypting the stream using TLS. | ||||
|  | ||||
|         Restarts the stream. | ||||
|         """ | ||||
|         log.debug("Starting TLS") | ||||
|         if self.start_tls(): | ||||
|             raise RestartStream() | ||||
|  | ||||
|     def _handle_sasl_auth(self, xml): | ||||
|         """ | ||||
|         Handle authenticating using SASL. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The SASL mechanisms stanza. | ||||
|         """ | ||||
|         if self.use_tls and \ | ||||
|            '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: | ||||
|             return False | ||||
|  | ||||
|         log.debug("Starting SASL Auth") | ||||
|         sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|         self.add_handler("<success xmlns='%s' />" % sasl_ns, | ||||
|                          self._handle_auth_success, | ||||
|                          name='SASL Sucess', | ||||
|                          instream=True) | ||||
|         self.add_handler("<failure xmlns='%s' />" % sasl_ns, | ||||
|                          self._handle_auth_fail, | ||||
|                          name='SASL Failure', | ||||
|                          instream=True) | ||||
|  | ||||
|         sasl_mechs = xml.findall('{%s}mechanism' % sasl_ns) | ||||
|         if sasl_mechs: | ||||
|             for sasl_mech in sasl_mechs: | ||||
|                 self.features.append("sasl:%s" % sasl_mech.text) | ||||
|             if 'sasl:PLAIN' in self.features and self.boundjid.user: | ||||
|                 if sys.version_info < (3, 0): | ||||
|                     user = bytes(self.boundjid.user) | ||||
|                     password = bytes(self.password) | ||||
|                 else: | ||||
|                     user = bytes(self.boundjid.user, 'utf-8') | ||||
|                     password = bytes(self.password, 'utf-8') | ||||
|  | ||||
|                 auth = base64.b64encode(b'\x00' + user + \ | ||||
|                                         b'\x00' + password).decode('utf-8') | ||||
|  | ||||
|                 self.send("<auth xmlns='%s' mechanism='PLAIN'>%s</auth>" % ( | ||||
|                     sasl_ns, | ||||
|                     auth), | ||||
|                     now=True) | ||||
|             elif 'sasl:ANONYMOUS' in self.features and not self.boundjid.user: | ||||
|                 self.send("<auth xmlns='%s' mechanism='%s' />" % ( | ||||
|                     sasl_ns, | ||||
|                     'ANONYMOUS'), | ||||
|                     now=True) | ||||
|             else: | ||||
|                 log.error("No appropriate login method.") | ||||
|                 self.disconnect() | ||||
|         return True | ||||
|  | ||||
|     def _handle_auth_success(self, xml): | ||||
|         """ | ||||
|         SASL authentication succeeded. Restart the stream. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The SASL authentication success element. | ||||
|         """ | ||||
|         self.authenticated = True | ||||
|         self.features = [] | ||||
|         raise RestartStream() | ||||
|  | ||||
|     def _handle_auth_fail(self, xml): | ||||
|         """ | ||||
|         SASL authentication failed. Disconnect and shutdown. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The SASL authentication failure element. | ||||
|         """ | ||||
|         log.info("Authentication failed.") | ||||
|         self.event("failed_auth", direct=True) | ||||
|         self.disconnect() | ||||
|  | ||||
|     def _handle_bind_resource(self, xml): | ||||
|         """ | ||||
|         Handle requesting a specific resource. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The bind feature element. | ||||
|         """ | ||||
|         log.debug("Requesting resource: %s" % self.boundjid.resource) | ||||
|         xml.clear() | ||||
|         iq = self.Iq(stype='set') | ||||
|         if self.boundjid.resource: | ||||
|             res = ET.Element('resource') | ||||
|             res.text = self.boundjid.resource | ||||
|             xml.append(res) | ||||
|         iq.append(xml) | ||||
|         response = iq.send(now=True) | ||||
|  | ||||
|         bind_ns = 'urn:ietf:params:xml:ns:xmpp-bind' | ||||
|         self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns, | ||||
|                                                              bind_ns)).text) | ||||
|         self.bound = True | ||||
|         log.info("Node set to: %s" % self.boundjid.full) | ||||
|         session_ns = 'urn:ietf:params:xml:ns:xmpp-session' | ||||
|         if "{%s}session" % session_ns not in self.features or self.bindfail: | ||||
|             log.debug("Established Session") | ||||
|             self.sessionstarted = True | ||||
|             self.session_started_event.set() | ||||
|             self.event("session_start") | ||||
|  | ||||
|     def _handle_start_session(self, xml): | ||||
|         """ | ||||
|         Handle the start of the session. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The session feature element. | ||||
|         """ | ||||
|         if self.authenticated and self.bound: | ||||
|             iq = self.makeIqSet(xml) | ||||
|             response = iq.send(now=True) | ||||
|             log.debug("Established Session") | ||||
|             self.sessionstarted = True | ||||
|             self.session_started_event.set() | ||||
|             self.event("session_start") | ||||
|         else: | ||||
|             # Bind probably hasn't happened yet. | ||||
|             self.bindfail = True | ||||
|         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): | ||||
|         """ | ||||
| @@ -458,13 +287,13 @@ class ClientXMPP(BaseXMPP): | ||||
|         """ | ||||
|         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]) | ||||
|                 item = iq['roster']['items'][jid] | ||||
|                 roster = self.roster[iq['to'].bare] | ||||
|                 roster[jid]['name'] = item['name'] | ||||
|                 roster[jid]['groups'] = item['groups'] | ||||
|                 roster[jid]['from'] = item['subscription'] in ['from', 'both'] | ||||
|                 roster[jid]['to'] = item['subscription'] in ['to', 'both'] | ||||
|                 roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||
|             self.event('roster_received', iq) | ||||
|  | ||||
|         self.event("roster_update", iq) | ||||
|   | ||||
| @@ -58,7 +58,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|             default_ns = 'jabber:client' | ||||
|         else: | ||||
|             default_ns = 'jabber:component:accept' | ||||
|         BaseXMPP.__init__(self, default_ns) | ||||
|         BaseXMPP.__init__(self, jid, default_ns) | ||||
|  | ||||
|         self.auto_authorize = None | ||||
|         self.stream_header = "<stream:stream %s %s to='%s'>" % ( | ||||
| @@ -68,8 +68,8 @@ class ComponentXMPP(BaseXMPP): | ||||
|         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 | ||||
| @@ -78,6 +78,8 @@ class ComponentXMPP(BaseXMPP): | ||||
|                 Callback('Handshake', | ||||
|                          MatchXPath('{jabber:component:accept}handshake'), | ||||
|                          self._handle_handshake)) | ||||
|         self.add_event_handler('presence_probe', | ||||
|                                self._handle_probe) | ||||
|  | ||||
|     def connect(self): | ||||
|         """ | ||||
| @@ -115,11 +117,13 @@ class ComponentXMPP(BaseXMPP): | ||||
|         Once the streams are established, attempt to handshake | ||||
|         with the server to be accepted as a component. | ||||
|  | ||||
|         Overrides XMLStream.start_stream_handler. | ||||
|         Overrides BaseXMPP.start_stream_handler. | ||||
|  | ||||
|         Arguments: | ||||
|             xml -- The incoming stream's root element. | ||||
|         """ | ||||
|         BaseXMPP.start_stream_handler(self, xml) | ||||
|  | ||||
|         # Construct a hash of the stream ID and the component secret. | ||||
|         sid = xml.get('id', '') | ||||
|         pre_hash = '%s%s' % (sid, self.secret) | ||||
| @@ -140,3 +144,8 @@ class ComponentXMPP(BaseXMPP): | ||||
|         """ | ||||
|         self.session_started_event.set() | ||||
|         self.event("session_start") | ||||
|  | ||||
|     def _handle_probe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_probe(presence) | ||||
|   | ||||
| @@ -20,9 +20,9 @@ class XMPPError(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): | ||||
|     def __init__(self, condition='undefined-condition', text=None, | ||||
|                 etype='cancel', extension=None, extension_ns=None, | ||||
|                 extension_args=None, clear=True): | ||||
|         """ | ||||
|         Create a new XMPPError exception. | ||||
|  | ||||
| @@ -31,8 +31,10 @@ class XMPPError(Exception): | ||||
|  | ||||
|         Arguments: | ||||
|             condition      -- The XMPP defined error condition. | ||||
|                               Defaults to 'undefined-condition'. | ||||
|             text           -- Human readable text describing the error. | ||||
|             etype          -- The XMPP error type, such as cancel or modify. | ||||
|                               Defaults to 'cancel'. | ||||
|             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 | ||||
| @@ -52,3 +54,33 @@ class XMPPError(Exception): | ||||
|         self.extension = extension | ||||
|         self.extension_ns = extension_ns | ||||
|         self.extension_args = extension_args | ||||
|  | ||||
|  | ||||
| class IqTimeout(XMPPError): | ||||
|  | ||||
|     """ | ||||
|     An exception which indicates that an IQ request response has not been | ||||
|     received within the alloted time window. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, iq): | ||||
|         super(IqTimeout, self).__init__( | ||||
|                 condition='remote-server-timeout', | ||||
|                 etype='cancel') | ||||
|  | ||||
|         self.iq = iq | ||||
|  | ||||
| class IqError(XMPPError): | ||||
|  | ||||
|     """ | ||||
|     An exception raised when an Iq stanza of type 'error' is received | ||||
|     after making a blocking send call. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, iq): | ||||
|         super(IqError, self).__init__( | ||||
|                 condition=iq['error']['condition'], | ||||
|                 text=iq['error']['text'], | ||||
|                 etype=iq['error']['type']) | ||||
|  | ||||
|         self.iq = iq | ||||
|   | ||||
							
								
								
									
										9
									
								
								sleekxmpp/features/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								sleekxmpp/features/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| """ | ||||
|     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'] | ||||
							
								
								
									
										10
									
								
								sleekxmpp/features/feature_bind/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/features/feature_bind/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| """ | ||||
|     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 | ||||
							
								
								
									
										64
									
								
								sleekxmpp/features/feature_bind/bind.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								sleekxmpp/features/feature_bind/bind.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| """ | ||||
|     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") | ||||
							
								
								
									
										22
									
								
								sleekxmpp/features/feature_bind/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								sleekxmpp/features/feature_bind/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| """ | ||||
|     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' | ||||
							
								
								
									
										13
									
								
								sleekxmpp/features/feature_mechanisms/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								sleekxmpp/features/feature_mechanisms/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| """ | ||||
|     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 | ||||
							
								
								
									
										129
									
								
								sleekxmpp/features/feature_mechanisms/mechanisms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								sleekxmpp/features/feature_mechanisms/mechanisms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| """ | ||||
|     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 | ||||
|  | ||||
|         self.use_mech = self.config.get('use_mech', None) | ||||
|  | ||||
|         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, | ||||
|                                 mech=self.use_mech) | ||||
|  | ||||
|         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 | ||||
							
								
								
									
										15
									
								
								sleekxmpp/features/feature_mechanisms/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/features/feature_mechanisms/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| """ | ||||
|     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 | ||||
							
								
								
									
										39
									
								
								sleekxmpp/features/feature_mechanisms/stanza/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								sleekxmpp/features/feature_mechanisms/stanza/auth.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| """ | ||||
|     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 = '' | ||||
							
								
								
									
										39
									
								
								sleekxmpp/features/feature_mechanisms/stanza/challenge.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								sleekxmpp/features/feature_mechanisms/stanza/challenge.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| """ | ||||
|     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 = '' | ||||
							
								
								
									
										78
									
								
								sleekxmpp/features/feature_mechanisms/stanza/failure.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								sleekxmpp/features/feature_mechanisms/stanza/failure.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| """ | ||||
|     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 | ||||
							
								
								
									
										55
									
								
								sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								sleekxmpp/features/feature_mechanisms/stanza/mechanisms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| """ | ||||
|     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) | ||||
							
								
								
									
										39
									
								
								sleekxmpp/features/feature_mechanisms/stanza/response.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								sleekxmpp/features/feature_mechanisms/stanza/response.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| """ | ||||
|     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 = '' | ||||
							
								
								
									
										26
									
								
								sleekxmpp/features/feature_mechanisms/stanza/success.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								sleekxmpp/features/feature_mechanisms/stanza/success.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| """ | ||||
|     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() | ||||
							
								
								
									
										10
									
								
								sleekxmpp/features/feature_session/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/features/feature_session/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| """ | ||||
|     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 | ||||
							
								
								
									
										56
									
								
								sleekxmpp/features/feature_session/session.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								sleekxmpp/features/feature_session/session.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| """ | ||||
|     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") | ||||
							
								
								
									
										21
									
								
								sleekxmpp/features/feature_session/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								sleekxmpp/features/feature_session/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| """ | ||||
|     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' | ||||
							
								
								
									
										10
									
								
								sleekxmpp/features/feature_starttls/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/features/feature_starttls/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| """ | ||||
|     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 * | ||||
							
								
								
									
										47
									
								
								sleekxmpp/features/feature_starttls/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								sleekxmpp/features/feature_starttls/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| """ | ||||
|     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() | ||||
							
								
								
									
										70
									
								
								sleekxmpp/features/feature_starttls/starttls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								sleekxmpp/features/feature_starttls/starttls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| """ | ||||
|     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() | ||||
| @@ -6,5 +6,8 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', | ||||
|            'xep_0045', 'xep_0050', 'xep_0060', 'xep_0085', 'xep_0086', | ||||
|            'xep_0092', 'xep_0128', 'xep_0199', 'xep_0202', 'gmail_notify'] | ||||
|            'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', | ||||
|            'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', | ||||
|            'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] | ||||
|  | ||||
| # Don't automatically load xep_0078 | ||||
|   | ||||
| @@ -66,7 +66,8 @@ class base_plugin(object): | ||||
|         """ | ||||
|         if config is None: | ||||
|             config = {} | ||||
|         self.xep = 'base' | ||||
|         self.xep = None | ||||
|         self.rfc = None | ||||
|         self.description = 'Base Plugin' | ||||
|         self.xmpp = xmpp | ||||
|         self.config = config | ||||
|   | ||||
| @@ -1,557 +0,0 @@ | ||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID | ||||
| from .. stanza.iq import Iq | ||||
| from .. stanza.message import Message | ||||
| from .. basexmpp import basexmpp | ||||
| from .. xmlstream.xmlstream import XMLStream | ||||
| import logging | ||||
| from . import xep_0004 | ||||
|  | ||||
|  | ||||
| class PubsubState(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/psstate' | ||||
| 	name = 'state' | ||||
| 	plugin_attrib = 'psstate' | ||||
| 	interfaces = set(('node', 'item', 'payload')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def setPayload(self, value): | ||||
| 		self.xml.append(value) | ||||
| 	 | ||||
| 	def getPayload(self): | ||||
| 		childs = self.xml.getchildren() | ||||
| 		if len(childs) > 0: | ||||
| 			return childs[0] | ||||
| 	 | ||||
| 	def delPayload(self): | ||||
| 		for child in self.xml.getchildren(): | ||||
| 			self.xml.remove(child) | ||||
|  | ||||
| registerStanzaPlugin(Iq, PubsubState) | ||||
|  | ||||
| class PubsubStateEvent(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/psstate#event' | ||||
| 	name = 'event' | ||||
| 	plugin_attrib = 'psstate_event' | ||||
| 	intefaces = set(tuple()) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Message, PubsubStateEvent) | ||||
| registerStanzaPlugin(PubsubStateEvent, PubsubState) | ||||
|  | ||||
| class Pubsub(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'pubsub' | ||||
| 	plugin_attrib = 'pubsub' | ||||
| 	interfaces = set(tuple()) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Iq, Pubsub) | ||||
|  | ||||
| class PubsubOwner(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	name = 'pubsub' | ||||
| 	plugin_attrib = 'pubsub_owner' | ||||
| 	interfaces = set(tuple()) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Iq, PubsubOwner) | ||||
|  | ||||
| class Affiliation(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'affiliation' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'affiliation')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| class Affiliations(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'affiliations' | ||||
| 	plugin_attrib = 'affiliations' | ||||
| 	interfaces = set(tuple()) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (Affiliation,) | ||||
|  | ||||
| 	def append(self, affiliation): | ||||
| 		if not isinstance(affiliation, Affiliation): | ||||
| 			raise TypeError | ||||
| 		self.xml.append(affiliation.xml) | ||||
| 		return self.iterables.append(affiliation) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Affiliations) | ||||
|  | ||||
|  | ||||
| class Subscription(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'subscription' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('jid', 'node', 'subscription', 'subid')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| 	def setjid(self, value): | ||||
| 		self._setattr('jid', str(value)) | ||||
| 	 | ||||
| 	def getjid(self): | ||||
| 		return jid(self._getattr('jid')) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Subscription) | ||||
|  | ||||
| class Subscriptions(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'subscriptions' | ||||
| 	plugin_attrib = 'subscriptions' | ||||
| 	interfaces = set(tuple()) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (Subscription,) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Subscriptions) | ||||
|  | ||||
| class OptionalSetting(object): | ||||
| 	interfaces = set(('required',)) | ||||
|  | ||||
| 	def setRequired(self, value): | ||||
| 		value = bool(value) | ||||
| 		if value and not self['required']: | ||||
| 			self.xml.append(ET.Element("{%s}required" % self.namespace)) | ||||
| 		elif not value and self['required']: | ||||
| 			self.delRequired() | ||||
| 	 | ||||
| 	def getRequired(self): | ||||
| 		required = self.xml.find("{%s}required" % self.namespace) | ||||
| 		if required is not None: | ||||
| 			return True | ||||
| 		else: | ||||
| 			return False | ||||
| 	 | ||||
| 	def delRequired(self): | ||||
| 		required = self.xml.find("{%s}required" % self.namespace) | ||||
| 		if required is not None: | ||||
| 			self.xml.remove(required) | ||||
|  | ||||
|  | ||||
| class SubscribeOptions(ElementBase, OptionalSetting): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'subscribe-options' | ||||
| 	plugin_attrib = 'suboptions' | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	interfaces = set(('required',)) | ||||
|  | ||||
| registerStanzaPlugin(Subscription, SubscribeOptions) | ||||
|  | ||||
| class Item(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'item' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('id', 'payload')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| 	def setPayload(self, value): | ||||
| 		self.xml.append(value) | ||||
| 	 | ||||
| 	def getPayload(self): | ||||
| 		childs = self.xml.getchildren() | ||||
| 		if len(childs) > 0: | ||||
| 			return childs[0] | ||||
| 	 | ||||
| 	def delPayload(self): | ||||
| 		for child in self.xml.getchildren(): | ||||
| 			self.xml.remove(child) | ||||
|  | ||||
| class Items(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'items' | ||||
| 	plugin_attrib = 'items' | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (Item,) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Items) | ||||
|  | ||||
| class Create(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'create' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Create) | ||||
|  | ||||
| #class Default(ElementBase): | ||||
| #	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| #	name = 'default' | ||||
| #	plugin_attrib = name | ||||
| #	interfaces = set(('node', 'type')) | ||||
| #	plugin_attrib_map = {} | ||||
| #	plugin_tag_map = {} | ||||
| # | ||||
| #	def getType(self): | ||||
| #		t = self._getAttr('type') | ||||
| #		if not t: t == 'leaf' | ||||
| #		return t | ||||
| # | ||||
| #registerStanzaPlugin(Pubsub, Default) | ||||
|  | ||||
| class Publish(Items): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'publish' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (Item,) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Publish) | ||||
|  | ||||
| class Retract(Items): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'retract' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'notify')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Retract) | ||||
|  | ||||
| class Unsubscribe(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'unsubscribe' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'jid')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def setJid(self, value): | ||||
| 		self._setAttr('jid', str(value)) | ||||
| 	 | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Unsubscribe) | ||||
|  | ||||
| class Subscribe(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'subscribe' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'jid')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| 	def setJid(self, value): | ||||
| 		self._setAttr('jid', str(value)) | ||||
| 	 | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Subscribe) | ||||
|  | ||||
| class Configure(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'configure' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'type')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| 	def getType(self): | ||||
| 		t = self._getAttr('type') | ||||
| 		if not t: t == 'leaf' | ||||
| 		return t | ||||
| 	 | ||||
| registerStanzaPlugin(Pubsub, Configure) | ||||
| registerStanzaPlugin(Configure, xep_0004.Form) | ||||
|  | ||||
| class DefaultConfig(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	name = 'default' | ||||
| 	plugin_attrib = 'default' | ||||
| 	interfaces = set(('node', 'type', 'config')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		ElementBase.__init__(self, *args, **kwargs) | ||||
|  | ||||
| 	def getType(self): | ||||
| 		t = self._getAttr('type') | ||||
| 		if not t: t = 'leaf' | ||||
| 		return t | ||||
| 	 | ||||
| 	def getConfig(self): | ||||
| 		return self['form'] | ||||
| 	 | ||||
| 	def setConfig(self, value): | ||||
| 		self['form'].setStanzaValues(value.getStanzaValues()) | ||||
| 		return self | ||||
|  | ||||
| registerStanzaPlugin(PubsubOwner, DefaultConfig) | ||||
| registerStanzaPlugin(DefaultConfig, xep_0004.Form) | ||||
|  | ||||
| class Options(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub' | ||||
| 	name = 'options' | ||||
| 	plugin_attrib = 'options' | ||||
| 	interfaces = set(('jid', 'node', 'options')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		ElementBase.__init__(self, *args, **kwargs) | ||||
| 		 | ||||
| 	def getOptions(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		form = xep_0004.Form() | ||||
| 		if config is not None: | ||||
| 			form.fromXML(config) | ||||
| 		return form | ||||
| 	 | ||||
| 	def setOptions(self, value): | ||||
| 		self.xml.append(value.getXML()) | ||||
| 		return self | ||||
| 	 | ||||
| 	def delOptions(self): | ||||
| 		config = self.xml.find('{jabber:x:data}x') | ||||
| 		self.xml.remove(config) | ||||
| 	 | ||||
| 	def setJid(self, value): | ||||
| 		self._setAttr('jid', str(value)) | ||||
| 	 | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| registerStanzaPlugin(Pubsub, Options) | ||||
| registerStanzaPlugin(Subscribe, Options) | ||||
|  | ||||
| class OwnerAffiliations(Affiliations): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	interfaces = set(('node')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def append(self, affiliation): | ||||
| 		if not isinstance(affiliation, OwnerAffiliation): | ||||
| 			raise TypeError | ||||
| 		self.xml.append(affiliation.xml) | ||||
| 		return self.affiliations.append(affiliation) | ||||
|  | ||||
| registerStanzaPlugin(PubsubOwner, OwnerAffiliations) | ||||
|  | ||||
| class OwnerAffiliation(Affiliation): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	interfaces = set(('affiliation', 'jid')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| class OwnerConfigure(Configure): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	interfaces = set(('node', 'config')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(PubsubOwner, OwnerConfigure) | ||||
|  | ||||
| class OwnerDefault(OwnerConfigure): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	interfaces = set(('node', 'config')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def getConfig(self): | ||||
| 		return self['form'] | ||||
| 	 | ||||
| 	def setConfig(self, value): | ||||
| 		self['form'].setStanzaValues(value.getStanzaValues()) | ||||
| 		return self | ||||
|  | ||||
| registerStanzaPlugin(PubsubOwner, OwnerDefault) | ||||
| registerStanzaPlugin(OwnerDefault, xep_0004.Form) | ||||
|  | ||||
| class OwnerDelete(ElementBase, OptionalSetting): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	name = 'delete' | ||||
| 	plugin_attrib = 'delete' | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	interfaces = set(('node',)) | ||||
|  | ||||
| registerStanzaPlugin(PubsubOwner, OwnerDelete) | ||||
|  | ||||
| class OwnerPurge(ElementBase, OptionalSetting): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	name = 'purge' | ||||
| 	plugin_attrib = name | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(PubsubOwner, OwnerPurge) | ||||
|  | ||||
| class OwnerRedirect(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	name = 'redirect' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'jid')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def setJid(self, value): | ||||
| 		self._setAttr('jid', str(value)) | ||||
| 	 | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| registerStanzaPlugin(OwnerDelete, OwnerRedirect) | ||||
|  | ||||
| class OwnerSubscriptions(Subscriptions): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def append(self, subscription): | ||||
| 		if not isinstance(subscription, OwnerSubscription): | ||||
| 			raise TypeError | ||||
| 		self.xml.append(subscription.xml) | ||||
| 		return self.subscriptions.append(subscription) | ||||
|  | ||||
| registerStanzaPlugin(PubsubOwner, OwnerSubscriptions) | ||||
|  | ||||
| class OwnerSubscription(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||
| 	name = 'subscription' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('jid', 'subscription')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| 	def setJid(self, value): | ||||
| 		self._setAttr('jid', str(value)) | ||||
| 	 | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('from')) | ||||
|  | ||||
| class Event(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'event' | ||||
| 	plugin_attrib = 'pubsub_event' | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Message, Event) | ||||
|  | ||||
| class EventItem(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'item' | ||||
| 	plugin_attrib = 'item' | ||||
| 	interfaces = set(('id', 'payload')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| 	def setPayload(self, value): | ||||
| 		self.xml.append(value) | ||||
| 	 | ||||
| 	def getPayload(self): | ||||
| 		childs = self.xml.getchildren() | ||||
| 		if len(childs) > 0: | ||||
| 			return childs[0] | ||||
| 	 | ||||
| 	def delPayload(self): | ||||
| 		for child in self.xml.getchildren(): | ||||
| 			self.xml.remove(child) | ||||
|  | ||||
|  | ||||
| class EventRetract(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'retract' | ||||
| 	plugin_attrib = 'retract' | ||||
| 	interfaces = set(('id',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| class EventItems(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'items' | ||||
| 	plugin_attrib = 'items' | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	subitem = (EventItem, EventRetract) | ||||
|  | ||||
| registerStanzaPlugin(Event, EventItems) | ||||
|  | ||||
| class EventCollection(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'collection' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Event, EventCollection) | ||||
|  | ||||
| class EventAssociate(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'associate' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(EventCollection, EventAssociate) | ||||
|  | ||||
| class EventDisassociate(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'disassociate' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(EventCollection, EventDisassociate) | ||||
|  | ||||
| class EventConfiguration(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'configuration' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node', 'config')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| registerStanzaPlugin(Event, EventConfiguration) | ||||
| registerStanzaPlugin(EventConfiguration, xep_0004.Form) | ||||
|  | ||||
| class EventPurge(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'purge' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node',)) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
|  | ||||
| registerStanzaPlugin(Event, EventPurge) | ||||
|  | ||||
| class EventSubscription(ElementBase): | ||||
| 	namespace = 'http://jabber.org/protocol/pubsub#event' | ||||
| 	name = 'subscription' | ||||
| 	plugin_attrib = name | ||||
| 	interfaces = set(('node','expiry', 'jid', 'subid', 'subscription')) | ||||
| 	plugin_attrib_map = {} | ||||
| 	plugin_tag_map = {} | ||||
| 	 | ||||
| 	def setJid(self, value): | ||||
| 		self._setAttr('jid', str(value)) | ||||
| 	 | ||||
| 	def getJid(self): | ||||
| 		return JID(self._getAttr('jid')) | ||||
|  | ||||
| registerStanzaPlugin(Event, EventSubscription) | ||||
| @@ -1,395 +0,0 @@ | ||||
| """ | ||||
| 	SleekXMPP: The Sleek XMPP Library | ||||
| 	Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
| 	This file is part of SleekXMPP. | ||||
|  | ||||
| 	See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| import copy | ||||
| from . import base | ||||
| from .. xmlstream.handler.callback import Callback | ||||
| from .. xmlstream.matcher.xpath import MatchXPath | ||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID | ||||
| from .. stanza.message import Message | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class Form(ElementBase): | ||||
| 	namespace = 'jabber:x:data' | ||||
| 	name = 'x' | ||||
| 	plugin_attrib = 'form' | ||||
| 	interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values')) | ||||
| 	sub_interfaces = set(('title',)) | ||||
| 	form_types = set(('cancel', 'form', 'result', 'submit')) | ||||
|  | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		title = None | ||||
| 		if 'title' in kwargs: | ||||
| 			title = kwargs['title'] | ||||
| 			del kwargs['title'] | ||||
| 		ElementBase.__init__(self, *args, **kwargs) | ||||
| 		if title is not None: | ||||
| 			self['title'] = title | ||||
| 		self.field = FieldAccessor(self) | ||||
|  | ||||
| 	def setup(self, xml=None): | ||||
| 		if ElementBase.setup(self, xml): #if we had to generate xml | ||||
| 			self['type'] = 'form' | ||||
|  | ||||
| 	def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs): | ||||
| 		kwtype = kwargs.get('type', None) | ||||
| 		if kwtype is None: | ||||
| 			kwtype = ftype | ||||
|  | ||||
| 		field = FormField(parent=self) | ||||
| 		field['var'] = var | ||||
| 		field['type'] = kwtype | ||||
| 		field['label'] = label | ||||
| 		field['desc'] = desc | ||||
| 		field['required'] = required | ||||
| 		field['value'] = value | ||||
| 		if options is not None: | ||||
| 			field['options'] = options | ||||
| 		return field | ||||
|  | ||||
| 	def getXML(self, type='submit'): | ||||
| 		self['type'] = type | ||||
| 		log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") | ||||
| 		return self.xml | ||||
|  | ||||
| 	def fromXML(self, xml): | ||||
| 		log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") | ||||
| 		n = Form(xml=xml) | ||||
| 		return n | ||||
|  | ||||
| 	def addItem(self, values): | ||||
| 		itemXML = ET.Element('{%s}item' % self.namespace) | ||||
| 		self.xml.append(itemXML) | ||||
| 		reported_vars = self['reported'].keys() | ||||
| 		for var in reported_vars: | ||||
| 			fieldXML = ET.Element('{%s}field' % FormField.namespace) | ||||
| 			itemXML.append(fieldXML) | ||||
| 			field = FormField(xml=fieldXML) | ||||
| 			field['var'] = var | ||||
| 			field['value'] = values.get(var, None) | ||||
|  | ||||
| 	def addReported(self, var, ftype=None, label='', desc='', **kwargs): | ||||
| 		kwtype = kwargs.get('type', None) | ||||
| 		if kwtype is None: | ||||
| 			kwtype = ftype | ||||
| 		reported = self.xml.find('{%s}reported' % self.namespace) | ||||
| 		if reported is None: | ||||
| 			reported = ET.Element('{%s}reported' % self.namespace) | ||||
| 			self.xml.append(reported) | ||||
| 		fieldXML = ET.Element('{%s}field' % FormField.namespace) | ||||
| 		reported.append(fieldXML) | ||||
| 		field = FormField(xml=fieldXML) | ||||
| 		field['var'] = var | ||||
| 		field['type'] = kwtype | ||||
| 		field['label'] = label | ||||
| 		field['desc'] = desc | ||||
| 		return field | ||||
|  | ||||
| 	def cancel(self): | ||||
| 		self['type'] = 'cancel' | ||||
|  | ||||
| 	def delFields(self): | ||||
| 		fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) | ||||
| 		for fieldXML in fieldsXML: | ||||
| 			self.xml.remove(fieldXML) | ||||
|  | ||||
| 	def delInstructions(self): | ||||
| 		instsXML = self.xml.findall('{%s}instructions') | ||||
| 		for instXML in instsXML: | ||||
| 			self.xml.remove(instXML) | ||||
|  | ||||
| 	def delItems(self): | ||||
| 		itemsXML = self.xml.find('{%s}item' % self.namespace) | ||||
| 		for itemXML in itemsXML: | ||||
| 			self.xml.remove(itemXML) | ||||
|  | ||||
| 	def delReported(self): | ||||
| 		reportedXML = self.xml.find('{%s}reported' % self.namespace) | ||||
| 		if reportedXML is not None: | ||||
| 			self.xml.remove(reportedXML) | ||||
|  | ||||
| 	def getFields(self, use_dict=False): | ||||
| 		fields = {} if use_dict else [] | ||||
| 		fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) | ||||
| 		for fieldXML in fieldsXML: | ||||
| 			field = FormField(xml=fieldXML) | ||||
| 			if use_dict: | ||||
| 				fields[field['var']] = field | ||||
| 			else: | ||||
| 				fields.append((field['var'], field)) | ||||
| 		return fields | ||||
|  | ||||
| 	def getInstructions(self): | ||||
| 		instructions = '' | ||||
| 		instsXML = self.xml.findall('{%s}instructions' % self.namespace) | ||||
| 		return "\n".join([instXML.text for instXML in instsXML]) | ||||
|  | ||||
| 	def getItems(self): | ||||
| 		items = [] | ||||
| 		itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||
| 		for itemXML in itemsXML: | ||||
| 			item = {} | ||||
| 			fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) | ||||
| 			for fieldXML in fieldsXML: | ||||
| 				field = FormField(xml=fieldXML) | ||||
| 				item[field['var']] = field['value'] | ||||
| 			items.append(item) | ||||
| 		return items | ||||
|  | ||||
| 	def getReported(self): | ||||
| 		fields = {} | ||||
| 		fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, | ||||
| 									 FormField.namespace)) | ||||
| 		for fieldXML in fieldsXML: | ||||
| 			field = FormField(xml=fieldXML) | ||||
| 			fields[field['var']] = field | ||||
| 		return fields | ||||
|  | ||||
| 	def getValues(self): | ||||
| 		values = {} | ||||
| 		fields = self.getFields(use_dict=True) | ||||
| 		for var in fields: | ||||
| 			values[var] = fields[var]['value'] | ||||
| 		return values | ||||
|  | ||||
| 	def reply(self): | ||||
| 		if self['type'] == 'form': | ||||
| 			self['type'] = 'submit' | ||||
| 		elif self['type'] == 'submit': | ||||
| 			self['type'] = 'result' | ||||
|  | ||||
| 	def setFields(self, fields, default=None): | ||||
| 		del self['fields'] | ||||
| 		for field_data in fields: | ||||
| 			var = field_data[0] | ||||
| 			field = field_data[1] | ||||
| 			field['var'] = var | ||||
|  | ||||
| 			self.addField(**field) | ||||
|  | ||||
| 	def setInstructions(self, instructions): | ||||
| 		del self['instructions'] | ||||
| 		if instructions in [None, '']: | ||||
| 			return | ||||
| 		instructions = instructions.split('\n') | ||||
| 		for instruction in instructions: | ||||
| 			inst = ET.Element('{%s}instructions' % self.namespace) | ||||
| 			inst.text = instruction | ||||
| 			self.xml.append(inst) | ||||
|  | ||||
| 	def setItems(self, items): | ||||
| 		for item in items: | ||||
| 			self.addItem(item) | ||||
|  | ||||
| 	def setReported(self, reported, default=None): | ||||
| 		for var in reported: | ||||
| 			field = reported[var] | ||||
| 			field['var'] = var | ||||
| 			self.addReported(var, **field) | ||||
|  | ||||
| 	def setValues(self, values): | ||||
| 		fields = self.getFields(use_dict=True) | ||||
| 		for field in values: | ||||
| 			fields[field]['value'] = values[field] | ||||
|  | ||||
| 	def merge(self, other): | ||||
| 		new = copy.copy(self) | ||||
| 		if type(other) == dict: | ||||
| 			new.setValues(other) | ||||
| 			return new | ||||
| 		nfields = new.getFields(use_dict=True) | ||||
| 		ofields = other.getFields(use_dict=True) | ||||
| 		nfields.update(ofields) | ||||
| 		new.setFields([(x, nfields[x]) for x in nfields]) | ||||
| 		return new | ||||
|  | ||||
| class FieldAccessor(object): | ||||
| 	def __init__(self, form): | ||||
| 		self.form = form | ||||
|  | ||||
| 	def __getitem__(self, key): | ||||
| 		return self.form.getFields(use_dict=True)[key] | ||||
|  | ||||
| 	def __contains__(self, key): | ||||
| 		return key in self.form.getFields(use_dict=True) | ||||
|  | ||||
| 	def has_key(self, key): | ||||
| 		return key in self.form.getFields(use_dict=True) | ||||
|  | ||||
|  | ||||
| class FormField(ElementBase): | ||||
| 	namespace = 'jabber:x:data' | ||||
| 	name = 'field' | ||||
| 	plugin_attrib = 'field' | ||||
| 	interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var')) | ||||
| 	sub_interfaces = set(('desc',)) | ||||
| 	field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', | ||||
| 			   'list-single', 'text-multi', 'text-private', 'text-single')) | ||||
| 	multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi')) | ||||
| 	multi_line_types = set(('hidden', 'text-multi')) | ||||
| 	option_types = set(('list-multi', 'list-single')) | ||||
| 	true_values = set((True, '1', 'true')) | ||||
|  | ||||
| 	def addOption(self, label='', value=''): | ||||
| 		if self['type'] in self.option_types: | ||||
| 			opt = FieldOption(parent=self) | ||||
| 			opt['label'] = label | ||||
| 			opt['value'] = value | ||||
| 		else: | ||||
| 			raise ValueError("Cannot add options to a %s field." % self['type']) | ||||
|  | ||||
| 	def delOptions(self): | ||||
| 		optsXML = self.xml.findall('{%s}option' % self.namespace) | ||||
| 		for optXML in optsXML: | ||||
| 			self.xml.remove(optXML) | ||||
|  | ||||
| 	def delRequired(self): | ||||
| 		reqXML = self.xml.find('{%s}required' % self.namespace) | ||||
| 		if reqXML is not None: | ||||
| 			self.xml.remove(reqXML) | ||||
|  | ||||
| 	def delValue(self): | ||||
| 		valsXML = self.xml.findall('{%s}value' % self.namespace) | ||||
| 		for valXML in valsXML: | ||||
| 			self.xml.remove(valXML) | ||||
|  | ||||
| 	def getAnswer(self): | ||||
| 		return self.getValue() | ||||
|  | ||||
| 	def getOptions(self): | ||||
| 		options = [] | ||||
| 		optsXML = self.xml.findall('{%s}option' % self.namespace) | ||||
| 		for optXML in optsXML: | ||||
| 			opt = FieldOption(xml=optXML) | ||||
| 			options.append({'label': opt['label'], 'value':opt['value']}) | ||||
| 		return options | ||||
|  | ||||
| 	def getRequired(self): | ||||
| 		reqXML = self.xml.find('{%s}required' % self.namespace) | ||||
| 		return reqXML is not None | ||||
|  | ||||
| 	def getValue(self): | ||||
| 		valsXML = self.xml.findall('{%s}value' % self.namespace) | ||||
| 		if len(valsXML) == 0: | ||||
| 			return None | ||||
| 		elif self['type'] == 'boolean': | ||||
| 			return valsXML[0].text in self.true_values | ||||
| 		elif self['type'] in self.multi_value_types: | ||||
| 			values = [] | ||||
| 			for valXML in valsXML: | ||||
| 				if valXML.text is None: | ||||
| 					valXML.text = '' | ||||
| 				values.append(valXML.text) | ||||
| 			if self['type'] == 'text-multi': | ||||
| 				values = "\n".join(values) | ||||
| 			return values | ||||
| 		else: | ||||
| 			return valsXML[0].text | ||||
|  | ||||
| 	def setAnswer(self, answer): | ||||
| 		self.setValue(answer) | ||||
|  | ||||
| 	def setFalse(self): | ||||
| 		self.setValue(False) | ||||
|  | ||||
| 	def setOptions(self, options): | ||||
| 		for value in options: | ||||
| 			if isinstance(value, dict): | ||||
| 				self.addOption(**value) | ||||
| 			else: | ||||
| 				self.addOption(value=value) | ||||
|  | ||||
| 	def setRequired(self, required): | ||||
| 		exists = self.getRequired() | ||||
| 		if not exists and required: | ||||
| 			self.xml.append(ET.Element('{%s}required' % self.namespace)) | ||||
| 		elif exists and not required: | ||||
| 			self.delRequired() | ||||
|  | ||||
| 	def setTrue(self): | ||||
| 		self.setValue(True) | ||||
|  | ||||
| 	def setValue(self, value): | ||||
| 		self.delValue() | ||||
| 		valXMLName = '{%s}value' % self.namespace | ||||
|  | ||||
| 		if self['type'] == 'boolean': | ||||
| 			if value in self.true_values: | ||||
| 				valXML = ET.Element(valXMLName) | ||||
| 				valXML.text = '1' | ||||
| 				self.xml.append(valXML) | ||||
| 			else: | ||||
| 				valXML = ET.Element(valXMLName) | ||||
| 				valXML.text = '0' | ||||
| 				self.xml.append(valXML) | ||||
| 		elif self['type'] in self.multi_value_types or self['type'] in ['', None]: | ||||
| 			if self['type'] in self.multi_line_types and isinstance(value, str): | ||||
| 				value = value.split('\n') | ||||
| 			if not isinstance(value, list): | ||||
| 				value = [value] | ||||
| 			for val in value: | ||||
| 				if self['type'] in ['', None] and val in self.true_values: | ||||
| 					val = '1' | ||||
| 				valXML = ET.Element(valXMLName) | ||||
| 				valXML.text = val | ||||
| 				self.xml.append(valXML) | ||||
| 		else: | ||||
| 			if isinstance(value, list): | ||||
| 				raise ValueError("Cannot add multiple values to a %s field." % self['type']) | ||||
| 			valXML = ET.Element(valXMLName) | ||||
| 			valXML.text = value | ||||
| 			self.xml.append(valXML) | ||||
|  | ||||
|  | ||||
| class FieldOption(ElementBase): | ||||
| 	namespace = 'jabber:x:data' | ||||
| 	name = 'option' | ||||
| 	plugin_attrib = 'option' | ||||
| 	interfaces = set(('label', 'value')) | ||||
| 	sub_interfaces = set(('value',)) | ||||
|  | ||||
|  | ||||
| class xep_0004(base.base_plugin): | ||||
| 	""" | ||||
| 	XEP-0004: Data Forms | ||||
| 	""" | ||||
|  | ||||
| 	def plugin_init(self): | ||||
| 		self.xep = '0004' | ||||
| 		self.description = 'Data Forms' | ||||
|  | ||||
| 		self.xmpp.registerHandler( | ||||
| 			Callback('Data Form', | ||||
| 				 MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, | ||||
| 								   Form.namespace)), | ||||
| 				 self.handle_form)) | ||||
|  | ||||
| 		registerStanzaPlugin(FormField, FieldOption) | ||||
| 		registerStanzaPlugin(Form, FormField) | ||||
| 		registerStanzaPlugin(Message, Form) | ||||
|  | ||||
| 	def makeForm(self, ftype='form', title='', instructions=''): | ||||
| 		f = Form() | ||||
| 		f['type'] = ftype | ||||
| 		f['title'] = title | ||||
| 		f['instructions'] = instructions | ||||
| 		return f | ||||
|  | ||||
| 	def post_init(self): | ||||
| 		base.base_plugin.post_init(self) | ||||
| 		self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') | ||||
|  | ||||
| 	def handle_form(self, message): | ||||
| 		self.xmpp.event("message_xform", message) | ||||
|  | ||||
| 	def buildForm(self, xml): | ||||
| 		return Form(xml=xml) | ||||
							
								
								
									
										11
									
								
								sleekxmpp/plugins/xep_0004/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								sleekxmpp/plugins/xep_0004/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0004.stanza import Form | ||||
| from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption | ||||
| from sleekxmpp.plugins.xep_0004.dataforms import xep_0004 | ||||
							
								
								
									
										60
									
								
								sleekxmpp/plugins/xep_0004/dataforms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								sleekxmpp/plugins/xep_0004/dataforms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import copy | ||||
|  | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
|  | ||||
| from sleekxmpp import Message | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.plugins.base import base_plugin | ||||
| from sleekxmpp.plugins.xep_0004 import stanza | ||||
| from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption | ||||
|  | ||||
|  | ||||
| class xep_0004(base_plugin): | ||||
|     """ | ||||
|     XEP-0004: Data Forms | ||||
|     """ | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xep = '0004' | ||||
|         self.description = 'Data Forms' | ||||
|         self.stanza = stanza | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|             Callback('Data Form', | ||||
|                  StanzaPath('message/form'), | ||||
|                  self.handle_form)) | ||||
|  | ||||
|         register_stanza_plugin(FormField, FieldOption, iterable=True) | ||||
|         register_stanza_plugin(Form, FormField, iterable=True) | ||||
|         register_stanza_plugin(Message, Form) | ||||
|  | ||||
|     def make_form(self, ftype='form', title='', instructions=''): | ||||
|         f = Form() | ||||
|         f['type'] = ftype | ||||
|         f['title'] = title | ||||
|         f['instructions'] = instructions | ||||
|         return f | ||||
|  | ||||
|     def post_init(self): | ||||
|         base_plugin.post_init(self) | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') | ||||
|  | ||||
|     def handle_form(self, message): | ||||
|         self.xmpp.event("message_xform", message) | ||||
|  | ||||
|     def build_form(self, xml): | ||||
|         return Form(xml=xml) | ||||
|  | ||||
|  | ||||
| xep_0004.makeForm = xep_0004.make_form | ||||
| xep_0004.buildForm = xep_0004.build_form | ||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0004/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0004/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption | ||||
| from sleekxmpp.plugins.xep_0004.stanza.form import Form | ||||
							
								
								
									
										178
									
								
								sleekxmpp/plugins/xep_0004/stanza/field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								sleekxmpp/plugins/xep_0004/stanza/field.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase, ET | ||||
|  | ||||
|  | ||||
| class FormField(ElementBase): | ||||
|     namespace = 'jabber:x:data' | ||||
|     name = 'field' | ||||
|     plugin_attrib = 'field' | ||||
|     interfaces = set(('answer', 'desc', 'required', 'value', | ||||
|                       'options', 'label', 'type', 'var')) | ||||
|     sub_interfaces = set(('desc',)) | ||||
|     plugin_tag_map = {} | ||||
|     plugin_attrib_map = {} | ||||
|  | ||||
|     field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', | ||||
|                        'jid-single', 'list-multi', 'list-single', | ||||
|                        'text-multi', 'text-private', 'text-single')) | ||||
|  | ||||
|     true_values = set((True, '1', 'true')) | ||||
|     option_types = set(('list-multi', 'list-single')) | ||||
|     multi_line_types = set(('hidden', 'text-multi')) | ||||
|     multi_value_types = set(('hidden', 'jid-multi', | ||||
|                              'list-multi', 'text-multi')) | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         if ElementBase.setup(self, xml): | ||||
|             self._type = None | ||||
|         else: | ||||
|             self._type = self['type'] | ||||
|  | ||||
|     def set_type(self, value): | ||||
|         self._set_attr('type', value) | ||||
|         if value: | ||||
|             self._type = value | ||||
|  | ||||
|     def add_option(self, label='', value=''): | ||||
|         if self._type in self.option_types: | ||||
|             opt = FieldOption(parent=self) | ||||
|             opt['label'] = label | ||||
|             opt['value'] = value | ||||
|         else: | ||||
|             raise ValueError("Cannot add options to " + \ | ||||
|                              "a %s field." % self['type']) | ||||
|  | ||||
|     def del_options(self): | ||||
|         optsXML = self.xml.findall('{%s}option' % self.namespace) | ||||
|         for optXML in optsXML: | ||||
|             self.xml.remove(optXML) | ||||
|  | ||||
|     def del_required(self): | ||||
|         reqXML = self.xml.find('{%s}required' % self.namespace) | ||||
|         if reqXML is not None: | ||||
|             self.xml.remove(reqXML) | ||||
|  | ||||
|     def del_value(self): | ||||
|         valsXML = self.xml.findall('{%s}value' % self.namespace) | ||||
|         for valXML in valsXML: | ||||
|             self.xml.remove(valXML) | ||||
|  | ||||
|     def get_answer(self): | ||||
|         return self['value'] | ||||
|  | ||||
|     def get_options(self): | ||||
|         options = [] | ||||
|         optsXML = self.xml.findall('{%s}option' % self.namespace) | ||||
|         for optXML in optsXML: | ||||
|             opt = FieldOption(xml=optXML) | ||||
|             options.append({'label': opt['label'], 'value': opt['value']}) | ||||
|         return options | ||||
|  | ||||
|     def get_required(self): | ||||
|         reqXML = self.xml.find('{%s}required' % self.namespace) | ||||
|         return reqXML is not None | ||||
|  | ||||
|     def get_value(self): | ||||
|         valsXML = self.xml.findall('{%s}value' % self.namespace) | ||||
|         if len(valsXML) == 0: | ||||
|             return None | ||||
|         elif self._type == 'boolean': | ||||
|             return valsXML[0].text in self.true_values | ||||
|         elif self._type in self.multi_value_types or len(valsXML) > 1: | ||||
|             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: | ||||
|             if valsXML[0].text is None: | ||||
|                 return '' | ||||
|             return valsXML[0].text | ||||
|  | ||||
|     def set_answer(self, answer): | ||||
|         self['value'] = answer | ||||
|  | ||||
|     def set_false(self): | ||||
|         self['value'] = False | ||||
|  | ||||
|     def set_options(self, options): | ||||
|         for value in options: | ||||
|             if isinstance(value, dict): | ||||
|                 self.add_option(**value) | ||||
|             else: | ||||
|                 self.add_option(value=value) | ||||
|  | ||||
|     def set_required(self, required): | ||||
|         exists = self['required'] | ||||
|         if not exists and required: | ||||
|             self.xml.append(ET.Element('{%s}required' % self.namespace)) | ||||
|         elif exists and not required: | ||||
|             del self['required'] | ||||
|  | ||||
|     def set_true(self): | ||||
|         self['value'] = True | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         del self['value'] | ||||
|         valXMLName = '{%s}value' % self.namespace | ||||
|  | ||||
|         if self._type == 'boolean': | ||||
|             if value in self.true_values: | ||||
|                 valXML = ET.Element(valXMLName) | ||||
|                 valXML.text = '1' | ||||
|                 self.xml.append(valXML) | ||||
|             else: | ||||
|                 valXML = ET.Element(valXMLName) | ||||
|                 valXML.text = '0' | ||||
|                 self.xml.append(valXML) | ||||
|         elif self._type in self.multi_value_types or self._type in ('', None): | ||||
|             if not isinstance(value, list): | ||||
|                 value = value.replace('\r', '') | ||||
|                 value = value.split('\n') | ||||
|             for val in value: | ||||
|                 if self._type in ('', None) and val in self.true_values: | ||||
|                     val = '1' | ||||
|                 valXML = ET.Element(valXMLName) | ||||
|                 valXML.text = val | ||||
|                 self.xml.append(valXML) | ||||
|         else: | ||||
|             if isinstance(value, list): | ||||
|                 raise ValueError("Cannot add multiple values " + \ | ||||
|                                  "to a %s field." % self._type) | ||||
|             valXML = ET.Element(valXMLName) | ||||
|             valXML.text = value | ||||
|             self.xml.append(valXML) | ||||
|  | ||||
|  | ||||
| class FieldOption(ElementBase): | ||||
|     namespace = 'jabber:x:data' | ||||
|     name = 'option' | ||||
|     plugin_attrib = 'option' | ||||
|     interfaces = set(('label', 'value')) | ||||
|     sub_interfaces = set(('value',)) | ||||
|  | ||||
|  | ||||
| FormField.addOption = FormField.add_option | ||||
| FormField.delOptions = FormField.del_options | ||||
| FormField.delRequired = FormField.del_required | ||||
| FormField.delValue = FormField.del_value | ||||
| FormField.getAnswer = FormField.get_answer | ||||
| FormField.getOptions = FormField.get_options | ||||
| FormField.getRequired = FormField.get_required | ||||
| FormField.getValue = FormField.get_value | ||||
| FormField.setAnswer = FormField.set_answer | ||||
| FormField.setFalse = FormField.set_false | ||||
| FormField.setOptions = FormField.set_options | ||||
| FormField.setRequired = FormField.set_required | ||||
| FormField.setTrue = FormField.set_true | ||||
| FormField.setValue = FormField.set_value | ||||
							
								
								
									
										254
									
								
								sleekxmpp/plugins/xep_0004/stanza/form.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								sleekxmpp/plugins/xep_0004/stanza/form.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,254 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import copy | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase, ET | ||||
| from sleekxmpp.plugins.xep_0004.stanza import FormField | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class Form(ElementBase): | ||||
|     namespace = 'jabber:x:data' | ||||
|     name = 'x' | ||||
|     plugin_attrib = 'form' | ||||
|     interfaces = set(('fields', 'instructions', 'items', | ||||
|                       'reported', 'title', 'type', 'values')) | ||||
|     sub_interfaces = set(('title',)) | ||||
|     form_types = set(('cancel', 'form', 'result', 'submit')) | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         title = None | ||||
|         if 'title' in kwargs: | ||||
|             title = kwargs['title'] | ||||
|             del kwargs['title'] | ||||
|         ElementBase.__init__(self, *args, **kwargs) | ||||
|         if title is not None: | ||||
|             self['title'] = title | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         if ElementBase.setup(self, xml): | ||||
|             # If we had to generate xml | ||||
|             self['type'] = 'form' | ||||
|  | ||||
|     @property | ||||
|     def field(self): | ||||
|         return self['fields'] | ||||
|  | ||||
|     def set_type(self, ftype): | ||||
|         self._set_attr('type', ftype) | ||||
|         if ftype == 'submit': | ||||
|             fields = self['fields'] | ||||
|             for var in fields: | ||||
|                 field = fields[var] | ||||
|                 del field['type'] | ||||
|                 del field['label'] | ||||
|                 del field['desc'] | ||||
|                 del field['required'] | ||||
|                 del field['options'] | ||||
|         elif ftype == 'cancel': | ||||
|             del self['fields'] | ||||
|  | ||||
|     def add_field(self, var='', ftype=None, label='', desc='', | ||||
|                   required=False, value=None, options=None, **kwargs): | ||||
|         kwtype = kwargs.get('type', None) | ||||
|         if kwtype is None: | ||||
|             kwtype = ftype | ||||
|  | ||||
|         field = FormField(parent=self) | ||||
|         field['var'] = var | ||||
|         field['type'] = kwtype | ||||
|         field['value'] = value | ||||
|         if self['type'] in ('form', 'result'): | ||||
|             field['label'] = label | ||||
|             field['desc'] = desc | ||||
|             field['required'] = required | ||||
|             if options is not None: | ||||
|                 field['options'] = options | ||||
|         else: | ||||
|             del field['type'] | ||||
|         return field | ||||
|  | ||||
|     def getXML(self, type='submit'): | ||||
|         self['type'] = type | ||||
|         log.warning("Form.getXML() is deprecated API compatibility " + \ | ||||
|                     "with plugins/old_0004.py") | ||||
|         return self.xml | ||||
|  | ||||
|     def fromXML(self, xml): | ||||
|         log.warning("Form.fromXML() is deprecated API compatibility " + \ | ||||
|                     "with plugins/old_0004.py") | ||||
|         n = Form(xml=xml) | ||||
|         return n | ||||
|  | ||||
|     def add_item(self, values): | ||||
|         itemXML = ET.Element('{%s}item' % self.namespace) | ||||
|         self.xml.append(itemXML) | ||||
|         reported_vars = self['reported'].keys() | ||||
|         for var in reported_vars: | ||||
|             fieldXML = ET.Element('{%s}field' % FormField.namespace) | ||||
|             itemXML.append(fieldXML) | ||||
|             field = FormField(xml=fieldXML) | ||||
|             field['var'] = var | ||||
|             field['value'] = values.get(var, None) | ||||
|  | ||||
|     def add_reported(self, var, ftype=None, label='', desc='', **kwargs): | ||||
|         kwtype = kwargs.get('type', None) | ||||
|         if kwtype is None: | ||||
|             kwtype = ftype | ||||
|         reported = self.xml.find('{%s}reported' % self.namespace) | ||||
|         if reported is None: | ||||
|             reported = ET.Element('{%s}reported' % self.namespace) | ||||
|             self.xml.append(reported) | ||||
|         fieldXML = ET.Element('{%s}field' % FormField.namespace) | ||||
|         reported.append(fieldXML) | ||||
|         field = FormField(xml=fieldXML) | ||||
|         field['var'] = var | ||||
|         field['type'] = kwtype | ||||
|         field['label'] = label | ||||
|         field['desc'] = desc | ||||
|         return field | ||||
|  | ||||
|     def cancel(self): | ||||
|         self['type'] = 'cancel' | ||||
|  | ||||
|     def del_fields(self): | ||||
|         fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) | ||||
|         for fieldXML in fieldsXML: | ||||
|             self.xml.remove(fieldXML) | ||||
|  | ||||
|     def del_instructions(self): | ||||
|         instsXML = self.xml.findall('{%s}instructions') | ||||
|         for instXML in instsXML: | ||||
|             self.xml.remove(instXML) | ||||
|  | ||||
|     def del_items(self): | ||||
|         itemsXML = self.xml.find('{%s}item' % self.namespace) | ||||
|         for itemXML in itemsXML: | ||||
|             self.xml.remove(itemXML) | ||||
|  | ||||
|     def del_reported(self): | ||||
|         reportedXML = self.xml.find('{%s}reported' % self.namespace) | ||||
|         if reportedXML is not None: | ||||
|             self.xml.remove(reportedXML) | ||||
|  | ||||
|     def get_fields(self, use_dict=False): | ||||
|         fields = OrderedDict() | ||||
|         fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) | ||||
|         for fieldXML in fieldsXML: | ||||
|             field = FormField(xml=fieldXML) | ||||
|             fields[field['var']] = field | ||||
|         return fields | ||||
|  | ||||
|     def get_instructions(self): | ||||
|         instructions = '' | ||||
|         instsXML = self.xml.findall('{%s}instructions' % self.namespace) | ||||
|         return "\n".join([instXML.text for instXML in instsXML]) | ||||
|  | ||||
|     def get_items(self): | ||||
|         items = [] | ||||
|         itemsXML = self.xml.findall('{%s}item' % self.namespace) | ||||
|         for itemXML in itemsXML: | ||||
|             item = {} | ||||
|             fieldsXML = itemXML.findall('{%s}field' % FormField.namespace) | ||||
|             for fieldXML in fieldsXML: | ||||
|                 field = FormField(xml=fieldXML) | ||||
|                 item[field['var']] = field['value'] | ||||
|             items.append(item) | ||||
|         return items | ||||
|  | ||||
|     def get_reported(self): | ||||
|         fields = {} | ||||
|         xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, | ||||
|                                      FormField.namespace)) | ||||
|         for field in xml: | ||||
|             field = FormField(xml=field) | ||||
|             fields[field['var']] = field | ||||
|         return fields | ||||
|  | ||||
|     def get_values(self): | ||||
|         values = {} | ||||
|         fields = self['fields'] | ||||
|         for var in fields: | ||||
|             values[var] = fields[var]['value'] | ||||
|         return values | ||||
|  | ||||
|     def reply(self): | ||||
|         if self['type'] == 'form': | ||||
|             self['type'] = 'submit' | ||||
|         elif self['type'] == 'submit': | ||||
|             self['type'] = 'result' | ||||
|  | ||||
|     def set_fields(self, fields): | ||||
|         del self['fields'] | ||||
|         if not isinstance(fields, list): | ||||
|             fields = fields.items() | ||||
|         for var, field in fields: | ||||
|             field['var'] = var | ||||
|             self.add_field(**field) | ||||
|  | ||||
|     def set_instructions(self, instructions): | ||||
|         del self['instructions'] | ||||
|         if instructions in [None, '']: | ||||
|             return | ||||
|         instructions = instructions.split('\n') | ||||
|         for instruction in instructions: | ||||
|             inst = ET.Element('{%s}instructions' % self.namespace) | ||||
|             inst.text = instruction | ||||
|             self.xml.append(inst) | ||||
|  | ||||
|     def set_items(self, items): | ||||
|         for item in items: | ||||
|             self.add_item(item) | ||||
|  | ||||
|     def set_reported(self, reported): | ||||
|         for var in reported: | ||||
|             field = reported[var] | ||||
|             field['var'] = var | ||||
|             self.add_reported(var, **field) | ||||
|  | ||||
|     def set_values(self, values): | ||||
|         fields = self['fields'] | ||||
|         for field in values: | ||||
|             fields[field]['value'] = values[field] | ||||
|  | ||||
|     def merge(self, other): | ||||
|         new = copy.copy(self) | ||||
|         if type(other) == dict: | ||||
|             new['values'] = other | ||||
|             return new | ||||
|         nfields = new['fields'] | ||||
|         ofields = other['fields'] | ||||
|         nfields.update(ofields) | ||||
|         new['fields'] = nfields | ||||
|         return new | ||||
|  | ||||
|  | ||||
| Form.setType = Form.set_type | ||||
| Form.addField = Form.add_field | ||||
| Form.addItem = Form.add_item | ||||
| Form.addReported = Form.add_reported | ||||
| Form.delFields = Form.del_fields | ||||
| Form.delInstructions = Form.del_instructions | ||||
| Form.delItems = Form.del_items | ||||
| Form.delReported = Form.del_reported | ||||
| Form.getFields = Form.get_fields | ||||
| Form.getInstructions = Form.get_instructions | ||||
| Form.getItems = Form.get_items | ||||
| Form.getReported = Form.get_reported | ||||
| Form.getValues = Form.get_values | ||||
| Form.setFields = Form.set_fields | ||||
| Form.setInstructions = Form.set_instructions | ||||
| Form.setItems = Form.set_items | ||||
| Form.setReported = Form.set_reported | ||||
| Form.setValues = Form.set_values | ||||
| @@ -463,7 +463,7 @@ class RemoteSession(object): | ||||
|         key = "%s.%s" % (endpoint, name) | ||||
|         log.debug("Registering call handler for %s (%s)." % (key, method)) | ||||
|         with self._lock: | ||||
|             if self._entries.has_key(key): | ||||
|             if key in self._entries: | ||||
|                 raise KeyError("A handler for %s has already been regisered!" % endpoint) | ||||
|             self._entries[key] = JabberRPCEntry(endpoint, method) | ||||
|         return key | ||||
|   | ||||
| @@ -112,7 +112,4 @@ class xep_0012(base.base_plugin): | ||||
|         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 | ||||
|         return result['last_activity']['seconds'] | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user