Compare commits
	
		
			292 Commits
		
	
	
		
			sleek-1.0-
			...
			sleek-1.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 79f1aa0e1b | ||
|   | fb5a6a7d71 | ||
|   | 7d1c5f4a2b | ||
|   | 1b0fd76b45 | ||
|   | 46e93bea09 | ||
|   | cbc6a0296b | ||
|   | cc63bef179 | ||
|   | cbcfa156c4 | ||
|   | 4a12e1059a | ||
|   | f9cd051209 | ||
|   | 03bc38f7e3 | ||
|   | 4e23a4e08e | ||
|   | 8cafa8578f | ||
|   | b74ea47650 | ||
|   | 2dc230a68b | ||
|   | 522f0dac16 | ||
|   | cd5ae944ec | ||
|   | 42a86fe0d4 | ||
|   | e928b9c434 | ||
|   | fb55d9e9d1 | ||
|   | 74e7e5a291 | ||
|   | 6c58b8cc4b | ||
|   | 2b3d11a7a5 | ||
|   | 9950208d06 | ||
|   | a67e16d1b7 | ||
|   | c98a22e065 | ||
|   | d496417deb | ||
|   | 116bb6e1b9 | ||
|   | 9c6dde5d22 | ||
|   | fc8a13df5a | ||
|   | 85e9042db6 | ||
|   | 62e6d6fb4c | ||
|   | 16c72e8efd | ||
|   | efe1b9f5a9 | ||
|   | 65dbddb6b6 | ||
|   | 2a67a31120 | ||
|   | a720c3348b | ||
|   | 79ac60b6e8 | ||
|   | e01c2d222a | ||
|   | 8922e2050a | ||
|   | a85891c611 | ||
|   | 2586fdffda | ||
|   | c9dc9ec11e | ||
|   | b9332142c9 | ||
|   | b7b53362e1 | ||
|   | 68cf66a5fe | ||
|   | 4eb7eeb40f | ||
|   | a1d64fa215 | ||
|   | 5f44c0e678 | ||
|   | b87c4d786d | ||
|   | 329b0df3f6 | ||
|   | 6906c15e8e | ||
|   | ff5421cefc | ||
|   | 4498e992a2 | ||
|   | 2d610dfdc8 | ||
|   | 2b0a05ee32 | ||
|   | bc2d0ee9a8 | ||
|   | 862a2a1440 | ||
|   | fba60ffff1 | ||
|   | d1a945a305 | ||
|   | 685b9ab102 | ||
|   | 24f27c0fe3 | ||
|   | 3019c82d8a | ||
|   | f9d0b55ca3 | ||
|   | b54cc97e4c | ||
|   | e3b9d5abbf | ||
|   | 2332970cf2 | ||
|   | 48af3d3322 | ||
|   | 429c94d6a9 | ||
|   | deb52ad350 | ||
|   | 6f3cc77bb5 | ||
|   | 1baf139ca4 | ||
|   | 7945b3e738 | ||
|   | d4c1ff5309 | ||
|   | 22868c3924 | ||
|   | 2de1be188c | ||
|   | 9faecec2db | ||
|   | 5d7111fe3b | ||
|   | 0c86f8288d | ||
|   | 5a6a65fd9f | ||
|   | 43c4d23896 | ||
|   | 9f9e8db814 | ||
|   | b8efcc7cf0 | ||
|   | 2f29d18e53 | ||
|   | 888e286a09 | ||
|   | 1a93a187f0 | ||
|   | a8d5da5091 | ||
|   | e2720fac9e | ||
|   | 4374729f20 | ||
|   | 87999333cb | ||
|   | 335dc2927b | ||
|   | ccbef6b696 | ||
|   | 3e384d3cfe | ||
|   | e33949c397 | ||
|   | eccac859ad | ||
|   | 7dd586f2fd | ||
|   | 3607c5b792 | ||
|   | e37adace62 | ||
|   | d10f591bf4 | ||
|   | 262da78ca7 | ||
|   | 0b83edf439 | ||
|   | cf7fcf496e | ||
|   | 1765271f84 | ||
|   | 0ec79f8dc3 | ||
|   | 6f72c05ebf | ||
|   | 20cacc84ba | ||
|   | 24a14a0284 | ||
|   | 982c2d9b83 | ||
|   | efa4a9b330 | ||
|   | 39ec1cff19 | ||
|   | 24c5f8d374 | ||
|   | d6b0158ddb | ||
|   | 7e5e9542e9 | ||
|   | d7fc2aaa9c | ||
|   | 8471a485d1 | ||
|   | 462b375c8f | ||
|   | afbd506cfc | ||
|   | ec01e45ed1 | ||
|   | 993829b23f | ||
|   | 002257b820 | ||
|   | 0af35c2224 | ||
|   | 76bc0a2ba6 | ||
|   | d2dc4824ee | ||
|   | 3f9ca0366b | ||
|   | b68785e19e | ||
|   | a1bbb719e1 | ||
|   | 46f23f7348 | ||
|   | 09252baa71 | ||
|   | 3623a7a16a | ||
|   | cc504ab07c | ||
|   | 2500a0649b | ||
|   | 5ec4e4a026 | ||
|   | c3df4dd052 | ||
|   | 730c3fada0 | ||
|   | 628978fc8c | ||
|   | 7fb9d68714 | ||
|   | e0a1c477d0 | ||
|   | b70565720f | ||
|   | 33ac0c9dd6 | ||
|   | 4699bdff60 | ||
|   | 354641a3ce | ||
|   | 58a43e40c7 | ||
|   | 6b7fde10d3 | ||
|   | 13fdab0139 | ||
|   | 2ce617b2ce | ||
|   | 63e0496c30 | ||
|   | 850e3bb99b | ||
|   | 2d90deb96a | ||
|   | 3fb3f63e51 | ||
|   | d12949ff1c | ||
|   | e3e985220e | ||
|   | 802dd8393d | ||
|   | fe6bc31c60 | ||
|   | 2162d6042e | ||
|   | b8a4ffece9 | ||
|   | d929e0deb2 | ||
|   | 4c08c9c524 | ||
|   | 63b8444abe | ||
|   | 82546d776d | ||
|   | 84f9505a8d | ||
|   | ede59ab40e | ||
|   | 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 | ||
|   | ea95811c4c | ||
|   | 08cb5f42e7 | ||
|   | 9abf37bbd1 | ||
|   | 89cffd43f4 | ||
|   | d7fe724145 | ||
|   | ad978700fc | ||
|   | 20df6348a4 | ||
|   | 48fb7006f7 | ||
|   | 7057984831 | ||
|   | 847510c6b5 | ||
|   | 774e0f2022 | ||
|   | d1e12cd46f | ||
|   | adf6d49fd1 | ||
|   | 0826a44d4b | ||
|   | ce145b04ac | ||
|   | 29d775e675 | ||
|   | 251a47db8c | ||
|   | e4b3a191d6 | ||
|   | 393d702e77 | ||
|   | a3597d6deb | ||
|   | 8fada4d015 | ||
|   | 6d59f55fd4 | ||
|   | 937dce8e65 | ||
|   | 5c1562f36b | ||
|   | 8eb59072b4 | ||
|   | 20d053807d | ||
|   | 8aa4396e44 | ||
|   | 788a5b73f9 | ||
|   | 5ed27bf5f6 | ||
|   | 62bdaab7c7 | ||
|   | bb2bc64d15 | ||
|   | baa1eaf73a | ||
|   | 4c7da3899e | ||
|   | 4d3593ac86 | ||
|   | c49a8e9114 | ||
|   | d3bd9cd31d | ||
|   | e3b14bc5a9 | ||
|   | 8e46aa7054 | ||
|   | 8a22597180 | ||
|   | e919906c8c | ||
|   | 46dc6eac88 | ||
|   | b9bf30e095 | ||
|   | b60c51ef13 | ||
|   | 6b05938573 | ||
|   | 77601f7262 | ||
|   | d9be51b2ef | ||
|   | 393259c24b | ||
|   | 756c4c032f | ||
|   | e1360ae049 | ||
|   | b048f8d733 | ||
|   | f65f88325b | ||
|   | 42c8f6ae87 | ||
|   | e4f3b777f9 | ||
|   | a278f79bdb | ||
|   | 606c369173 | ||
|   | 3c871920b1 | ||
|   | de6170a13d | ||
|   | 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 | 
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,8 @@ | |||||||
| *.pyc | *.pyc | ||||||
| build/ | build/ | ||||||
|  | dist/ | ||||||
|  | MANIFEST | ||||||
|  | docs/_build/ | ||||||
|  | *.swp | ||||||
|  | .tox/ | ||||||
|  | .coverage | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -21,8 +21,8 @@ THE SOFTWARE. | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Licences of Bundled Third Pary Code | Licenses of Bundled Third Party Code | ||||||
| ----------------------------------- | ------------------------------------ | ||||||
|  |  | ||||||
| dateutil - Extensions to the standard python 2.3+ datetime module. | dateutil - Extensions to the standard python 2.3+ datetime module. | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | include README.rst | ||||||
|  | include LICENSE | ||||||
|  | include testall.py | ||||||
|  | recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png | ||||||
|  | recursive-include examples *.py | ||||||
|  | recursive-include tests *.py | ||||||
							
								
								
									
										50
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								README
									
									
									
									
									
								
							| @@ -1,50 +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 |  | ||||||
|  |  | ||||||
| Dave Cridland, for his Suelta SASL library. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Feel free to add fritzy@netflint.net to your roster for direct support and comments. |  | ||||||
| Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion. |  | ||||||
| Join sleek@conference.jabber.org for groupchat discussion. |  | ||||||
							
								
								
									
										182
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,182 @@ | |||||||
|  | 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 | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | Get the latest stable version from PyPI:: | ||||||
|  |  | ||||||
|  |     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. | ||||||
|  |  | ||||||
|  | **Latest Release** | ||||||
|  |     - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_ | ||||||
|  |  | ||||||
|  | **Develop Releases** | ||||||
|  |     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||||
|  |  | ||||||
|  | **Older Stable Releases** | ||||||
|  |     - `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_   | ||||||
|  |     - `1.0 RC2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC2>`_   | ||||||
|  |     - `1.0 RC1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC1>`_   | ||||||
|  |     - `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>`_ | ||||||
|  |  | ||||||
|  | Installing DNSPython | ||||||
|  | --------------------- | ||||||
|  | If you are using Python3 and wish to use dnspython, you will have to checkout and | ||||||
|  | install the ``python3`` branch:: | ||||||
|  |  | ||||||
|  |     git clone http://github.com/rthalley/dnspython | ||||||
|  |     cd dnspython | ||||||
|  |     git checkout python3 | ||||||
|  |     python3 setup.py install | ||||||
|  |  | ||||||
|  | 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.session_start) | ||||||
|  |             self.add_event_handler("message", self.message) | ||||||
|  |  | ||||||
|  |             self.register_plugin('xep_0030') # Service Discovery | ||||||
|  |             self.register_plugin('xep_0199') # XMPP Ping | ||||||
|  |  | ||||||
|  |             # If you are working with an OpenFire server, you will | ||||||
|  |             # need to use a different SSL version: | ||||||
|  |             # import ssl | ||||||
|  |             # self.ssl_version = ssl.PROTOCOL_SSLv3 | ||||||
|  |  | ||||||
|  |         def session_start(self, event): | ||||||
|  |             self.send_presence() | ||||||
|  |  | ||||||
|  |             # Most get_*/set_* 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.connect() | ||||||
|  |         xmpp.process(block=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 former 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>`_) | ||||||
| @@ -122,14 +122,14 @@ if __name__ == '__main__': | |||||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
| 	#load xml config | 	#load xml config | ||||||
| 	logging.info("Loading config file: %s" % opts.configfile) | 	logging.info("Loading config file: %s" , opts.configfile) | ||||||
| 	config = configparser.RawConfigParser() | 	config = configparser.RawConfigParser() | ||||||
| 	config.read(opts.configfile) | 	config.read(opts.configfile) | ||||||
| 	 | 	 | ||||||
| 	#init | 	#init | ||||||
| 	logging.info("Account 1 is %s" % config.get('account1', 'jid')) | 	logging.info("Account 1 is %s" , config.get('account1', 'jid')) | ||||||
| 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | ||||||
| 	logging.info("Account 2 is %s" % config.get('account2', 'jid')) | 	logging.info("Account 2 is %s" , config.get('account2', 'jid')) | ||||||
| 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | ||||||
| 	 | 	 | ||||||
| 	xmpp1.registerPlugin('xep_0004') | 	xmpp1.registerPlugin('xep_0004') | ||||||
|   | |||||||
| @@ -186,14 +186,14 @@ if __name__ == '__main__': | |||||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
| 	#load xml config | 	#load xml config | ||||||
| 	logging.info("Loading config file: %s" % opts.configfile) | 	logging.info("Loading config file: %s" , opts.configfile) | ||||||
| 	config = configparser.RawConfigParser() | 	config = configparser.RawConfigParser() | ||||||
| 	config.read(opts.configfile) | 	config.read(opts.configfile) | ||||||
| 	 | 	 | ||||||
| 	#init | 	#init | ||||||
| 	logging.info("Account 1 is %s" % config.get('account1', 'jid')) | 	logging.info("Account 1 is %s" , config.get('account1', 'jid')) | ||||||
| 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | ||||||
| 	logging.info("Account 2 is %s" % config.get('account2', 'jid')) | 	logging.info("Account 2 is %s" , config.get('account2', 'jid')) | ||||||
| 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | ||||||
| 	 | 	 | ||||||
| 	xmpp1.registerPlugin('xep_0004') | 	xmpp1.registerPlugin('xep_0004') | ||||||
|   | |||||||
| @@ -329,11 +329,11 @@ if __name__ == '__main__': | |||||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
| 	#load xml config | 	#load xml config | ||||||
| 	logging.info("Loading config file: %s" % opts.configfile) | 	logging.info("Loading config file: %s" , opts.configfile) | ||||||
| 	config = ET.parse(os.path.expanduser(opts.configfile)).find('auth') | 	config = ET.parse(os.path.expanduser(opts.configfile)).find('auth') | ||||||
| 	 | 	 | ||||||
| 	#init | 	#init | ||||||
| 	logging.info("Logging in as %s" % config.attrib['jid']) | 	logging.info("Logging in as %s" , config.attrib['jid']) | ||||||
| 	 | 	 | ||||||
| 	 | 	 | ||||||
| 	plugin_config = {} | 	plugin_config = {} | ||||||
|   | |||||||
							
								
								
									
										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 | 
							
								
								
									
										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; | ||||||
|  | } | ||||||
							
								
								
									
										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: | ||||||
							
								
								
									
										8
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | ========== | ||||||
|  | ClientXMPP | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | .. module:: sleekxmpp.clientxmpp | ||||||
|  |  | ||||||
|  | .. autoclass:: ClientXMPP | ||||||
|  |     :members: | ||||||
							
								
								
									
										8
									
								
								docs/api/componentxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/componentxmpp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | ============= | ||||||
|  | ComponentXMPP | ||||||
|  | ============= | ||||||
|  |  | ||||||
|  | .. module:: sleekxmpp.componentxmpp | ||||||
|  |  | ||||||
|  | .. autoclass:: ComponentXMPP | ||||||
|  |     :members: | ||||||
							
								
								
									
										14
									
								
								docs/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								docs/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | Exceptions | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | .. module:: sleekxmpp.exceptions | ||||||
|  |  | ||||||
|  |      | ||||||
|  | .. autoexception:: XMPPError | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: IqError | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: IqTimeout | ||||||
|  |     :members: | ||||||
							
								
								
									
										12
									
								
								docs/api/xmlstream/filesocket.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docs/api/xmlstream/filesocket.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | .. module:: sleekxmpp.xmlstream.filesocket | ||||||
|  |  | ||||||
|  | .. _filesocket: | ||||||
|  |  | ||||||
|  | Python 2.6 File Socket Shims | ||||||
|  | ============================ | ||||||
|  |  | ||||||
|  | .. autoclass:: FileSocket | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: Socket26 | ||||||
|  |     :members: | ||||||
							
								
								
									
										24
									
								
								docs/api/xmlstream/handler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								docs/api/xmlstream/handler.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | Stanza Handlers | ||||||
|  | =============== | ||||||
|  |  | ||||||
|  | The Basic Handler | ||||||
|  | ----------------- | ||||||
|  | .. module:: sleekxmpp.xmlstream.handler.base | ||||||
|  |  | ||||||
|  | .. autoclass:: BaseHandler | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | Callback | ||||||
|  | -------- | ||||||
|  | .. module:: sleekxmpp.xmlstream.handler.callback | ||||||
|  |  | ||||||
|  | .. autoclass:: Callback | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Waiter | ||||||
|  | ------ | ||||||
|  | .. module:: sleekxmpp.xmlstream.handler.waiter | ||||||
|  |  | ||||||
|  | .. autoclass:: Waiter | ||||||
|  |     :members: | ||||||
							
								
								
									
										7
									
								
								docs/api/xmlstream/jid.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/api/xmlstream/jid.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | Jabber IDs (JID) | ||||||
|  | ================= | ||||||
|  |  | ||||||
|  | .. module:: sleekxmpp.xmlstream.jid | ||||||
|  |  | ||||||
|  | .. autoclass:: JID | ||||||
|  |     :members: | ||||||
							
								
								
									
										41
									
								
								docs/api/xmlstream/matcher.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								docs/api/xmlstream/matcher.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | Stanza Matchers | ||||||
|  | =============== | ||||||
|  |  | ||||||
|  | The Basic Matcher | ||||||
|  | ----------------- | ||||||
|  | .. module:: sleekxmpp.xmlstream.matcher.base | ||||||
|  |  | ||||||
|  | .. autoclass:: MatcherBase | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ID Matching | ||||||
|  | ----------- | ||||||
|  | .. module:: sleekxmpp.xmlstream.matcher.id | ||||||
|  |  | ||||||
|  | .. autoclass:: MatcherId | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Stanza Path Matching | ||||||
|  | -------------------- | ||||||
|  | .. module:: sleekxmpp.xmlstream.matcher.stanzapath | ||||||
|  |  | ||||||
|  | .. autoclass:: StanzaPath | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | XPath | ||||||
|  | ----- | ||||||
|  | .. module:: sleekxmpp.xmlstream.matcher.xpath | ||||||
|  |  | ||||||
|  | .. autoclass:: MatchXPath | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | XMLMask | ||||||
|  | ------- | ||||||
|  | .. module:: sleekxmpp.xmlstream.matcher.xmlmask | ||||||
|  |  | ||||||
|  | .. autoclass:: MatchXMLMask | ||||||
|  |     :members: | ||||||
							
								
								
									
										11
									
								
								docs/api/xmlstream/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								docs/api/xmlstream/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | ========= | ||||||
|  | Scheduler | ||||||
|  | ========= | ||||||
|  |  | ||||||
|  | .. module:: sleekxmpp.xmlstream.scheduler | ||||||
|  |  | ||||||
|  | .. autoclass:: Task | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoclass:: Scheduler | ||||||
|  |     :members: | ||||||
							
								
								
									
										123
									
								
								docs/api/xmlstream/stanzabase.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								docs/api/xmlstream/stanzabase.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | .. _stanzabase: | ||||||
|  |  | ||||||
|  | ============== | ||||||
|  | Stanza Objects | ||||||
|  | ============== | ||||||
|  |  | ||||||
|  | .. module:: sleekxmpp.xmlstream.stanzabase | ||||||
|  |  | ||||||
|  | The :mod:`~sleekmxpp.xmlstream.stanzabase` module provides a wrapper for the | ||||||
|  | standard :mod:`~xml.etree.ElementTree` module that makes working with XML | ||||||
|  | less painful. Instead of having to manually move up and down an element | ||||||
|  | tree and insert subelements and attributes, you can interact with an object | ||||||
|  | that behaves like a normal dictionary or JSON object, which silently maps | ||||||
|  | keys to XML attributes and elements behind the scenes. | ||||||
|  |  | ||||||
|  | Overview | ||||||
|  | -------- | ||||||
|  |  | ||||||
|  | The usefulness of this layer grows as the XML you have to work with | ||||||
|  | becomes nested. The base unit here, :class:`ElementBase`, can map to a | ||||||
|  | single XML element, or several depending on how advanced of a mapping | ||||||
|  | is desired from interface keys to XML structures. For example, a single | ||||||
|  | :class:`ElementBase` derived class could easily describe: | ||||||
|  |  | ||||||
|  | .. code-block:: xml | ||||||
|  |  | ||||||
|  |     <message to="user@example.com" from="friend@example.com"> | ||||||
|  |       <body>Hi!</body> | ||||||
|  |       <x:extra> | ||||||
|  |         <x:item>Custom item 1</x:item> | ||||||
|  |         <x:item>Custom item 2</x:item> | ||||||
|  |         <x:item>Custom item 3</x:item> | ||||||
|  |       </x:extra> | ||||||
|  |     </message> | ||||||
|  |  | ||||||
|  | If that chunk of XML were put in the :class:`ElementBase` instance | ||||||
|  | ``msg``, we could extract the data from the XML using:: | ||||||
|  |  | ||||||
|  |     >>> msg['extra'] | ||||||
|  |     ['Custom item 1', 'Custom item 2', 'Custom item 3'] | ||||||
|  |  | ||||||
|  | Provided we set up the handler for the ``'extra'`` interface to load the | ||||||
|  | ``<x:item>`` element content into a list. | ||||||
|  |  | ||||||
|  | The key concept is that given an XML structure that will be repeatedly | ||||||
|  | used, we can define a set of :term:`interfaces` which when we read from, | ||||||
|  | write to, or delete, will automatically manipulate the underlying XML | ||||||
|  | as needed. In addition, some of these interfaces may in turn reference | ||||||
|  | child objects which expose interfaces for particularly complex child | ||||||
|  | elements of the original XML chunk. | ||||||
|  |  | ||||||
|  | .. seealso:: | ||||||
|  |     :ref:`create-stanza-interfaces`. | ||||||
|  |  | ||||||
|  | Because the :mod:`~sleekxmpp.xmlstream.stanzabase` module was developed | ||||||
|  | as part of an `XMPP <http://xmpp.org>`_ library, these chunks of XML are | ||||||
|  | referred to as :term:`stanzas <stanza>`, and in SleekXMPP we refer to a | ||||||
|  | subclass of :class:`ElementBase` which defines the interfaces needed for | ||||||
|  | interacting with a given :term:`stanza` a :term:`stanza object`. | ||||||
|  |  | ||||||
|  | To make dealing with more complicated and nested :term:`stanzas <stanza>` | ||||||
|  | or XML chunks easier, :term:`stanza objects <stanza object>` can be | ||||||
|  | composed in two ways: as iterable child objects or as plugins. Iterable | ||||||
|  | child stanzas, or :term:`substanzas`, are accessible through a special | ||||||
|  | ``'substanzas'`` interface. This option is useful for stanzas which | ||||||
|  | may contain more than one of the same kind of element. When there is | ||||||
|  | only one child element, the plugin method is more useful. For plugins, | ||||||
|  | a parent stanza object delegates one of its XML child elements to the | ||||||
|  | plugin stanza object. Here is an example: | ||||||
|  |  | ||||||
|  | .. code-block:: xml | ||||||
|  |  | ||||||
|  |     <iq type="result"> | ||||||
|  |       <query xmlns="http://jabber.org/protocol/disco#info"> | ||||||
|  |         <identity category="client" type="bot" name="SleekXMPP Bot" /> | ||||||
|  |       </query> | ||||||
|  |     </iq> | ||||||
|  |  | ||||||
|  | We can can arrange this stanza into two objects: an outer, wrapper object for | ||||||
|  | dealing with the ``<iq />`` element and its attributes, and a plugin object to | ||||||
|  | control the ``<query />`` payload element. If we give the plugin object the | ||||||
|  | name ``'disco_info'`` (using its :attr:`ElementBase.plugin_attrib` value), then | ||||||
|  | we can access the plugin as so:: | ||||||
|  |  | ||||||
|  |     >>> iq['disco_info'] | ||||||
|  |     '<query xmlns="http://jabber.org/protocol/disco#info"> | ||||||
|  |       <identity category="client" type="bot" name="SleekXMPP Bot" /> | ||||||
|  |     </query>' | ||||||
|  |  | ||||||
|  | We can then drill down through the plugin object's interfaces as desired:: | ||||||
|  |  | ||||||
|  |     >>> iq['disco_info']['identities'] | ||||||
|  |     [('client', 'bot', 'SleekXMPP Bot')] | ||||||
|  |  | ||||||
|  | Plugins may also add new interfaces to the parent stanza object as if they | ||||||
|  | had been defined by the parent directly, and can also override the behaviour | ||||||
|  | of an interface defined by the parent. | ||||||
|  |  | ||||||
|  | .. seealso:: | ||||||
|  |  | ||||||
|  |     - :ref:`create-stanza-plugins` | ||||||
|  |     - :ref:`create-extension-plugins` | ||||||
|  |     - :ref:`override-parent-interfaces` | ||||||
|  |       | ||||||
|  |  | ||||||
|  | Registering Stanza Plugins | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  | .. autofunction:: register_stanza_plugin | ||||||
|  |  | ||||||
|  | ElementBase | ||||||
|  | ----------- | ||||||
|  |  | ||||||
|  | .. autoclass:: ElementBase | ||||||
|  |     :members: | ||||||
|  |     :private-members: | ||||||
|  |     :special-members: | ||||||
|  |  | ||||||
|  | StanzaBase | ||||||
|  | ---------- | ||||||
|  |  | ||||||
|  | .. autoclass:: StanzaBase | ||||||
|  |     :members: | ||||||
							
								
								
									
										46
									
								
								docs/api/xmlstream/tostring.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								docs/api/xmlstream/tostring.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | .. module:: sleekxmpp.xmlstream.tostring | ||||||
|  |  | ||||||
|  | .. _tostring: | ||||||
|  |  | ||||||
|  | XML Serialization | ||||||
|  | ================= | ||||||
|  |  | ||||||
|  | Since the XML layer of SleekXMPP is based on :mod:`~xml.etree.ElementTree`, | ||||||
|  | why not just use the built-in :func:`~xml.etree.ElementTree.tostring` | ||||||
|  | method? The answer is that using that method produces ugly results when | ||||||
|  | using namespaces. The :func:`tostring()` method used here intelligently | ||||||
|  | hides namespaces when able and does not introduce excessive namespace | ||||||
|  | prefixes:: | ||||||
|  |  | ||||||
|  |     >>> from sleekxmpp.xmlstream.tostring import tostring | ||||||
|  |     >>> from xml.etree import cElementTree as ET | ||||||
|  |     >>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>') | ||||||
|  |     >>> ET.tostring(xml) | ||||||
|  |     '<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>' | ||||||
|  |     >>> tostring(xml) | ||||||
|  |     '<foo xmlns="bar"><baz /></foo>' | ||||||
|  |  | ||||||
|  | As a side effect of this namespace hiding, using :func:`tostring()` may | ||||||
|  | produce unexpected results depending on how the :func:`tostring()` method | ||||||
|  | is invoked. For example, when sending XML on the wire, the main XMPP | ||||||
|  | stanzas with their namespace of ``jabber:client`` will not include the | ||||||
|  | namespace because that is already declared by the stream header. But, if | ||||||
|  | you create a :class:`~sleekxmpp.stanza.message.Message` instance and dump | ||||||
|  | it to the terminal, the ``jabber:client`` namespace will appear. | ||||||
|  |  | ||||||
|  | .. autofunction:: tostring | ||||||
|  |  | ||||||
|  | Escaping Special Characters | ||||||
|  | --------------------------- | ||||||
|  |  | ||||||
|  | In order to prevent errors when sending arbitrary text as the textual | ||||||
|  | content of an XML element, certain characters must be escaped. These | ||||||
|  | are: ``&``, ``<``, ``>``, ``"``, and ``'``. The default escaping | ||||||
|  | mechanism is to replace those characters with their equivalent escape | ||||||
|  | entities: ``&``, ``<``, ``>``, ``'``, and ``"``. | ||||||
|  |  | ||||||
|  | In the future, the use of CDATA sections may be allowed to reduce the | ||||||
|  | size of escaped text or for when other XMPP processing agents do not | ||||||
|  | undertand these entities. | ||||||
|  |  | ||||||
|  | .. autofunction:: xml_escape | ||||||
							
								
								
									
										10
									
								
								docs/api/xmlstream/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docs/api/xmlstream/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | ========== | ||||||
|  | XML Stream | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | .. module:: sleekxmpp.xmlstream.xmlstream | ||||||
|  |  | ||||||
|  | .. autoexception:: RestartStream | ||||||
|  |  | ||||||
|  | .. autoclass:: XMLStream | ||||||
|  |     :members: | ||||||
							
								
								
									
										177
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | |||||||
|  | .. 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 | ||||||
|  | ------------------------- | ||||||
|  | :class:`~sleekxmpp.xmlstream.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 | ||||||
|  | ~~~~~~~~~~~~~~~~ | ||||||
|  | :class:`~sleekxmpp.xmlstream.xmlstream.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 :class:`~sleekxmpp.stanza.Message` | ||||||
|  |    :term:`stanza object` because the namespaced element name | ||||||
|  |    ``{jabber:client}message`` is associated with the class | ||||||
|  |    :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 :class:`~sleekxmpp.stanza.Message` object is thus paired with the message stanza handler | ||||||
|  |    :meth:`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 event | ||||||
|  |        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 | ||||||
|  |    :class:`~sleekxmpp.stanza.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 :meth:`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 :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` attempts to shy away | ||||||
|  | from anything too XMPP specific, :class:`~sleekxmpp.basexmpp.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 | ||||||
|  | :class:`~sleekxmpp.basexmpp.BaseXMPP`. | ||||||
|  |  | ||||||
|  | .. index:: ClientXMPP, BaseXMPP | ||||||
|  |  | ||||||
|  | ClientXMPP | ||||||
|  | ---------- | ||||||
|  | :class:`~sleekxmpp.clientxmpp.ClientXMPP` extends | ||||||
|  | :class:`~sleekxmpp.clientxmpp.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 | ||||||
|  | ------------- | ||||||
|  | :class:`~sleekxmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of | ||||||
|  | :class:`~sleekxmpp.basexmpp.BaseXMPP` that implements the component handshake | ||||||
|  | protocol. | ||||||
							
								
								
									
										222
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,222 @@ | |||||||
|  | # -*- 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', 'sphinx.ext.intersphinx'] | ||||||
|  |  | ||||||
|  | # 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.0RC3' | ||||||
|  |  | ||||||
|  | # 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 = 'tango' | ||||||
|  |  | ||||||
|  | # 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 = 'nature' | ||||||
|  |  | ||||||
|  | # 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) | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')} | ||||||
							
								
								
									
										679
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										679
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,679 @@ | |||||||
|  | .. _create-plugin: | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | ========================== | ||||||
							
								
								
									
										75
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | .. _echocomponent: | ||||||
|  |  | ||||||
|  | ================================= | ||||||
|  | Create and Run a Server Component | ||||||
|  | ================================= | ||||||
|  |  | ||||||
|  | .. 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 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Many XMPP applications eventually graduate to requiring to run as a server  | ||||||
|  | component in order to meet scalability requirements. To demonstrate how to  | ||||||
|  | turn an XMPP client bot into a component, we'll turn the echobot example | ||||||
|  | (:ref:`echobot`) into a component version. | ||||||
|  |  | ||||||
|  | The first difference is that we will add an additional import statement: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     from sleekxmpp.componentxmpp import ComponentXMPP | ||||||
|  |  | ||||||
|  | Likewise, we will change the bot's class definition to match: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     class EchoComponent(ComponentXMPP): | ||||||
|  |  | ||||||
|  |         def __init__(self, jid, secret, server, port): | ||||||
|  |             ComponentXMPP.__init__(self, jid, secret, server, port) | ||||||
|  |  | ||||||
|  | A component instance requires two extra parameters compared to a client | ||||||
|  | instance: ``server`` and ``port``. These specifiy the name and port of | ||||||
|  | the XMPP server that will be accepting the component. For example, for | ||||||
|  | a MUC component, the following could be used: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     muc = ComponentXMPP('muc.sleekxmpp.com', '******', 'sleekxmpp.com', 5555) | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     The ``server`` value is **NOT** derived from the provided JID for the | ||||||
|  |     component, unlike with client connections. | ||||||
|  |  | ||||||
|  | One difference with the component version is that we do not have | ||||||
|  | to handle the :term:`session_start` event if we don't wish to deal | ||||||
|  | with presence. | ||||||
|  |  | ||||||
|  | The other, main difference with components is that the | ||||||
|  | ``'from'`` value for every stanza must be explicitly set, since | ||||||
|  | components may send stanzas from multiple JIDs. To do so, | ||||||
|  | the :meth:`~sleekxmpp.basexmpp.BaseXMPP.send_message()` and | ||||||
|  | :meth:`~sleekxmpp.basexmpp.BaseXMPP.send_presence()` accept the parameters | ||||||
|  | ``mfrom`` and ``pfrom``, respectively. For any method that uses | ||||||
|  | :class:`~sleekxmpp.stanza.iq.Iq` stanzas, ``ifrom`` may be used. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Final Product | ||||||
|  | ------------- | ||||||
|  |  | ||||||
|  | .. include:: ../../examples/echo_component.py | ||||||
|  |     :literal: | ||||||
							
								
								
									
										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: | ||||||
							
								
								
									
										182
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,182 @@ | |||||||
|  | Send/Receive IQ Stanzas | ||||||
|  | ======================= | ||||||
|  |  | ||||||
|  | Unlike :class:`~sleekxmpp.stanza.message.Message` and | ||||||
|  | :class:`~sleekxmpp.stanza.presence.Presence` stanzas which only use | ||||||
|  | text data for basic usage, :class:`~sleekxmpp.stanza.iq.Iq` stanzas | ||||||
|  | require using XML payloads, and generally entail creating a new | ||||||
|  | SleekXMPP plugin to provide the necessary convenience methods to | ||||||
|  | make working with them easier. | ||||||
|  |  | ||||||
|  | Basic Use | ||||||
|  | --------- | ||||||
|  |  | ||||||
|  | XMPP's use of :class:`~sleekxmpp.stanza.iq.Iq` stanzas is built around | ||||||
|  | namespaced ``<query />`` elements. For clients, just sending the | ||||||
|  | empty ``<query />`` element will suffice for retrieving information. For | ||||||
|  | example, a very basic implementation of service discovery would just | ||||||
|  | need to be able to send: | ||||||
|  |  | ||||||
|  | .. code-block:: xml | ||||||
|  |  | ||||||
|  |     <iq to="user@example.com" type="get" id="1"> | ||||||
|  |       <query xmlns="http://jabber.org/protocol/disco#info" /> | ||||||
|  |     </iq> | ||||||
|  |  | ||||||
|  | Creating Iq Stanzas | ||||||
|  | ~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | SleekXMPP provides built-in support for creating basic :class:`~sleekxmpp.stanza.iq.Iq` | ||||||
|  | stanzas this way. The relevant methods are: | ||||||
|  |  | ||||||
|  | * :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq` | ||||||
|  | * :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_get` | ||||||
|  | * :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_set` | ||||||
|  | * :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_result` | ||||||
|  | * :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_error` | ||||||
|  | * :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_query` | ||||||
|  |  | ||||||
|  | These methods all follow the same pattern: create or modify an existing  | ||||||
|  | :class:`~sleekxmpp.stanza.iq.Iq` stanza, set the ``'type'`` value based | ||||||
|  | on the method name, and finally add a ``<query />`` element with the given | ||||||
|  | namespace. For example, to produce the query above, you would use: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     self.make_iq_get(queryxmlns='http://jabber.org/protocol/disco#info', | ||||||
|  |                      ito='user@example.com') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Sending Iq Stanzas | ||||||
|  | ~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | Once an :class:`~sleekxmpp.stanza.iq.Iq` stanza is created, sending it | ||||||
|  | over the wire is done using its :meth:`~sleekxmpp.stanza.iq.Iq.send()` | ||||||
|  | method, like any other stanza object. However, there are a few extra | ||||||
|  | options to control how to wait for the query's response. | ||||||
|  |  | ||||||
|  | These options are: | ||||||
|  |  | ||||||
|  | * ``block``: The default behaviour is that :meth:`~sleekxmpp.stanza.iq.Iq.send()` | ||||||
|  |   will block until a response is received and the response stanza will be the | ||||||
|  |   return value. Setting ``block`` to ``False`` will cause the call to return | ||||||
|  |   immediately. In which case, you will need to arrange some way to capture | ||||||
|  |   the response stanza if you need it. | ||||||
|  |  | ||||||
|  | * ``timeout``: When using the blocking behaviour, the call will eventually | ||||||
|  |   timeout with an error. The default timeout is 30 seconds, but this may | ||||||
|  |   be overidden two ways. To change the timeout globally, set: | ||||||
|  |  | ||||||
|  |     .. code-block:: python | ||||||
|  |  | ||||||
|  |         self.response_timeout = 10 | ||||||
|  |  | ||||||
|  |   To change the timeout for a single call, the ``timeout`` parameter works: | ||||||
|  |  | ||||||
|  |     .. code-block:: python | ||||||
|  |          | ||||||
|  |         iq.send(timeout=60) | ||||||
|  |  | ||||||
|  | * ``callback``: When not using a blocking call, using the ``callback`` | ||||||
|  |   argument is a simple way to register a handler that will execute | ||||||
|  |   whenever a response is finally received. Using this method, there | ||||||
|  |   is no timeout limit. In case you need to remove the callback, the | ||||||
|  |   name of the newly created callback is returned. | ||||||
|  |  | ||||||
|  |     .. code-block:: python | ||||||
|  |  | ||||||
|  |        cb_name = iq.send(callback=self.a_callback)  | ||||||
|  |  | ||||||
|  |        # ... later if we need to cancel | ||||||
|  |        self.remove_handler(cb_name) | ||||||
|  |  | ||||||
|  | Properly working with :class:`~sleekxmpp.stanza.iq.Iq` stanzas requires | ||||||
|  | handling the intended, normal flow, error responses, and timed out | ||||||
|  | requests. To make this easier, two exceptions may be thrown by | ||||||
|  | :meth:`~sleekxmpp.stanza.iq.Iq.send()`: :exc:`~sleekxmpp.exceptions.IqError` | ||||||
|  | and :exc:`~sleekxmpp.exceptions.IqTimeout`. These exceptions only | ||||||
|  | apply to the default, blocking calls. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         resp = iq.send() | ||||||
|  |         # ... do stuff with expected Iq result | ||||||
|  |     except IqError as e: | ||||||
|  |         err_resp = e.iq | ||||||
|  |         # ... handle error case | ||||||
|  |     except IqTimeout: | ||||||
|  |         # ... no response received in time | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  | If you do not care to distinguish between errors and timeouts, then you | ||||||
|  | can combine both cases with a generic :exc:`~sleekxmpp.exceptions.XMPPError` | ||||||
|  | exception: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         resp = iq.send() | ||||||
|  |     except XMPPError: | ||||||
|  |         # ... Don't care about the response | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  | Advanced Use | ||||||
|  | ------------ | ||||||
|  |  | ||||||
|  | Going beyond the basics provided by SleekXMPP requires building at least a | ||||||
|  | rudimentary SleekXMPP plugin to create a :term:`stanza object` for | ||||||
|  | interfacting with the :class:`~sleekxmpp.stanza.iq.Iq` payload. | ||||||
|  |  | ||||||
|  | .. seealso:: | ||||||
|  |  | ||||||
|  |     * :ref:`create-plugin` | ||||||
|  |     * :ref:`work-with-stanzas` | ||||||
|  |     * :ref:`using-handlers-matchers` | ||||||
|  |      | ||||||
|  |  | ||||||
|  | The typical way to respond to :class:`~sleekxmpp.stanza.iq.Iq` requests is | ||||||
|  | to register stream handlers. As an example, suppose we create a stanza class | ||||||
|  | named ``CustomXEP`` which uses the XML element ``<query xmlns="custom-xep" />``, | ||||||
|  | and has a :attr:`~sleekxmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value | ||||||
|  | of ``custom_xep``. | ||||||
|  |  | ||||||
|  | There are two types of incoming :class:`~sleekxmpp.stanza.iq.Iq` requests: | ||||||
|  | ``get`` and ``set``. You can register a handler that will accept both and then | ||||||
|  | filter by type as needed, as so: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     self.register_handler(Callback( | ||||||
|  |         'CustomXEP Handler', | ||||||
|  |         StanzaPath('iq/custom_xep'), | ||||||
|  |         self._handle_custom_iq)) | ||||||
|  |  | ||||||
|  |     # ... | ||||||
|  |  | ||||||
|  |     def _handle_custom_iq(self, iq): | ||||||
|  |         if iq['type'] == 'get': | ||||||
|  |             # ... | ||||||
|  |             pass | ||||||
|  |         elif iq['type'] == 'set': | ||||||
|  |             # ... | ||||||
|  |             pass | ||||||
|  |         else: | ||||||
|  |             # ... This will capture error responses too | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  | If you want to filter out query types beforehand, you can adjust the matching | ||||||
|  | filter by using ``@type=get`` or ``@type=set`` if you are using the recommended | ||||||
|  | :class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     self.register_handler(Callback( | ||||||
|  |         'CustomXEP Handler', | ||||||
|  |         StanzaPath('iq@type=get/custom_xep'), | ||||||
|  |         self._handle_custom_iq_get)) | ||||||
|  |  | ||||||
|  |     # ... | ||||||
|  |  | ||||||
|  |     def _handle_custom_iq_get(self, iq): | ||||||
|  |         assert(iq['type'] == 'get') | ||||||
							
								
								
									
										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 | ||||||
|  | ============================= | ||||||
							
								
								
									
										42
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | .. _proxy: | ||||||
|  |  | ||||||
|  | ========================= | ||||||
|  | Enable HTTP Proxy Support | ||||||
|  | ========================= | ||||||
|  |  | ||||||
|  | .. 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>`_. | ||||||
|  |  | ||||||
|  | In some instances, you may wish to route XMPP traffic through | ||||||
|  | an HTTP proxy, probably to get around restrictive firewalls. | ||||||
|  | SleekXMPP provides support for basic HTTP proxying with DIGEST | ||||||
|  | authentication. | ||||||
|  |  | ||||||
|  | Enabling proxy support is done in two steps. The first is to instruct SleekXMPP | ||||||
|  | to use a proxy, and the second is to configure the proxy details: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     xmpp = ClientXMPP(...) | ||||||
|  |     xmpp.use_proxy = True | ||||||
|  |     xmpp.proxy_config = { | ||||||
|  |         'host': 'proxy.example.com', | ||||||
|  |         'port': 5555, | ||||||
|  |         'username': 'example_user', | ||||||
|  |         'password': '******' | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | The ``'username'`` and ``'password'`` fields are optional if the proxy does not | ||||||
|  | require authentication. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | The Final Product | ||||||
|  | ----------------- | ||||||
|  |  | ||||||
|  | .. include:: ../../examples/proxy_echo_client.py | ||||||
|  |     :literal: | ||||||
							
								
								
									
										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) | ||||||
							
								
								
									
										4
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | .. _using-handlers-matchers: | ||||||
|  |  | ||||||
|  | Using Stream Handlers and Matchers | ||||||
|  | ================================== | ||||||
							
								
								
									
										30
									
								
								docs/howto/stanzas.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docs/howto/stanzas.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | .. _work-with-stanzas: | ||||||
|  |  | ||||||
|  | How to Work with Stanza Objects | ||||||
|  | =============================== | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. _create-stanza-interfaces: | ||||||
|  |  | ||||||
|  | Defining Stanza Interfaces | ||||||
|  | -------------------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. _create-stanza-plugins: | ||||||
|  |  | ||||||
|  | Creating Stanza Plugins | ||||||
|  | ----------------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. _create-extension-plugins: | ||||||
|  |  | ||||||
|  | Creating a Stanza Extension | ||||||
|  | --------------------------- | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. _override-parent-interfaces: | ||||||
|  |  | ||||||
|  | Overriding a Parent Stanza | ||||||
|  | -------------------------- | ||||||
							
								
								
									
										179
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | |||||||
|  | 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. | ||||||
|  |  | ||||||
|  |     **Latest Stable Release** | ||||||
|  |         - `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_   | ||||||
|  |  | ||||||
|  |     **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 | ||||||
|  |     | ||||||
|  |     faq | ||||||
|  |     xeps | ||||||
|  |     xmpp_tdg | ||||||
|  |     howto/stanzas | ||||||
|  |     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/componentxmpp | ||||||
|  |     api/basexmpp | ||||||
|  |     api/exceptions | ||||||
|  |     api/xmlstream/jid | ||||||
|  |     api/xmlstream/stanzabase | ||||||
|  |     api/xmlstream/handler | ||||||
|  |     api/xmlstream/matcher | ||||||
|  |     api/xmlstream/xmlstream | ||||||
|  |     api/xmlstream/scheduler | ||||||
|  |     api/xmlstream/tostring | ||||||
|  |     api/xmlstream/filesocket | ||||||
|  |  | ||||||
|  | Core Stanzas | ||||||
|  | ~~~~~~~~~~~~ | ||||||
|  | .. toctree:: | ||||||
|  |     :maxdepth: 2 | ||||||
|  |  | ||||||
|  |     api/stanza/rootstanza | ||||||
|  |     api/stanza/message | ||||||
|  |     api/stanza/presence | ||||||
|  |     api/stanza/iq | ||||||
|  |     api/stanza/error | ||||||
|  |     api/stanza/stream_error | ||||||
|  |  | ||||||
|  | Plugins | ||||||
|  | ~~~~~~~ | ||||||
|  | .. toctree:: | ||||||
|  |     :maxdepth: 2 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | =================== | ||||||
							
								
								
									
										
											BIN
										
									
								
								docs/python-objects.inv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/python-objects.inv
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | How SASL Authentication Works | ||||||
|  | ============================= | ||||||
							
								
								
									
										50
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | Supported XEPS | ||||||
|  | ============== | ||||||
|  |  | ||||||
|  | ======= ============================= ================ | ||||||
|  | XEP     Description                   Notes | ||||||
|  | ======= ============================= ================ | ||||||
|  | `0004`_ Data forms                    | ||||||
|  | `0009`_ Jabber RPC                    | ||||||
|  | `0012`_ Last Activity              | ||||||
|  | `0030`_ Service Discovery | ||||||
|  | `0033`_ Extended Stanza Addressing | ||||||
|  | `0045`_ Multi-User Chat (MUC)         Client-side only | ||||||
|  | `0050`_ Ad-hoc Commands | ||||||
|  | `0059`_ Result Set Management | ||||||
|  | `0060`_ Publish/Subscribe (PubSub)    Client-side only | ||||||
|  | `0066`_ Out-of-band Data | ||||||
|  | `0078`_ Non-SASL Authentication | ||||||
|  | `0082`_ XMPP Date and Time Profiles | ||||||
|  | `0085`_ Chat-State Notifications | ||||||
|  | `0086`_ Error Condition Mappings | ||||||
|  | `0092`_ Software Version | ||||||
|  | `0128`_ Service Discovery Extensions | ||||||
|  | `0202`_ Entity Time | ||||||
|  | `0203`_ Delayed Delivery | ||||||
|  | `0224`_ Attention | ||||||
|  | `0249`_ Direct MUC Invitations | ||||||
|  | ======= ============================= ================ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. _0004: http://xmpp.org/extensions/xep-0004.html | ||||||
|  | .. _0009: http://xmpp.org/extensions/xep-0009.html | ||||||
|  | .. _0012: http://xmpp.org/extensions/xep-0012.html | ||||||
|  | .. _0030: http://xmpp.org/extensions/xep-0030.html | ||||||
|  | .. _0033: http://xmpp.org/extensions/xep-0033.html | ||||||
|  | .. _0045: http://xmpp.org/extensions/xep-0045.html | ||||||
|  | .. _0050: http://xmpp.org/extensions/xep-0050.html | ||||||
|  | .. _0059: http://xmpp.org/extensions/xep-0059.html | ||||||
|  | .. _0060: http://xmpp.org/extensions/xep-0060.html | ||||||
|  | .. _0066: http://xmpp.org/extensions/xep-0066.html | ||||||
|  | .. _0078: http://xmpp.org/extensions/xep-0078.html | ||||||
|  | .. _0082: http://xmpp.org/extensions/xep-0082.html | ||||||
|  | .. _0085: http://xmpp.org/extensions/xep-0085.html | ||||||
|  | .. _0086: http://xmpp.org/extensions/xep-0086.html | ||||||
|  | .. _0092: http://xmpp.org/extensions/xep-0092.html | ||||||
|  | .. _0128: http://xmpp.org/extensions/xep-0128.html | ||||||
|  | .. _0199: http://xmpp.org/extensions/xep-0199.html | ||||||
|  | .. _0202: http://xmpp.org/extensions/xep-0202.html | ||||||
|  | .. _0203: http://xmpp.org/extensions/xep-0203.html | ||||||
|  | .. _0224: http://xmpp.org/extensions/xep-0224.html | ||||||
|  | .. _0249: http://xmpp.org/extensions/xep-0249.html | ||||||
							
								
								
									
										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>`_  | ||||||
| @@ -11,7 +11,6 @@ | |||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import time |  | ||||||
| import getpass | import getpass | ||||||
| from optparse import OptionParser | from optparse import OptionParser | ||||||
|  |  | ||||||
| @@ -24,6 +23,8 @@ import sleekxmpp | |||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|     reload(sys) |     reload(sys) | ||||||
|     sys.setdefaultencoding('utf8') |     sys.setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
| class CommandBot(sleekxmpp.ClientXMPP): | class CommandBot(sleekxmpp.ClientXMPP): | ||||||
| @@ -39,7 +40,7 @@ class CommandBot(sleekxmpp.ClientXMPP): | |||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
|         # the bot establishes its connection with the server |         # the bot establishes its connection with the server | ||||||
|         # and the XML streams are ready for use. We want to |         # and the XML streams are ready for use. We want to | ||||||
|         # listen for this event so that we we can intialize |         # listen for this event so that we we can initialize | ||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
| @@ -48,7 +49,7 @@ class CommandBot(sleekxmpp.ClientXMPP): | |||||||
|         Process the session_start event. |         Process the session_start event. | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |         Typical actions for the session_start event are | ||||||
|         requesting the roster and broadcasting an intial |         requesting the roster and broadcasting an initial | ||||||
|         presence stanza. |         presence stanza. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
| @@ -70,7 +71,7 @@ class CommandBot(sleekxmpp.ClientXMPP): | |||||||
|  |  | ||||||
|     def _handle_command(self, iq, session): |     def _handle_command(self, iq, session): | ||||||
|         """ |         """ | ||||||
|         Respond to the intial request for a command. |         Respond to the initial request for a command. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
|             iq      -- The iq stanza containing the command request. |             iq      -- The iq stanza containing the command request. | ||||||
| @@ -79,6 +80,7 @@ class CommandBot(sleekxmpp.ClientXMPP): | |||||||
|                        here to persist across handler callbacks. |                        here to persist across handler callbacks. | ||||||
|         """ |         """ | ||||||
|         form = self['xep_0004'].makeForm('form', 'Greeting') |         form = self['xep_0004'].makeForm('form', 'Greeting') | ||||||
|  |         form['instructions'] = 'Send a custom greeting to a JID' | ||||||
|         form.addField(var='greeting', |         form.addField(var='greeting', | ||||||
|                       ftype='text-single', |                       ftype='text-single', | ||||||
|                       label='Your greeting') |                       label='Your greeting') | ||||||
| @@ -123,8 +125,10 @@ class CommandBot(sleekxmpp.ClientXMPP): | |||||||
|         form = payload |         form = payload | ||||||
|  |  | ||||||
|         greeting = form['values']['greeting'] |         greeting = form['values']['greeting'] | ||||||
|  |  | ||||||
|         self.send_message(mto=session['from'], |         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' |         # Having no return statement is the same as unsetting the 'payload' | ||||||
|         # and 'next' session values and returning the session. |         # and 'next' session values and returning the session. | ||||||
| @@ -176,6 +180,7 @@ if __name__ == '__main__': | |||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |     xmpp.register_plugin('xep_0030') # Service Discovery | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |     xmpp.register_plugin('xep_0004') # Data Forms | ||||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands |     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 |     # If you are working with an OpenFire server, you may need | ||||||
|     # to adjust the SSL version used: |     # to adjust the SSL version used: | ||||||
| @@ -186,14 +191,14 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     if xmpp.connect(): | ||||||
|         # If you do not have the pydns library installed, you will need |         # If you do not have the dnspython library installed, you will need | ||||||
|         # to manually specify the name of the server if it does not match |         # 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 |         # the one in the JID. For example, to use Google Talk you would | ||||||
|         # need to use: |         # need to use: | ||||||
|         # |         # | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |         # if xmpp.connect(('talk.google.com', 5222)): | ||||||
|         #     ... |         #     ... | ||||||
|         xmpp.process(threaded=False) |         xmpp.process(block=True) | ||||||
|         print("Done") |         print("Done") | ||||||
|     else: |     else: | ||||||
|         print("Unable to connect.") |         print("Unable to connect.") | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import time |  | ||||||
| import getpass | import getpass | ||||||
| from optparse import OptionParser | from optparse import OptionParser | ||||||
|  |  | ||||||
| @@ -24,6 +23,8 @@ import sleekxmpp | |||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|     reload(sys) |     reload(sys) | ||||||
|     sys.setdefaultencoding('utf8') |     sys.setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
| class CommandUserBot(sleekxmpp.ClientXMPP): | class CommandUserBot(sleekxmpp.ClientXMPP): | ||||||
| @@ -42,7 +43,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP): | |||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
|         # the bot establishes its connection with the server |         # the bot establishes its connection with the server | ||||||
|         # and the XML streams are ready for use. We want to |         # and the XML streams are ready for use. We want to | ||||||
|         # listen for this event so that we we can intialize |         # listen for this event so that we we can initialize | ||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|         self.add_event_handler("message", self.message) |         self.add_event_handler("message", self.message) | ||||||
| @@ -52,7 +53,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP): | |||||||
|         Process the session_start event. |         Process the session_start event. | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |         Typical actions for the session_start event are | ||||||
|         requesting the roster and broadcasting an intial |         requesting the roster and broadcasting an initial | ||||||
|         presence stanza. |         presence stanza. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
| @@ -136,6 +137,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP): | |||||||
|         # The session will automatically be cleared if no error |         # The session will automatically be cleared if no error | ||||||
|         # handler is provided. |         # handler is provided. | ||||||
|         self['xep_0050'].terminate_command(session) |         self['xep_0050'].terminate_command(session) | ||||||
|  |         self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
| @@ -176,7 +178,7 @@ if __name__ == '__main__': | |||||||
|     if opts.other is None: |     if opts.other is None: | ||||||
|         opts.other = raw_input("JID Providing Commands: ") |         opts.other = raw_input("JID Providing Commands: ") | ||||||
|     if opts.greeting is None: |     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 |     # Setup the CommandBot and register plugins. Note that while plugins may | ||||||
|     # have interdependencies, the order in which you register them does |     # have interdependencies, the order in which you register them does | ||||||
| @@ -195,14 +197,14 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     if xmpp.connect(): | ||||||
|         # If you do not have the pydns library installed, you will need |         # If you do not have the dnspython library installed, you will need | ||||||
|         # to manually specify the name of the server if it does not match |         # 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 |         # the one in the JID. For example, to use Google Talk you would | ||||||
|         # need to use: |         # need to use: | ||||||
|         # |         # | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |         # if xmpp.connect(('talk.google.com', 5222)): | ||||||
|         #     ... |         #     ... | ||||||
|         xmpp.process(threaded=False) |         xmpp.process(block=True) | ||||||
|         print("Done") |         print("Done") | ||||||
|     else: |     else: | ||||||
|         print("Unable to connect.") |         print("Unable to connect.") | ||||||
|   | |||||||
| @@ -1,10 +0,0 @@ | |||||||
| <config xmlns="sleekxmpp:config"> |  | ||||||
|   <jid>component.localhost</jid> |  | ||||||
|   <secret>ssshh</secret> |  | ||||||
|   <server>localhost</server> |  | ||||||
|   <port>8888</port> |  | ||||||
|  |  | ||||||
|   <query xmlns="jabber:iq:roster"> |  | ||||||
|     <item jid="user@example.com" subscription="both" /> |  | ||||||
|   </query> |  | ||||||
| </config> |  | ||||||
| @@ -1,190 +0,0 @@ | |||||||
| #!/usr/bin/env python |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| import time |  | ||||||
| from optparse import OptionParser |  | ||||||
|  |  | ||||||
| import sleekxmpp |  | ||||||
| from sleekxmpp.componentxmpp import ComponentXMPP |  | ||||||
| from sleekxmpp.stanza.roster import Roster |  | ||||||
| from sleekxmpp.xmlstream import ElementBase |  | ||||||
| from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin |  | ||||||
|  |  | ||||||
| # Python versions before 3.0 do not use UTF-8 encoding |  | ||||||
| # by default. To ensure that Unicode is handled properly |  | ||||||
| # throughout SleekXMPP, we will set the default encoding |  | ||||||
| # ourselves to UTF-8. |  | ||||||
| if sys.version_info < (3, 0): |  | ||||||
|     reload(sys) |  | ||||||
|     sys.setdefaultencoding('utf8') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Config(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     In order to make loading and manipulating an XML config |  | ||||||
|     file easier, we will create a custom stanza object for |  | ||||||
|     our config XML file contents. See the documentation |  | ||||||
|     on stanza objects for more information on how to create |  | ||||||
|     and use stanza objects and stanza plugins. |  | ||||||
|  |  | ||||||
|     We will reuse the IQ roster query stanza to store roster |  | ||||||
|     information since it already exists. |  | ||||||
|  |  | ||||||
|     Example config XML: |  | ||||||
|       <config xmlns="sleekxmpp:config"> |  | ||||||
|         <jid>component.localhost</jid> |  | ||||||
|         <secret>ssshh</secret> |  | ||||||
|         <server>localhost</server> |  | ||||||
|         <port>8888</port> |  | ||||||
|  |  | ||||||
|         <query xmlns="jabber:iq:roster"> |  | ||||||
|           <item jid="user@example.com" subscription="both" /> |  | ||||||
|         </query> |  | ||||||
|       </config> |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     name = "config" |  | ||||||
|     namespace = "sleekxmpp:config" |  | ||||||
|     interfaces = set(('jid', 'secret', 'server', 'port')) |  | ||||||
|     sub_interfaces = interfaces |  | ||||||
|  |  | ||||||
|  |  | ||||||
| registerStanzaPlugin(Config, Roster) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ConfigComponent(ComponentXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple SleekXMPP component that uses an external XML |  | ||||||
|     file to store its configuration data. To make testing |  | ||||||
|     that the component works, it will also echo messages sent |  | ||||||
|     to it. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, config): |  | ||||||
|         """ |  | ||||||
|         Create a ConfigComponent. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             config      -- The XML contents of the config file. |  | ||||||
|             config_file -- The XML config file object itself. |  | ||||||
|         """ |  | ||||||
|         ComponentXMPP.__init__(self, config['jid'], |  | ||||||
|                                      config['secret'], |  | ||||||
|                                      config['server'], |  | ||||||
|                                      config['port']) |  | ||||||
|  |  | ||||||
|         # Store the roster information. |  | ||||||
|         self.roster = config['roster']['items'] |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the component establishes its connection with the |  | ||||||
|         # server and the XML streams are ready for use. We |  | ||||||
|         # want to listen for this event so that we we can |  | ||||||
|         # broadcast any needed initial presence stanzas. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The message event is triggered whenever a message |  | ||||||
|         # stanza is received. Be aware that that includes |  | ||||||
|         # MUC messages and error messages. |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         The typical action for the session_start event in a component |  | ||||||
|         is to broadcast presence stanzas to all subscribers to the |  | ||||||
|         component. Note that the component does not have a roster |  | ||||||
|         provided by the XMPP server. In this case, we have possibly |  | ||||||
|         saved a roster in the component's configuration file. |  | ||||||
|  |  | ||||||
|         Since the component may use any number of JIDs, you should |  | ||||||
|         also include the JID that is sending the presence. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         for jid in self.roster: |  | ||||||
|             if self.roster[jid]['subscription'] != 'none': |  | ||||||
|                 self.sendPresence(pfrom=self.jid, pto=jid) |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. Be aware that this also |  | ||||||
|         includes MUC messages and error messages. It is usually |  | ||||||
|         a good idea to check the messages's type before processing |  | ||||||
|         or sending replies. |  | ||||||
|  |  | ||||||
|         Since a component may send messages from any number of JIDs, |  | ||||||
|         it is best to always include a from JID. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         # The reply method will use the messages 'to' JID as the |  | ||||||
|         # outgoing reply's 'from' JID. |  | ||||||
|         msg.reply("Thanks for sending\n%(body)s" % msg).send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     optp = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', |  | ||||||
|                     action='store_const', dest='loglevel', |  | ||||||
|                     const=5, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # Component name and secret options. |  | ||||||
|     optp.add_option("-c", "--config", help="path to config file", |  | ||||||
|                     dest="config", default="config.xml") |  | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     # Load configuration data. |  | ||||||
|     config_file = open(opts.config, 'r+') |  | ||||||
|     config_data = "\n".join([line for line in config_file]) |  | ||||||
|     config = Config(xml=ET.fromstring(config_data)) |  | ||||||
|     config_file.close() |  | ||||||
|  |  | ||||||
|     # Setup the ConfigComponent and register plugins. Note that while plugins |  | ||||||
|     # may have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = ConfigComponent(config) |  | ||||||
|     xmpp.registerPlugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.registerPlugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.registerPlugin('xep_0060') # PubSub |  | ||||||
|     xmpp.registerPlugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     if xmpp.connect(): |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
| @@ -10,7 +10,6 @@ | |||||||
| """ | """ | ||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import time |  | ||||||
| import logging | import logging | ||||||
| import getpass | import getpass | ||||||
| from optparse import OptionParser | from optparse import OptionParser | ||||||
| @@ -25,6 +24,8 @@ import sleekxmpp | |||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|     reload(sys) |     reload(sys) | ||||||
|     sys.setdefaultencoding('utf8') |     sys.setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
| class Disco(sleekxmpp.ClientXMPP): | class Disco(sleekxmpp.ClientXMPP): | ||||||
| @@ -59,7 +60,7 @@ class Disco(sleekxmpp.ClientXMPP): | |||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
|         # the bot establishes its connection with the server |         # the bot establishes its connection with the server | ||||||
|         # and the XML streams are ready for use. We want to |         # and the XML streams are ready for use. We want to | ||||||
|         # listen for this event so that we we can intialize |         # listen for this event so that we we can initialize | ||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
| @@ -68,7 +69,7 @@ class Disco(sleekxmpp.ClientXMPP): | |||||||
|         Process the session_start event. |         Process the session_start event. | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |         Typical actions for the session_start event are | ||||||
|         requesting the roster and broadcasting an intial |         requesting the roster and broadcasting an initial | ||||||
|         presence stanza. |         presence stanza. | ||||||
|  |  | ||||||
|         In this case, we send disco#info and disco#items |         In this case, we send disco#info and disco#items | ||||||
| @@ -186,13 +187,13 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     if xmpp.connect(): | ||||||
|         # If you do not have the pydns library installed, you will need |         # If you do not have the dnspython library installed, you will need | ||||||
|         # to manually specify the name of the server if it does not match |         # 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 |         # the one in the JID. For example, to use Google Talk you would | ||||||
|         # need to use: |         # need to use: | ||||||
|         # |         # | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |         # if xmpp.connect(('talk.google.com', 5222)): | ||||||
|         #     ... |         #     ... | ||||||
|         xmpp.process(threaded=False) |         xmpp.process(block=True) | ||||||
|     else: |     else: | ||||||
|         print("Unable to connect.") |         print("Unable to connect.") | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import time |  | ||||||
| import getpass | import getpass | ||||||
| from optparse import OptionParser | from optparse import OptionParser | ||||||
|  |  | ||||||
| @@ -24,6 +23,8 @@ import sleekxmpp | |||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|     reload(sys) |     reload(sys) | ||||||
|     sys.setdefaultencoding('utf8') |     sys.setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
| class EchoBot(sleekxmpp.ClientXMPP): | class EchoBot(sleekxmpp.ClientXMPP): | ||||||
| @@ -39,7 +40,7 @@ class EchoBot(sleekxmpp.ClientXMPP): | |||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
|         # the bot establishes its connection with the server |         # the bot establishes its connection with the server | ||||||
|         # and the XML streams are ready for use. We want to |         # and the XML streams are ready for use. We want to | ||||||
|         # listen for this event so that we we can intialize |         # listen for this event so that we we can initialize | ||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
| @@ -53,7 +54,7 @@ class EchoBot(sleekxmpp.ClientXMPP): | |||||||
|         Process the session_start event. |         Process the session_start event. | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |         Typical actions for the session_start event are | ||||||
|         requesting the roster and broadcasting an intial |         requesting the roster and broadcasting an initial | ||||||
|         presence stanza. |         presence stanza. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
| @@ -76,7 +77,8 @@ class EchoBot(sleekxmpp.ClientXMPP): | |||||||
|                    for stanza objects and the Message stanza to see |                    for stanza objects and the Message stanza to see | ||||||
|                    how it may be used. |                    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__': | if __name__ == '__main__': | ||||||
| @@ -129,14 +131,14 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     if xmpp.connect(): | ||||||
|         # If you do not have the pydns library installed, you will need |         # If you do not have the dnspython library installed, you will need | ||||||
|         # to manually specify the name of the server if it does not match |         # 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 |         # the one in the JID. For example, to use Google Talk you would | ||||||
|         # need to use: |         # need to use: | ||||||
|         # |         # | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |         # if xmpp.connect(('talk.google.com', 5222)): | ||||||
|         #     ... |         #     ... | ||||||
|         xmpp.process(threaded=False) |         xmpp.process(block=True) | ||||||
|         print("Done") |         print("Done") | ||||||
|     else: |     else: | ||||||
|         print("Unable to connect.") |         print("Unable to connect.") | ||||||
|   | |||||||
							
								
								
									
										122
									
								
								examples/echo_component.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										122
									
								
								examples/echo_component.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | #!/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 getpass | ||||||
|  | from optparse import OptionParser | ||||||
|  |  | ||||||
|  | import sleekxmpp | ||||||
|  | from sleekxmpp.componentxmpp import ComponentXMPP | ||||||
|  |  | ||||||
|  | # 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 EchoComponent(ComponentXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple SleekXMPP component that echoes messages. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, secret, server, port): | ||||||
|  |         ComponentXMPP.__init__(self, jid, secret, server, port) | ||||||
|  |  | ||||||
|  |         # You don't need a session_start handler, but that is | ||||||
|  |         # where you would broadcast initial presence. | ||||||
|  |  | ||||||
|  |         # 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 message(self, msg): | ||||||
|  |         """ | ||||||
|  |         Process incoming message stanzas. Be aware that this also | ||||||
|  |         includes MUC messages and error messages. It is usually | ||||||
|  |         a good idea to check the messages's type before processing | ||||||
|  |         or sending replies. | ||||||
|  |  | ||||||
|  |         Since a component may send messages from any number of JIDs, | ||||||
|  |         it is best to always include a from JID. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             msg -- The received message stanza. See the documentation | ||||||
|  |                    for stanza objects and the Message stanza to see | ||||||
|  |                    how it may be used. | ||||||
|  |         """ | ||||||
|  |         # The reply method will use the messages 'to' JID as the | ||||||
|  |         # outgoing reply's 'from' JID. | ||||||
|  |         msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     optp = OptionParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=logging.ERROR, default=logging.INFO) | ||||||
|  |     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=logging.DEBUG, default=logging.INFO) | ||||||
|  |     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=5, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # 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("-s", "--server", dest="server", | ||||||
|  |                     help="server to connect to") | ||||||
|  |     optp.add_option("-P", "--port", dest="port", | ||||||
|  |                     help="port to connect to") | ||||||
|  |  | ||||||
|  |     opts, args = optp.parse_args() | ||||||
|  |  | ||||||
|  |     if opts.jid is None: | ||||||
|  |         opts.jid = raw_input("Component JID: ") | ||||||
|  |     if opts.password is None: | ||||||
|  |         opts.password = getpass.getpass("Password: ") | ||||||
|  |     if opts.server is None: | ||||||
|  |         opts.server = raw_input("Server: ") | ||||||
|  |     if opts.port is None: | ||||||
|  |         opts.port = int(raw_input("Port: ")) | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=opts.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     # Setup the EchoComponent and register plugins. Note that while plugins | ||||||
|  |     # may have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = EchoComponent(opts.jid, opts.password, opts.server, opts.port) | ||||||
|  |     xmpp.registerPlugin('xep_0030') # Service Discovery | ||||||
|  |     xmpp.registerPlugin('xep_0004') # Data Forms | ||||||
|  |     xmpp.registerPlugin('xep_0060') # PubSub | ||||||
|  |     xmpp.registerPlugin('xep_0199') # XMPP Ping | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     if xmpp.connect(): | ||||||
|  |         xmpp.process(block=True) | ||||||
|  |         print("Done") | ||||||
|  |     else: | ||||||
|  |         print("Unable to connect.") | ||||||
| @@ -11,7 +11,7 @@ | |||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import time | import getpass | ||||||
| from optparse import OptionParser | from optparse import OptionParser | ||||||
|  |  | ||||||
| import sleekxmpp | import sleekxmpp | ||||||
| @@ -23,6 +23,8 @@ import sleekxmpp | |||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|     reload(sys) |     reload(sys) | ||||||
|     sys.setdefaultencoding('utf8') |     sys.setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
| class MUCBot(sleekxmpp.ClientXMPP): | class MUCBot(sleekxmpp.ClientXMPP): | ||||||
| @@ -42,7 +44,7 @@ class MUCBot(sleekxmpp.ClientXMPP): | |||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
|         # the bot establishes its connection with the server |         # the bot establishes its connection with the server | ||||||
|         # and the XML streams are ready for use. We want to |         # and the XML streams are ready for use. We want to | ||||||
|         # listen for this event so that we we can intialize |         # listen for this event so that we we can initialize | ||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
| @@ -59,14 +61,14 @@ class MUCBot(sleekxmpp.ClientXMPP): | |||||||
|         # muc::room@server::got_online, or muc::room@server::got_offline. |         # muc::room@server::got_online, or muc::room@server::got_offline. | ||||||
|         self.add_event_handler("muc::%s::got_online" % self.room, |         self.add_event_handler("muc::%s::got_online" % self.room, | ||||||
|                                self.muc_online) |                                self.muc_online) | ||||||
|          |  | ||||||
|  |  | ||||||
|     def start(self, event): |     def start(self, event): | ||||||
|         """ |         """ | ||||||
|         Process the session_start event. |         Process the session_start event. | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |         Typical actions for the session_start event are | ||||||
|         requesting the roster and broadcasting an intial |         requesting the roster and broadcasting an initial | ||||||
|         presence stanza. |         presence stanza. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
| @@ -76,15 +78,15 @@ class MUCBot(sleekxmpp.ClientXMPP): | |||||||
|         """ |         """ | ||||||
|         self.getRoster() |         self.getRoster() | ||||||
|         self.sendPresence() |         self.sendPresence() | ||||||
|         self.plugin['xep_0045'].joinMUC(self.room,  |         self.plugin['xep_0045'].joinMUC(self.room, | ||||||
|                                         self.nick,  |                                         self.nick, | ||||||
|                                         # If a room password is needed, use: |                                         # If a room password is needed, use: | ||||||
|                                         # password=the_room_password, |                                         # password=the_room_password, | ||||||
|                                         wait=True) |                                         wait=True) | ||||||
|  |  | ||||||
|     def muc_message(self, msg): |     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, |         that if you also have any handlers for the 'message' event, | ||||||
|         message stanzas may be processed by both handlers, so check |         message stanzas may be processed by both handlers, so check | ||||||
|         the 'type' attribute when using a 'message' event handler. |         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 |                    otherwise you will create an infinite loop responding | ||||||
|                    to your own messages. |                    to your own messages. | ||||||
|  |  | ||||||
|         This handler will reply to messages that mention  |         This handler will reply to messages that mention | ||||||
|         the bot's nickname. |         the bot's nickname. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
| @@ -112,12 +114,12 @@ class MUCBot(sleekxmpp.ClientXMPP): | |||||||
|     def muc_online(self, presence): |     def muc_online(self, presence): | ||||||
|         """ |         """ | ||||||
|         Process a presence stanza from a chat room. In this case, |         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 |         handled by sending a welcome message that includes | ||||||
|         the user's nickname and role in the room. |         the user's nickname and role in the room. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
|             presence -- The received presence stanza. See the  |             presence -- The received presence stanza. See the | ||||||
|                         documentation for the Presence stanza |                         documentation for the Presence stanza | ||||||
|                         to see how else it may be used. |                         to see how else it may be used. | ||||||
|         """ |         """ | ||||||
| @@ -159,9 +161,14 @@ if __name__ == '__main__': | |||||||
|     logging.basicConfig(level=opts.loglevel, |     logging.basicConfig(level=opts.loglevel, | ||||||
|                         format='%(levelname)-8s %(message)s') |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|     if None in [opts.jid, opts.password, opts.room, opts.nick]: |     if opts.jid is None: | ||||||
|         optp.print_help() |         opts.jid = raw_input("Username: ") | ||||||
|         sys.exit(1) |     if opts.password is None: | ||||||
|  |         opts.password = getpass.getpass("Password: ") | ||||||
|  |     if opts.room is None: | ||||||
|  |         opts.room = raw_input("MUC room: ") | ||||||
|  |     if opts.nick is None: | ||||||
|  |         opts.nick = raw_input("MUC nickname: ") | ||||||
|  |  | ||||||
|     # Setup the MUCBot and register plugins. Note that while plugins may |     # Setup the MUCBot and register plugins. Note that while plugins may | ||||||
|     # have interdependencies, the order in which you register them does |     # have interdependencies, the order in which you register them does | ||||||
| @@ -173,14 +180,14 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     if xmpp.connect(): | ||||||
|         # If you do not have the pydns library installed, you will need |         # If you do not have the dnspython library installed, you will need | ||||||
|         # to manually specify the name of the server if it does not match |         # 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 |         # the one in the JID. For example, to use Google Talk you would | ||||||
|         # need to use: |         # need to use: | ||||||
|         # |         # | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |         # if xmpp.connect(('talk.google.com', 5222)): | ||||||
|         #     ... |         #     ... | ||||||
|         xmpp.process(threaded=False) |         xmpp.process(block=True) | ||||||
|         print("Done") |         print("Done") | ||||||
|     else: |     else: | ||||||
|         print("Unable to connect.") |         print("Unable to connect.") | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import time |  | ||||||
| import getpass | import getpass | ||||||
| from optparse import OptionParser | from optparse import OptionParser | ||||||
|  |  | ||||||
| @@ -24,6 +23,8 @@ import sleekxmpp | |||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|     reload(sys) |     reload(sys) | ||||||
|     sys.setdefaultencoding('utf8') |     sys.setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
| class PingTest(sleekxmpp.ClientXMPP): | class PingTest(sleekxmpp.ClientXMPP): | ||||||
| @@ -42,7 +43,7 @@ class PingTest(sleekxmpp.ClientXMPP): | |||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
|         # the bot establishes its connection with the server |         # the bot establishes its connection with the server | ||||||
|         # and the XML streams are ready for use. We want to |         # and the XML streams are ready for use. We want to | ||||||
|         # listen for this event so that we we can intialize |         # listen for this event so that we we can initialize | ||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
| @@ -51,7 +52,7 @@ class PingTest(sleekxmpp.ClientXMPP): | |||||||
|         Process the session_start event. |         Process the session_start event. | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |         Typical actions for the session_start event are | ||||||
|         requesting the roster and broadcasting an intial |         requesting the roster and broadcasting an initial | ||||||
|         presence stanza. |         presence stanza. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
| @@ -70,7 +71,7 @@ class PingTest(sleekxmpp.ClientXMPP): | |||||||
|             self.disconnect() |             self.disconnect() | ||||||
|             sys.exit(1) |             sys.exit(1) | ||||||
|         else: |         else: | ||||||
|             logging.info("Success! RTT: %s" % str(result)) |             logging.info("Success! RTT: %s", str(result)) | ||||||
|             self.disconnect() |             self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -127,14 +128,14 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     if xmpp.connect(): | ||||||
|         # If you do not have the pydns library installed, you will need |         # If you do not have the dnspython library installed, you will need | ||||||
|         # to manually specify the name of the server if it does not match |         # 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 |         # the one in the JID. For example, to use Google Talk you would | ||||||
|         # need to use: |         # need to use: | ||||||
|         # |         # | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |         # if xmpp.connect(('talk.google.com', 5222)): | ||||||
|         #     ... |         #     ... | ||||||
|         xmpp.process(threaded=False) |         xmpp.process(block=True) | ||||||
|         print("Done") |         print("Done") | ||||||
|     else: |     else: | ||||||
|         print("Unable to connect.") |         print("Unable to connect.") | ||||||
|   | |||||||
| @@ -11,7 +11,6 @@ | |||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import time |  | ||||||
| import getpass | import getpass | ||||||
| from optparse import OptionParser | from optparse import OptionParser | ||||||
|  |  | ||||||
| @@ -24,6 +23,8 @@ import sleekxmpp | |||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|     reload(sys) |     reload(sys) | ||||||
|     sys.setdefaultencoding('utf8') |     sys.setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
| class EchoBot(sleekxmpp.ClientXMPP): | class EchoBot(sleekxmpp.ClientXMPP): | ||||||
| @@ -39,7 +40,7 @@ class EchoBot(sleekxmpp.ClientXMPP): | |||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
|         # the bot establishes its connection with the server |         # the bot establishes its connection with the server | ||||||
|         # and the XML streams are ready for use. We want to |         # and the XML streams are ready for use. We want to | ||||||
|         # listen for this event so that we we can intialize |         # listen for this event so that we we can initialize | ||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
| @@ -53,7 +54,7 @@ class EchoBot(sleekxmpp.ClientXMPP): | |||||||
|         Process the session_start event. |         Process the session_start event. | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |         Typical actions for the session_start event are | ||||||
|         requesting the roster and broadcasting an intial |         requesting the roster and broadcasting an initial | ||||||
|         presence stanza. |         presence stanza. | ||||||
|  |  | ||||||
|         Arguments: |         Arguments: | ||||||
| @@ -154,14 +155,14 @@ if __name__ == '__main__': | |||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     if xmpp.connect(): | ||||||
|         # If you do not have the pydns library installed, you will need |         # If you do not have the dnspython library installed, you will need | ||||||
|         # to manually specify the name of the server if it does not match |         # 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 |         # the one in the JID. For example, to use Google Talk you would | ||||||
|         # need to use: |         # need to use: | ||||||
|         # |         # | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |         # if xmpp.connect(('talk.google.com', 5222)): | ||||||
|         #     ... |         #     ... | ||||||
|         xmpp.process(threaded=False) |         xmpp.process(block=True) | ||||||
|         print("Done") |         print("Done") | ||||||
|     else: |     else: | ||||||
|         print("Unable to connect.") |         print("Unable to connect.") | ||||||
|   | |||||||
							
								
								
									
										172
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,172 @@ | |||||||
|  | #!/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 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 initialize | ||||||
|  |         # 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 initial | ||||||
|  |         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 dnspython 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(block=True) | ||||||
|  |     else: | ||||||
|  |         print("Unable to connect.") | ||||||
|  |  | ||||||
							
								
								
									
										143
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										143
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | #!/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 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 initialize | ||||||
|  |         # 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 initial | ||||||
|  |         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 dnspython 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(block=True) | ||||||
|  |         print("Done") | ||||||
|  |     else: | ||||||
|  |         print("Unable to connect.") | ||||||
							
								
								
									
										195
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										195
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,94 +1,101 @@ | |||||||
| #!/usr/bin/env python | #!/usr/bin/env python | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
| # | # | ||||||
| # Copyright (C) 2007-2011 Nathanael C. Fritz | # Copyright (C) 2007-2011 Nathanael C. Fritz | ||||||
| # All Rights Reserved | # All Rights Reserved | ||||||
| # | # | ||||||
| # This software is licensed as described in the README file, | # This software is licensed as described in the README.rst and LICENSE | ||||||
| # which you should have received as part of this distribution. | # file, which you should have received as part of this distribution. | ||||||
| # |  | ||||||
|  | import sys | ||||||
| # from ez_setup import use_setuptools | try: | ||||||
| from distutils.core import setup |     from setuptools import setup, Command | ||||||
| import sys | except ImportError: | ||||||
|  |     from distutils.core import setup, Command | ||||||
| import sleekxmpp | # from ez_setup import use_setuptools | ||||||
|  |  | ||||||
| # if 'cygwin' in sys.platform.lower(): | from testall import TestCommand | ||||||
| #     min_version = '0.6c6' | from sleekxmpp.version import __version__ | ||||||
| # else: | # if 'cygwin' in sys.platform.lower(): | ||||||
| #     min_version = '0.6a9' | #     min_version = '0.6c6' | ||||||
| # | # else: | ||||||
| # try: | #     min_version = '0.6a9' | ||||||
| #     use_setuptools(min_version=min_version) | # | ||||||
| # except TypeError: | # try: | ||||||
| #     # locally installed ez_setup won't have min_version | #     use_setuptools(min_version=min_version) | ||||||
| #     use_setuptools() | # except TypeError: | ||||||
| # | #     # locally installed ez_setup won't have min_version | ||||||
| # from setuptools import setup, find_packages, Extension, Feature | #     use_setuptools() | ||||||
|  | # | ||||||
| VERSION          = sleekxmpp.__version__ | # from setuptools import setup, find_packages, Extension, Feature | ||||||
| DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' |  | ||||||
| with open('README') as readme: | VERSION          = __version__ | ||||||
|     LONG_DESCRIPTION = '\n'.join(readme) | DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' | ||||||
|  | with open('README.rst') as readme: | ||||||
| CLASSIFIERS      = [ 'Intended Audience :: Developers', |     LONG_DESCRIPTION = ''.join(readme) | ||||||
|                      'License :: OSI Approved :: MIT License', |  | ||||||
|                      'Programming Language :: Python', | CLASSIFIERS      = [ 'Intended Audience :: Developers', | ||||||
|                      'Programming Language :: Python 2.6', |                      'License :: OSI Approved :: MIT License', | ||||||
|                      'Programming Language :: Python 2.7', |                      'Programming Language :: Python', | ||||||
|                      'Programming Language :: Python 3.1', |                      'Programming Language :: Python :: 2.6', | ||||||
|                      'Programming Language :: Python 3.2', |                      'Programming Language :: Python :: 2.7', | ||||||
|                      'Topic :: Software Development :: Libraries :: Python Modules', |                      'Programming Language :: Python :: 3.1', | ||||||
|                    ] |                      'Programming Language :: Python :: 3.2', | ||||||
|  |                      'Topic :: Software Development :: Libraries :: Python Modules', | ||||||
| packages     = [ 'sleekxmpp', |                    ] | ||||||
|                  'sleekxmpp/stanza', |  | ||||||
|                  'sleekxmpp/test', | packages     = [ 'sleekxmpp', | ||||||
|                  'sleekxmpp/xmlstream', |                  'sleekxmpp/stanza', | ||||||
|                  'sleekxmpp/xmlstream/matcher', |                  'sleekxmpp/test', | ||||||
|                  'sleekxmpp/xmlstream/handler', |                  'sleekxmpp/roster', | ||||||
|                  'sleekxmpp/plugins', |                  'sleekxmpp/xmlstream', | ||||||
|                  'sleekxmpp/plugins/xep_0009', |                  'sleekxmpp/xmlstream/matcher', | ||||||
|                  'sleekxmpp/plugins/xep_0009/stanza', |                  'sleekxmpp/xmlstream/handler', | ||||||
|                  'sleekxmpp/plugins/xep_0030', |                  'sleekxmpp/plugins', | ||||||
|                  'sleekxmpp/plugins/xep_0030/stanza', |                  'sleekxmpp/plugins/xep_0004', | ||||||
|                  'sleekxmpp/plugins/xep_0050', |                  'sleekxmpp/plugins/xep_0004/stanza', | ||||||
|                  'sleekxmpp/plugins/xep_0059', |                  'sleekxmpp/plugins/xep_0009', | ||||||
|                  'sleekxmpp/plugins/xep_0060', |                  'sleekxmpp/plugins/xep_0009/stanza', | ||||||
|                  'sleekxmpp/plugins/xep_0060/stanza', |                  'sleekxmpp/plugins/xep_0030', | ||||||
|                  'sleekxmpp/plugins/xep_0066', |                  'sleekxmpp/plugins/xep_0030/stanza', | ||||||
|                  'sleekxmpp/plugins/xep_0085', |                  'sleekxmpp/plugins/xep_0050', | ||||||
|                  'sleekxmpp/plugins/xep_0086', |                  'sleekxmpp/plugins/xep_0059', | ||||||
|                  'sleekxmpp/plugins/xep_0092', |                  'sleekxmpp/plugins/xep_0060', | ||||||
|                  'sleekxmpp/plugins/xep_0128', |                  'sleekxmpp/plugins/xep_0060/stanza', | ||||||
|                  'sleekxmpp/plugins/xep_0199', |                  'sleekxmpp/plugins/xep_0066', | ||||||
|                  'sleekxmpp/plugins/xep_0202', |                  'sleekxmpp/plugins/xep_0078', | ||||||
|                  'sleekxmpp/plugins/xep_0203', |                  'sleekxmpp/plugins/xep_0085', | ||||||
|                  'sleekxmpp/plugins/xep_0224', |                  'sleekxmpp/plugins/xep_0086', | ||||||
|                  'sleekxmpp/plugins/xep_0249', |                  'sleekxmpp/plugins/xep_0092', | ||||||
|                  'sleekxmpp/features', |                  'sleekxmpp/plugins/xep_0128', | ||||||
|                  'sleekxmpp/features/feature_mechanisms', |                  'sleekxmpp/plugins/xep_0199', | ||||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', |                  'sleekxmpp/plugins/xep_0202', | ||||||
|                  'sleekxmpp/features/feature_starttls', |                  'sleekxmpp/plugins/xep_0203', | ||||||
|                  'sleekxmpp/features/feature_bind', |                  'sleekxmpp/plugins/xep_0224', | ||||||
|                  'sleekxmpp/features/feature_session', |                  'sleekxmpp/plugins/xep_0249', | ||||||
|                  'sleekxmpp/thirdparty', |                  'sleekxmpp/features', | ||||||
|                  'sleekxmpp/thirdparty/suelta', |                  'sleekxmpp/features/feature_mechanisms', | ||||||
|                  'sleekxmpp/thirdparty/suelta/mechanisms', |                  'sleekxmpp/features/feature_mechanisms/stanza', | ||||||
|                  ] |                  'sleekxmpp/features/feature_starttls', | ||||||
|  |                  'sleekxmpp/features/feature_bind', | ||||||
| setup( |                  'sleekxmpp/features/feature_session', | ||||||
|     name             = "sleekxmpp", |                  'sleekxmpp/thirdparty', | ||||||
|     version          = VERSION, |                  'sleekxmpp/thirdparty/suelta', | ||||||
|     description      = DESCRIPTION, |                  'sleekxmpp/thirdparty/suelta/mechanisms', | ||||||
|     long_description = LONG_DESCRIPTION, |                  ] | ||||||
|     author       = 'Nathanael Fritz', |  | ||||||
|     author_email = 'fritzy [at] netflint.net', | setup( | ||||||
|     url          = 'http://github.com/fritzy/SleekXMPP', |     name             = "sleekxmpp", | ||||||
|     license      = 'MIT', |     version          = VERSION, | ||||||
|     platforms    = [ 'any' ], |     description      = DESCRIPTION, | ||||||
|     packages     = packages, |     long_description = LONG_DESCRIPTION, | ||||||
|     requires     = [ 'tlslite', 'pythondns' ], |     author       = 'Nathanael Fritz', | ||||||
|     ) |     author_email = 'fritzy [at] netflint.net', | ||||||
|  |     url          = 'http://github.com/fritzy/SleekXMPP', | ||||||
|  |     license      = 'MIT', | ||||||
|  |     platforms    = [ 'any' ], | ||||||
|  |     packages     = packages, | ||||||
|  |     requires     = [ 'dnspython' ], | ||||||
|  |     classifiers  = CLASSIFIERS, | ||||||
|  |     cmdclass     = {'test': TestCommand} | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -15,5 +15,4 @@ from sleekxmpp.xmlstream import XMLStream, RestartStream | |||||||
| from sleekxmpp.xmlstream.matcher import * | from sleekxmpp.xmlstream.matcher import * | ||||||
| from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET | from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET | ||||||
|  |  | ||||||
| __version__ = '1.0beta6.1' | from sleekxmpp.version import __version__, __version_info__ | ||||||
| __version_info__ = (1, 0, 0, 'beta6', 1) |  | ||||||
|   | |||||||
| @@ -1,9 +1,15 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     sleekxmpp.basexmpp | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |     ~~~~~~~~~~~~~~~~~~ | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |     This module provides the common XMPP functionality | ||||||
|  |     for both clients and components. | ||||||
|  |  | ||||||
|  |     Part of SleekXMPP: The Sleek XMPP Library | ||||||
|  |  | ||||||
|  |     :copyright: (c) 2011 Nathanael C. Fritz | ||||||
|  |     :license: MIT, see LICENSE for more details | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from __future__ import with_statement, unicode_literals | from __future__ import with_statement, unicode_literals | ||||||
| @@ -13,7 +19,8 @@ import copy | |||||||
| import logging | import logging | ||||||
|  |  | ||||||
| import sleekxmpp | 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 import Message, Presence, Iq, Error, StreamError | ||||||
| from sleekxmpp.stanza.roster import Roster | from sleekxmpp.stanza.roster import Roster | ||||||
| @@ -42,70 +49,59 @@ class BaseXMPP(XMLStream): | |||||||
|     with XMPP. It also provides a plugin mechanism to easily extend |     with XMPP. It also provides a plugin mechanism to easily extend | ||||||
|     and add support for new XMPP features. |     and add support for new XMPP features. | ||||||
|  |  | ||||||
|     Attributes: |     :param default_ns: Ensure that the correct default XML namespace | ||||||
|        auto_authorize   -- Manage automatically accepting roster |                        is used during initialization. | ||||||
|                            subscriptions. |  | ||||||
|        auto_subscribe   -- Manage automatically requesting mutual |  | ||||||
|                            subscriptions. |  | ||||||
|        is_component     -- Indicates if this stream is for an XMPP component. |  | ||||||
|        jid              -- The XMPP JID for this stream. |  | ||||||
|        plugin           -- A dictionary of loaded plugins. |  | ||||||
|        plugin_config    -- A dictionary of plugin configurations. |  | ||||||
|        plugin_whitelist -- A list of approved plugins. |  | ||||||
|        sentpresence     -- Indicates if an initial presence has been sent. |  | ||||||
|        roster           -- A dictionary containing subscribed JIDs and |  | ||||||
|                            their presence statuses. |  | ||||||
|  |  | ||||||
|     Methods: |  | ||||||
|        Iq                      -- Factory for creating an Iq stanzas. |  | ||||||
|        Message                 -- Factory for creating Message stanzas. |  | ||||||
|        Presence                -- Factory for creating Presence stanzas. |  | ||||||
|        get                     -- Return a plugin given its name. |  | ||||||
|        make_iq                 -- Create and initialize an Iq stanza. |  | ||||||
|        make_iq_error           -- Create an Iq stanza of type 'error'. |  | ||||||
|        make_iq_get             -- Create an Iq stanza of type 'get'. |  | ||||||
|        make_iq_query           -- Create an Iq stanza with a given query. |  | ||||||
|        make_iq_result          -- Create an Iq stanza of type 'result'. |  | ||||||
|        make_iq_set             -- Create an Iq stanza of type 'set'. |  | ||||||
|        make_message            -- Create and initialize a Message stanza. |  | ||||||
|        make_presence           -- Create and initialize a Presence stanza. |  | ||||||
|        make_query_roster       -- Create a roster query. |  | ||||||
|        process                 -- Overrides XMLStream.process. |  | ||||||
|        register_plugin         -- Load and configure a plugin. |  | ||||||
|        register_plugins        -- Load and configure multiple plugins. |  | ||||||
|        send_message            -- Create and send a Message stanza. |  | ||||||
|        send_presence           -- Create and send a Presence stanza. |  | ||||||
|        send_presence_subscribe -- Send a subscription request. |  | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, default_ns='jabber:client'): |     def __init__(self, jid='', default_ns='jabber:client'): | ||||||
|         """ |  | ||||||
|         Adapt an XML stream for use with XMPP. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             default_ns -- Ensure that the correct default XML namespace |  | ||||||
|                           is used during initialization. |  | ||||||
|         """ |  | ||||||
|         XMLStream.__init__(self) |         XMLStream.__init__(self) | ||||||
|  |  | ||||||
|         # To comply with PEP8, method names now use underscores. |  | ||||||
|         # Deprecated method names are re-mapped for backwards compatibility. |  | ||||||
|         self.default_ns = default_ns |         self.default_ns = default_ns | ||||||
|         self.stream_ns = 'http://etherx.jabber.org/streams' |         self.stream_ns = 'http://etherx.jabber.org/streams' | ||||||
|         self.namespace_map[self.stream_ns] = 'stream' |         self.namespace_map[self.stream_ns] = 'stream' | ||||||
|  |  | ||||||
|         self.boundjid = JID("") |         #: An identifier for the stream as given by the server. | ||||||
|  |         self.stream_id = None | ||||||
|  |  | ||||||
|  |         #: The JabberID (JID) used by this connection.  | ||||||
|  |         self.boundjid = JID(jid) | ||||||
|  |  | ||||||
|  |         #: A dictionary mapping plugin names to plugins. | ||||||
|         self.plugin = {} |         self.plugin = {} | ||||||
|         self.plugin_config = {} |  | ||||||
|         self.plugin_whitelist = [] |  | ||||||
|         self.roster = {} |  | ||||||
|         self.is_component = False |  | ||||||
|         self.auto_authorize = True |  | ||||||
|         self.auto_subscribe = True |  | ||||||
|  |  | ||||||
|  |         #: Configuration options for whitelisted plugins. | ||||||
|  |         #: If a plugin is registered without any configuration, | ||||||
|  |         #: and there is an entry here, it will be used. | ||||||
|  |         self.plugin_config = {} | ||||||
|  |  | ||||||
|  |         #: A list of plugins that will be loaded if | ||||||
|  |         #: :meth:`register_plugins` is called. | ||||||
|  |         self.plugin_whitelist = [] | ||||||
|  |  | ||||||
|  |         #: The main roster object. This roster supports multiple | ||||||
|  |         #: owner JIDs, as in the case for components. For clients | ||||||
|  |         #: which only have a single JID, see :attr:`client_roster`. | ||||||
|  |         self.roster = roster.Roster(self) | ||||||
|  |         self.roster.add(self.boundjid.bare) | ||||||
|  |  | ||||||
|  |         #: The single roster for the bound JID. This is the | ||||||
|  |         #: equivalent of:: | ||||||
|  |         #: | ||||||
|  |         #:     self.roster[self.boundjid.bare] | ||||||
|  |         self.client_roster = self.roster[self.boundjid.bare] | ||||||
|  |  | ||||||
|  |         #: The distinction between clients and components can be | ||||||
|  |         #: important, primarily for choosing how to handle the | ||||||
|  |         #: ``'to'`` and ``'from'`` JIDs of stanzas. | ||||||
|  |         self.is_component = False | ||||||
|  |  | ||||||
|  |         #: Flag indicating that the initial presence broadcast has | ||||||
|  |         #: been sent. Until this happens, some servers may not | ||||||
|  |         #: behave as expected when sending stanzas. | ||||||
|         self.sentpresence = False |         self.sentpresence = False | ||||||
|  |  | ||||||
|  |         #: A reference to :mod:`sleekxmpp.stanza` to make accessing | ||||||
|  |         #: stanza classes easier. | ||||||
|         self.stanza = sleekxmpp.stanza |         self.stanza = sleekxmpp.stanza | ||||||
|  |  | ||||||
|         self.register_handler( |         self.register_handler( | ||||||
| @@ -122,10 +118,30 @@ class BaseXMPP(XMLStream): | |||||||
|                      MatchXPath("{%s}error" % self.stream_ns), |                      MatchXPath("{%s}error" % self.stream_ns), | ||||||
|                      self._handle_stream_error)) |                      self._handle_stream_error)) | ||||||
|  |  | ||||||
|         self.add_event_handler('presence_subscribe', |  | ||||||
|                                self._handle_subscribe) |  | ||||||
|         self.add_event_handler('disconnected', |         self.add_event_handler('disconnected', | ||||||
|                                self._handle_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. |         # Set up the XML stream with XMPP's root stanzas. | ||||||
|         self.register_stanza(Message) |         self.register_stanza(Message) | ||||||
| @@ -138,30 +154,37 @@ class BaseXMPP(XMLStream): | |||||||
|         register_stanza_plugin(Message, Nick) |         register_stanza_plugin(Message, Nick) | ||||||
|         register_stanza_plugin(Message, HTMLIM) |         register_stanza_plugin(Message, HTMLIM) | ||||||
|  |  | ||||||
|     def process(self, *args, **kwargs): |     def start_stream_handler(self, xml): | ||||||
|         """ |         """Save the stream ID once the streams have been established. | ||||||
|         Overrides XMLStream.process. |  | ||||||
|  |  | ||||||
|         Initialize the XML streams and begin processing events. |         :param xml: The incoming stream's root element. | ||||||
|  |         """ | ||||||
|  |         self.stream_id = xml.get('id', '') | ||||||
|  |  | ||||||
|  |     def process(self, *args, **kwargs): | ||||||
|  |         """Initialize plugins and begin processing the XML stream. | ||||||
|  |  | ||||||
|         The number of threads used for processing stream events is determined |         The number of threads used for processing stream events is determined | ||||||
|         by HANDLER_THREADS. |         by :data:`HANDLER_THREADS`. | ||||||
|  |  | ||||||
|         Arguments: |         :param bool block: If ``False``, then event dispatcher will run | ||||||
|             block -- If block=False then event dispatcher will run |                     in a separate thread, allowing for the stream to be | ||||||
|                      in a separate thread, allowing for the stream to be |                     used in the background for another application. | ||||||
|                      used in the background for another application. |                     Otherwise, ``process(block=True)`` blocks the current | ||||||
|                      Otherwise, process(block=True) blocks the current thread. |                     thread. Defaults to ``False``. | ||||||
|                      Defaults to False. |         :param bool threaded: **DEPRECATED** | ||||||
|  |                     If ``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``. This does **not** mean that no | ||||||
|  |                     threads are used at all if ``threaded=False``. | ||||||
|  |  | ||||||
|             **threaded is deprecated and included for API compatibility** |         Regardless of these threading options, these threads will  | ||||||
|             threaded -- If threaded=True then event dispatcher will run |         always exist: | ||||||
|                         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 |         - The event queue processor | ||||||
|             regardless of these parameters. |         - The send queue processor | ||||||
|  |         - The scheduler | ||||||
|         """ |         """ | ||||||
|         for name in self.plugin: |         for name in self.plugin: | ||||||
|             if not self.plugin[name].post_inited: |             if not self.plugin[name].post_inited: | ||||||
| @@ -169,15 +192,13 @@ class BaseXMPP(XMLStream): | |||||||
|         return XMLStream.process(self, *args, **kwargs) |         return XMLStream.process(self, *args, **kwargs) | ||||||
|  |  | ||||||
|     def register_plugin(self, plugin, pconfig={}, module=None): |     def register_plugin(self, plugin, pconfig={}, module=None): | ||||||
|         """ |         """Register and configure  a plugin for use in this stream. | ||||||
|         Register and configure  a plugin for use in this stream. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param plugin: The name of the plugin class. Plugin names must | ||||||
|             plugin  -- The name of the plugin class. Plugin names must |  | ||||||
|                        be unique. |                        be unique. | ||||||
|             pconfig -- A dictionary of configuration data for the plugin. |         :param pconfig: A dictionary of configuration data for the plugin. | ||||||
|                        Defaults to an empty dictionary. |                         Defaults to an empty dictionary. | ||||||
|             module  -- Optional refence to the module containing the plugin |         :param module: Optional refence to the module containing the plugin | ||||||
|                        class if using custom plugins. |                        class if using custom plugins. | ||||||
|         """ |         """ | ||||||
|         try: |         try: | ||||||
| @@ -198,29 +219,32 @@ class BaseXMPP(XMLStream): | |||||||
|                 # the sleekxmpp package, so leave out the globals(). |                 # the sleekxmpp package, so leave out the globals(). | ||||||
|                 module = __import__(module, fromlist=[plugin]) |                 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. |             # Load the plugin class from the module. | ||||||
|             self.plugin[plugin] = getattr(module, plugin)(self, pconfig) |             self.plugin[plugin] = getattr(module, plugin)(self, pconfig) | ||||||
|  |  | ||||||
|             # Let XEP/RFC implementing plugins have some extra logging info. |             # Let XEP/RFC implementing plugins have some extra logging info. | ||||||
|             spec = '(CUSTOM) ' |             spec = '(CUSTOM) %s' | ||||||
|             if self.plugin[plugin].xep: |             if self.plugin[plugin].xep: | ||||||
|                 spec = "(XEP-%s) " % self.plugin[plugin].xep |                 spec = "(XEP-%s) " % self.plugin[plugin].xep | ||||||
|             elif self.plugin[plugin].rfc: |             elif self.plugin[plugin].rfc: | ||||||
|                 spec = "(RFC-%s) " % self.plugin[plugin].rfc |                 spec = "(RFC-%s) " % self.plugin[plugin].rfc | ||||||
|  |  | ||||||
|             desc = (spec, self.plugin[plugin].description) |             desc = (spec, self.plugin[plugin].description) | ||||||
|             log.debug("Loaded Plugin %s%s" % desc) |             log.debug("Loaded Plugin %s %s" % desc) | ||||||
|         except: |         except: | ||||||
|             log.exception("Unable to load plugin: %s", plugin) |             log.exception("Unable to load plugin: %s", plugin) | ||||||
|  |  | ||||||
|     def register_plugins(self): |     def register_plugins(self): | ||||||
|         """ |         """Register and initialize all built-in plugins. | ||||||
|         Register and initialize all built-in plugins. |  | ||||||
|  |  | ||||||
|         Optionally, the list of plugins loaded may be limited to those |         Optionally, the list of plugins loaded may be limited to those | ||||||
|         contained in self.plugin_whitelist. |         contained in :attr:`plugin_whitelist`. | ||||||
|  |  | ||||||
|         Plugin configurations stored in self.plugin_config will be used. |         Plugin configurations stored in :attr:`plugin_config` will be used. | ||||||
|         """ |         """ | ||||||
|         if self.plugin_whitelist: |         if self.plugin_whitelist: | ||||||
|             plugin_list = self.plugin_whitelist |             plugin_list = self.plugin_whitelist | ||||||
| @@ -239,19 +263,15 @@ class BaseXMPP(XMLStream): | |||||||
|             self.plugin[plugin].post_init() |             self.plugin[plugin].post_init() | ||||||
|  |  | ||||||
|     def __getitem__(self, key): |     def __getitem__(self, key): | ||||||
|         """ |         """Return a plugin given its name, if it has been registered.""" | ||||||
|         Return a plugin given its name, if it has been registered. |  | ||||||
|         """ |  | ||||||
|         if key in self.plugin: |         if key in self.plugin: | ||||||
|             return self.plugin[key] |             return self.plugin[key] | ||||||
|         else: |         else: | ||||||
|             log.warning("""Plugin "%s" is not loaded.""" % key) |             log.warning("Plugin '%s' is not loaded.", key) | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|     def get(self, key, default): |     def get(self, key, default): | ||||||
|         """ |         """Return a plugin given its name, if it has been registered.""" | ||||||
|         Return a plugin given its name, if it has been registered. |  | ||||||
|         """ |  | ||||||
|         return self.plugin.get(key, default) |         return self.plugin.get(key, default) | ||||||
|  |  | ||||||
|     def Message(self, *args, **kwargs): |     def Message(self, *args, **kwargs): | ||||||
| @@ -267,16 +287,18 @@ class BaseXMPP(XMLStream): | |||||||
|         return Presence(self, *args, **kwargs) |         return Presence(self, *args, **kwargs) | ||||||
|  |  | ||||||
|     def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None): |     def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None): | ||||||
|         """ |         """Create a new Iq stanza with a given Id and from JID. | ||||||
|         Create a new Iq stanza with a given Id and from JID. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param id: An ideally unique ID value for this stanza thread. | ||||||
|             id     -- An ideally unique ID value for this stanza thread. |                    Defaults to 0. | ||||||
|                       Defaults to 0. |         :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`  | ||||||
|             ifrom  -- The from JID to use for this stanza. |                       to use for this stanza. | ||||||
|             ito    -- The destination JID for this stanza. |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             itype  -- The Iq's type, one of: get, set, result, or error. |                     for this stanza. | ||||||
|             iquery -- Optional namespace for adding a query element. |         :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,  | ||||||
|  |                       one of: ``'get'``, ``'set'``, ``'result'``, | ||||||
|  |                       or ``'error'``. | ||||||
|  |         :param iquery: Optional namespace for adding a query element. | ||||||
|         """ |         """ | ||||||
|         iq = self.Iq() |         iq = self.Iq() | ||||||
|         iq['id'] = str(id) |         iq['id'] = str(id) | ||||||
| @@ -287,17 +309,17 @@ class BaseXMPP(XMLStream): | |||||||
|         return iq |         return iq | ||||||
|  |  | ||||||
|     def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None): |     def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None): | ||||||
|         """ |         """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``. | ||||||
|         Create an Iq stanza of type 'get'. |  | ||||||
|  |  | ||||||
|         Optionally, a query element may be added. |         Optionally, a query element may be added. | ||||||
|  |  | ||||||
|         Arguments: |         :param queryxmlns: The namespace of the query to use. | ||||||
|             queryxmlns -- The namespace of the query to use. |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             ito        -- The destination JID for this stanza. |                     for this stanza. | ||||||
|             ifrom      -- The from JID to use for this stanza. |         :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             iq         -- Optionally use an existing stanza instead |                       to use for this stanza. | ||||||
|                           of generating a new one. |         :param iq: Optionally use an existing stanza instead | ||||||
|  |                    of generating a new one. | ||||||
|         """ |         """ | ||||||
|         if not iq: |         if not iq: | ||||||
|             iq = self.Iq() |             iq = self.Iq() | ||||||
| @@ -311,14 +333,16 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): |     def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None): | ||||||
|         """ |         """ | ||||||
|         Create an Iq stanza of type 'result' with the given ID value. |         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type  | ||||||
|  |         ``'result'`` with the given ID value. | ||||||
|  |  | ||||||
|         Arguments: |         :param id: An ideally unique ID value. May use :meth:`new_id()`. | ||||||
|             id    -- An ideally unique ID value. May use self.new_id(). |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             ito   -- The destination JID for this stanza. |                     for this stanza. | ||||||
|             ifrom -- The from JID to use for this stanza. |         :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             iq    -- Optionally use an existing stanza instead |                       to use for this stanza. | ||||||
|                      of generating a new one. |         :param iq: Optionally use an existing stanza instead | ||||||
|  |                    of generating a new one. | ||||||
|         """ |         """ | ||||||
|         if not iq: |         if not iq: | ||||||
|             iq = self.Iq() |             iq = self.Iq() | ||||||
| @@ -334,17 +358,22 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None): |     def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None): | ||||||
|         """ |         """ | ||||||
|         Create an Iq stanza of type 'set'. |         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``. | ||||||
|  |  | ||||||
|         Optionally, a substanza may be given to use as the |         Optionally, a substanza may be given to use as the | ||||||
|         stanza's payload. |         stanza's payload. | ||||||
|  |  | ||||||
|         Arguments: |         :param sub: Either an  | ||||||
|             sub   -- A stanza or XML object to use as the Iq's payload. |                     :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` | ||||||
|             ito   -- The destination JID for this stanza. |                     stanza object or an | ||||||
|             ifrom -- The from JID to use for this stanza. |                     :class:`~xml.etree.ElementTree.Element` XML object  | ||||||
|             iq    -- Optionally use an existing stanza instead |                     to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload. | ||||||
|                      of generating a new one. |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|  |                     for this stanza. | ||||||
|  |         :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|  |                       to use for this stanza. | ||||||
|  |         :param iq: Optionally use an existing stanza instead | ||||||
|  |                    of generating a new one. | ||||||
|         """ |         """ | ||||||
|         if not iq: |         if not iq: | ||||||
|             iq = self.Iq() |             iq = self.Iq() | ||||||
| @@ -361,19 +390,20 @@ class BaseXMPP(XMLStream): | |||||||
|                       condition='feature-not-implemented', |                       condition='feature-not-implemented', | ||||||
|                       text=None, ito=None, ifrom=None, iq=None): |                       text=None, ito=None, ifrom=None, iq=None): | ||||||
|         """ |         """ | ||||||
|         Create an Iq stanza of type 'error'. |         Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``. | ||||||
|  |  | ||||||
|         Arguments: |         :param id: An ideally unique ID value. May use :meth:`new_id()`. | ||||||
|             id        -- An ideally unique ID value. May use self.new_id(). |         :param type: The type of the error, such as ``'cancel'`` or  | ||||||
|             type      -- The type of the error, such as 'cancel' or 'modify'. |                      ``'modify'``. Defaults to ``'cancel'``. | ||||||
|                          Defaults to 'cancel'. |         :param condition: The error condition. Defaults to  | ||||||
|             condition -- The error condition. |                           ``'feature-not-implemented'``. | ||||||
|                          Defaults to 'feature-not-implemented'. |         :param text: A message describing the cause of the error. | ||||||
|             text      -- A message describing the cause of the error. |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             ito       -- The destination JID for this stanza. |                     for this stanza. | ||||||
|             ifrom     -- The from JID to use for this stanza. |         :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             iq        -- Optionally use an existing stanza instead |                       to use for this stanza. | ||||||
|                          of generating a new one. |         :param iq: Optionally use an existing stanza instead | ||||||
|  |                    of generating a new one. | ||||||
|         """ |         """ | ||||||
|         if not iq: |         if not iq: | ||||||
|             iq = self.Iq() |             iq = self.Iq() | ||||||
| @@ -389,15 +419,16 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): |     def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None): | ||||||
|         """ |         """ | ||||||
|         Create or modify an Iq stanza to use the given |         Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza  | ||||||
|         query namespace. |         to use the given query namespace. | ||||||
|  |  | ||||||
|         Arguments: |         :param iq: Optionally use an existing stanza instead | ||||||
|             iq    -- Optional Iq stanza to modify. A new |                    of generating a new one. | ||||||
|                      stanza is created otherwise. |         :param xmlns: The query's namespace. | ||||||
|             xmlns -- The query's namespace. |         :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|             ito   -- The destination JID for this stanza. |                     for this stanza. | ||||||
|             ifrom -- The from JID to use for this stanza. |         :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID` | ||||||
|  |                       to use for this stanza. | ||||||
|         """ |         """ | ||||||
|         if not iq: |         if not iq: | ||||||
|             iq = self.Iq() |             iq = self.Iq() | ||||||
| @@ -409,12 +440,10 @@ class BaseXMPP(XMLStream): | |||||||
|         return iq |         return iq | ||||||
|  |  | ||||||
|     def make_query_roster(self, iq=None): |     def make_query_roster(self, iq=None): | ||||||
|         """ |         """Create a roster query element. | ||||||
|         Create a roster query element. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param iq: Optionally use an existing stanza instead | ||||||
|             iq -- Optional Iq stanza to modify. A new stanza |                    of generating a new one. | ||||||
|                   is created otherwise. |  | ||||||
|         """ |         """ | ||||||
|         if iq: |         if iq: | ||||||
|             iq['query'] = 'jabber:iq:roster' |             iq['query'] = 'jabber:iq:roster' | ||||||
| @@ -423,18 +452,19 @@ class BaseXMPP(XMLStream): | |||||||
|     def make_message(self, mto, mbody=None, msubject=None, mtype=None, |     def make_message(self, mto, mbody=None, msubject=None, mtype=None, | ||||||
|                      mhtml=None, mfrom=None, mnick=None): |                      mhtml=None, mfrom=None, mnick=None): | ||||||
|         """ |         """ | ||||||
|         Create and initialize a new Message stanza. |         Create and initialize a new  | ||||||
|  |         :class:`~sleekxmpp.stanza.message.Message` stanza. | ||||||
|  |  | ||||||
|         Arguments: |         :param mto: The recipient of the message. | ||||||
|             mto      -- The recipient of the message. |         :param mbody: The main contents of the message. | ||||||
|             mbody    -- The main contents of the message. |         :param msubject: Optional subject for the message. | ||||||
|             msubject -- Optional subject for the message. |         :param mtype: The message's type, such as ``'chat'`` or | ||||||
|             mtype    -- The message's type, such as 'chat' or 'groupchat'. |                       ``'groupchat'``. | ||||||
|             mhtml    -- Optional HTML body content. |         :param mhtml: Optional HTML body content in the form of a string. | ||||||
|             mfrom    -- The sender of the message. If sending from a client, |         :param mfrom: The sender of the message. if sending from a client, | ||||||
|                         be aware that some servers require that the full JID |                       be aware that some servers require that the full JID | ||||||
|                         of the sender be used. |                       of the sender be used. | ||||||
|             mnick    -- Optional nickname of the sender. |         :param mnick: Optional nickname of the sender. | ||||||
|         """ |         """ | ||||||
|         message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) |         message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) | ||||||
|         message['body'] = mbody |         message['body'] = mbody | ||||||
| @@ -446,67 +476,87 @@ class BaseXMPP(XMLStream): | |||||||
|         return message |         return message | ||||||
|  |  | ||||||
|     def make_presence(self, pshow=None, pstatus=None, ppriority=None, |     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. |         Create and initialize a new  | ||||||
|  |         :class:`~sleekxmpp.stanza.presence.Presence` stanza. | ||||||
|  |  | ||||||
|         Arguments: |         :param pshow: The presence's show value. | ||||||
|             pshow     -- The presence's show value. |         :param pstatus: The presence's status message. | ||||||
|             pstatus   -- The presence's status message. |         :param ppriority: This connection's priority. | ||||||
|             ppriority -- This connections' priority. |         :param pto: The recipient of a directed presence. | ||||||
|             pto       -- The recipient of a directed presence. |         :param ptype: The type of presence, such as ``'subscribe'``. | ||||||
|             ptype     -- The type of presence, such as 'subscribe'. |         :param pfrom: The sender of the presence. | ||||||
|             pfrom     -- The sender of the presence. |         :param pnick: Optional nickname of the presence's sender. | ||||||
|         """ |         """ | ||||||
|         presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) |         presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) | ||||||
|         if pshow is not None: |         if pshow is not None: | ||||||
|             presence['type'] = pshow |             presence['type'] = pshow | ||||||
|         if pfrom is None: |         if pfrom is None and self.is_component: | ||||||
|             presence['from'] = self.boundjid.full |             presence['from'] = self.boundjid.full | ||||||
|         presence['priority'] = ppriority |         presence['priority'] = ppriority | ||||||
|         presence['status'] = pstatus |         presence['status'] = pstatus | ||||||
|  |         presence['nick'] = pnick | ||||||
|         return presence |         return presence | ||||||
|  |  | ||||||
|     def send_message(self, mto, mbody, msubject=None, mtype=None, |     def send_message(self, mto, mbody, msubject=None, mtype=None, | ||||||
|                      mhtml=None, mfrom=None, mnick=None): |                      mhtml=None, mfrom=None, mnick=None): | ||||||
|         """ |         """ | ||||||
|         Create, initialize, and send a Message stanza. |         Create, initialize, and send a new  | ||||||
|  |         :class:`~sleekxmpp.stanza.message.Message` stanza. | ||||||
|  |  | ||||||
|  |         :param mto: The recipient of the message. | ||||||
|  |         :param mbody: The main contents of the message. | ||||||
|  |         :param msubject: Optional subject for the message. | ||||||
|  |         :param mtype: The message's type, such as ``'chat'`` or | ||||||
|  |                       ``'groupchat'``. | ||||||
|  |         :param mhtml: Optional HTML body content in the form of a string. | ||||||
|  |         :param 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. | ||||||
|  |         :param mnick: Optional nickname of the sender. | ||||||
|         """ |         """ | ||||||
|         self.makeMessage(mto, mbody, msubject, mtype, |         self.make_message(mto, mbody, msubject, mtype, | ||||||
|                          mhtml, mfrom, mnick).send() |                           mhtml, mfrom, mnick).send() | ||||||
|  |  | ||||||
|     def send_presence(self, pshow=None, pstatus=None, ppriority=None, |     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. |         Create, initialize, and send a new  | ||||||
|  |         :class:`~sleekxmpp.stanza.presence.Presence` stanza. | ||||||
|  |  | ||||||
|         Arguments: |         :param pshow: The presence's show value. | ||||||
|             pshow     -- The presence's show value. |         :param pstatus: The presence's status message. | ||||||
|             pstatus   -- The presence's status message. |         :param ppriority: This connection's priority. | ||||||
|             ppriority -- This connections' priority. |         :param pto: The recipient of a directed presence. | ||||||
|             pto       -- The recipient of a directed presence. |         :param ptype: The type of presence, such as ``'subscribe'``. | ||||||
|             ptype     -- The type of presence, such as 'subscribe'. |         :param pfrom: The sender of the presence. | ||||||
|             pfrom     -- The sender of the presence. |         :param pnick: Optional nickname of the presence's sender. | ||||||
|         """ |         """ | ||||||
|         self.makePresence(pshow, pstatus, ppriority, pto, |         # Python2.6 chokes on Unicode strings for dict keys. | ||||||
|                           ptype=ptype, pfrom=pfrom).send() |         args = {str('pto'): pto, | ||||||
|         # Unexpected errors may occur if |                 str('ptype'): ptype, | ||||||
|         if not self.sentpresence: |                 str('pshow'): pshow, | ||||||
|             self.event('sent_presence') |                 str('pstatus'): pstatus, | ||||||
|             self.sentpresence = True |                 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, |     def send_presence_subscription(self, pto, pfrom=None, | ||||||
|                                    ptype='subscribe', pnick=None): |                                    ptype='subscribe', pnick=None): | ||||||
|         """ |         """ | ||||||
|         Create, initialize, and send a Presence stanza of type 'subscribe'. |         Create, initialize, and send a new  | ||||||
|  |         :class:`~sleekxmpp.stanza.presence.Presence` stanza of | ||||||
|  |         type ``'subscribe'``. | ||||||
|  |  | ||||||
|         Arguments: |         :param pto: The recipient of a directed presence. | ||||||
|             pto   -- The recipient of a directed presence. |         :param pfrom: The sender of the presence. | ||||||
|             pfrom -- The sender of the presence. |         :param ptype: The type of presence, such as ``'subscribe'``. | ||||||
|             ptype -- The type of presence. Defaults to 'subscribe'. |         :param pnick: Optional nickname of the presence's sender. | ||||||
|             pnick -- Nickname of the presence's sender. |  | ||||||
|         """ |         """ | ||||||
|         presence = self.makePresence(ptype=ptype, |         presence = self.makePresence(ptype=ptype, | ||||||
|                                      pfrom=pfrom, |                                      pfrom=pfrom, | ||||||
| @@ -519,9 +569,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def jid(self): |     def jid(self): | ||||||
|         """ |         """Attribute accessor for bare jid""" | ||||||
|         Attribute accessor for bare jid |  | ||||||
|         """ |  | ||||||
|         log.warning("jid property deprecated. Use boundjid.bare") |         log.warning("jid property deprecated. Use boundjid.bare") | ||||||
|         return self.boundjid.bare |         return self.boundjid.bare | ||||||
|  |  | ||||||
| @@ -532,9 +580,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def fulljid(self): |     def fulljid(self): | ||||||
|         """ |         """Attribute accessor for full jid""" | ||||||
|         Attribute accessor for full jid |  | ||||||
|         """ |  | ||||||
|         log.warning("fulljid property deprecated. Use boundjid.full") |         log.warning("fulljid property deprecated. Use boundjid.full") | ||||||
|         return self.boundjid.full |         return self.boundjid.full | ||||||
|  |  | ||||||
| @@ -545,9 +591,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def resource(self): |     def resource(self): | ||||||
|         """ |         """Attribute accessor for jid resource""" | ||||||
|         Attribute accessor for jid resource |  | ||||||
|         """ |  | ||||||
|         log.warning("resource property deprecated. Use boundjid.resource") |         log.warning("resource property deprecated. Use boundjid.resource") | ||||||
|         return self.boundjid.resource |         return self.boundjid.resource | ||||||
|  |  | ||||||
| @@ -558,9 +602,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def username(self): |     def username(self): | ||||||
|         """ |         """Attribute accessor for jid usernode""" | ||||||
|         Attribute accessor for jid usernode |  | ||||||
|         """ |  | ||||||
|         log.warning("username property deprecated. Use boundjid.user") |         log.warning("username property deprecated. Use boundjid.user") | ||||||
|         return self.boundjid.user |         return self.boundjid.user | ||||||
|  |  | ||||||
| @@ -571,9 +613,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def server(self): |     def server(self): | ||||||
|         """ |         """Attribute accessor for jid host""" | ||||||
|         Attribute accessor for jid host |  | ||||||
|         """ |  | ||||||
|         log.warning("server property deprecated. Use boundjid.host") |         log.warning("server property deprecated. Use boundjid.host") | ||||||
|         return self.boundjid.server |         return self.boundjid.server | ||||||
|  |  | ||||||
| @@ -582,9 +622,35 @@ class BaseXMPP(XMLStream): | |||||||
|         log.warning("server property deprecated. Use boundjid.host") |         log.warning("server property deprecated. Use boundjid.host") | ||||||
|         self.boundjid.server = value |         self.boundjid.server = value | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def auto_authorize(self): | ||||||
|  |         """Auto accept or deny subscription requests. | ||||||
|  |  | ||||||
|  |         If ``True``, auto accept subscription requests. | ||||||
|  |         If ``False``, auto deny subscription requests. | ||||||
|  |         If ``None``, don't automatically respond. | ||||||
|  |         """ | ||||||
|  |         return self.roster.auto_authorize | ||||||
|  |  | ||||||
|  |     @auto_authorize.setter | ||||||
|  |     def auto_authorize(self, value): | ||||||
|  |         self.roster.auto_authorize = value | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def auto_subscribe(self): | ||||||
|  |         """Auto send requests for mutual subscriptions. | ||||||
|  |  | ||||||
|  |         If ``True``, auto send mutual subscription requests. | ||||||
|  |         """ | ||||||
|  |         return self.roster.auto_subscribe | ||||||
|  |  | ||||||
|  |     @auto_subscribe.setter | ||||||
|  |     def auto_subscribe(self, value): | ||||||
|  |         self.roster.auto_subscribe = value | ||||||
|  |  | ||||||
|     def set_jid(self, jid): |     def set_jid(self, jid): | ||||||
|         """Rip a JID apart and claim it as our own.""" |         """Rip a JID apart and claim it as our own.""" | ||||||
|         log.debug("setting jid to %s" % jid) |         log.debug("setting jid to %s", jid) | ||||||
|         self.boundjid.full = jid |         self.boundjid.full = jid | ||||||
|  |  | ||||||
|     def getjidresource(self, fulljid): |     def getjidresource(self, fulljid): | ||||||
| @@ -598,7 +664,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|     def _handle_disconnected(self, event): |     def _handle_disconnected(self, event): | ||||||
|         """When disconnected, reset the roster""" |         """When disconnected, reset the roster""" | ||||||
|         self.roster = {} |         self.roster.reset() | ||||||
|  |  | ||||||
|     def _handle_stream_error(self, error): |     def _handle_stream_error(self, error): | ||||||
|         self.event('stream_error', error) |         self.event('stream_error', error) | ||||||
| @@ -607,9 +673,66 @@ class BaseXMPP(XMLStream): | |||||||
|         """Process incoming message stanzas.""" |         """Process incoming message stanzas.""" | ||||||
|         self.event('message', msg) |         self.event('message', msg) | ||||||
|  |  | ||||||
|     def _handle_presence(self, presence): |     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 :attr:`auto_authorize` is True. They | ||||||
|  |         will be rejected if :attr:`auto_authorize` is False. Setting | ||||||
|  |         :attr:`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 :attr:`auto_subscribe` is ``True``. | ||||||
|         """ |         """ | ||||||
|         Process incoming presence stanzas. |         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. | ||||||
|  |  | ||||||
|         Update the roster with presence information. |         Update the roster with presence information. | ||||||
|         """ |         """ | ||||||
| @@ -624,97 +747,25 @@ class BaseXMPP(XMLStream): | |||||||
|              not presence['type'] in presence.showtypes: |              not presence['type'] in presence.showtypes: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         # Strip the information from the stanza. |     def exception(self, exception): | ||||||
|         jid = presence['from'].bare |         """Process any uncaught exceptions, notably  | ||||||
|         resource = presence['from'].resource |         :class:`~sleekxmpp.exceptions.IqError` and | ||||||
|         show = presence['type'] |         :class:`~sleekxmpp.exceptions.IqTimeout` exceptions. | ||||||
|         status = presence['status'] |  | ||||||
|         priority = presence['priority'] |  | ||||||
|  |  | ||||||
|         was_offline = False |         :param exception: An unhandled :class:`Exception` object. | ||||||
|         got_online = False |  | ||||||
|         old_roster = self.roster.get(jid, {}).get(resource, {}) |  | ||||||
|  |  | ||||||
|         # Create a new roster entry if needed. |  | ||||||
|         if not jid in self.roster: |  | ||||||
|             self.roster[jid] = {'groups': [], |  | ||||||
|                                 'name': '', |  | ||||||
|                                 'subscription': 'none', |  | ||||||
|                                 'presence': {}, |  | ||||||
|                                 'in_roster': False} |  | ||||||
|  |  | ||||||
|         # Alias to simplify some references. |  | ||||||
|         connections = self.roster[jid].get('presence', {}) |  | ||||||
|  |  | ||||||
|         # Determine if the user has just come online. |  | ||||||
|         if not resource in connections: |  | ||||||
|             if show == 'available' or show in presence.showtypes: |  | ||||||
|                 got_online = True |  | ||||||
|             was_offline = True |  | ||||||
|             connections[resource] = {} |  | ||||||
|  |  | ||||||
|         if connections[resource].get('show', 'unavailable') == 'unavailable': |  | ||||||
|             was_offline = True |  | ||||||
|  |  | ||||||
|         # Update the roster's state for this JID's resource. |  | ||||||
|         connections[resource] = {'show': show, |  | ||||||
|                                 'status': status, |  | ||||||
|                                 'priority': priority} |  | ||||||
|  |  | ||||||
|         name = self.roster[jid].get('name', '') |  | ||||||
|  |  | ||||||
|         # Remove unneeded state information after a resource |  | ||||||
|         # disconnects. Determine if this was the last connection |  | ||||||
|         # for the JID. |  | ||||||
|         if show == 'unavailable': |  | ||||||
|             log.debug("%s %s got offline" % (jid, resource)) |  | ||||||
|             del connections[resource] |  | ||||||
|  |  | ||||||
|             if not connections and \ |  | ||||||
|                not self.roster[jid].get('in_roster', False): |  | ||||||
|                 del self.roster[jid] |  | ||||||
|             if not was_offline: |  | ||||||
|                 self.event("got_offline", presence) |  | ||||||
|             else: |  | ||||||
|                 return False |  | ||||||
|  |  | ||||||
|         name = '(%s) ' % name if name else '' |  | ||||||
|  |  | ||||||
|         # Presence state has changed. |  | ||||||
|         self.event("changed_status", presence) |  | ||||||
|         if got_online: |  | ||||||
|             self.event("got_online", presence) |  | ||||||
|         log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, |  | ||||||
|                                                    show, status)) |  | ||||||
|  |  | ||||||
|     def _handle_subscribe(self, presence): |  | ||||||
|         """ |         """ | ||||||
|         Automatically managage subscription requests. |         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) | ||||||
|  |  | ||||||
|         Subscription behavior is controlled by the settings |  | ||||||
|         self.auto_authorize and self.auto_subscribe. |  | ||||||
|  |  | ||||||
|         auto_auth  auto_sub   Result: |  | ||||||
|         True       True       Create bi-directional subsriptions. |  | ||||||
|         True       False      Create only directed subscriptions. |  | ||||||
|         False      *          Decline all subscriptions. |  | ||||||
|         None       *          Disable automatic handling and use |  | ||||||
|                               a custom handler. |  | ||||||
|         """ |  | ||||||
|         presence.reply() |  | ||||||
|         presence['to'] = presence['to'].bare |  | ||||||
|  |  | ||||||
|         # We are using trinary logic, so conditions have to be |  | ||||||
|         # more explicit than usual. |  | ||||||
|         if self.auto_authorize == True: |  | ||||||
|             presence['type'] = 'subscribed' |  | ||||||
|             presence.send() |  | ||||||
|             if self.auto_subscribe: |  | ||||||
|                 presence['type'] = 'subscribe' |  | ||||||
|                 presence.send() |  | ||||||
|         elif self.auto_authorize == False: |  | ||||||
|             presence['type'] = 'unsubscribed' |  | ||||||
|             presence.send() |  | ||||||
|  |  | ||||||
| # Restore the old, lowercased name for backwards compatibility. | # Restore the old, lowercased name for backwards compatibility. | ||||||
| basexmpp = BaseXMPP | basexmpp = BaseXMPP | ||||||
|   | |||||||
| @@ -1,9 +1,15 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     sleekxmpp.clientxmpp | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |     ~~~~~~~~~~~~~~~~~~~~ | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |     This module provides XMPP functionality that | ||||||
|  |     is specific to client connections. | ||||||
|  |  | ||||||
|  |     Part of SleekXMPP: The Sleek XMPP Library | ||||||
|  |  | ||||||
|  |     :copyright: (c) 2011 Nathanael C. Fritz | ||||||
|  |     :license: MIT, see LICENSE for more details | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from __future__ import absolute_import, unicode_literals | from __future__ import absolute_import, unicode_literals | ||||||
| @@ -27,11 +33,12 @@ from sleekxmpp.xmlstream.matcher import * | |||||||
| from sleekxmpp.xmlstream.handler import * | from sleekxmpp.xmlstream.handler import * | ||||||
|  |  | ||||||
| # Flag indicating if DNS SRV records are available for use. | # Flag indicating if DNS SRV records are available for use. | ||||||
| SRV_SUPPORT = True |  | ||||||
| try: | try: | ||||||
|     import dns.resolver |     import dns.resolver | ||||||
| except: | except ImportError: | ||||||
|     SRV_SUPPORT = False |     DNSPYTHON = False | ||||||
|  | else: | ||||||
|  |     DNSPYTHON = True | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
| @@ -40,45 +47,38 @@ log = logging.getLogger(__name__) | |||||||
| class ClientXMPP(BaseXMPP): | class ClientXMPP(BaseXMPP): | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|     SleekXMPP's client class. ( Use only for good, not for evil.) |     SleekXMPP's client class. (Use only for good, not for evil.) | ||||||
|  |  | ||||||
|     Typical Use: |     Typical use pattern: | ||||||
|     xmpp = ClientXMPP('user@server.tld/resource', 'password') |  | ||||||
|     xmpp.process(block=False) // when block is True, it blocks the current |  | ||||||
|     //                           thread. False by default. |  | ||||||
|  |  | ||||||
|     Attributes: |     .. code-block:: python | ||||||
|  |  | ||||||
|     Methods: |         xmpp = ClientXMPP('user@server.tld/resource', 'password') | ||||||
|         connect          -- Overrides XMLStream.connect. |         # ... Register plugins and event handlers ... | ||||||
|         del_roster_item  -- Delete a roster item. |         xmpp.connect() | ||||||
|         get_roster       -- Retrieve the roster from the server. |         xmpp.process(block=False) # block=True will block the current | ||||||
|         register_feature -- Register a stream feature. |                                   # thread. By default, block=False | ||||||
|         update_roster    -- Update a roster item. |  | ||||||
|  |     :param jid: The JID of the XMPP user account. | ||||||
|  |     :param password: The password for the XMPP user account. | ||||||
|  |     :param ssl: **Deprecated.** | ||||||
|  |     :param plugin_config: A dictionary of plugin configurations. | ||||||
|  |     :param plugin_whitelist: A list of approved plugins that  | ||||||
|  |                     will be loaded when calling  | ||||||
|  |                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. | ||||||
|  |     :param escape_quotes: **Deprecated.** | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, ssl=False, plugin_config={}, |     def __init__(self, jid, password, ssl=False, plugin_config={}, | ||||||
|                  plugin_whitelist=[], escape_quotes=True): |                  plugin_whitelist=[], escape_quotes=True, sasl_mech=None): | ||||||
|         """ |         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||||
|         Create a new SleekXMPP client. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             jid              -- The JID of the XMPP user account. |  | ||||||
|             password         -- The password for the XMPP user account. |  | ||||||
|             ssl              -- Deprecated. |  | ||||||
|             plugin_config    -- A dictionary of plugin configurations. |  | ||||||
|             plugin_whitelist -- A list of approved plugins that will be loaded |  | ||||||
|                                 when calling register_plugins. |  | ||||||
|             escape_quotes    -- Deprecated. |  | ||||||
|         """ |  | ||||||
|         BaseXMPP.__init__(self, 'jabber:client') |  | ||||||
|  |  | ||||||
|         self.set_jid(jid) |         self.set_jid(jid) | ||||||
|         self.password = password |         self.password = password | ||||||
|         self.escape_quotes = escape_quotes |         self.escape_quotes = escape_quotes | ||||||
|         self.plugin_config = plugin_config |         self.plugin_config = plugin_config | ||||||
|         self.plugin_whitelist = plugin_whitelist |         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.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( | ||||||
|                 self.boundjid.host, |                 self.boundjid.host, | ||||||
| @@ -113,87 +113,73 @@ class ClientXMPP(BaseXMPP): | |||||||
|  |  | ||||||
|         # Setup default stream features |         # Setup default stream features | ||||||
|         self.register_plugin('feature_starttls') |         self.register_plugin('feature_starttls') | ||||||
|         self.register_plugin('feature_mechanisms') |  | ||||||
|         self.register_plugin('feature_bind') |         self.register_plugin('feature_bind') | ||||||
|         self.register_plugin('feature_session') |         self.register_plugin('feature_session') | ||||||
|  |         self.register_plugin('feature_mechanisms', | ||||||
|  |                 pconfig={'use_mech': sasl_mech} if sasl_mech else None) | ||||||
|  |  | ||||||
|     def connect(self, address=tuple(), reattempt=True, use_tls=True): |     def connect(self, address=tuple(), reattempt=True, | ||||||
|         """ |                 use_tls=True, use_ssl=False): | ||||||
|         Connect to the XMPP server. |         """Connect to the XMPP server. | ||||||
|  |  | ||||||
|         When no address is given, a SRV lookup for the server will |         When no address is given, a SRV lookup for the server will | ||||||
|         be attempted. If that fails, the server user in the JID |         be attempted. If that fails, the server user in the JID | ||||||
|         will be used. |         will be used. | ||||||
|  |  | ||||||
|         Arguments: |         :param address   -- A tuple containing the server's host and port. | ||||||
|             address   -- A tuple containing the server's host and port. |         :param reattempt: If ``True``, repeat attempting to connect if an | ||||||
|             reattempt -- If True, reattempt the connection if an |                          error occurs. Defaults to ``True``. | ||||||
|                          error occurs. Defaults to True. |         :param use_tls: Indicates if TLS should be used for the | ||||||
|             use_tls   -- Indicates if TLS should be used for the |                         connection. Defaults to ``True``. | ||||||
|                          connection. Defaults to True. |         :param use_ssl: Indicates if the older SSL connection method | ||||||
|  |                         should be used. Defaults to ``False``. | ||||||
|         """ |         """ | ||||||
|         self.session_started_event.clear() |         self.session_started_event.clear() | ||||||
|         if not address or len(address) < 2: |  | ||||||
|             if not self.srv_support: |  | ||||||
|                 log.debug("Did not supply (address, port) to connect" + \ |  | ||||||
|                               " to and no SRV support is installed" + \ |  | ||||||
|                               " (http://www.dnspython.org)." + \ |  | ||||||
|                               " Continuing to attempt connection, using" + \ |  | ||||||
|                               " server hostname from JID.") |  | ||||||
|             else: |  | ||||||
|                 log.debug("Since no address is supplied," + \ |  | ||||||
|                               "attempting SRV lookup.") |  | ||||||
|                 try: |  | ||||||
|                     xmpp_srv = "_xmpp-client._tcp.%s" % self.boundjid.host |  | ||||||
|                     answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) |  | ||||||
|                 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): |  | ||||||
|                     log.debug("No appropriate SRV record found." + \ |  | ||||||
|                                   " Using JID server name.") |  | ||||||
|                 except (dns.exception.Timeout,): |  | ||||||
|                     log.debug("DNS resolution timed out.") |  | ||||||
|                 else: |  | ||||||
|                     # Pick a random server, weighted by priority. |  | ||||||
|  |  | ||||||
|                     addresses = {} |  | ||||||
|                     intmax = 0 |  | ||||||
|                     topprio = 65535 |  | ||||||
|                     for answer in answers: |  | ||||||
|                         topprio = min(topprio, answer.priority) |  | ||||||
|                     for answer in answers: |  | ||||||
|                         if answer.priority == topprio: |  | ||||||
|                             intmax += answer.weight |  | ||||||
|                             addresses[intmax] = (answer.target.to_text()[:-1], |  | ||||||
|                                              answer.port) |  | ||||||
|  |  | ||||||
|                     #python3 returns a generator for dictionary keys |  | ||||||
|                     items = [x for x in addresses.keys()] |  | ||||||
|                     items.sort() |  | ||||||
|  |  | ||||||
|                     picked = random.randint(0, intmax) |  | ||||||
|                     for item in items: |  | ||||||
|                         if picked <= item: |  | ||||||
|                             address = addresses[item] |  | ||||||
|                             break |  | ||||||
|  |  | ||||||
|         if not address: |         if not address: | ||||||
|             # If all else fails, use the server from the JID. |  | ||||||
|             address = (self.boundjid.host, 5222) |             address = (self.boundjid.host, 5222) | ||||||
|  |  | ||||||
|         return XMLStream.connect(self, address[0], address[1], |         return XMLStream.connect(self, address[0], address[1], | ||||||
|                                  use_tls=use_tls, reattempt=reattempt) |                                  use_tls=use_tls, use_ssl=use_ssl, | ||||||
|  |                                  reattempt=reattempt) | ||||||
|  |  | ||||||
|  |     def get_dns_records(self, domain, port=None): | ||||||
|  |         """Get the DNS records for a domain, including SRV records. | ||||||
|  |  | ||||||
|  |         :param domain: The domain in question. | ||||||
|  |         :param 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): |     def register_feature(self, name, handler, restart=False, order=5000): | ||||||
|         """ |         """Register a stream feature handler. | ||||||
|         Register a stream feature. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param name: The name of the stream feature. | ||||||
|             name    -- The name of the stream feature. |         :param handler: The function to execute if the feature is received. | ||||||
|             handler -- The function to execute if the feature is received. |         :param restart: Indicates if feature processing should halt with | ||||||
|             restart -- Indicates if feature processing should halt with |                         this feature. Defaults to ``False``. | ||||||
|                        this feature. Defaults to False. |         :param order: The relative ordering in which the feature should | ||||||
|             order   -- The relative ordering in which the feature should |                       be negotiated. Lower values will be attempted | ||||||
|                        be negotiated. Lower values will be attempted |                       earlier when available. | ||||||
|                        earlier when available. |  | ||||||
|         """ |         """ | ||||||
|         self._stream_feature_handlers[name] = (handler, restart) |         self._stream_feature_handlers[name] = (handler, restart) | ||||||
|         self._stream_feature_order.append((order, name)) |         self._stream_feature_order.append((order, name)) | ||||||
| @@ -201,72 +187,58 @@ class ClientXMPP(BaseXMPP): | |||||||
|  |  | ||||||
|     def update_roster(self, jid, name=None, subscription=None, groups=[], |     def update_roster(self, jid, name=None, subscription=None, groups=[], | ||||||
|                             block=True, timeout=None, callback=None): |                             block=True, timeout=None, callback=None): | ||||||
|         """ |         """Add or change a roster item. | ||||||
|         Add or change a roster item. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param jid: The JID of the entry to modify. | ||||||
|             jid          -- The JID of the entry to modify. |         :param name: The user's nickname for this JID. | ||||||
|             name         -- The user's nickname for this JID. |         :param subscription: The subscription status. May be one of | ||||||
|             subscription -- The subscription status. May be one of |                              ``'to'``, ``'from'``, ``'both'``, or | ||||||
|                             'to', 'from', 'both', or 'none'. If set |                              ``'none'``. If set to ``'remove'``, | ||||||
|                             to 'remove', the entry will be deleted. |                              the entry will be deleted. | ||||||
|             groups       -- The roster groups that contain this item. |         :param groups: The roster groups that contain this item. | ||||||
|             block        -- Specify if the roster request will block |         :param block: Specify if the roster request will block | ||||||
|                             until a response is received, or a timeout |                       until a response is received, or a timeout | ||||||
|                             occurs. Defaults to True. |                       occurs. Defaults to ``True``. | ||||||
|             timeout      -- The length of time (in seconds) to wait |         :param timeout: The length of time (in seconds) to wait | ||||||
|                             for a response before continuing if blocking |                         for a response before continuing if blocking | ||||||
|                             is used. Defaults to self.response_timeout. |                         is used. Defaults to  | ||||||
|             callback     -- Optional reference to a stream handler function. |             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. | ||||||
|                             Will be executed when the roster is received. |         :param callback: Optional reference to a stream handler function. | ||||||
|                             Implies block=False. |                          Will be executed when the roster is received. | ||||||
|  |                          Implies ``block=False``. | ||||||
|         """ |         """ | ||||||
|         iq = self.Iq() |         return self.client_roster.update(jid, name, subscription, groups, | ||||||
|         iq['type'] = 'set' |                                          block, timeout, callback) | ||||||
|         iq['roster']['items'] = {jid: {'name': name, |  | ||||||
|                                        'subscription': subscription, |  | ||||||
|                                        'groups': groups}} |  | ||||||
|         response = iq.send(block, timeout, callback) |  | ||||||
|         if response in [False, None] or not isinstance(response, Iq): |  | ||||||
|             return response |  | ||||||
|         return response['type'] == 'result' |  | ||||||
|  |  | ||||||
|     def del_roster_item(self, jid): |     def del_roster_item(self, jid): | ||||||
|         """ |         """Remove an item from the roster. | ||||||
|         Remove an item from the roster by setting its subscription |          | ||||||
|         status to 'remove'. |         This is done by setting its subscription status to ``'remove'``. | ||||||
|  |  | ||||||
|         Arguments: |         :param jid: The JID of the item to remove. | ||||||
|             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): |     def get_roster(self, block=True, timeout=None, callback=None): | ||||||
|         """ |         """Request the roster from the server. | ||||||
|         Request the roster from the server. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param block: Specify if the roster request will block until a | ||||||
|             block    -- Specify if the roster request will block until a |                       response is received, or a timeout occurs. | ||||||
|                         response is received, or a timeout occurs. |                       Defaults to ``True``. | ||||||
|                         Defaults to True. |         :param timeout: The length of time (in seconds) to wait for a response | ||||||
|             timeout  -- The length of time (in seconds) to wait for a response |  | ||||||
|                         before continuing if blocking is used. |                         before continuing if blocking is used. | ||||||
|                         Defaults to self.response_timeout. |                         Defaults to  | ||||||
|             callback -- Optional reference to a stream handler function. Will |             :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`. | ||||||
|                         be executed when the roster is received. |         :param callback: Optional reference to a stream handler function. Will | ||||||
|                         Implies block=False. |                          be executed when the roster is received. | ||||||
|  |                          Implies ``block=False``. | ||||||
|         """ |         """ | ||||||
|         iq = self.Iq() |         iq = self.Iq() | ||||||
|         iq['type'] = 'get' |         iq['type'] = 'get' | ||||||
|         iq.enable('roster') |         iq.enable('roster') | ||||||
|         response = iq.send(block, timeout, callback) |         response = iq.send(block, timeout, callback) | ||||||
|  |  | ||||||
|         if response == False: |         if callback is None: | ||||||
|             self.event('roster_timeout') |  | ||||||
|  |  | ||||||
|         if response in [False, None] or not isinstance(response, Iq): |  | ||||||
|             return response |  | ||||||
|         else: |  | ||||||
|             return self._handle_roster(response, request=True) |             return self._handle_roster(response, request=True) | ||||||
|  |  | ||||||
|     def _handle_connected(self, event=None): |     def _handle_connected(self, event=None): | ||||||
| @@ -277,19 +249,10 @@ class ClientXMPP(BaseXMPP): | |||||||
|         self.bindfail = False |         self.bindfail = False | ||||||
|         self.features = set() |         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): |     def _handle_stream_features(self, features): | ||||||
|         """ |         """Process the received stream features. | ||||||
|         Process the received stream features. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param features: The features stanza. | ||||||
|             features -- The features stanza. |  | ||||||
|         """ |         """ | ||||||
|         for order, name in self._stream_feature_order: |         for order, name in self._stream_feature_order: | ||||||
|             if name in features['features']: |             if name in features['features']: | ||||||
| @@ -300,23 +263,22 @@ class ClientXMPP(BaseXMPP): | |||||||
|                     return True |                     return True | ||||||
|  |  | ||||||
|     def _handle_roster(self, iq, request=False): |     def _handle_roster(self, iq, request=False): | ||||||
|         """ |         """Update the roster after receiving a roster stanza. | ||||||
|         Update the roster after receiving a roster stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param iq: The roster stanza. | ||||||
|             iq      -- The roster stanza. |         :param request: Indicates if this stanza is a response | ||||||
|             request -- Indicates if this stanza is a response |                         to a request for the roster, and not an | ||||||
|                        to a request for the roster. |                         empty acknowledgement from the server. | ||||||
|         """ |         """ | ||||||
|         if iq['type'] == 'set' or (iq['type'] == 'result' and request): |         if iq['type'] == 'set' or (iq['type'] == 'result' and request): | ||||||
|             for jid in iq['roster']['items']: |             for jid in iq['roster']['items']: | ||||||
|                 if not jid in self.roster: |                 item = iq['roster']['items'][jid] | ||||||
|                     self.roster[jid] = {'groups': [], |                 roster = self.roster[iq['to'].bare] | ||||||
|                                         'name': '', |                 roster[jid]['name'] = item['name'] | ||||||
|                                         'subscription': 'none', |                 roster[jid]['groups'] = item['groups'] | ||||||
|                                         'presence': {}, |                 roster[jid]['from'] = item['subscription'] in ['from', 'both'] | ||||||
|                                         'in_roster': True} |                 roster[jid]['to'] = item['subscription'] in ['to', 'both'] | ||||||
|                 self.roster[jid].update(iq['roster']['items'][jid]) |                 roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||||
|             self.event('roster_received', iq) |             self.event('roster_received', iq) | ||||||
|  |  | ||||||
|         self.event("roster_update", iq) |         self.event("roster_update", iq) | ||||||
|   | |||||||
| @@ -1,9 +1,15 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     sleekxmpp.clientxmpp | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |     ~~~~~~~~~~~~~~~~~~~~ | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |     This module provides XMPP functionality that | ||||||
|  |     is specific to external server component connections. | ||||||
|  |  | ||||||
|  |     Part of SleekXMPP: The Sleek XMPP Library | ||||||
|  |  | ||||||
|  |     :copyright: (c) 2011 Nathanael C. Fritz | ||||||
|  |     :license: MIT, see LICENSE for more details | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||||
| @@ -32,33 +38,27 @@ class ComponentXMPP(BaseXMPP): | |||||||
|  |  | ||||||
|     Use only for good, not for evil. |     Use only for good, not for evil. | ||||||
|  |  | ||||||
|     Methods: |     :param jid: The JID of the component. | ||||||
|         connect              -- Overrides XMLStream.connect. |     :param secret: The secret or password for the component. | ||||||
|         incoming_filter      -- Overrides XMLStream.incoming_filter. |     :param host: The server accepting the component. | ||||||
|         start_stream_handler -- Overrides XMLStream.start_stream_handler. |     :param port: The port used to connect to the server. | ||||||
|  |     :param plugin_config: A dictionary of plugin configurations. | ||||||
|  |     :param plugin_whitelist: A list of approved plugins that  | ||||||
|  |                     will be loaded when calling  | ||||||
|  |                     :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`. | ||||||
|  |     :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace | ||||||
|  |                       should be used instead of the standard | ||||||
|  |                       ``'jabber:component:accept'`` namespace. | ||||||
|  |                       Defaults to ``False``. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, jid, secret, host, port, |     def __init__(self, jid, secret, host=None, port=None, | ||||||
|                  plugin_config={}, plugin_whitelist=[], use_jc_ns=False): |                  plugin_config={}, plugin_whitelist=[], use_jc_ns=False): | ||||||
|         """ |  | ||||||
|         Arguments: |  | ||||||
|             jid              -- The JID of the component. |  | ||||||
|             secret           -- The secret or password for the component. |  | ||||||
|             host             -- The server accepting the component. |  | ||||||
|             port             -- The port used to connect to the server. |  | ||||||
|             plugin_config    -- A dictionary of plugin configurations. |  | ||||||
|             plugin_whitelist -- A list of desired plugins to load |  | ||||||
|                                 when using register_plugins. |  | ||||||
|             use_js_ns        -- Indicates if the 'jabber:client' namespace |  | ||||||
|                                 should be used instead of the standard |  | ||||||
|                                 'jabber:component:accept' namespace. |  | ||||||
|                                 Defaults to False. |  | ||||||
|         """ |  | ||||||
|         if use_jc_ns: |         if use_jc_ns: | ||||||
|             default_ns = 'jabber:client' |             default_ns = 'jabber:client' | ||||||
|         else: |         else: | ||||||
|             default_ns = 'jabber:component:accept' |             default_ns = 'jabber:component:accept' | ||||||
|         BaseXMPP.__init__(self, default_ns) |         BaseXMPP.__init__(self, jid, default_ns) | ||||||
|  |  | ||||||
|         self.auto_authorize = None |         self.auto_authorize = None | ||||||
|         self.stream_header = "<stream:stream %s %s to='%s'>" % ( |         self.stream_header = "<stream:stream %s %s to='%s'>" % ( | ||||||
| @@ -68,8 +68,8 @@ class ComponentXMPP(BaseXMPP): | |||||||
|         self.stream_footer = "</stream:stream>" |         self.stream_footer = "</stream:stream>" | ||||||
|         self.server_host = host |         self.server_host = host | ||||||
|         self.server_port = port |         self.server_port = port | ||||||
|         self.set_jid(jid) |  | ||||||
|         self.secret = secret |         self.secret = secret | ||||||
|  |  | ||||||
|         self.plugin_config = plugin_config |         self.plugin_config = plugin_config | ||||||
|         self.plugin_whitelist = plugin_whitelist |         self.plugin_whitelist = plugin_whitelist | ||||||
|         self.is_component = True |         self.is_component = True | ||||||
| @@ -78,27 +78,45 @@ class ComponentXMPP(BaseXMPP): | |||||||
|                 Callback('Handshake', |                 Callback('Handshake', | ||||||
|                          MatchXPath('{jabber:component:accept}handshake'), |                          MatchXPath('{jabber:component:accept}handshake'), | ||||||
|                          self._handle_handshake)) |                          self._handle_handshake)) | ||||||
|  |         self.add_event_handler('presence_probe', | ||||||
|  |                                self._handle_probe) | ||||||
|  |  | ||||||
|     def connect(self): |     def connect(self, host=None, port=None, use_ssl=False,  | ||||||
|         """ |                       use_tls=True, reattempt=True): | ||||||
|         Connect to the server. |         """Connect to the server. | ||||||
|  |  | ||||||
|         Overrides XMLStream.connect. |         Setting ``reattempt`` to ``True`` will cause connection attempts to | ||||||
|  |         be made every second until a successful connection is established. | ||||||
|  |  | ||||||
|  |         :param host: The name of the desired server for the connection. | ||||||
|  |                      Defaults to :attr:`server_host`. | ||||||
|  |         :param port: Port to connect to on the server. | ||||||
|  |                      Defauts to :attr:`server_port`. | ||||||
|  |         :param use_ssl: Flag indicating if SSL should be used by connecting | ||||||
|  |                         directly to a port using SSL. | ||||||
|  |         :param use_tls: Flag indicating if TLS should be used, allowing for | ||||||
|  |                         connecting to a port without using SSL immediately and | ||||||
|  |                         later upgrading the connection. | ||||||
|  |         :param reattempt: Flag indicating if the socket should reconnect | ||||||
|  |                           after disconnections. | ||||||
|         """ |         """ | ||||||
|         log.debug("Connecting to %s:%s" % (self.server_host, |         if host is None: | ||||||
|                                                self.server_port)) |             host = self.server_host | ||||||
|         return XMLStream.connect(self, self.server_host, |         if port is None: | ||||||
|                                        self.server_port) |             port = self.server_port | ||||||
|  |         log.debug("Connecting to %s:%s", host, port) | ||||||
|  |         return XMLStream.connect(self, host=host, port=port, | ||||||
|  |                                        use_ssl=use_ssl, | ||||||
|  |                                        use_tls=use_tls, | ||||||
|  |                                        reattempt=reattempt) | ||||||
|  |  | ||||||
|     def incoming_filter(self, xml): |     def incoming_filter(self, xml): | ||||||
|         """ |         """ | ||||||
|         Pre-process incoming XML stanzas by converting any 'jabber:client' |         Pre-process incoming XML stanzas by converting any | ||||||
|         namespaced elements to the component's default namespace. |         ``'jabber:client'`` namespaced elements to the component's | ||||||
|  |         default namespace. | ||||||
|  |  | ||||||
|         Overrides XMLStream.incoming_filter. |         :param xml: The XML stanza to pre-process. | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             xml -- The XML stanza to pre-process. |  | ||||||
|         """ |         """ | ||||||
|         if xml.tag.startswith('{jabber:client}'): |         if xml.tag.startswith('{jabber:client}'): | ||||||
|             xml.tag = xml.tag.replace('jabber:client', self.default_ns) |             xml.tag = xml.tag.replace('jabber:client', self.default_ns) | ||||||
| @@ -115,11 +133,10 @@ class ComponentXMPP(BaseXMPP): | |||||||
|         Once the streams are established, attempt to handshake |         Once the streams are established, attempt to handshake | ||||||
|         with the server to be accepted as a component. |         with the server to be accepted as a component. | ||||||
|  |  | ||||||
|         Overrides XMLStream.start_stream_handler. |         :param xml: The incoming stream's root element. | ||||||
|  |  | ||||||
|         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. |         # Construct a hash of the stream ID and the component secret. | ||||||
|         sid = xml.get('id', '') |         sid = xml.get('id', '') | ||||||
|         pre_hash = '%s%s' % (sid, self.secret) |         pre_hash = '%s%s' % (sid, self.secret) | ||||||
| @@ -132,11 +149,14 @@ class ComponentXMPP(BaseXMPP): | |||||||
|         self.send_xml(handshake, now=True) |         self.send_xml(handshake, now=True) | ||||||
|  |  | ||||||
|     def _handle_handshake(self, xml): |     def _handle_handshake(self, xml): | ||||||
|         """ |         """The handshake has been accepted. | ||||||
|         The handshake has been accepted. |  | ||||||
|  |  | ||||||
|         Arguments: |         :param xml: The reply handshake stanza. | ||||||
|             xml -- The reply handshake stanza. |  | ||||||
|         """ |         """ | ||||||
|         self.session_started_event.set() |         self.session_started_event.set() | ||||||
|         self.event("session_start") |         self.event("session_start") | ||||||
|  |  | ||||||
|  |     def _handle_probe(self, presence): | ||||||
|  |         pto = presence['to'].bare | ||||||
|  |         pfrom = presence['from'].bare | ||||||
|  |         self.roster[pto][pfrom].handle_probe(presence) | ||||||
|   | |||||||
| @@ -1,9 +1,12 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     sleekxmpp.exceptions | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |     ~~~~~~~~~~~~~~~~~~~~ | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |     Part of SleekXMPP: The Sleek XMPP Library | ||||||
|  |  | ||||||
|  |     :copyright: (c) 2011 Nathanael C. Fritz | ||||||
|  |     :license: MIT, see LICENSE for more details | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -13,35 +16,35 @@ class XMPPError(Exception): | |||||||
|     A generic exception that may be raised while processing an XMPP stanza |     A generic exception that may be raised while processing an XMPP stanza | ||||||
|     to indicate that an error response stanza should be sent. |     to indicate that an error response stanza should be sent. | ||||||
|  |  | ||||||
|     The exception method for stanza objects extending RootStanza will create |     The exception method for stanza objects extending | ||||||
|     an error stanza and initialize any additional substanzas using the |     :class:`~sleekxmpp.stanza.rootstanza.RootStanza` will create an error | ||||||
|     extension information included in the exception. |     stanza and initialize any additional substanzas using the extension | ||||||
|  |     information included in the exception. | ||||||
|  |  | ||||||
|     Meant for use in SleekXMPP plugins and applications using SleekXMPP. |     Meant for use in SleekXMPP plugins and applications using SleekXMPP. | ||||||
|  |  | ||||||
|  |     Extension information can be included to add additional XML elements | ||||||
|  |     to the generated error stanza. | ||||||
|  |  | ||||||
|  |     :param condition: The XMPP defined error condition. | ||||||
|  |                       Defaults to ``'undefined-condition'``. | ||||||
|  |     :param text: Human readable text describing the error. | ||||||
|  |     :param etype: The XMPP error type, such as ``'cancel'`` or ``'modify'``. | ||||||
|  |                   Defaults to ``'cancel'``. | ||||||
|  |     :param extension: Tag name of the extension's XML content. | ||||||
|  |     :param extension_ns: XML namespace of the extensions' XML content. | ||||||
|  |     :param extension_args: Content and attributes for the extension | ||||||
|  |                            element. Same as the additional arguments to | ||||||
|  |                            the :class:`~xml.etree.ElementTree.Element` | ||||||
|  |                            constructor. | ||||||
|  |     :param clear: Indicates if the stanza's contents should be | ||||||
|  |                   removed before replying with an error. | ||||||
|  |                   Defaults to ``True``. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, condition='undefined-condition', text=None, etype=None, |     def __init__(self, condition='undefined-condition', text=None, | ||||||
|                  extension=None, extension_ns=None, extension_args=None, |                 etype='cancel', extension=None, extension_ns=None, | ||||||
|                  clear=True): |                 extension_args=None, clear=True): | ||||||
|         """ |  | ||||||
|         Create a new XMPPError exception. |  | ||||||
|  |  | ||||||
|         Extension information can be included to add additional XML elements |  | ||||||
|         to the generated error stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             condition      -- The XMPP defined error condition. |  | ||||||
|             text           -- Human readable text describing the error. |  | ||||||
|             etype          -- The XMPP error type, such as cancel or modify. |  | ||||||
|             extension      -- Tag name of the extension's XML content. |  | ||||||
|             extension_ns   -- XML namespace of the extensions' XML content. |  | ||||||
|             extension_args -- Content and attributes for the extension |  | ||||||
|                               element. Same as the additional arguments to |  | ||||||
|                               the ET.Element constructor. |  | ||||||
|             clear          -- Indicates if the stanza's contents should be |  | ||||||
|                               removed before replying with an error. |  | ||||||
|                               Defaults to True. |  | ||||||
|         """ |  | ||||||
|         if extension_args is None: |         if extension_args is None: | ||||||
|             extension_args = {} |             extension_args = {} | ||||||
|  |  | ||||||
| @@ -52,3 +55,36 @@ class XMPPError(Exception): | |||||||
|         self.extension = extension |         self.extension = extension | ||||||
|         self.extension_ns = extension_ns |         self.extension_ns = extension_ns | ||||||
|         self.extension_args = extension_args |         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') | ||||||
|  |  | ||||||
|  |         #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response  | ||||||
|  |         #: did not arrive before the timeout expired. | ||||||
|  |         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']) | ||||||
|  |  | ||||||
|  |         #: The :class:`~sleekxmpp.stanza.iq.Iq` error result stanza. | ||||||
|  |         self.iq = iq | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ class feature_bind(base_plugin): | |||||||
|         Arguments: |         Arguments: | ||||||
|             features -- The stream features stanza. |             features -- The stream features stanza. | ||||||
|         """ |         """ | ||||||
|         log.debug("Requesting resource: %s" % self.xmpp.boundjid.resource) |         log.debug("Requesting resource: %s", self.xmpp.boundjid.resource) | ||||||
|         iq = self.xmpp.Iq() |         iq = self.xmpp.Iq() | ||||||
|         iq['type'] = 'set' |         iq['type'] = 'set' | ||||||
|         iq.enable('bind') |         iq.enable('bind') | ||||||
| @@ -55,7 +55,7 @@ class feature_bind(base_plugin): | |||||||
|  |  | ||||||
|         self.xmpp.features.add('bind') |         self.xmpp.features.add('bind') | ||||||
|  |  | ||||||
|         log.info("Node set to: %s" % self.xmpp.boundjid.full) |         log.info("Node set to: %s", self.xmpp.boundjid.full) | ||||||
|  |  | ||||||
|         if 'session' not in features['features']: |         if 'session' not in features['features']: | ||||||
|             log.debug("Established Session") |             log.debug("Established Session") | ||||||
|   | |||||||
| @@ -29,6 +29,8 @@ class feature_mechanisms(base_plugin): | |||||||
|         self.description = "SASL Stream Feature" |         self.description = "SASL Stream Feature" | ||||||
|         self.stanza = stanza |         self.stanza = stanza | ||||||
|  |  | ||||||
|  |         self.use_mech = self.config.get('use_mech', None) | ||||||
|  |  | ||||||
|         def tls_active(): |         def tls_active(): | ||||||
|             return 'starttls' in self.xmpp.features |             return 'starttls' in self.xmpp.features | ||||||
|  |  | ||||||
| @@ -48,7 +50,8 @@ class feature_mechanisms(base_plugin): | |||||||
|                                 username=self.xmpp.boundjid.user, |                                 username=self.xmpp.boundjid.user, | ||||||
|                                 sec_query=suelta.sec_query_allow, |                                 sec_query=suelta.sec_query_allow, | ||||||
|                                 request_values=sasl_callback, |                                 request_values=sasl_callback, | ||||||
|                                 tls_active=tls_active) |                                 tls_active=tls_active, | ||||||
|  |                                 mech=self.use_mech) | ||||||
|  |  | ||||||
|         register_stanza_plugin(StreamFeatures, stanza.Mechanisms) |         register_stanza_plugin(StreamFeatures, stanza.Mechanisms) | ||||||
|  |  | ||||||
| @@ -120,7 +123,7 @@ class feature_mechanisms(base_plugin): | |||||||
|  |  | ||||||
|     def _handle_fail(self, stanza): |     def _handle_fail(self, stanza): | ||||||
|         """SASL authentication failed. Disconnect and shutdown.""" |         """SASL authentication failed. Disconnect and shutdown.""" | ||||||
|         log.info("Authentication failed: %s" % stanza['condition']) |         log.info("Authentication failed: %s", stanza['condition']) | ||||||
|         self.xmpp.event("failed_auth", stanza, direct=True) |         self.xmpp.event("failed_auth", stanza, direct=True) | ||||||
|         self.xmpp.disconnect() |         self.xmpp.disconnect() | ||||||
|         return True |         return True | ||||||
|   | |||||||
| @@ -58,8 +58,8 @@ class feature_starttls(base_plugin): | |||||||
|             self.xmpp.send(features['starttls'], now=True) |             self.xmpp.send(features['starttls'], now=True) | ||||||
|             return True |             return True | ||||||
|         else: |         else: | ||||||
|             log.warning("The module tlslite is required to log in" +\ |             log.warning("The module tlslite is required to log in" + \ | ||||||
|                             " to some servers, and has not been found.") |                         " to some servers, and has not been found.") | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|     def _handle_starttls_proceed(self, proceed): |     def _handle_starttls_proceed(self, proceed): | ||||||
|   | |||||||
| @@ -9,3 +9,5 @@ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', | |||||||
|            'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', |            'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', | ||||||
|            'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', |            'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', | ||||||
|            'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] |            'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] | ||||||
|  |  | ||||||
|  | # Don't automatically load xep_0078 | ||||||
|   | |||||||
| @@ -121,7 +121,7 @@ class gmail_notify(base.base_plugin): | |||||||
|     def handle_gmail(self, iq): |     def handle_gmail(self, iq): | ||||||
|         mailbox = iq['mailbox'] |         mailbox = iq['mailbox'] | ||||||
|         approx = ' approximately' if mailbox['estimated'] else '' |         approx = ' approximately' if mailbox['estimated'] else '' | ||||||
|         log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) |         log.info('Gmail: Received%s %s emails', approx, mailbox['total-matched']) | ||||||
|         self.last_result_time = mailbox['result-time'] |         self.last_result_time = mailbox['result-time'] | ||||||
|         self.xmpp.event('gmail_messages', iq) |         self.xmpp.event('gmail_messages', iq) | ||||||
|  |  | ||||||
| @@ -140,7 +140,7 @@ class gmail_notify(base.base_plugin): | |||||||
|         if query is None: |         if query is None: | ||||||
|             log.info("Gmail: Checking for new emails") |             log.info("Gmail: Checking for new emails") | ||||||
|         else: |         else: | ||||||
|             log.info('Gmail: Searching for emails matching: "%s"' % query) |             log.info('Gmail: Searching for emails matching: "%s"', query) | ||||||
|         iq = self.xmpp.Iq() |         iq = self.xmpp.Iq() | ||||||
|         iq['type'] = 'get' |         iq['type'] = 'get' | ||||||
|         iq['to'] = self.xmpp.boundjid.bare |         iq['to'] = self.xmpp.boundjid.bare | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ class jobs(base.base_plugin): | |||||||
| 		iq['psstate']['payload'] = state | 		iq['psstate']['payload'] = state | ||||||
| 		result = iq.send() | 		result = iq.send() | ||||||
| 		if result is None or type(result) == bool or result['type'] != 'result': | 		if result is None or type(result) == bool or result['type'] != 'result': | ||||||
| 			log.error("Unable to change %s:%s to %s" % (node, jobid, state)) | 			log.error("Unable to change %s:%s to %s", node, jobid, state) | ||||||
| 			return False | 			return False | ||||||
| 		return True | 		return True | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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: | ||||||
|  |             field = FormField() | ||||||
|  |             field._type = self['reported'][var]['type'] | ||||||
|  |             field['var'] = var | ||||||
|  |             field['value'] = values.get(var, None) | ||||||
|  |             itemXML.append(field.xml) | ||||||
|  |  | ||||||
|  |     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 = OrderedDict() | ||||||
|  |             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 = OrderedDict() | ||||||
|  |         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 = OrderedDict() | ||||||
|  |         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 | ||||||
| @@ -42,46 +42,46 @@ def py2xml(*args): | |||||||
|  |  | ||||||
| def _py2xml(*args): | def _py2xml(*args): | ||||||
|     for x in args: |     for x in args: | ||||||
|         val = ET.Element("value") |         val = ET.Element("{%s}value" % _namespace) | ||||||
|         if x is None: |         if x is None: | ||||||
|             nil = ET.Element("nil") |             nil = ET.Element("{%s}nil" % _namespace) | ||||||
|             val.append(nil) |             val.append(nil) | ||||||
|         elif type(x) is int: |         elif type(x) is int: | ||||||
|             i4 = ET.Element("i4") |             i4 = ET.Element("{%s}i4" % _namespace) | ||||||
|             i4.text = str(x) |             i4.text = str(x) | ||||||
|             val.append(i4) |             val.append(i4) | ||||||
|         elif type(x) is bool: |         elif type(x) is bool: | ||||||
|             boolean = ET.Element("boolean") |             boolean = ET.Element("{%s}boolean" % _namespace) | ||||||
|             boolean.text = str(int(x)) |             boolean.text = str(int(x)) | ||||||
|             val.append(boolean) |             val.append(boolean) | ||||||
|         elif type(x) is str: |         elif type(x) is str: | ||||||
|             string = ET.Element("string") |             string = ET.Element("{%s}string" % _namespace) | ||||||
|             string.text = x |             string.text = x | ||||||
|             val.append(string) |             val.append(string) | ||||||
|         elif type(x) is float: |         elif type(x) is float: | ||||||
|             double = ET.Element("double") |             double = ET.Element("{%s}double" % _namespace) | ||||||
|             double.text = str(x) |             double.text = str(x) | ||||||
|             val.append(double) |             val.append(double) | ||||||
|         elif type(x) is rpcbase64: |         elif type(x) is rpcbase64: | ||||||
|             b64 = ET.Element("Base64") |             b64 = ET.Element("{%s}base64" % _namespace) | ||||||
|             b64.text = x.encoded() |             b64.text = x.encoded() | ||||||
|             val.append(b64) |             val.append(b64) | ||||||
|         elif type(x) is rpctime: |         elif type(x) is rpctime: | ||||||
|             iso = ET.Element("dateTime.iso8601") |             iso = ET.Element("{%s}dateTime.iso8601" % _namespace) | ||||||
|             iso.text = str(x) |             iso.text = str(x) | ||||||
|             val.append(iso) |             val.append(iso) | ||||||
|         elif type(x) in (list, tuple): |         elif type(x) in (list, tuple): | ||||||
|             array = ET.Element("array") |             array = ET.Element("{%s}array" % _namespace) | ||||||
|             data = ET.Element("data") |             data = ET.Element("{%s}data" % _namespace) | ||||||
|             for y in x: |             for y in x: | ||||||
|                 data.append(_py2xml(y)) |                 data.append(_py2xml(y)) | ||||||
|             array.append(data) |             array.append(data) | ||||||
|             val.append(array) |             val.append(array) | ||||||
|         elif type(x) is dict: |         elif type(x) is dict: | ||||||
|             struct = ET.Element("struct") |             struct = ET.Element("{%s}struct" % _namespace) | ||||||
|             for y in x.keys(): |             for y in x.keys(): | ||||||
|                 member = ET.Element("member") |                 member = ET.Element("{%s}member" % _namespace) | ||||||
|                 name = ET.Element("name") |                 name = ET.Element("{%s}name" % _namespace) | ||||||
|                 name.text = y |                 name.text = y | ||||||
|                 member.append(name) |                 member.append(name) | ||||||
|                 member.append(_py2xml(x[y])) |                 member.append(_py2xml(x[y])) | ||||||
| @@ -105,15 +105,18 @@ def _xml2py(value): | |||||||
|     if value.find('{%s}int' % namespace) is not None: |     if value.find('{%s}int' % namespace) is not None: | ||||||
|         return int(value.find('{%s}int' % namespace).text) |         return int(value.find('{%s}int' % namespace).text) | ||||||
|     if value.find('{%s}boolean' % namespace) is not None: |     if value.find('{%s}boolean' % namespace) is not None: | ||||||
|         return bool(value.find('{%s}boolean' % namespace).text) |         return bool(int(value.find('{%s}boolean' % namespace).text)) | ||||||
|     if value.find('{%s}string' % namespace) is not None: |     if value.find('{%s}string' % namespace) is not None: | ||||||
|         return value.find('{%s}string' % namespace).text |         return value.find('{%s}string' % namespace).text | ||||||
|     if value.find('{%s}double' % namespace) is not None: |     if value.find('{%s}double' % namespace) is not None: | ||||||
|         return float(value.find('{%s}double' % namespace).text) |         return float(value.find('{%s}double' % namespace).text) | ||||||
|     if value.find('{%s}Base64') is not None: |     if value.find('{%s}base64' % namespace) is not None: | ||||||
|         return rpcbase64(value.find('Base64' % namespace).text) |         return rpcbase64(value.find('{%s}base64' % namespace).text.encode()) | ||||||
|     if value.find('{%s}dateTime.iso8601') is not None: |     if value.find('{%s}Base64' % namespace) is not None: | ||||||
|         return rpctime(value.find('{%s}dateTime.iso8601')) |         # Older versions of XEP-0009 used Base64 | ||||||
|  |         return rpcbase64(value.find('{%s}Base64' % namespace).text.encode()) | ||||||
|  |     if value.find('{%s}dateTime.iso8601' % namespace) is not None: | ||||||
|  |         return rpctime(value.find('{%s}dateTime.iso8601' % namespace).text) | ||||||
|     if value.find('{%s}struct' % namespace) is not None: |     if value.find('{%s}struct' % namespace) is not None: | ||||||
|         struct = {} |         struct = {} | ||||||
|         for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace): |         for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace): | ||||||
| @@ -135,13 +138,13 @@ class rpcbase64(object): | |||||||
|         self.data = data |         self.data = data | ||||||
|  |  | ||||||
|     def decode(self): |     def decode(self): | ||||||
|         return base64.decodestring(self.data) |         return base64.b64decode(self.data) | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         return self.decode() |         return self.decode().decode() | ||||||
|  |  | ||||||
|     def encoded(self): |     def encoded(self): | ||||||
|         return self.data |         return self.data.decode() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) | |||||||
|  |  | ||||||
| def _intercept(method, name, public): | def _intercept(method, name, public): | ||||||
|     def _resolver(instance, *args, **kwargs): |     def _resolver(instance, *args, **kwargs): | ||||||
|         log.debug("Locally calling %s.%s with arguments %s." % (instance.FQN(), method.__name__, args)) |         log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args) | ||||||
|         try: |         try: | ||||||
|             value = method(instance, *args, **kwargs) |             value = method(instance, *args, **kwargs) | ||||||
|             if value == NotImplemented: |             if value == NotImplemented: | ||||||
| @@ -113,6 +113,9 @@ class ACL: | |||||||
|     def check(cls, rules, jid, resource): |     def check(cls, rules, jid, resource): | ||||||
|         if rules is None: |         if rules is None: | ||||||
|             return cls.DENY                  # No rules means no access! |             return cls.DENY                  # No rules means no access! | ||||||
|  |         jid = str(jid)     # Check the string representation of the JID. | ||||||
|  |         if not jid: | ||||||
|  |             return cls.DENY                  # Can't check an empty JID. | ||||||
|         for rule in rules: |         for rule in rules: | ||||||
|             policy = cls._check(rule, jid, resource) |             policy = cls._check(rule, jid, resource) | ||||||
|             if policy is not None: |             if policy is not None: | ||||||
| @@ -381,7 +384,7 @@ class Proxy(Endpoint): | |||||||
|                 try: |                 try: | ||||||
|                     if attribute._rpc: |                     if attribute._rpc: | ||||||
|                         def _remote_call(*args, **kwargs): |                         def _remote_call(*args, **kwargs): | ||||||
|                             log.debug("Remotely calling '%s.%s' with arguments %s." % (self._endpoint.FQN(), attribute._rpc_name, args)) |                             log.debug("Remotely calling '%s.%s' with arguments %s.", self._endpoint.FQN(), attribute._rpc_name, args) | ||||||
|                             return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs) |                             return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs) | ||||||
|                         return _remote_call |                         return _remote_call | ||||||
|                 except: |                 except: | ||||||
| @@ -449,7 +452,7 @@ class RemoteSession(object): | |||||||
|         self._event.wait() |         self._event.wait() | ||||||
|  |  | ||||||
|     def _notify(self, event): |     def _notify(self, event): | ||||||
|         log.debug("RPC Session as %s started." % self._client.boundjid.full) |         log.debug("RPC Session as %s started.", self._client.boundjid.full) | ||||||
|         self._client.sendPresence() |         self._client.sendPresence() | ||||||
|         self._event.set() |         self._event.set() | ||||||
|         pass |         pass | ||||||
| @@ -461,7 +464,7 @@ class RemoteSession(object): | |||||||
|         if name is None: |         if name is None: | ||||||
|             name = method.__name__ |             name = method.__name__ | ||||||
|         key = "%s.%s" % (endpoint, name) |         key = "%s.%s" % (endpoint, name) | ||||||
|         log.debug("Registering call handler for %s (%s)." % (key, method)) |         log.debug("Registering call handler for %s (%s).", key, method) | ||||||
|         with self._lock: |         with self._lock: | ||||||
|             if key in self._entries: |             if key in self._entries: | ||||||
|                 raise KeyError("A handler for %s has already been regisered!" % endpoint) |                 raise KeyError("A handler for %s has already been regisered!" % endpoint) | ||||||
| @@ -469,7 +472,7 @@ class RemoteSession(object): | |||||||
|         return key |         return key | ||||||
|  |  | ||||||
|     def _register_acl(self, endpoint, acl): |     def _register_acl(self, endpoint, acl): | ||||||
|         log.debug("Registering ACL %s for endpoint %s." % (repr(acl), endpoint)) |         log.debug("Registering ACL %s for endpoint %s.", repr(acl), endpoint) | ||||||
|         with self._lock: |         with self._lock: | ||||||
|             self._acls[endpoint] = acl |             self._acls[endpoint] = acl | ||||||
|  |  | ||||||
| @@ -562,7 +565,7 @@ class RemoteSession(object): | |||||||
|             iq.send() |             iq.send() | ||||||
|             return future.get_value(30) |             return future.get_value(30) | ||||||
|         else: |         else: | ||||||
|             log.debug("[RemoteSession] _call_remote %s" % callback) |             log.debug("[RemoteSession] _call_remote %s", callback) | ||||||
|             self._register_callback(pid, callback) |             self._register_callback(pid, callback) | ||||||
|             iq.send() |             iq.send() | ||||||
|  |  | ||||||
| @@ -601,11 +604,11 @@ class RemoteSession(object): | |||||||
|             error.send() |             error.send() | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             if isinstance(e, KeyError): |             if isinstance(e, KeyError): | ||||||
|                 log.error("No handler available for %s!" % pmethod) |                 log.error("No handler available for %s!", pmethod) | ||||||
|                 error = self._client.plugin['xep_0009']._item_not_found(iq) |                 error = self._client.plugin['xep_0009']._item_not_found(iq) | ||||||
|             else: |             else: | ||||||
|                 traceback.print_exc(file=sys.stderr) |                 traceback.print_exc(file=sys.stderr) | ||||||
|                 log.error("An unexpected problem occurred invoking method %s!" % pmethod) |                 log.error("An unexpected problem occurred invoking method %s!", pmethod) | ||||||
|                 error = self._client.plugin['xep_0009']._undefined_condition(iq) |                 error = self._client.plugin['xep_0009']._undefined_condition(iq) | ||||||
|             #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e |             #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e | ||||||
|             error.send() |             error.send() | ||||||
| @@ -699,10 +702,10 @@ class Remote(object): | |||||||
|             with Remote._lock: |             with Remote._lock: | ||||||
|                 del cls._sessions[client.boundjid.bare] |                 del cls._sessions[client.boundjid.bare] | ||||||
|         result = RemoteSession(client, _session_close_callback) |         result = RemoteSession(client, _session_close_callback) | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call) |         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call, threaded=True) | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response) |         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response, threaded=True) | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault) |         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault, threaded=True) | ||||||
|         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error) |         client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error, threaded=True) | ||||||
|         if callback is None: |         if callback is None: | ||||||
|             start_event_handler = result._notify |             start_event_handler = result._notify | ||||||
|         else: |         else: | ||||||
|   | |||||||
| @@ -128,22 +128,22 @@ class xep_0009(base.base_plugin): | |||||||
|     def _handle_method_call(self, iq): |     def _handle_method_call(self, iq): | ||||||
|         type = iq['type'] |         type = iq['type'] | ||||||
|         if type == 'set': |         if type == 'set': | ||||||
|             log.debug("Incoming Jabber-RPC call from %s" % iq['from']) |             log.debug("Incoming Jabber-RPC call from %s", iq['from']) | ||||||
|             self.xmpp.event('jabber_rpc_method_call', iq) |             self.xmpp.event('jabber_rpc_method_call', iq) | ||||||
|         else: |         else: | ||||||
|             if type == 'error' and ['rpc_query'] is None: |             if type == 'error' and ['rpc_query'] is None: | ||||||
|                 self.handle_error(iq) |                 self.handle_error(iq) | ||||||
|             else: |             else: | ||||||
|                 log.debug("Incoming Jabber-RPC error from %s" % iq['from']) |                 log.debug("Incoming Jabber-RPC error from %s", iq['from']) | ||||||
|                 self.xmpp.event('jabber_rpc_error', iq) |                 self.xmpp.event('jabber_rpc_error', iq) | ||||||
|  |  | ||||||
|     def _handle_method_response(self, iq): |     def _handle_method_response(self, iq): | ||||||
|         if iq['rpc_query']['method_response']['fault'] is not None: |         if iq['rpc_query']['method_response']['fault'] is not None: | ||||||
|             log.debug("Incoming Jabber-RPC fault from %s" % iq['from']) |             log.debug("Incoming Jabber-RPC fault from %s", iq['from']) | ||||||
|             #self._on_jabber_rpc_method_fault(iq) |             #self._on_jabber_rpc_method_fault(iq) | ||||||
|             self.xmpp.event('jabber_rpc_method_fault', iq) |             self.xmpp.event('jabber_rpc_method_fault', iq) | ||||||
|         else: |         else: | ||||||
|             log.debug("Incoming Jabber-RPC response from %s" % iq['from']) |             log.debug("Incoming Jabber-RPC response from %s", iq['from']) | ||||||
|             self.xmpp.event('jabber_rpc_method_response', iq) |             self.xmpp.event('jabber_rpc_method_response', iq) | ||||||
|  |  | ||||||
|     def _handle_error(self, iq): |     def _handle_error(self, iq): | ||||||
|   | |||||||
| @@ -71,10 +71,10 @@ class xep_0012(base.base_plugin): | |||||||
|  |  | ||||||
|     def handle_last_activity_query(self, iq): |     def handle_last_activity_query(self, iq): | ||||||
|         if iq['type'] == 'get': |         if iq['type'] == 'get': | ||||||
|             log.debug("Last activity requested by %s" % iq['from']) |             log.debug("Last activity requested by %s", iq['from']) | ||||||
|             self.xmpp.event('last_activity_request', iq) |             self.xmpp.event('last_activity_request', iq) | ||||||
|         elif iq['type'] == 'result': |         elif iq['type'] == 'result': | ||||||
|             log.debug("Last activity result from %s" % iq['from']) |             log.debug("Last activity result from %s", iq['from']) | ||||||
|             self.xmpp.event('last_activity', iq) |             self.xmpp.event('last_activity', iq) | ||||||
|  |  | ||||||
|     def handle_last_activity(self, iq): |     def handle_last_activity(self, iq): | ||||||
| @@ -112,7 +112,4 @@ class xep_0012(base.base_plugin): | |||||||
|         iq.attrib['from'] = self.xmpp.boundjid.full |         iq.attrib['from'] = self.xmpp.boundjid.full | ||||||
|         id = iq.get('id') |         id = iq.get('id') | ||||||
|         result = iq.send() |         result = iq.send() | ||||||
|         if result and result is not None and result.get('type', 'error') != 'error': |         return result['last_activity']['seconds'] | ||||||
|             return result['last_activity']['seconds'] |  | ||||||
|         else: |  | ||||||
|             return False |  | ||||||
|   | |||||||
| @@ -268,7 +268,7 @@ class xep_0030(base_plugin): | |||||||
|         """ |         """ | ||||||
|         if local or jid is None: |         if local or jid is None: | ||||||
|             log.debug("Looking up local disco#info data " + \ |             log.debug("Looking up local disco#info data " + \ | ||||||
|                       "for %s, node %s." % (jid, node)) |                       "for %s, node %s.", jid, node) | ||||||
|             info = self._run_node_handler('get_info', jid, node, kwargs) |             info = self._run_node_handler('get_info', jid, node, kwargs) | ||||||
|             return self._fix_default_info(info) |             return self._fix_default_info(info) | ||||||
|  |  | ||||||
| @@ -542,7 +542,7 @@ class xep_0030(base_plugin): | |||||||
|         """ |         """ | ||||||
|         if iq['type'] == 'get': |         if iq['type'] == 'get': | ||||||
|             log.debug("Received disco info query from " + \ |             log.debug("Received disco info query from " + \ | ||||||
|                       "<%s> to <%s>." % (iq['from'], iq['to'])) |                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||||
|             if self.xmpp.is_component: |             if self.xmpp.is_component: | ||||||
|                 jid = iq['to'].full |                 jid = iq['to'].full | ||||||
|             else: |             else: | ||||||
| @@ -551,14 +551,17 @@ class xep_0030(base_plugin): | |||||||
|                                           jid, |                                           jid, | ||||||
|                                           iq['disco_info']['node'], |                                           iq['disco_info']['node'], | ||||||
|                                           iq) |                                           iq) | ||||||
|             iq.reply() |             if isinstance(info, Iq): | ||||||
|             if info: |                 info.send() | ||||||
|                 info = self._fix_default_info(info) |             else: | ||||||
|                 iq.set_payload(info.xml) |                 iq.reply() | ||||||
|             iq.send() |                 if info: | ||||||
|  |                     info = self._fix_default_info(info) | ||||||
|  |                     iq.set_payload(info.xml) | ||||||
|  |                 iq.send() | ||||||
|         elif iq['type'] == 'result': |         elif iq['type'] == 'result': | ||||||
|             log.debug("Received disco info result from" + \ |             log.debug("Received disco info result from" + \ | ||||||
|                       "%s to %s." % (iq['from'], iq['to'])) |                       "%s to %s.", iq['from'], iq['to']) | ||||||
|             self.xmpp.event('disco_info', iq) |             self.xmpp.event('disco_info', iq) | ||||||
|  |  | ||||||
|     def _handle_disco_items(self, iq): |     def _handle_disco_items(self, iq): | ||||||
| @@ -572,21 +575,25 @@ class xep_0030(base_plugin): | |||||||
|         """ |         """ | ||||||
|         if iq['type'] == 'get': |         if iq['type'] == 'get': | ||||||
|             log.debug("Received disco items query from " + \ |             log.debug("Received disco items query from " + \ | ||||||
|                       "<%s> to <%s>." % (iq['from'], iq['to'])) |                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||||
|             if self.xmpp.is_component: |             if self.xmpp.is_component: | ||||||
|                 jid = iq['to'].full |                 jid = iq['to'].full | ||||||
|             else: |             else: | ||||||
|                 jid = iq['to'].bare |                 jid = iq['to'].bare | ||||||
|             items = self._run_node_handler('get_items', |             items = self._run_node_handler('get_items', | ||||||
|                                           jid, |                                           jid, | ||||||
|                                           iq['disco_items']['node']) |                                           iq['disco_items']['node'], | ||||||
|             iq.reply() |                                           iq) | ||||||
|             if items: |             if isinstance(items, Iq): | ||||||
|                 iq.set_payload(items.xml) |                 items.send() | ||||||
|             iq.send() |             else: | ||||||
|  |                 iq.reply() | ||||||
|  |                 if items: | ||||||
|  |                     iq.set_payload(items.xml) | ||||||
|  |                 iq.send() | ||||||
|         elif iq['type'] == 'result': |         elif iq['type'] == 'result': | ||||||
|             log.debug("Received disco items result from" + \ |             log.debug("Received disco items result from" + \ | ||||||
|                       "%s to %s." % (iq['from'], iq['to'])) |                       "%s to %s.", iq['from'], iq['to']) | ||||||
|             self.xmpp.event('disco_items', iq) |             self.xmpp.event('disco_items', iq) | ||||||
|  |  | ||||||
|     def _fix_default_info(self, info): |     def _fix_default_info(self, info): | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ from .. stanza.presence import Presence | |||||||
| from .. xmlstream.handler.callback import Callback | from .. xmlstream.handler.callback import Callback | ||||||
| from .. xmlstream.matcher.xpath import MatchXPath | from .. xmlstream.matcher.xpath import MatchXPath | ||||||
| from .. xmlstream.matcher.xmlmask import MatchXMLMask | from .. xmlstream.matcher.xmlmask import MatchXMLMask | ||||||
|  | from sleekxmpp.exceptions import IqError, IqTimeout | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
| @@ -126,7 +127,7 @@ class xep_0045(base.base_plugin): | |||||||
|     def handle_groupchat_invite(self, inv): |     def handle_groupchat_invite(self, inv): | ||||||
|         """ Handle an invite into a muc. |         """ Handle an invite into a muc. | ||||||
|         """ |         """ | ||||||
|         logging.debug("MUC invite to %s from %s: %s" % (inv['from'], inv["from"], inv)) |         logging.debug("MUC invite to %s from %s: %s", inv['from'], inv["from"], inv) | ||||||
|         if inv['from'] not in self.rooms.keys(): |         if inv['from'] not in self.rooms.keys(): | ||||||
|             self.xmpp.event("groupchat_invite", inv) |             self.xmpp.event("groupchat_invite", inv) | ||||||
|  |  | ||||||
| @@ -148,7 +149,7 @@ class xep_0045(base.base_plugin): | |||||||
|             if entry['nick'] not in self.rooms[entry['room']]: |             if entry['nick'] not in self.rooms[entry['room']]: | ||||||
|                 got_online = True |                 got_online = True | ||||||
|             self.rooms[entry['room']][entry['nick']] = entry |             self.rooms[entry['room']][entry['nick']] = entry | ||||||
|         log.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry)) |         log.debug("MUC presence from %s/%s : %s", entry['room'],entry['nick'], entry) | ||||||
|         self.xmpp.event("groupchat_presence", pr) |         self.xmpp.event("groupchat_presence", pr) | ||||||
|         self.xmpp.event("muc::%s::presence" % entry['room'], pr) |         self.xmpp.event("muc::%s::presence" % entry['room'], pr) | ||||||
|         if got_offline: |         if got_offline: | ||||||
| @@ -188,8 +189,12 @@ class xep_0045(base.base_plugin): | |||||||
|             iq['from'] = ifrom |             iq['from'] = ifrom | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||||
|         iq.append(query) |         iq.append(query) | ||||||
|         result = iq.send() |         # For now, swallow errors to preserve existing API | ||||||
|         if result['type'] == 'error': |         try: | ||||||
|  |             result = iq.send() | ||||||
|  |         except IqError: | ||||||
|  |             return False | ||||||
|  |         except IqTimeout: | ||||||
|             return False |             return False | ||||||
|         xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') |         xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') | ||||||
|         if xform is None: return False |         if xform is None: return False | ||||||
| @@ -209,15 +214,19 @@ class xep_0045(base.base_plugin): | |||||||
|         form = form.getXML('submit') |         form = form.getXML('submit') | ||||||
|         query.append(form) |         query.append(form) | ||||||
|         iq.append(query) |         iq.append(query) | ||||||
|         result = iq.send() |         # For now, swallow errors to preserve existing API | ||||||
|         if result['type'] == 'error': |         try: | ||||||
|  |             result = iq.send() | ||||||
|  |         except IqError: | ||||||
|  |             return False | ||||||
|  |         except IqTimeout: | ||||||
|             return False |             return False | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None): |     def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None): | ||||||
|         """ Join the specified room, requesting 'maxhistory' lines of history. |         """ Join the specified room, requesting 'maxhistory' lines of history. | ||||||
|         """ |         """ | ||||||
|         stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow) |         stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom) | ||||||
|         x = ET.Element('{http://jabber.org/protocol/muc}x') |         x = ET.Element('{http://jabber.org/protocol/muc}x') | ||||||
|         if password: |         if password: | ||||||
|             passelement = ET.Element('password') |             passelement = ET.Element('password') | ||||||
| @@ -254,12 +263,16 @@ class xep_0045(base.base_plugin): | |||||||
|         destroy.append(xreason) |         destroy.append(xreason) | ||||||
|         query.append(destroy) |         query.append(destroy) | ||||||
|         iq.append(query) |         iq.append(query) | ||||||
|         r = iq.send() |         # For now, swallow errors to preserve existing API | ||||||
|         if r is False or r['type'] == 'error': |         try: | ||||||
|  |             r = iq.send() | ||||||
|  |         except IqError: | ||||||
|  |             return False | ||||||
|  |         except IqTimeout: | ||||||
|             return False |             return False | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def setAffiliation(self, room, jid=None, nick=None, affiliation='member'): |     def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None): | ||||||
|         """ Change room affiliation.""" |         """ Change room affiliation.""" | ||||||
|         if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): |         if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): | ||||||
|             raise TypeError |             raise TypeError | ||||||
| @@ -271,9 +284,14 @@ class xep_0045(base.base_plugin): | |||||||
|         query.append(item) |         query.append(item) | ||||||
|         iq = self.xmpp.makeIqSet(query) |         iq = self.xmpp.makeIqSet(query) | ||||||
|         iq['to'] = room |         iq['to'] = room | ||||||
|         result = iq.send() |         iq['from'] = ifrom | ||||||
|         if result is False or result['type'] != 'result': |         # For now, swallow errors to preserve existing API | ||||||
|             raise ValueError |         try: | ||||||
|  |             result = iq.send() | ||||||
|  |         except IqError: | ||||||
|  |             return False | ||||||
|  |         except IqTimeout: | ||||||
|  |             return False | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def invite(self, room, jid, reason='', mfrom=''): |     def invite(self, room, jid, reason='', mfrom=''): | ||||||
| @@ -290,33 +308,38 @@ class xep_0045(base.base_plugin): | |||||||
|         msg.append(x) |         msg.append(x) | ||||||
|         self.xmpp.send(msg) |         self.xmpp.send(msg) | ||||||
|  |  | ||||||
|     def leaveMUC(self, room, nick, msg=''): |     def leaveMUC(self, room, nick, msg='', pfrom=None): | ||||||
|         """ Leave the specified room. |         """ Leave the specified room. | ||||||
|         """ |         """ | ||||||
|         if msg: |         if msg: | ||||||
|             self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg) |             self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg, pfrom=pfrom) | ||||||
|         else: |         else: | ||||||
|             self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick)) |             self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom) | ||||||
|         del self.rooms[room] |         del self.rooms[room] | ||||||
|  |  | ||||||
|     def getRoomConfig(self, room, ifrom=''): |     def getRoomConfig(self, room, ifrom=''): | ||||||
|         iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') |         iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') | ||||||
|         iq['to'] = room |         iq['to'] = room | ||||||
|         iq['from'] = ifrom |         iq['from'] = ifrom | ||||||
|         result = iq.send() |         # For now, swallow errors to preserve existing API | ||||||
|         if result is None or result['type'] != 'result': |         try: | ||||||
|  |             result = iq.send() | ||||||
|  |         except IqError: | ||||||
|  |             raise ValueError | ||||||
|  |         except IqTimeout: | ||||||
|             raise ValueError |             raise ValueError | ||||||
|         form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') |         form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') | ||||||
|         if form is None: |         if form is None: | ||||||
|             raise ValueError |             raise ValueError | ||||||
|         return self.xmpp.plugin['xep_0004'].buildForm(form) |         return self.xmpp.plugin['xep_0004'].buildForm(form) | ||||||
|  |  | ||||||
|     def cancelConfig(self, room): |     def cancelConfig(self, room, ifrom=None): | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||||
|         x = ET.Element('{jabber:x:data}x', type='cancel') |         x = ET.Element('{jabber:x:data}x', type='cancel') | ||||||
|         query.append(x) |         query.append(x) | ||||||
|         iq = self.xmpp.makeIqSet(query) |         iq = self.xmpp.makeIqSet(query) | ||||||
|         iq['to'] = room |         iq['to'] = room | ||||||
|  |         iq['from'] = ifrom | ||||||
|         iq.send() |         iq.send() | ||||||
|  |  | ||||||
|     def setRoomConfig(self, room, config, ifrom=''): |     def setRoomConfig(self, room, config, ifrom=''): | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user