Compare commits
	
		
			220 Commits
		
	
	
		
			1.0-Beta6.
			...
			sleek-1.0-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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
 | 
			
		||||
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.
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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.
 | 
			
		||||
							
								
								
									
										179
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
**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>`_
 | 
			
		||||
 | 
			
		||||
**Develop Releases**
 | 
			
		||||
    - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
 | 
			
		||||
 | 
			
		||||
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 member of the XMPP
 | 
			
		||||
    Council.
 | 
			
		||||
 | 
			
		||||
**Co-Author:** Lance Stout
 | 
			
		||||
    `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, 
 | 
			
		||||
    `@lancestout <http://twitter.com/lancestout>`_
 | 
			
		||||
 | 
			
		||||
**Contributors:**
 | 
			
		||||
    - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_)
 | 
			
		||||
    - Dann Martens (`dannmartens <http://github.com/dannmartens>`_)
 | 
			
		||||
    - Florent Le Coz (`louiz <http://github.com/louiz>`_)
 | 
			
		||||
    - Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk)
 | 
			
		||||
    - Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be)
 | 
			
		||||
    - Te-jé Rogers (`te-je <http://github.com/te-je>`_)
 | 
			
		||||
    - Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_)
 | 
			
		||||
							
								
								
									
										130
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
# Makefile for Sphinx documentation
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# You can set these variables from the command line.
 | 
			
		||||
SPHINXOPTS    =
 | 
			
		||||
SPHINXBUILD   = sphinx-build
 | 
			
		||||
PAPER         =
 | 
			
		||||
BUILDDIR      = _build
 | 
			
		||||
 | 
			
		||||
# Internal variables.
 | 
			
		||||
PAPEROPT_a4     = -D latex_paper_size=a4
 | 
			
		||||
PAPEROPT_letter = -D latex_paper_size=letter
 | 
			
		||||
ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
 | 
			
		||||
 | 
			
		||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
 | 
			
		||||
 | 
			
		||||
help:
 | 
			
		||||
	@echo "Please use \`make <target>' where <target> is one of"
 | 
			
		||||
	@echo "  html       to make standalone HTML files"
 | 
			
		||||
	@echo "  dirhtml    to make HTML files named index.html in directories"
 | 
			
		||||
	@echo "  singlehtml to make a single large HTML file"
 | 
			
		||||
	@echo "  pickle     to make pickle files"
 | 
			
		||||
	@echo "  json       to make JSON files"
 | 
			
		||||
	@echo "  htmlhelp   to make HTML files and a HTML help project"
 | 
			
		||||
	@echo "  qthelp     to make HTML files and a qthelp project"
 | 
			
		||||
	@echo "  devhelp    to make HTML files and a Devhelp project"
 | 
			
		||||
	@echo "  epub       to make an epub"
 | 
			
		||||
	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
 | 
			
		||||
	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
 | 
			
		||||
	@echo "  text       to make text files"
 | 
			
		||||
	@echo "  man        to make manual pages"
 | 
			
		||||
	@echo "  changes    to make an overview of all changed/added/deprecated items"
 | 
			
		||||
	@echo "  linkcheck  to check all external links for integrity"
 | 
			
		||||
	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	-rm -rf $(BUILDDIR)/*
 | 
			
		||||
 | 
			
		||||
html:
 | 
			
		||||
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 | 
			
		||||
 | 
			
		||||
dirhtml:
 | 
			
		||||
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
 | 
			
		||||
 | 
			
		||||
singlehtml:
 | 
			
		||||
	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
 | 
			
		||||
 | 
			
		||||
pickle:
 | 
			
		||||
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can process the pickle files."
 | 
			
		||||
 | 
			
		||||
json:
 | 
			
		||||
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can process the JSON files."
 | 
			
		||||
 | 
			
		||||
htmlhelp:
 | 
			
		||||
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can run HTML Help Workshop with the" \
 | 
			
		||||
	      ".hhp project file in $(BUILDDIR)/htmlhelp."
 | 
			
		||||
 | 
			
		||||
qthelp:
 | 
			
		||||
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
 | 
			
		||||
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
 | 
			
		||||
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SleekXMPP.qhcp"
 | 
			
		||||
	@echo "To view the help file:"
 | 
			
		||||
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SleekXMPP.qhc"
 | 
			
		||||
 | 
			
		||||
devhelp:
 | 
			
		||||
	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished."
 | 
			
		||||
	@echo "To view the help file:"
 | 
			
		||||
	@echo "# mkdir -p $$HOME/.local/share/devhelp/SleekXMPP"
 | 
			
		||||
	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SleekXMPP"
 | 
			
		||||
	@echo "# devhelp"
 | 
			
		||||
 | 
			
		||||
epub:
 | 
			
		||||
	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
 | 
			
		||||
 | 
			
		||||
latex:
 | 
			
		||||
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
 | 
			
		||||
	@echo "Run \`make' in that directory to run these through (pdf)latex" \
 | 
			
		||||
	      "(use \`make latexpdf' here to do that automatically)."
 | 
			
		||||
 | 
			
		||||
latexpdf:
 | 
			
		||||
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 | 
			
		||||
	@echo "Running LaTeX files through pdflatex..."
 | 
			
		||||
	make -C $(BUILDDIR)/latex all-pdf
 | 
			
		||||
	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
 | 
			
		||||
 | 
			
		||||
text:
 | 
			
		||||
	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The text files are in $(BUILDDIR)/text."
 | 
			
		||||
 | 
			
		||||
man:
 | 
			
		||||
	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
 | 
			
		||||
 | 
			
		||||
changes:
 | 
			
		||||
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "The overview file is in $(BUILDDIR)/changes."
 | 
			
		||||
 | 
			
		||||
linkcheck:
 | 
			
		||||
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
 | 
			
		||||
	@echo
 | 
			
		||||
	@echo "Link check complete; look for any errors in the above output " \
 | 
			
		||||
	      "or in $(BUILDDIR)/linkcheck/output.txt."
 | 
			
		||||
 | 
			
		||||
doctest:
 | 
			
		||||
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
 | 
			
		||||
	@echo "Testing of doctests in the sources finished, look at the " \
 | 
			
		||||
	      "results in $(BUILDDIR)/doctest/output.txt."
 | 
			
		||||
							
								
								
									
										452
									
								
								docs/_static/agogo.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										452
									
								
								docs/_static/agogo.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,452 @@
 | 
			
		||||
/*
 | 
			
		||||
 * agogo.css_t
 | 
			
		||||
 * ~~~~~~~~~~~
 | 
			
		||||
 *
 | 
			
		||||
 * Sphinx stylesheet -- agogo theme.
 | 
			
		||||
 *
 | 
			
		||||
 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
 * :license: BSD, see LICENSE for details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
* {
 | 
			
		||||
  margin: 0px;
 | 
			
		||||
  padding: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  font-family: "Verdana", Arial, sans-serif;
 | 
			
		||||
  line-height: 1.4em;
 | 
			
		||||
  color: black;
 | 
			
		||||
  background-color: #eeeeec;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Page layout */
 | 
			
		||||
 | 
			
		||||
div.header, div.content, div.footer {
 | 
			
		||||
  width: 70em;
 | 
			
		||||
  margin-left: auto;
 | 
			
		||||
  margin-right: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header-wrapper {
 | 
			
		||||
  background: url(bgtop.png) top left repeat-x;
 | 
			
		||||
  border-bottom: 3px solid #2e3436;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Default body styles */
 | 
			
		||||
a {
 | 
			
		||||
  color: #ce5c00;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.bodywrapper a, div.footer a {
 | 
			
		||||
  text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clearer {
 | 
			
		||||
  clear: both;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.left {
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.right {
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-block {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin-top: 1em;
 | 
			
		||||
    margin-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-block .line-block {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
    margin-left: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1, h2, h3, h4 {
 | 
			
		||||
  font-family: "Georgia", "Times New Roman", serif;
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
  color: #3465a4;
 | 
			
		||||
  margin-bottom: .8em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
  color: #204a87;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 {
 | 
			
		||||
  padding-bottom: .5em;
 | 
			
		||||
  border-bottom: 1px solid #3465a4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.headerlink {
 | 
			
		||||
  visibility: hidden;
 | 
			
		||||
  color: #dddddd;
 | 
			
		||||
  padding-left: .3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1:hover > a.headerlink,
 | 
			
		||||
h2:hover > a.headerlink,
 | 
			
		||||
h3:hover > a.headerlink,
 | 
			
		||||
h4:hover > a.headerlink,
 | 
			
		||||
h5:hover > a.headerlink,
 | 
			
		||||
h6:hover > a.headerlink,
 | 
			
		||||
dt:hover > a.headerlink {
 | 
			
		||||
  visibility: visible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img {
 | 
			
		||||
  border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition {
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
  padding: 2px 7px 1px 7px;
 | 
			
		||||
  border-left: 0.2em solid black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.admonition-title {
 | 
			
		||||
  margin: 0px 10px 5px 0px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dt:target, .highlighted {
 | 
			
		||||
  background-color: #fbe54e;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Header */
 | 
			
		||||
 | 
			
		||||
div.header {
 | 
			
		||||
  padding-top: 10px;
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header h1 {
 | 
			
		||||
  font-family: "Georgia", "Times New Roman", serif;
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
  font-size: 180%;
 | 
			
		||||
  letter-spacing: .08em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header h1 a {
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header div.rel {
 | 
			
		||||
  margin-top: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header div.rel a {
 | 
			
		||||
  color: #fcaf3e;
 | 
			
		||||
  letter-spacing: .1em;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.logo {
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.logo {
 | 
			
		||||
    border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Content */
 | 
			
		||||
div.content-wrapper {
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document {
 | 
			
		||||
  width: 50em;
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body {
 | 
			
		||||
  padding-right: 2em;
 | 
			
		||||
  text-align: justify;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document ul {
 | 
			
		||||
  margin: 1.5em;
 | 
			
		||||
  list-style-type: square;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document dd {
 | 
			
		||||
  margin-left: 1.2em;
 | 
			
		||||
  margin-top: .4em;
 | 
			
		||||
  margin-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document .section {
 | 
			
		||||
  margin-top: 1.7em;
 | 
			
		||||
}
 | 
			
		||||
div.document .section:first-child {
 | 
			
		||||
  margin-top: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document div.highlight {
 | 
			
		||||
  padding: 3px;
 | 
			
		||||
  background-color: #eeeeec;
 | 
			
		||||
  border-top: 2px solid #dddddd;
 | 
			
		||||
  border-bottom: 2px solid #dddddd;
 | 
			
		||||
  margin-top: .8em;
 | 
			
		||||
  margin-bottom: .8em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document h2 {
 | 
			
		||||
  margin-top: .7em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document p {
 | 
			
		||||
  margin-bottom: .5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document li.toctree-l1 {
 | 
			
		||||
  margin-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document .descname {
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document .docutils.literal {
 | 
			
		||||
  background-color: #eeeeec;
 | 
			
		||||
  padding: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document .docutils.xref.literal {
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  padding: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document blockquote {
 | 
			
		||||
  margin: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document ol {
 | 
			
		||||
  margin: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Sidebar */
 | 
			
		||||
 | 
			
		||||
div.sidebar {
 | 
			
		||||
  width: 20em;
 | 
			
		||||
  float: right;
 | 
			
		||||
  font-size: .9em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar a, div.header a {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar a:hover, div.header a:hover {
 | 
			
		||||
  text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar h3 {
 | 
			
		||||
  color: #2e3436;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  font-size: 130%;
 | 
			
		||||
  letter-spacing: .1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar ul {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar li.toctree-l1 a {
 | 
			
		||||
  display: block;
 | 
			
		||||
  padding: 1px;
 | 
			
		||||
  border: 1px solid #dddddd;
 | 
			
		||||
  background-color: #eeeeec;
 | 
			
		||||
  margin-bottom: .4em;
 | 
			
		||||
  padding-left: 3px;
 | 
			
		||||
  color: #2e3436;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar li.toctree-l2 a {
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  border: none;
 | 
			
		||||
  margin-left: 1em;
 | 
			
		||||
  border-bottom: 1px solid #dddddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar li.toctree-l3 a {
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  border: none;
 | 
			
		||||
  margin-left: 2em;
 | 
			
		||||
  border-bottom: 1px solid #dddddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar li.toctree-l2:last-child a {
 | 
			
		||||
  border-bottom: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar li.toctree-l1.current a {
 | 
			
		||||
  border-right: 5px solid #fcaf3e;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sidebar li.toctree-l1.current li.toctree-l2 a {
 | 
			
		||||
  border-right: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Footer */
 | 
			
		||||
 | 
			
		||||
div.footer-wrapper {
 | 
			
		||||
  background: url(bgfooter.png) top left repeat-x;
 | 
			
		||||
  border-top: 4px solid #babdb6;
 | 
			
		||||
  padding-top: 10px;
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
  min-height: 80px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer, div.footer a {
 | 
			
		||||
  color: #888a85;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer .right {
 | 
			
		||||
  text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer .left {
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Styles copied from basic theme */
 | 
			
		||||
 | 
			
		||||
img.align-left, .figure.align-left, object.align-left {
 | 
			
		||||
    clear: left;
 | 
			
		||||
    float: left;
 | 
			
		||||
    margin-right: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.align-right, .figure.align-right, object.align-right {
 | 
			
		||||
    clear: right;
 | 
			
		||||
    float: right;
 | 
			
		||||
    margin-left: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.align-center, .figure.align-center, object.align-center {
 | 
			
		||||
  display: block;
 | 
			
		||||
  margin-left: auto;
 | 
			
		||||
  margin-right: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-left {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-center {
 | 
			
		||||
    clear: both;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-right {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- search page ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
ul.search {
 | 
			
		||||
    margin: 10px 0 0 20px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.search li {
 | 
			
		||||
    padding: 5px 0 5px 20px;
 | 
			
		||||
    background-image: url(file.png);
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    background-position: 0 7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.search li a {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.search li div.context {
 | 
			
		||||
    color: #888;
 | 
			
		||||
    margin: 2px 0 0 30px;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.keywordmatches li.goodmatch a {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- index page ------------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
table.contentstable {
 | 
			
		||||
    width: 90%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.contentstable p.biglink {
 | 
			
		||||
    line-height: 150%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.biglink {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
span.linkdescr {
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
    padding-top: 5px;
 | 
			
		||||
    font-size: 90%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- general index --------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
table.indextable td {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.indextable dl, table.indextable dd {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.indextable tr.pcap {
 | 
			
		||||
    height: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.indextable tr.cap {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    background-color: #f2f2f2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.toggler {
 | 
			
		||||
    margin-right: 3px;
 | 
			
		||||
    margin-top: 3px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- viewcode extension ---------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
.viewcode-link {
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.viewcode-back {
 | 
			
		||||
    float: right;
 | 
			
		||||
    font-family:: "Verdana", Arial, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.viewcode-block:target {
 | 
			
		||||
    margin: -1px -3px;
 | 
			
		||||
    padding: 0 3px;
 | 
			
		||||
    background-color: #f4debf;
 | 
			
		||||
    border-top: 1px solid #ac9;
 | 
			
		||||
    border-bottom: 1px solid #ac9;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										532
									
								
								docs/_static/basic.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								docs/_static/basic.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,532 @@
 | 
			
		||||
/*
 | 
			
		||||
 * basic.css
 | 
			
		||||
 * ~~~~~~~~~
 | 
			
		||||
 *
 | 
			
		||||
 * Sphinx stylesheet -- basic theme.
 | 
			
		||||
 *
 | 
			
		||||
 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
 * :license: BSD, see LICENSE for details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* -- main layout ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
div.clearer {
 | 
			
		||||
    clear: both;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- relbar ---------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
div.related {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-size: 90%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related h3 {
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related ul {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0 0 0 10px;
 | 
			
		||||
    list-style: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related li {
 | 
			
		||||
    display: inline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related li.right {
 | 
			
		||||
    float: right;
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- sidebar --------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebarwrapper {
 | 
			
		||||
    padding: 10px 5px 0 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar {
 | 
			
		||||
    float: left;
 | 
			
		||||
    width: 230px;
 | 
			
		||||
    margin-left: -100%;
 | 
			
		||||
    font-size: 90%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar ul {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar ul ul,
 | 
			
		||||
div.sphinxsidebar ul.want-points {
 | 
			
		||||
    margin-left: 20px;
 | 
			
		||||
    list-style: square;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar ul ul {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar form {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar input {
 | 
			
		||||
    border: 1px solid #98dbcc;
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img {
 | 
			
		||||
    border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- search page ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
ul.search {
 | 
			
		||||
    margin: 10px 0 0 20px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.search li {
 | 
			
		||||
    padding: 5px 0 5px 20px;
 | 
			
		||||
    background-image: url(file.png);
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    background-position: 0 7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.search li a {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.search li div.context {
 | 
			
		||||
    color: #888;
 | 
			
		||||
    margin: 2px 0 0 30px;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul.keywordmatches li.goodmatch a {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- index page ------------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
table.contentstable {
 | 
			
		||||
    width: 90%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.contentstable p.biglink {
 | 
			
		||||
    line-height: 150%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.biglink {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
span.linkdescr {
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
    padding-top: 5px;
 | 
			
		||||
    font-size: 90%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- general index --------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
table.indextable {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.indextable td {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.indextable dl, table.indextable dd {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.indextable tr.pcap {
 | 
			
		||||
    height: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.indextable tr.cap {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    background-color: #f2f2f2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.toggler {
 | 
			
		||||
    margin-right: 3px;
 | 
			
		||||
    margin-top: 3px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.modindex-jumpbox {
 | 
			
		||||
    border-top: 1px solid #ddd;
 | 
			
		||||
    border-bottom: 1px solid #ddd;
 | 
			
		||||
    margin: 1em 0 1em 0;
 | 
			
		||||
    padding: 0.4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.genindex-jumpbox {
 | 
			
		||||
    border-top: 1px solid #ddd;
 | 
			
		||||
    border-bottom: 1px solid #ddd;
 | 
			
		||||
    margin: 1em 0 1em 0;
 | 
			
		||||
    padding: 0.4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- general body styles --------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
a.headerlink {
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1:hover > a.headerlink,
 | 
			
		||||
h2:hover > a.headerlink,
 | 
			
		||||
h3:hover > a.headerlink,
 | 
			
		||||
h4:hover > a.headerlink,
 | 
			
		||||
h5:hover > a.headerlink,
 | 
			
		||||
h6:hover > a.headerlink,
 | 
			
		||||
dt:hover > a.headerlink {
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body p.caption {
 | 
			
		||||
    text-align: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body td {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.field-list ul {
 | 
			
		||||
    padding-left: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.first {
 | 
			
		||||
    margin-top: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.rubric {
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.align-left, .figure.align-left, object.align-left {
 | 
			
		||||
    clear: left;
 | 
			
		||||
    float: left;
 | 
			
		||||
    margin-right: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.align-right, .figure.align-right, object.align-right {
 | 
			
		||||
    clear: right;
 | 
			
		||||
    float: right;
 | 
			
		||||
    margin-left: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
img.align-center, .figure.align-center, object.align-center {
 | 
			
		||||
  display: block;
 | 
			
		||||
  margin-left: auto;
 | 
			
		||||
  margin-right: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-left {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-center {
 | 
			
		||||
    clear: both;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-right {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- sidebars -------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
div.sidebar {
 | 
			
		||||
    margin: 0 0 0.5em 1em;
 | 
			
		||||
    border: 1px solid #ddb;
 | 
			
		||||
    padding: 7px 7px 0 7px;
 | 
			
		||||
    background-color: #efefef;
 | 
			
		||||
    width: 40%;
 | 
			
		||||
    float: right;
 | 
			
		||||
    -mox-border-radius: 4px;
 | 
			
		||||
    -webkit-border-radius: 4px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.sidebar-title {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- topics ---------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
div.topic {
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
    padding: 7px 7px 0 7px;
 | 
			
		||||
    margin: 10px 0 10px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.topic-title {
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- admonitions ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
div.admonition {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    padding: 7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition dt {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition dl {
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.admonition-title {
 | 
			
		||||
    margin: 0px 10px 5px 0px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body p.centered {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-top: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- tables ---------------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
table.docutils {
 | 
			
		||||
    border: 0;
 | 
			
		||||
    border-collapse: collapse;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.docutils td, table.docutils th {
 | 
			
		||||
    padding: 1px 8px 1px 5px;
 | 
			
		||||
    border-top: 0;
 | 
			
		||||
    border-left: 0;
 | 
			
		||||
    border-right: 0;
 | 
			
		||||
    border-bottom: 1px solid #aaa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.field-list td, table.field-list th {
 | 
			
		||||
    border: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.footnote td, table.footnote th {
 | 
			
		||||
    border: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
th {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    padding-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.citation {
 | 
			
		||||
    border-left: solid 1px gray;
 | 
			
		||||
    margin-left: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.citation td {
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- other body styles ----------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
ol.arabic {
 | 
			
		||||
    list-style: decimal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ol.loweralpha {
 | 
			
		||||
    list-style: lower-alpha;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ol.upperalpha {
 | 
			
		||||
    list-style: upper-alpha;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ol.lowerroman {
 | 
			
		||||
    list-style: lower-roman;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ol.upperroman {
 | 
			
		||||
    list-style: upper-roman;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dl {
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dd p {
 | 
			
		||||
    margin-top: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dd ul, dd table {
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dd {
 | 
			
		||||
    margin-top: 3px;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    margin-left: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dt:target, .highlighted {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dl.glossary dt {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.field-list ul {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding-left: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.field-list p {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.refcount {
 | 
			
		||||
    color: #060;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.optional {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.versionmodified {
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.system-message {
 | 
			
		||||
    background-color: #fda;
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
    border: 3px solid red;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footnote:target  {
 | 
			
		||||
    background-color: #ffa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-block {
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin-top: 1em;
 | 
			
		||||
    margin-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-block .line-block {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
    margin-left: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.guilabel, .menuselection {
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.accelerator {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.classifier {
 | 
			
		||||
    font-style: oblique;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- code displays --------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
pre {
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    overflow-y: hidden;  /* fixes display issues on Chrome browsers */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td.linenos pre {
 | 
			
		||||
    padding: 5px 0px;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    color: #aaa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.highlighttable {
 | 
			
		||||
    margin-left: 0.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.highlighttable td {
 | 
			
		||||
    padding: 0 0.5em 0 0.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tt.descname {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    font-size: 1.2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tt.descclassname {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tt.xref, a tt {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.viewcode-link {
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.viewcode-back {
 | 
			
		||||
    float: right;
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.viewcode-block:target {
 | 
			
		||||
    margin: -1px -10px;
 | 
			
		||||
    padding: 0 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- math display ---------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
img.math {
 | 
			
		||||
    vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body div.math p {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
span.eqno {
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- printout stylesheet --------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
@media print {
 | 
			
		||||
    div.document,
 | 
			
		||||
    div.documentwrapper,
 | 
			
		||||
    div.bodywrapper {
 | 
			
		||||
        margin: 0 !important;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    div.sphinxsidebar,
 | 
			
		||||
    div.related,
 | 
			
		||||
    div.footer,
 | 
			
		||||
    #top-link {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										256
									
								
								docs/_static/default.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								docs/_static/default.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,256 @@
 | 
			
		||||
/*
 | 
			
		||||
 * default.css_t
 | 
			
		||||
 * ~~~~~~~~~~~~~
 | 
			
		||||
 *
 | 
			
		||||
 * Sphinx stylesheet -- default theme.
 | 
			
		||||
 *
 | 
			
		||||
 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
 * :license: BSD, see LICENSE for details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@import url("basic.css");
 | 
			
		||||
 | 
			
		||||
/* -- page layout ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
    font-size: 100%;
 | 
			
		||||
    background-color: #11303d;
 | 
			
		||||
    color: #000;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document {
 | 
			
		||||
    background-color: #1c4e63;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.documentwrapper {
 | 
			
		||||
    float: left;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.bodywrapper {
 | 
			
		||||
    margin: 0 0 0 230px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body {
 | 
			
		||||
    background-color: #ffffff;
 | 
			
		||||
    color: #000000;
 | 
			
		||||
    padding: 0 20px 30px 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 9px 0 9px 0;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-size: 75%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer a {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related {
 | 
			
		||||
    background-color: #133f52;
 | 
			
		||||
    line-height: 30px;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related a {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar h3 {
 | 
			
		||||
    font-family: 'Trebuchet MS', sans-serif;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    font-size: 1.4em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar h3 a {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar h4 {
 | 
			
		||||
    font-family: 'Trebuchet MS', sans-serif;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    margin: 5px 0 0 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar p {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar p.topless {
 | 
			
		||||
    margin: 5px 10px 10px 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar ul {
 | 
			
		||||
    margin: 10px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar a {
 | 
			
		||||
    color: #98dbcc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar input {
 | 
			
		||||
    border: 1px solid #98dbcc;
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* -- hyperlink styles ------------------------------------------------------ */
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
    color: #355f7c;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:visited {
 | 
			
		||||
    color: #355f7c;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* -- body styles ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
div.body h1,
 | 
			
		||||
div.body h2,
 | 
			
		||||
div.body h3,
 | 
			
		||||
div.body h4,
 | 
			
		||||
div.body h5,
 | 
			
		||||
div.body h6 {
 | 
			
		||||
    font-family: 'Trebuchet MS', sans-serif;
 | 
			
		||||
    background-color: #f2f2f2;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: #20435c;
 | 
			
		||||
    border-bottom: 1px solid #ccc;
 | 
			
		||||
    margin: 20px -20px 10px -20px;
 | 
			
		||||
    padding: 3px 0 3px 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body h1 { margin-top: 0; font-size: 200%; }
 | 
			
		||||
div.body h2 { font-size: 160%; }
 | 
			
		||||
div.body h3 { font-size: 140%; }
 | 
			
		||||
div.body h4 { font-size: 120%; }
 | 
			
		||||
div.body h5 { font-size: 110%; }
 | 
			
		||||
div.body h6 { font-size: 100%; }
 | 
			
		||||
 | 
			
		||||
a.headerlink {
 | 
			
		||||
    color: #c60f0f;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    padding: 0 4px 0 4px;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.headerlink:hover {
 | 
			
		||||
    background-color: #c60f0f;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body p, div.body dd, div.body li {
 | 
			
		||||
    text-align: justify;
 | 
			
		||||
    line-height: 130%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition p.admonition-title + p {
 | 
			
		||||
    display: inline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition p {
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition pre {
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition ul, div.admonition ol {
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.note {
 | 
			
		||||
    background-color: #eee;
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.seealso {
 | 
			
		||||
    background-color: #ffc;
 | 
			
		||||
    border: 1px solid #ff6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.topic {
 | 
			
		||||
    background-color: #eee;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.warning {
 | 
			
		||||
    background-color: #ffe4e4;
 | 
			
		||||
    border: 1px solid #f66;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.admonition-title {
 | 
			
		||||
    display: inline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.admonition-title:after {
 | 
			
		||||
    content: ":";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pre {
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
    background-color: #eeffcc;
 | 
			
		||||
    color: #333333;
 | 
			
		||||
    line-height: 120%;
 | 
			
		||||
    border: 1px solid #ac9;
 | 
			
		||||
    border-left: none;
 | 
			
		||||
    border-right: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tt {
 | 
			
		||||
    background-color: #ecf0f3;
 | 
			
		||||
    padding: 0 1px 0 1px;
 | 
			
		||||
    font-size: 0.95em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
th {
 | 
			
		||||
    background-color: #ede;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.warning tt {
 | 
			
		||||
    background: #efc2c2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.note tt {
 | 
			
		||||
    background: #d6d6d6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.viewcode-back {
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.viewcode-block:target {
 | 
			
		||||
    background-color: #f4debf;
 | 
			
		||||
    border-top: 1px solid #ac9;
 | 
			
		||||
    border-bottom: 1px solid #ac9;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										406
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										406
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,406 @@
 | 
			
		||||
/*
 | 
			
		||||
 * haiku.css_t
 | 
			
		||||
 * ~~~~~~~~~~~
 | 
			
		||||
 *
 | 
			
		||||
 * Sphinx stylesheet -- haiku theme.
 | 
			
		||||
 *
 | 
			
		||||
 * Adapted from http://haiku-os.org/docs/Haiku-doc.css.
 | 
			
		||||
 * Original copyright message:
 | 
			
		||||
 *
 | 
			
		||||
 *     Copyright 2008-2009, Haiku. All rights reserved.
 | 
			
		||||
 *     Distributed under the terms of the MIT License.
 | 
			
		||||
 *
 | 
			
		||||
 *     Authors:
 | 
			
		||||
 *              Francois Revol <revol@free.fr>
 | 
			
		||||
 *              Stephan Assmus <superstippi@gmx.de>
 | 
			
		||||
 *              Braden Ewing <brewin@gmail.com>
 | 
			
		||||
 *              Humdinger <humdingerb@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
 * :license: BSD, see LICENSE for details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@import url("basic.css");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
    font-family: "Museo Slab";
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
    src: local("Museo Slab"),
 | 
			
		||||
         url("fonts/Museo_Slab_500.otf") format("opentype");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
    font-family: "Yanone Kaffeesatz";
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
    src: local("Yanone Kaffeesatz"),
 | 
			
		||||
         url("fonts/YanoneKaffeesatz-Bold.ttf") format("truetype");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@font-face {
 | 
			
		||||
    font-family: "Yanone Kaffeesatz";
 | 
			
		||||
    font-weight: lighter;
 | 
			
		||||
    font-style: normal;
 | 
			
		||||
    src: local("Yanone Kaffeesatz"),
 | 
			
		||||
         url("fonts/YanoneKaffeesatz-Regular.ttf") format("truetype");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
    margin: 0px;
 | 
			
		||||
    padding: 0px;
 | 
			
		||||
    background: #FFF url(header.png) top left repeat-x;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    line-height: 1.5;
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    padding: 0px;
 | 
			
		||||
    font-family: "Helvetica Neueu", Helvetica, sans-serif;
 | 
			
		||||
    min-width: 59em;
 | 
			
		||||
    max-width: 70em;
 | 
			
		||||
    color: #444;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer {
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
    font-size: 11px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    letter-spacing: 0.5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* link colors and text decoration */
 | 
			
		||||
 | 
			
		||||
a:link {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #00ADEE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:visited {
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #00ADEE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover, a:active {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
    color: #F46DBA;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Some headers act as anchors, don't give them a hover effect */
 | 
			
		||||
 | 
			
		||||
h1 a:hover, a:active {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #CFCFCF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 a:hover, a:active {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #CFCFCF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h3 a:hover, a:active {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #CFCFCF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h4 a:hover, a:active {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #CFCFCF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.headerlink {
 | 
			
		||||
    color: #a7ce38;
 | 
			
		||||
    padding-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.headerlink:hover {
 | 
			
		||||
    color: #a7ce38;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* basic text elements */
 | 
			
		||||
 | 
			
		||||
div.content {
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
    margin-left: 40px;
 | 
			
		||||
    margin-right: 40px;
 | 
			
		||||
    margin-bottom: 50px;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* heading and navigation */
 | 
			
		||||
 | 
			
		||||
div.header {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin-top: 125px;
 | 
			
		||||
    height: 85px;
 | 
			
		||||
    padding: 0 40px;
 | 
			
		||||
    font-family: "Yanone Kaffeesatz";
 | 
			
		||||
}
 | 
			
		||||
div.header h1 {
 | 
			
		||||
    font-size: 2.6em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    letter-spacing: 1px;
 | 
			
		||||
    color: #CFCFCF;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding-top: 15px;
 | 
			
		||||
    font-family: "Yanone Kaffeesatz";
 | 
			
		||||
    text-shadow: 1px 1px 1px rgba(175, 175, 175, .8);
 | 
			
		||||
    font-variant: small-caps;
 | 
			
		||||
}
 | 
			
		||||
div.header h1 a {
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: #00ADEE;
 | 
			
		||||
}
 | 
			
		||||
div.header h2 {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    letter-spacing: 1px;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
    color: #aaa;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    margin-top: -3px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    font-family: "Yanone Kaffeesatz";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header img.rightlogo {
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
div.title {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    color: #CFCFCF;
 | 
			
		||||
    border-bottom: dotted thin #e0e0e0;
 | 
			
		||||
    margin-bottom: 25px;
 | 
			
		||||
}
 | 
			
		||||
div.topnav {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    z-index: 0;
 | 
			
		||||
}
 | 
			
		||||
div.topnav p {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin-left: 40px;
 | 
			
		||||
    margin-right: 40px;
 | 
			
		||||
    margin-bottom: 0px;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
}
 | 
			
		||||
div.bottomnav {
 | 
			
		||||
    background: #eeeeee;
 | 
			
		||||
}
 | 
			
		||||
div.bottomnav p {
 | 
			
		||||
    margin-right: 40px;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.uplink {
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* contents box */
 | 
			
		||||
 | 
			
		||||
table.index {
 | 
			
		||||
    margin: 0px 0px 30px 30px;
 | 
			
		||||
    padding: 1px;
 | 
			
		||||
    border-width: 1px;
 | 
			
		||||
    border-style: dotted;
 | 
			
		||||
    border-color: #e0e0e0;
 | 
			
		||||
}
 | 
			
		||||
table.index tr.heading {
 | 
			
		||||
    background-color: #e0e0e0;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
}
 | 
			
		||||
table.index tr.index {
 | 
			
		||||
    background-color: #eeeeee;
 | 
			
		||||
}
 | 
			
		||||
table.index td {
 | 
			
		||||
    padding: 5px 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.index a:link, table.index a:visited {
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: #4A7389;
 | 
			
		||||
}
 | 
			
		||||
table.index a:hover, table.index a:active {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
    color: #ff4500;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Haiku User Guide styles and layout */
 | 
			
		||||
 | 
			
		||||
/* Rounded corner boxes */
 | 
			
		||||
/* Common declarations */
 | 
			
		||||
div.admonition {
 | 
			
		||||
    -webkit-border-radius: 10px;
 | 
			
		||||
    -khtml-border-radius: 10px;
 | 
			
		||||
    -moz-border-radius: 10px;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    border-style: dotted;
 | 
			
		||||
    border-width: thin;
 | 
			
		||||
    border-color: #dcdcdc;
 | 
			
		||||
    padding: 10px 15px 10px 15px;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    margin-top: 15px;
 | 
			
		||||
}
 | 
			
		||||
div.note {
 | 
			
		||||
    padding: 10px 15px 10px 15px;
 | 
			
		||||
    background-color: #e4ffde;
 | 
			
		||||
    /*background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;*/
 | 
			
		||||
    min-height: 42px;
 | 
			
		||||
}
 | 
			
		||||
div.warning {
 | 
			
		||||
    padding: 10px 15px 10px 15px;
 | 
			
		||||
    background-color: #fffbc6;
 | 
			
		||||
    /*background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;*/
 | 
			
		||||
    min-height: 42px;
 | 
			
		||||
}
 | 
			
		||||
div.seealso {
 | 
			
		||||
    background: #e4ffde;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* More layout and styles */
 | 
			
		||||
h1 {
 | 
			
		||||
    font-size: 1.6em;
 | 
			
		||||
    color: #aaa;
 | 
			
		||||
    border-bottom: dotted thin #e0e0e0;
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
    font-family: "Museo Slab";
 | 
			
		||||
    text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 {
 | 
			
		||||
    font-size: 1.5em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: #aaa;
 | 
			
		||||
    border-bottom: dotted thin #e0e0e0;
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
    font-family: "Museo Slab";
 | 
			
		||||
    text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h3 {
 | 
			
		||||
    font-size: 1.4em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: #aaa;
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
    font-family: "Museo Slab";
 | 
			
		||||
    text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h4 {
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: #CFCFCF;
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p {
 | 
			
		||||
    text-align: justify;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.last {
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ol {
 | 
			
		||||
    padding-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul {
 | 
			
		||||
    padding-left: 5px;
 | 
			
		||||
    margin-top: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
li {
 | 
			
		||||
    line-height: 1.3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.content ul > li {
 | 
			
		||||
    -moz-background-clip:border;
 | 
			
		||||
    -moz-background-inline-policy:continuous;
 | 
			
		||||
    -moz-background-origin:padding;
 | 
			
		||||
    background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
 | 
			
		||||
    list-style-image: none;
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
    padding: 0 0 0 1.666em;
 | 
			
		||||
    margin-bottom: 3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td {
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tt {
 | 
			
		||||
    background-color: #e2e2e2;
 | 
			
		||||
    font-size: 1.0em;
 | 
			
		||||
    font-family: monospace;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pre {
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
    margin: 0 0 12px 0;
 | 
			
		||||
    padding: 0.8em;
 | 
			
		||||
    background-image: url(noise_dk.png);
 | 
			
		||||
    background-color: #222;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
hr {
 | 
			
		||||
    border-top: 1px solid #ccc;
 | 
			
		||||
    border-bottom: 0;
 | 
			
		||||
    border-right: 0;
 | 
			
		||||
    border-left: 0;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* printer only pretty stuff */
 | 
			
		||||
@media print {
 | 
			
		||||
    .noprint {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    /* for acronyms we want their definitions inlined at print time */
 | 
			
		||||
    acronym[title]:after {
 | 
			
		||||
        font-size: small;
 | 
			
		||||
        content: " (" attr(title) ")";
 | 
			
		||||
        font-style: italic;
 | 
			
		||||
    }
 | 
			
		||||
    /* and not have mozilla dotted underline */
 | 
			
		||||
    acronym {
 | 
			
		||||
        border: none;
 | 
			
		||||
    }
 | 
			
		||||
    div.topnav, div.bottomnav, div.header, table.index {
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
    div.content {
 | 
			
		||||
        margin: 0px;
 | 
			
		||||
        padding: 0px;
 | 
			
		||||
    }
 | 
			
		||||
    html {
 | 
			
		||||
        background: #FFF;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.viewcode-back {
 | 
			
		||||
    font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.viewcode-block:target {
 | 
			
		||||
    border-top: 1px solid #ac9;
 | 
			
		||||
    border-bottom: 1px solid #ac9;
 | 
			
		||||
    margin: -1px -12px;
 | 
			
		||||
    padding: 0 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 16 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
.highlight .hll { background-color: #ffffcc }
 | 
			
		||||
.highlight  { background: #000000; color: #f6f3e8; }
 | 
			
		||||
.highlight .c { color: #7C7C7C; } /* Comment */
 | 
			
		||||
.highlight .err { color: #f6f3e8; } /* Error */
 | 
			
		||||
.highlight .g { color: #f6f3e8; } /* Generic */
 | 
			
		||||
.highlight .k { color: #00ADEE; } /* Keyword */
 | 
			
		||||
.highlight .l { color: #f6f3e8; } /* Literal */
 | 
			
		||||
.highlight .n { color: #f6f3e8; } /* Name */
 | 
			
		||||
.highlight .o { color: #f6f3e8; } /* Operator */
 | 
			
		||||
.highlight .x { color: #f6f3e8; } /* Other */
 | 
			
		||||
.highlight .p { color: #f6f3e8; } /* Punctuation */
 | 
			
		||||
.highlight .cm { color: #7C7C7C; } /* Comment.Multiline */
 | 
			
		||||
.highlight .cp { color: #96CBFE; } /* Comment.Preproc */
 | 
			
		||||
.highlight .c1 { color: #7C7C7C; } /* Comment.Single */
 | 
			
		||||
.highlight .cs { color: #7C7C7C; } /* Comment.Special */
 | 
			
		||||
.highlight .gd { color: #f6f3e8; } /* Generic.Deleted */
 | 
			
		||||
.highlight .ge { color: #f6f3e8; } /* Generic.Emph */
 | 
			
		||||
.highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */
 | 
			
		||||
.highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */
 | 
			
		||||
.highlight .gi { color: #f6f3e8; } /* Generic.Inserted */
 | 
			
		||||
.highlight .go { color: #070707; } /* Generic.Output */
 | 
			
		||||
.highlight .gp { color: #f6f3e8; } /* Generic.Prompt */
 | 
			
		||||
.highlight .gs { color: #f6f3e8; } /* Generic.Strong */
 | 
			
		||||
.highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */
 | 
			
		||||
.highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */
 | 
			
		||||
.highlight .kc { color: #6699CC; } /* Keyword.Constant */
 | 
			
		||||
.highlight .kd { color: #6699CC; } /* Keyword.Declaration */
 | 
			
		||||
.highlight .kn { color: #6699CC; } /* Keyword.Namespace */
 | 
			
		||||
.highlight .kp { color: #6699CC; } /* Keyword.Pseudo */
 | 
			
		||||
.highlight .kr { color: #6699CC; } /* Keyword.Reserved */
 | 
			
		||||
.highlight .kt { color: #FFFFB6; } /* Keyword.Type */
 | 
			
		||||
.highlight .ld { color: #f6f3e8; } /* Literal.Date */
 | 
			
		||||
.highlight .m { color: #FF73FD; } /* Literal.Number */
 | 
			
		||||
.highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */
 | 
			
		||||
.highlight .na { color: #f6f3e8; } /* Name.Attribute */
 | 
			
		||||
.highlight .nb { color: #f6f3e8; } /* Name.Builtin */
 | 
			
		||||
.highlight .nc { color: #f6f3e8; } /* Name.Class */
 | 
			
		||||
.highlight .no { color: #99CC99; } /* Name.Constant */
 | 
			
		||||
.highlight .nd { color: #f6f3e8; } /* Name.Decorator */
 | 
			
		||||
.highlight .ni { color: #E18964; } /* Name.Entity */
 | 
			
		||||
.highlight .ne { color: #f6f3e8; } /* Name.Exception */
 | 
			
		||||
.highlight .nf { color: #F64DBA; } /* Name.Function */
 | 
			
		||||
.highlight .nl { color: #f6f3e8; } /* Name.Label */
 | 
			
		||||
.highlight .nn { color: #f6f3e8; } /* Name.Namespace */
 | 
			
		||||
.highlight .nx { color: #f6f3e8; } /* Name.Other */
 | 
			
		||||
.highlight .py { color: #f6f3e8; } /* Name.Property */
 | 
			
		||||
.highlight .nt { color: #00ADEE; } /* Name.Tag */
 | 
			
		||||
.highlight .nv { color: #C6C5FE; } /* Name.Variable */
 | 
			
		||||
.highlight .ow { color: #ffffff; } /* Operator.Word */
 | 
			
		||||
.highlight .w { color: #f6f3e8; } /* Text.Whitespace */
 | 
			
		||||
.highlight .mf { color: #FF73FD; } /* Literal.Number.Float */
 | 
			
		||||
.highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */
 | 
			
		||||
.highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */
 | 
			
		||||
.highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */
 | 
			
		||||
.highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */
 | 
			
		||||
.highlight .sc { color: #A8FF60; } /* Literal.String.Char */
 | 
			
		||||
.highlight .sd { color: #A8FF60; } /* Literal.String.Doc */
 | 
			
		||||
.highlight .s2 { color: #A8FF60; } /* Literal.String.Double */
 | 
			
		||||
.highlight .se { color: #A8FF60; } /* Literal.String.Escape */
 | 
			
		||||
.highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */
 | 
			
		||||
.highlight .si { color: #A8FF60; } /* Literal.String.Interpol */
 | 
			
		||||
.highlight .sx { color: #A8FF60; } /* Literal.String.Other */
 | 
			
		||||
.highlight .sr { color: #A8FF60; } /* Literal.String.Regex */
 | 
			
		||||
.highlight .s1 { color: #A8FF60; } /* Literal.String.Single */
 | 
			
		||||
.highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */
 | 
			
		||||
.highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */
 | 
			
		||||
.highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */
 | 
			
		||||
.highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */
 | 
			
		||||
.highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */
 | 
			
		||||
.highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */
 | 
			
		||||
							
								
								
									
										245
									
								
								docs/_static/nature.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								docs/_static/nature.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
			
		||||
/*
 | 
			
		||||
 * nature.css_t
 | 
			
		||||
 * ~~~~~~~~~~~~
 | 
			
		||||
 *
 | 
			
		||||
 * Sphinx stylesheet -- nature theme.
 | 
			
		||||
 *
 | 
			
		||||
 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
 * :license: BSD, see LICENSE for details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@import url("basic.css");
 | 
			
		||||
 
 | 
			
		||||
/* -- page layout ----------------------------------------------------------- */
 | 
			
		||||
 
 | 
			
		||||
body {
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
    font-size: 100%;
 | 
			
		||||
    background-color: #111;
 | 
			
		||||
    color: #555;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.documentwrapper {
 | 
			
		||||
    float: left;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.bodywrapper {
 | 
			
		||||
    margin: 0 0 0 230px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
hr {
 | 
			
		||||
    border: 1px solid #B1B4B6;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.document {
 | 
			
		||||
    background-color: #eee;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.body {
 | 
			
		||||
    background-color: #ffffff;
 | 
			
		||||
    color: #3E4349;
 | 
			
		||||
    padding: 0 30px 30px 30px;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.footer {
 | 
			
		||||
    color: #555;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding: 13px 0;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-size: 75%;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.footer a {
 | 
			
		||||
    color: #444;
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.related {
 | 
			
		||||
    background-color: #6BA81E;
 | 
			
		||||
    line-height: 32px;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    text-shadow: 0px 1px 0 #444;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.related a {
 | 
			
		||||
    color: #E2F3CC;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar {
 | 
			
		||||
    font-size: 0.75em;
 | 
			
		||||
    line-height: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebarwrapper{
 | 
			
		||||
    padding: 20px 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar h3,
 | 
			
		||||
div.sphinxsidebar h4 {
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
    color: #222;
 | 
			
		||||
    font-size: 1.2em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
    background-color: #ddd;
 | 
			
		||||
    text-shadow: 1px 1px 0 white
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar h4{
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar h3 a {
 | 
			
		||||
    color: #444;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar p {
 | 
			
		||||
    color: #888;
 | 
			
		||||
    padding: 5px 20px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar p.topless {
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar ul {
 | 
			
		||||
    margin: 10px 20px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    color: #000;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar a {
 | 
			
		||||
    color: #444;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.sphinxsidebar input {
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
    font-family: sans-serif;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar input[type=text]{
 | 
			
		||||
    margin-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
/* -- body styles ----------------------------------------------------------- */
 | 
			
		||||
 
 | 
			
		||||
a {
 | 
			
		||||
    color: #005B81;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
a:hover {
 | 
			
		||||
    color: #E32E00;
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.body h1,
 | 
			
		||||
div.body h2,
 | 
			
		||||
div.body h3,
 | 
			
		||||
div.body h4,
 | 
			
		||||
div.body h5,
 | 
			
		||||
div.body h6 {
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
    background-color: #BED4EB;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: #212224;
 | 
			
		||||
    margin: 30px 0px 10px 0px;
 | 
			
		||||
    padding: 5px 0 5px 10px;
 | 
			
		||||
    text-shadow: 0px 1px 0 white
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
 | 
			
		||||
div.body h2 { font-size: 150%; background-color: #C8D5E3; }
 | 
			
		||||
div.body h3 { font-size: 120%; background-color: #D8DEE3; }
 | 
			
		||||
div.body h4 { font-size: 110%; background-color: #D8DEE3; }
 | 
			
		||||
div.body h5 { font-size: 100%; background-color: #D8DEE3; }
 | 
			
		||||
div.body h6 { font-size: 100%; background-color: #D8DEE3; }
 | 
			
		||||
 
 | 
			
		||||
a.headerlink {
 | 
			
		||||
    color: #c60f0f;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    padding: 0 4px 0 4px;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
a.headerlink:hover {
 | 
			
		||||
    background-color: #c60f0f;
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.body p, div.body dd, div.body li {
 | 
			
		||||
    line-height: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.admonition p.admonition-title + p {
 | 
			
		||||
    display: inline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.highlight{
 | 
			
		||||
    background-color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.note {
 | 
			
		||||
    background-color: #eee;
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.seealso {
 | 
			
		||||
    background-color: #ffc;
 | 
			
		||||
    border: 1px solid #ff6;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.topic {
 | 
			
		||||
    background-color: #eee;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
div.warning {
 | 
			
		||||
    background-color: #ffe4e4;
 | 
			
		||||
    border: 1px solid #f66;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
p.admonition-title {
 | 
			
		||||
    display: inline;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
p.admonition-title:after {
 | 
			
		||||
    content: ":";
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
pre {
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    background-color: White;
 | 
			
		||||
    color: #222;
 | 
			
		||||
    line-height: 1.2em;
 | 
			
		||||
    border: 1px solid #C6C9CB;
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
    margin: 1.5em 0 1.5em 0;
 | 
			
		||||
    -webkit-box-shadow: 1px 1px 1px #d8d8d8;
 | 
			
		||||
    -moz-box-shadow: 1px 1px 1px #d8d8d8;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
tt {
 | 
			
		||||
    background-color: #ecf0f3;
 | 
			
		||||
    color: #222;
 | 
			
		||||
    /* padding: 1px 2px; */
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
    font-family: monospace;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.viewcode-back {
 | 
			
		||||
    font-family: Arial, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.viewcode-block:target {
 | 
			
		||||
    background-color: #f4debf;
 | 
			
		||||
    border-top: 1px solid #ac9;
 | 
			
		||||
    border-bottom: 1px solid #ac9;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/noise_dk.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/noise_dk.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
.highlight .hll { background-color: #ffffcc }
 | 
			
		||||
.highlight  { background: #000000; color: #f6f3e8; }
 | 
			
		||||
.highlight .c { color: #7C7C7C; } /* Comment */
 | 
			
		||||
.highlight .err { color: #f6f3e8; } /* Error */
 | 
			
		||||
.highlight .g { color: #f6f3e8; } /* Generic */
 | 
			
		||||
.highlight .k { color: #00ADEE; } /* Keyword */
 | 
			
		||||
.highlight .l { color: #f6f3e8; } /* Literal */
 | 
			
		||||
.highlight .n { color: #f6f3e8; } /* Name */
 | 
			
		||||
.highlight .o { color: #f6f3e8; } /* Operator */
 | 
			
		||||
.highlight .x { color: #f6f3e8; } /* Other */
 | 
			
		||||
.highlight .p { color: #f6f3e8; } /* Punctuation */
 | 
			
		||||
.highlight .cm { color: #7C7C7C; } /* Comment.Multiline */
 | 
			
		||||
.highlight .cp { color: #96CBFE; } /* Comment.Preproc */
 | 
			
		||||
.highlight .c1 { color: #7C7C7C; } /* Comment.Single */
 | 
			
		||||
.highlight .cs { color: #7C7C7C; } /* Comment.Special */
 | 
			
		||||
.highlight .gd { color: #f6f3e8; } /* Generic.Deleted */
 | 
			
		||||
.highlight .ge { color: #f6f3e8; } /* Generic.Emph */
 | 
			
		||||
.highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */
 | 
			
		||||
.highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */
 | 
			
		||||
.highlight .gi { color: #f6f3e8; } /* Generic.Inserted */
 | 
			
		||||
.highlight .go { color: #070707; } /* Generic.Output */
 | 
			
		||||
.highlight .gp { color: #f6f3e8; } /* Generic.Prompt */
 | 
			
		||||
.highlight .gs { color: #f6f3e8; } /* Generic.Strong */
 | 
			
		||||
.highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */
 | 
			
		||||
.highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */
 | 
			
		||||
.highlight .kc { color: #6699CC; } /* Keyword.Constant */
 | 
			
		||||
.highlight .kd { color: #6699CC; } /* Keyword.Declaration */
 | 
			
		||||
.highlight .kn { color: #6699CC; } /* Keyword.Namespace */
 | 
			
		||||
.highlight .kp { color: #6699CC; } /* Keyword.Pseudo */
 | 
			
		||||
.highlight .kr { color: #6699CC; } /* Keyword.Reserved */
 | 
			
		||||
.highlight .kt { color: #FFFFB6; } /* Keyword.Type */
 | 
			
		||||
.highlight .ld { color: #f6f3e8; } /* Literal.Date */
 | 
			
		||||
.highlight .m { color: #FF73FD; } /* Literal.Number */
 | 
			
		||||
.highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */
 | 
			
		||||
.highlight .na { color: #f6f3e8; } /* Name.Attribute */
 | 
			
		||||
.highlight .nb { color: #f6f3e8; } /* Name.Builtin */
 | 
			
		||||
.highlight .nc { color: #f6f3e8; } /* Name.Class */
 | 
			
		||||
.highlight .no { color: #99CC99; } /* Name.Constant */
 | 
			
		||||
.highlight .nd { color: #f6f3e8; } /* Name.Decorator */
 | 
			
		||||
.highlight .ni { color: #E18964; } /* Name.Entity */
 | 
			
		||||
.highlight .ne { color: #f6f3e8; } /* Name.Exception */
 | 
			
		||||
.highlight .nf { color: #F64DBA; } /* Name.Function */
 | 
			
		||||
.highlight .nl { color: #f6f3e8; } /* Name.Label */
 | 
			
		||||
.highlight .nn { color: #f6f3e8; } /* Name.Namespace */
 | 
			
		||||
.highlight .nx { color: #f6f3e8; } /* Name.Other */
 | 
			
		||||
.highlight .py { color: #f6f3e8; } /* Name.Property */
 | 
			
		||||
.highlight .nt { color: #00ADEE; } /* Name.Tag */
 | 
			
		||||
.highlight .nv { color: #C6C5FE; } /* Name.Variable */
 | 
			
		||||
.highlight .ow { color: #ffffff; } /* Operator.Word */
 | 
			
		||||
.highlight .w { color: #f6f3e8; } /* Text.Whitespace */
 | 
			
		||||
.highlight .mf { color: #FF73FD; } /* Literal.Number.Float */
 | 
			
		||||
.highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */
 | 
			
		||||
.highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */
 | 
			
		||||
.highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */
 | 
			
		||||
.highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */
 | 
			
		||||
.highlight .sc { color: #A8FF60; } /* Literal.String.Char */
 | 
			
		||||
.highlight .sd { color: #A8FF60; } /* Literal.String.Doc */
 | 
			
		||||
.highlight .s2 { color: #A8FF60; } /* Literal.String.Double */
 | 
			
		||||
.highlight .se { color: #A8FF60; } /* Literal.String.Escape */
 | 
			
		||||
.highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */
 | 
			
		||||
.highlight .si { color: #A8FF60; } /* Literal.String.Interpol */
 | 
			
		||||
.highlight .sx { color: #A8FF60; } /* Literal.String.Other */
 | 
			
		||||
.highlight .sr { color: #A8FF60; } /* Literal.String.Regex */
 | 
			
		||||
.highlight .s1 { color: #A8FF60; } /* Literal.String.Single */
 | 
			
		||||
.highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */
 | 
			
		||||
.highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */
 | 
			
		||||
.highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */
 | 
			
		||||
.highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */
 | 
			
		||||
.highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */
 | 
			
		||||
.highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */
 | 
			
		||||
							
								
								
									
										339
									
								
								docs/_static/sphinxdoc.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								docs/_static/sphinxdoc.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,339 @@
 | 
			
		||||
/*
 | 
			
		||||
 * sphinxdoc.css_t
 | 
			
		||||
 * ~~~~~~~~~~~~~~~
 | 
			
		||||
 *
 | 
			
		||||
 * Sphinx stylesheet -- sphinxdoc theme.  Originally created by
 | 
			
		||||
 * Armin Ronacher for Werkzeug.
 | 
			
		||||
 *
 | 
			
		||||
 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
 * :license: BSD, see LICENSE for details.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@import url("basic.css");
 | 
			
		||||
 | 
			
		||||
/* -- page layout ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
 | 
			
		||||
                 'Verdana', sans-serif;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    letter-spacing: -0.01em;
 | 
			
		||||
    line-height: 150%;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    background-color: #BFD1D4;
 | 
			
		||||
    color: black;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    border: 1px solid #aaa;
 | 
			
		||||
 | 
			
		||||
    margin: 0px 80px 0px 80px;
 | 
			
		||||
    min-width: 740px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.document {
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    background-image: url(contents.png);
 | 
			
		||||
    background-repeat: repeat-x;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.bodywrapper {
 | 
			
		||||
    margin: 0 240px 0 0;
 | 
			
		||||
    border-right: 1px solid #ccc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0.5em 20px 20px 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related {
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related ul {
 | 
			
		||||
    background-image: url(navigation.png);
 | 
			
		||||
    height: 2em;
 | 
			
		||||
    border-top: 1px solid #ddd;
 | 
			
		||||
    border-bottom: 1px solid #ddd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related ul li {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    height: 2em;
 | 
			
		||||
    float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related ul li.right {
 | 
			
		||||
    float: right;
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related ul li a {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0 5px 0 5px;
 | 
			
		||||
    line-height: 1.75em;
 | 
			
		||||
    color: #EE9816;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.related ul li a:hover {
 | 
			
		||||
    color: #3CA8E7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebarwrapper {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0.5em 15px 15px 0;
 | 
			
		||||
    width: 210px;
 | 
			
		||||
    float: right;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar h3, div.sphinxsidebar h4 {
 | 
			
		||||
    margin: 1em 0 0.5em 0;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
    padding: 0.1em 0 0.1em 0.5em;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border: 1px solid #86989B;
 | 
			
		||||
    background-color: #AFC1C4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar h3 a {
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar ul {
 | 
			
		||||
    padding-left: 1.5em;
 | 
			
		||||
    margin-top: 7px;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    line-height: 130%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.sphinxsidebar ul ul {
 | 
			
		||||
    margin-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer {
 | 
			
		||||
    background-color: #E3EFF1;
 | 
			
		||||
    color: #86989B;
 | 
			
		||||
    padding: 3px 8px 3px 0;
 | 
			
		||||
    clear: both;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer a {
 | 
			
		||||
    color: #86989B;
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* -- body styles ----------------------------------------------------------- */
 | 
			
		||||
 | 
			
		||||
p {    
 | 
			
		||||
    margin: 0.8em 0 0.5em 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a {
 | 
			
		||||
    color: #CA7900;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a:hover {
 | 
			
		||||
    color: #2491CF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body a {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0.7em 0 0.3em 0;
 | 
			
		||||
    font-size: 1.5em;
 | 
			
		||||
    color: #11557C;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h2 {
 | 
			
		||||
    margin: 1.3em 0 0.2em 0;
 | 
			
		||||
    font-size: 1.35em;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h3 {
 | 
			
		||||
    margin: 1em 0 -0.3em 0;
 | 
			
		||||
    font-size: 1.2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
 | 
			
		||||
    color: black!important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
 | 
			
		||||
    display: none;
 | 
			
		||||
    margin: 0 0 0 0.3em;
 | 
			
		||||
    padding: 0 0.2em 0 0.2em;
 | 
			
		||||
    color: #aaa!important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
 | 
			
		||||
h5:hover a.anchor, h6:hover a.anchor {
 | 
			
		||||
    display: inline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
 | 
			
		||||
h5 a.anchor:hover, h6 a.anchor:hover {
 | 
			
		||||
    color: #777;
 | 
			
		||||
    background-color: #eee;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.headerlink {
 | 
			
		||||
    color: #c60f0f!important;
 | 
			
		||||
    font-size: 1em;
 | 
			
		||||
    margin-left: 6px;
 | 
			
		||||
    padding: 0 4px 0 4px;
 | 
			
		||||
    text-decoration: none!important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a.headerlink:hover {
 | 
			
		||||
    background-color: #ccc;
 | 
			
		||||
    color: white!important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
cite, code, tt {
 | 
			
		||||
    font-family: 'Consolas', 'Deja Vu Sans Mono',
 | 
			
		||||
                 'Bitstream Vera Sans Mono', monospace;
 | 
			
		||||
    font-size: 0.95em;
 | 
			
		||||
    letter-spacing: 0.01em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tt {
 | 
			
		||||
    background-color: #f2f2f2;
 | 
			
		||||
    border-bottom: 1px solid #ddd;
 | 
			
		||||
    color: #333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tt.descname, tt.descclassname, tt.xref {
 | 
			
		||||
    border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
hr {
 | 
			
		||||
    border: 1px solid #abc;
 | 
			
		||||
    margin: 2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a tt {
 | 
			
		||||
    border: 0;
 | 
			
		||||
    color: #CA7900;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
a tt:hover {
 | 
			
		||||
    color: #2491CF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pre {
 | 
			
		||||
    font-family: 'Consolas', 'Deja Vu Sans Mono',
 | 
			
		||||
                 'Bitstream Vera Sans Mono', monospace;
 | 
			
		||||
    font-size: 0.95em;
 | 
			
		||||
    letter-spacing: 0.015em;
 | 
			
		||||
    line-height: 120%;
 | 
			
		||||
    padding: 0.5em;
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
    background-color: #f8f8f8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pre a {
 | 
			
		||||
    color: inherit;
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td.linenos pre {
 | 
			
		||||
    padding: 0.5em 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.quotebar {
 | 
			
		||||
    background-color: #f8f8f8;
 | 
			
		||||
    max-width: 250px;
 | 
			
		||||
    float: right;
 | 
			
		||||
    padding: 2px 7px;
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.topic {
 | 
			
		||||
    background-color: #f8f8f8;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table {
 | 
			
		||||
    border-collapse: collapse;
 | 
			
		||||
    margin: 0 -0.5em 0 -0.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table td, table th {
 | 
			
		||||
    padding: 0.2em 0.5em 0.2em 0.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition, div.warning {
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
    margin: 1em 0 1em 0;
 | 
			
		||||
    border: 1px solid #86989B;
 | 
			
		||||
    background-color: #f7f7f7;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition p, div.warning p {
 | 
			
		||||
    margin: 0.5em 1em 0.5em 1em;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition pre, div.warning pre {
 | 
			
		||||
    margin: 0.4em 1em 0.4em 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition p.admonition-title,
 | 
			
		||||
div.warning p.admonition-title {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0.1em 0 0.1em 0.5em;
 | 
			
		||||
    color: white;
 | 
			
		||||
    border-bottom: 1px solid #86989B;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    background-color: #AFC1C4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.warning {
 | 
			
		||||
    border: 1px solid #940000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.warning p.admonition-title {
 | 
			
		||||
    background-color: #CF0000;
 | 
			
		||||
    border-bottom-color: #940000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.admonition ul, div.admonition ol,
 | 
			
		||||
div.warning ul, div.warning ol {
 | 
			
		||||
    margin: 0.1em 0.5em 0.5em 3em;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.versioninfo {
 | 
			
		||||
    margin: 1em 0 0 0;
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
    background-color: #DDEAF0;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
    line-height: 1.3em;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.viewcode-back {
 | 
			
		||||
    font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
 | 
			
		||||
                 'Verdana', sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.viewcode-block:target {
 | 
			
		||||
    background-color: #f4debf;
 | 
			
		||||
    border-top: 1px solid #ac9;
 | 
			
		||||
    border-bottom: 1px solid #ac9;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								docs/_templates/defindex.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/_templates/defindex.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
{#
 | 
			
		||||
    basic/defindex.html
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    Default template for the "index" page.
 | 
			
		||||
 | 
			
		||||
    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
    :license: BSD, see LICENSE for details.
 | 
			
		||||
#}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% set title = _('Overview') %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
  <h1>{{ docstitle|e }}</h1>
 | 
			
		||||
  <p>
 | 
			
		||||
    Welcome! This is
 | 
			
		||||
    {% block description %}the documentation for {{ project|e }}
 | 
			
		||||
    {{ release|e }}{% if last_updated %}, last updated {{ last_updated|e }}{% endif %}{% endblock %}.
 | 
			
		||||
  </p>
 | 
			
		||||
  {% block tables %}
 | 
			
		||||
  <p><strong>{{ _('Indices and tables:') }}</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">{{ _('Complete Table of Contents') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('lists all sections and subsections') }}</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">{{ _('Search Page') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('search this documentation') }}</span></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("modindex") }}">{{ _('Global Module Index') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('quick access to all modules') }}</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">{{ _('General Index') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('all functions, classes, terms') }}</span></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
  {% endblock %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										61
									
								
								docs/_templates/indexcontent.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								docs/_templates/indexcontent.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
{% extends "defindex.html" %}
 | 
			
		||||
{% block tables %}
 | 
			
		||||
  <p><strong>Parts of the documentation:</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("whatsnew/" + version) }}">What's new in Python {{ version }}?</a><br/>
 | 
			
		||||
         <span class="linkdescr">or <a href="{{ pathto("whatsnew/index") }}">all "What's new" documents</a> since 2.0</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("tutorial/index") }}">Tutorial</a><br/>
 | 
			
		||||
         <span class="linkdescr">start here</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("library/index") }}">Library Reference</a><br/>
 | 
			
		||||
         <span class="linkdescr">keep this under your pillow</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("reference/index") }}">Language Reference</a><br/>
 | 
			
		||||
         <span class="linkdescr">describes syntax and language elements</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("using/index") }}">Python Setup and Usage</a><br/>
 | 
			
		||||
         <span class="linkdescr">how to use Python on different platforms</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("howto/index") }}">Python HOWTOs</a><br/>
 | 
			
		||||
         <span class="linkdescr">in-depth documents on specific topics</span></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("extending/index") }}">Extending and Embedding</a><br/>
 | 
			
		||||
         <span class="linkdescr">tutorial for C/C++ programmers</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("c-api/index") }}">Python/C API</a><br/>
 | 
			
		||||
         <span class="linkdescr">reference for C/C++ programmers</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("install/index") }}">Installing Python Modules</a><br/>
 | 
			
		||||
         <span class="linkdescr">information for installers & sys-admins</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("distutils/index") }}">Distributing Python Modules</a><br/>
 | 
			
		||||
         <span class="linkdescr">sharing modules with others</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("documenting/index") }}">Documenting Python</a><br/>
 | 
			
		||||
         <span class="linkdescr">guide for documentation authors</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("faq/index") }}">FAQs</a><br/>
 | 
			
		||||
         <span class="linkdescr">frequently asked questions (with answers!)</span></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  <p><strong>Indices and tables:</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("py-modindex") }}">Global Module Index</a><br/>
 | 
			
		||||
         <span class="linkdescr">quick access to all modules</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">General Index</a><br/>
 | 
			
		||||
         <span class="linkdescr">all functions, classes, terms</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("glossary") }}">Glossary</a><br/>
 | 
			
		||||
         <span class="linkdescr">the most important terms explained</span></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">Search page</a><br/>
 | 
			
		||||
         <span class="linkdescr">search this documentation</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br/>
 | 
			
		||||
         <span class="linkdescr">lists all sections and subsections</span></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  <p><strong>Meta information:</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("bugs") }}">Reporting bugs</a></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("about") }}">About the documentation</a></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("license") }}">History and License of Python</a></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("copyright") }}">Copyright</a></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										69
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
{#
 | 
			
		||||
    haiku/layout.html
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    Sphinx layout template for the haiku theme.
 | 
			
		||||
 | 
			
		||||
    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
    :license: BSD, see LICENSE for details.
 | 
			
		||||
#}
 | 
			
		||||
{% extends "basic/layout.html" %}
 | 
			
		||||
{% set script_files = script_files + ['_static/theme_extras.js'] %}
 | 
			
		||||
{% set css_files = css_files + ['_static/print.css'] %}
 | 
			
		||||
 | 
			
		||||
{# do not display relbars #}
 | 
			
		||||
{% block relbar1 %}{% endblock %}
 | 
			
		||||
{% block relbar2 %}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% macro nav() %}
 | 
			
		||||
        <p>
 | 
			
		||||
        {%- block haikurel1 %}
 | 
			
		||||
        {%- endblock %}
 | 
			
		||||
        {%- if prev %}
 | 
			
		||||
        «  <a href="{{ prev.link|e }}">{{ prev.title }}</a>
 | 
			
		||||
          ::  
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
        <a class="uplink" href="{{ pathto(master_doc) }}">{{ _('Contents') }}</a>
 | 
			
		||||
        {%- if next %}
 | 
			
		||||
          ::  
 | 
			
		||||
        <a href="{{ next.link|e }}">{{ next.title }}</a>  »
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
        {%- block haikurel2 %}
 | 
			
		||||
        {%- endblock %}
 | 
			
		||||
        </p>
 | 
			
		||||
{% endmacro %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
      <div class="header">
 | 
			
		||||
        {%- block haikuheader %}
 | 
			
		||||
        {%- if theme_full_logo != "false" %}
 | 
			
		||||
        <a href="{{ pathto('index') }}">
 | 
			
		||||
          <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
 | 
			
		||||
        </a>
 | 
			
		||||
        {%- else %}
 | 
			
		||||
        {%- if logo -%}
 | 
			
		||||
          <img class="rightlogo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
 | 
			
		||||
        {%- endif -%}
 | 
			
		||||
        <h1 class="heading">
 | 
			
		||||
            <a href="{{ pathto('index') }}"><span>{{ project|e }}</span></a>
 | 
			
		||||
        </h1>
 | 
			
		||||
        <h2 class="heading"><span>{{ shorttitle|e }}</span></h2>
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
        {%- endblock %}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="topnav">
 | 
			
		||||
      {{ nav() }}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="content">
 | 
			
		||||
        {#{%- if display_toc %}
 | 
			
		||||
        <div id="toc">
 | 
			
		||||
          <h3>Table Of Contents</h3>
 | 
			
		||||
          {{ toc }}
 | 
			
		||||
        </div>
 | 
			
		||||
        {%- endif %}#}
 | 
			
		||||
        {% block body %}{% endblock %}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="bottomnav">
 | 
			
		||||
      {{ nav() }}
 | 
			
		||||
      </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										8
									
								
								docs/api/basexmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/basexmpp.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
========
 | 
			
		||||
basexmpp
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.basexmpp
 | 
			
		||||
 | 
			
		||||
.. autoclass:: BaseXMPP
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										17
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
==========
 | 
			
		||||
clientxmpp
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.clientxmpp
 | 
			
		||||
 | 
			
		||||
.. autoclass:: ClientXMPP
 | 
			
		||||
    
 | 
			
		||||
    .. automethod:: connect
 | 
			
		||||
 | 
			
		||||
    .. automethod:: register_feature
 | 
			
		||||
 | 
			
		||||
    .. automethod:: get_roster
 | 
			
		||||
 | 
			
		||||
    .. automethod:: update_roster
 | 
			
		||||
 | 
			
		||||
    .. automethod:: del_roster_item
 | 
			
		||||
							
								
								
									
										8
									
								
								docs/api/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
=========
 | 
			
		||||
xmlstream
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.xmlstream
 | 
			
		||||
 | 
			
		||||
.. autoclass:: XMLStream
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										269
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,269 @@
 | 
			
		||||
.. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP
 | 
			
		||||
 | 
			
		||||
SleekXMPP Architecture
 | 
			
		||||
======================
 | 
			
		||||
 | 
			
		||||
The core of SleekXMPP is contained in four classes: ``XMLStream``,
 | 
			
		||||
``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this
 | 
			
		||||
stack is a library for working with XML objects that eliminates most
 | 
			
		||||
of the tedium of creating/manipulating XML.
 | 
			
		||||
 | 
			
		||||
.. image:: _static/images/arch_layers.png
 | 
			
		||||
    :height: 300px
 | 
			
		||||
    :align: center
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. index:: XMLStream
 | 
			
		||||
 | 
			
		||||
The Foundation: XMLStream
 | 
			
		||||
-------------------------
 | 
			
		||||
``XMLStream`` is a mostly XMPP-agnostic class whose purpose is to read
 | 
			
		||||
and write from a bi-directional XML stream. It also allows for callback
 | 
			
		||||
functions to execute when XML matching given patterns is received; these
 | 
			
		||||
callbacks are also referred to as :term:`stream handlers <stream handler>`.
 | 
			
		||||
The class also provides a basic eventing system which can be triggered
 | 
			
		||||
either manually or on a timed schedule.
 | 
			
		||||
 | 
			
		||||
The Main Threads
 | 
			
		||||
~~~~~~~~~~~~~~~~
 | 
			
		||||
``XMLStream`` instances run using at least three background threads: the
 | 
			
		||||
send thread, the read thread, and the scheduler thread. The send thread is
 | 
			
		||||
in charge of monitoring the send queue and writing text to the outgoing
 | 
			
		||||
XML stream. The read thread pulls text off of the incoming XML stream and
 | 
			
		||||
stores the results in an event queue. The scheduler thread is used to emit
 | 
			
		||||
events after a given period of time.
 | 
			
		||||
 | 
			
		||||
Additionally, the main event processing loop may be executed in its
 | 
			
		||||
own thread if SleekXMPP is being used in the background for another
 | 
			
		||||
application.
 | 
			
		||||
 | 
			
		||||
Short-lived threads may also be spawned as requested for threaded
 | 
			
		||||
:term:`event handlers <event handler>`.
 | 
			
		||||
 | 
			
		||||
How XML Text is Turned into Action
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
To demonstrate the flow of information, let's consider what happens
 | 
			
		||||
when this bit of XML is received (with an assumed namespace of
 | 
			
		||||
``jabber:client``):
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <message to="user@example.com" from="friend@example.net">
 | 
			
		||||
      <body>Hej!</body>
 | 
			
		||||
    </message>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
1. **Convert XML strings into objects.**
 | 
			
		||||
 | 
			
		||||
   Incoming text is parsed and converted into XML objects (using
 | 
			
		||||
   ElementTree) which are then wrapped into what are referred to as
 | 
			
		||||
   :term:`Stanza objects <stanza object>`. The appropriate class for the
 | 
			
		||||
   new object is determined using a map of namespaced element names to
 | 
			
		||||
   classes.
 | 
			
		||||
 | 
			
		||||
   Our incoming XML is thus turned into a ``Message`` :term:`stanza object`
 | 
			
		||||
   because the namespaced element name ``{jabber:client}message`` is
 | 
			
		||||
   associated with the class ``sleekxmpp.stanza.Message``.
 | 
			
		||||
 | 
			
		||||
2. **Match stanza objects to callbacks.**
 | 
			
		||||
 | 
			
		||||
   These objects are then compared against the stored patterns associated
 | 
			
		||||
   with the registered callback handlers. For each match, a copy of the
 | 
			
		||||
   :term:`stanza object` is paired with a reference to the handler and
 | 
			
		||||
   placed into the event queue.
 | 
			
		||||
 | 
			
		||||
   Our ``Message`` object is thus paired with the message stanza handler
 | 
			
		||||
   ``BaseXMPP._handle_message`` to create the tuple::
 | 
			
		||||
 | 
			
		||||
       ('stanza', stanza_obj, handler)
 | 
			
		||||
 | 
			
		||||
3. **Process the event queue.**
 | 
			
		||||
 | 
			
		||||
   The event queue is the heart of SleekXMPP. Nearly every action that
 | 
			
		||||
   takes place is first inserted into this queue, whether that be received
 | 
			
		||||
   stanzas, custom events, or scheduled events.
 | 
			
		||||
 | 
			
		||||
   When the stanza is pulled out of the event queue with an associated
 | 
			
		||||
   callback, the callback function is executed with the stanza as its only
 | 
			
		||||
   parameter.
 | 
			
		||||
 | 
			
		||||
   .. warning:: 
 | 
			
		||||
       The callback, aka :term:`stream handler`, is executed in the main
 | 
			
		||||
       processing thread. If the handler blocks, event processing will also
 | 
			
		||||
       block.
 | 
			
		||||
 | 
			
		||||
4. **Raise Custom Events**
 | 
			
		||||
 | 
			
		||||
   Since a :term:`stream handler` shouldn't block, if extensive processing
 | 
			
		||||
   for a stanza is required (such as needing to send and receive an
 | 
			
		||||
   ``Iq`` stanza), then custom events must be used. These events are not
 | 
			
		||||
   explicitly tied to the incoming XML stream and may be raised at any
 | 
			
		||||
   time. Importantly, these events may be handled in their own thread.
 | 
			
		||||
 | 
			
		||||
   When the event is raised, a copy of the stanza is created for each
 | 
			
		||||
   handler registered for the event. In contrast to :term:`stream handlers <stream handler>`, 
 | 
			
		||||
   these functions are referred to as :term:`event handlers <event handler>`. 
 | 
			
		||||
   Each stanza/handler pair is then put into the event queue.
 | 
			
		||||
 | 
			
		||||
   .. note::
 | 
			
		||||
       It is possible to skip the event queue and process an event immediately
 | 
			
		||||
       by using ``direct=True`` when raising the event.
 | 
			
		||||
 | 
			
		||||
   The code for ``BaseXMPP._handle_message`` follows this pattern, and
 | 
			
		||||
   raises a ``'message'`` event::
 | 
			
		||||
 | 
			
		||||
       self.event('message', msg)
 | 
			
		||||
 | 
			
		||||
   The event call then places the message object back into the event queue
 | 
			
		||||
   paired with an :term:`event handler`::
 | 
			
		||||
 | 
			
		||||
       ('event', 'message', msg_copy1, custom_event_handler_1)
 | 
			
		||||
       ('event', 'message', msg_copy2, custom_evetn_handler_2) 
 | 
			
		||||
 | 
			
		||||
5. **Process Custom Events**
 | 
			
		||||
 | 
			
		||||
   The stanza and :term:`event handler` are then pulled from the event
 | 
			
		||||
   queue, and the handler is executed, passing the stanza as its only
 | 
			
		||||
   argument. If the handler was registered as threaded, then a new thread
 | 
			
		||||
   will be spawned for it.
 | 
			
		||||
 | 
			
		||||
   .. note::
 | 
			
		||||
       Events may be raised without needing :term:`stanza objects <stanza object>`. 
 | 
			
		||||
       For example, you could use ``self.event('custom', {'a': 'b'})``. 
 | 
			
		||||
       You don't even need any arguments: ``self.event('no_parameters')``. 
 | 
			
		||||
       However, every event handler MUST accept at least one argument.
 | 
			
		||||
 | 
			
		||||
   Finally, after a long trek, our message is handed off to the user's
 | 
			
		||||
   custom handler in order to do awesome stuff::
 | 
			
		||||
 | 
			
		||||
       msg.reply()
 | 
			
		||||
       msg['body'] = "Hey! This is awesome!"
 | 
			
		||||
       msg.send()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. index:: BaseXMPP, XMLStream
 | 
			
		||||
 | 
			
		||||
Raising XMPP Awareness: BaseXMPP
 | 
			
		||||
--------------------------------
 | 
			
		||||
While ``XMLStream`` attempts to shy away from anything too XMPP specific,
 | 
			
		||||
``BaseXMPP``'s sole purpose is to provide foundational support for sending
 | 
			
		||||
and receiving XMPP stanzas. This support includes registering the basic
 | 
			
		||||
message, presence, and iq stanzas, methods for creating and sending
 | 
			
		||||
stanzas, and default handlers for incoming messages and keeping track of
 | 
			
		||||
presence notifications.
 | 
			
		||||
 | 
			
		||||
The plugin system for adding new XEP support is also maintained by
 | 
			
		||||
``BaseXMPP``.
 | 
			
		||||
 | 
			
		||||
.. index:: ClientXMPP, BaseXMPP
 | 
			
		||||
 | 
			
		||||
ClientXMPP
 | 
			
		||||
----------
 | 
			
		||||
``ClientXMPP`` extends ``BaseXMPP`` with additional logic for connecting to
 | 
			
		||||
an XMPP server by performing DNS lookups. It also adds support for stream
 | 
			
		||||
features such as STARTTLS and SASL.
 | 
			
		||||
 | 
			
		||||
.. index:: ComponentXMPP, BaseXMPP
 | 
			
		||||
 | 
			
		||||
ComponentXMPP
 | 
			
		||||
-------------
 | 
			
		||||
``ComponentXMPP`` is only a thin layer on top of ``BaseXMPP`` that
 | 
			
		||||
implements the component handshake protocol.
 | 
			
		||||
 | 
			
		||||
.. index::
 | 
			
		||||
    double: object; stanza
 | 
			
		||||
 | 
			
		||||
Stanza Objects: A Brief Look
 | 
			
		||||
----------------------------
 | 
			
		||||
.. seealso::
 | 
			
		||||
    See :ref:`api-stanza-objects` for a more detailed overview.
 | 
			
		||||
 | 
			
		||||
Almost worthy of their own standalone library, :term:`stanza objects <stanza object>`
 | 
			
		||||
are wrappers for XML objects which expose dictionary like interfaces
 | 
			
		||||
for manipulating their XML content. For example, consider the XML:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <message />
 | 
			
		||||
 | 
			
		||||
A very plain element to start with, but we can create a :term:`stanza object`
 | 
			
		||||
using ``sleekxmpp.stanza.Message`` as so::
 | 
			
		||||
 | 
			
		||||
    msg = Message(xml=ET.fromstring("<message />"))
 | 
			
		||||
 | 
			
		||||
The ``Message`` stanza class defines interfaces such as ``'body'`` and
 | 
			
		||||
``'to'``, so we can assign values to those interfaces to include new XML
 | 
			
		||||
content::
 | 
			
		||||
 | 
			
		||||
    msg['body'] = "Following so far?"
 | 
			
		||||
    msg['to'] = 'user@example.com'
 | 
			
		||||
 | 
			
		||||
Dumping the XML content of ``msg`` (using ``msg.xml``), we find:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <message to="user@example.com">
 | 
			
		||||
      <body>Following so far?</body>
 | 
			
		||||
    </message>
 | 
			
		||||
 | 
			
		||||
The process is similar for reading from interfaces and deleting interface
 | 
			
		||||
contents. A :term:`stanza object` behaves very similarly to a regular
 | 
			
		||||
``dict`` object: you may assign to keys, read from keys, and ``del`` keys.
 | 
			
		||||
 | 
			
		||||
Stanza interfaces come with built-in behaviours such as adding/removing
 | 
			
		||||
attribute and sub element values. However, a lot of the time more custom
 | 
			
		||||
logic is needed. This can be provided by defining methods of the form
 | 
			
		||||
``get_*``, ``set_*``, and ``del_*`` for any interface which requires custom
 | 
			
		||||
behaviour.
 | 
			
		||||
 | 
			
		||||
Stanza Plugins
 | 
			
		||||
~~~~~~~~~~~~~~
 | 
			
		||||
Since it is generally possible to embed one XML element inside another,
 | 
			
		||||
:term:`stanza objects <stanza object>` may be nested. Nested 
 | 
			
		||||
:term:`stanza objects <stanza object>` are referred to as :term:`stanza plugins <stanza plugin>`
 | 
			
		||||
or :term:`substanzas <substanza>`.
 | 
			
		||||
 | 
			
		||||
A :term:`stanza plugin` exposes its own interfaces by adding a new
 | 
			
		||||
interface to its parent stanza. To demonstrate, consider these two stanza
 | 
			
		||||
class definitions using ``sleekxmpp.xmlstream.ElementBase``:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    class Parent(ElementBase):
 | 
			
		||||
        name = "the-parent-xml-element-name"
 | 
			
		||||
        namespace = "the-parent-namespace"
 | 
			
		||||
        interfaces = set(('foo', 'bar'))
 | 
			
		||||
 | 
			
		||||
    class Child(ElementBase):
 | 
			
		||||
        name = "the-child-xml-element-name"
 | 
			
		||||
        namespace = "the-child-namespace"
 | 
			
		||||
        plugin_attrib = 'child'
 | 
			
		||||
        interfaces = set(('baz',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
If we register the ``Child`` stanza as a plugin of the ``Parent`` stanza as
 | 
			
		||||
so, using ``sleekxmpp.xmlstream.register_stanza_plugin``::
 | 
			
		||||
 | 
			
		||||
    register_stanza_plugin(Parent, Child)
 | 
			
		||||
 | 
			
		||||
Then we can access content in the child stanza through the parent.
 | 
			
		||||
Note that the interface used to access the child stanza is the same as
 | 
			
		||||
``Child.plugin_attrib``::
 | 
			
		||||
 | 
			
		||||
    parent = Parent()
 | 
			
		||||
    parent['foo'] = 'a'
 | 
			
		||||
    parent['child']['baz'] = 'b'
 | 
			
		||||
 | 
			
		||||
The above code would produce:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <the-parent-xml-element xmlns="the-parent-namespace" foo="a">
 | 
			
		||||
      <the-child-xml-element xmlsn="the-child-namespace" baz="b" />
 | 
			
		||||
    </the-parent-xml-element>
 | 
			
		||||
 | 
			
		||||
It is also possible to allow a :term:`substanza` to appear multiple times
 | 
			
		||||
by using ``iterable=True`` in the ``register_stanza_plugin`` call. All
 | 
			
		||||
iterable :term:`substanzas <substanza>` can be accessed using a standard
 | 
			
		||||
``substanzas`` interface.
 | 
			
		||||
							
								
								
									
										220
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,220 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# SleekXMPP documentation build configuration file, created by
 | 
			
		||||
# sphinx-quickstart on Tue Aug  9 22:27:06 2011.
 | 
			
		||||
#
 | 
			
		||||
# This file is execfile()d with the current directory set to its containing dir.
 | 
			
		||||
#
 | 
			
		||||
# Note that not all possible configuration values are present in this
 | 
			
		||||
# autogenerated file.
 | 
			
		||||
#
 | 
			
		||||
# All configuration values have a default; values that are commented out
 | 
			
		||||
# serve to show the default.
 | 
			
		||||
 | 
			
		||||
import sys, os
 | 
			
		||||
 | 
			
		||||
# If extensions (or modules to document with autodoc) are in another directory,
 | 
			
		||||
# add these directories to sys.path here. If the directory is relative to the
 | 
			
		||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
 | 
			
		||||
#sys.path.insert(0, os.path.abspath('.'))
 | 
			
		||||
 | 
			
		||||
# -- General configuration -----------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# If your documentation needs a minimal Sphinx version, state it here.
 | 
			
		||||
#needs_sphinx = '1.0'
 | 
			
		||||
 | 
			
		||||
# Add any Sphinx extension module names here, as strings. They can be extensions
 | 
			
		||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 | 
			
		||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain templates here, relative to this directory.
 | 
			
		||||
templates_path = ['_templates']
 | 
			
		||||
 | 
			
		||||
# The suffix of source filenames.
 | 
			
		||||
source_suffix = '.rst'
 | 
			
		||||
 | 
			
		||||
# The encoding of source files.
 | 
			
		||||
#source_encoding = 'utf-8-sig'
 | 
			
		||||
 | 
			
		||||
# The master toctree document.
 | 
			
		||||
master_doc = 'index'
 | 
			
		||||
 | 
			
		||||
# General information about the project.
 | 
			
		||||
project = u'SleekXMPP'
 | 
			
		||||
copyright = u'2011, Nathan Fritz, Lance Stout'
 | 
			
		||||
 | 
			
		||||
# The version info for the project you're documenting, acts as replacement for
 | 
			
		||||
# |version| and |release|, also used in various other places throughout the
 | 
			
		||||
# built documents.
 | 
			
		||||
#
 | 
			
		||||
# The short X.Y version.
 | 
			
		||||
version = '1.0'
 | 
			
		||||
# The full version, including alpha/beta/rc tags.
 | 
			
		||||
release = '1.0RC1'
 | 
			
		||||
 | 
			
		||||
# The language for content autogenerated by Sphinx. Refer to documentation
 | 
			
		||||
# for a list of supported languages.
 | 
			
		||||
#language = None
 | 
			
		||||
 | 
			
		||||
# There are two options for replacing |today|: either, you set today to some
 | 
			
		||||
# non-false value, then it is used:
 | 
			
		||||
#today = ''
 | 
			
		||||
# Else, today_fmt is used as the format for a strftime call.
 | 
			
		||||
#today_fmt = '%B %d, %Y'
 | 
			
		||||
 | 
			
		||||
# List of patterns, relative to source directory, that match files and
 | 
			
		||||
# directories to ignore when looking for source files.
 | 
			
		||||
exclude_patterns = ['_build']
 | 
			
		||||
 | 
			
		||||
# The reST default role (used for this markup: `text`) to use for all documents.
 | 
			
		||||
#default_role = None
 | 
			
		||||
 | 
			
		||||
# If true, '()' will be appended to :func: etc. cross-reference text.
 | 
			
		||||
#add_function_parentheses = True
 | 
			
		||||
 | 
			
		||||
# If true, the current module name will be prepended to all description
 | 
			
		||||
# unit titles (such as .. function::).
 | 
			
		||||
#add_module_names = True
 | 
			
		||||
 | 
			
		||||
# If true, sectionauthor and moduleauthor directives will be shown in the
 | 
			
		||||
# output. They are ignored by default.
 | 
			
		||||
#show_authors = False
 | 
			
		||||
 | 
			
		||||
# The name of the Pygments (syntax highlighting) style to use.
 | 
			
		||||
pygments_style = 'default'
 | 
			
		||||
 | 
			
		||||
# A list of ignored prefixes for module index sorting.
 | 
			
		||||
#modindex_common_prefix = []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for HTML output ---------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# The theme to use for HTML and HTML Help pages.  See the documentation for
 | 
			
		||||
# a list of builtin themes.
 | 
			
		||||
html_theme = 'haiku'
 | 
			
		||||
 | 
			
		||||
# Theme options are theme-specific and customize the look and feel of a theme
 | 
			
		||||
# further.  For a list of options available for each theme, see the
 | 
			
		||||
# documentation.
 | 
			
		||||
#html_theme_options = {'headingcolor': '#CFCFCF', 'linkcolor': '#4A7389'}
 | 
			
		||||
 | 
			
		||||
# 00ADEE
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain custom themes here, relative to this directory.
 | 
			
		||||
#html_theme_path = []
 | 
			
		||||
 | 
			
		||||
# The name for this set of Sphinx documents.  If None, it defaults to
 | 
			
		||||
# "<project> v<release> documentation".
 | 
			
		||||
html_title = 'SleekXMPP'
 | 
			
		||||
 | 
			
		||||
# A shorter title for the navigation bar.  Default is the same as html_title.
 | 
			
		||||
html_short_title = '%s Documentation' % release
 | 
			
		||||
 | 
			
		||||
# The name of an image file (relative to this directory) to place at the top
 | 
			
		||||
# of the sidebar.
 | 
			
		||||
#html_logo = None
 | 
			
		||||
 | 
			
		||||
# The name of an image file (within the static path) to use as favicon of the
 | 
			
		||||
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 | 
			
		||||
# pixels large.
 | 
			
		||||
#html_favicon = None
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain custom static files (such as style sheets) here,
 | 
			
		||||
# relative to this directory. They are copied after the builtin static files,
 | 
			
		||||
# so a file named "default.css" will overwrite the builtin "default.css".
 | 
			
		||||
html_static_path = ['_static']
 | 
			
		||||
 | 
			
		||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 | 
			
		||||
# using the given strftime format.
 | 
			
		||||
#html_last_updated_fmt = '%b %d, %Y'
 | 
			
		||||
 | 
			
		||||
# If true, SmartyPants will be used to convert quotes and dashes to
 | 
			
		||||
# typographically correct entities.
 | 
			
		||||
#html_use_smartypants = True
 | 
			
		||||
 | 
			
		||||
# Custom sidebar templates, maps document names to template names.
 | 
			
		||||
#html_sidebars = {}
 | 
			
		||||
 | 
			
		||||
# Additional templates that should be rendered to pages, maps page names to
 | 
			
		||||
# template names.
 | 
			
		||||
html_additional_pages = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# If false, no module index is generated.
 | 
			
		||||
#html_domain_indices = True
 | 
			
		||||
 | 
			
		||||
# If false, no index is generated.
 | 
			
		||||
#html_use_index = True
 | 
			
		||||
 | 
			
		||||
# If true, the index is split into individual pages for each letter.
 | 
			
		||||
#html_split_index = False
 | 
			
		||||
 | 
			
		||||
# If true, links to the reST sources are added to the pages.
 | 
			
		||||
#html_show_sourcelink = True
 | 
			
		||||
 | 
			
		||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
 | 
			
		||||
#html_show_sphinx = True
 | 
			
		||||
 | 
			
		||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
 | 
			
		||||
#html_show_copyright = True
 | 
			
		||||
 | 
			
		||||
# If true, an OpenSearch description file will be output, and all pages will
 | 
			
		||||
# contain a <link> tag referring to it.  The value of this option must be the
 | 
			
		||||
# base URL from which the finished HTML is served.
 | 
			
		||||
#html_use_opensearch = ''
 | 
			
		||||
 | 
			
		||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
 | 
			
		||||
#html_file_suffix = None
 | 
			
		||||
 | 
			
		||||
# Output file base name for HTML help builder.
 | 
			
		||||
htmlhelp_basename = 'SleekXMPPdoc'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for LaTeX output --------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# The paper size ('letter' or 'a4').
 | 
			
		||||
#latex_paper_size = 'letter'
 | 
			
		||||
 | 
			
		||||
# The font size ('10pt', '11pt' or '12pt').
 | 
			
		||||
#latex_font_size = '10pt'
 | 
			
		||||
 | 
			
		||||
# Grouping the document tree into LaTeX files. List of tuples
 | 
			
		||||
# (source start file, target name, title, author, documentclass [howto/manual]).
 | 
			
		||||
latex_documents = [
 | 
			
		||||
  ('index', 'SleekXMPP.tex', u'SleekXMPP Documentation',
 | 
			
		||||
   u'Nathan Fritz, Lance Stout', 'manual'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# The name of an image file (relative to this directory) to place at the top of
 | 
			
		||||
# the title page.
 | 
			
		||||
#latex_logo = None
 | 
			
		||||
 | 
			
		||||
# For "manual" documents, if this is true, then toplevel headings are parts,
 | 
			
		||||
# not chapters.
 | 
			
		||||
#latex_use_parts = False
 | 
			
		||||
 | 
			
		||||
# If true, show page references after internal links.
 | 
			
		||||
#latex_show_pagerefs = False
 | 
			
		||||
 | 
			
		||||
# If true, show URL addresses after external links.
 | 
			
		||||
#latex_show_urls = False
 | 
			
		||||
 | 
			
		||||
# Additional stuff for the LaTeX preamble.
 | 
			
		||||
#latex_preamble = ''
 | 
			
		||||
 | 
			
		||||
# Documents to append as an appendix to all manuals.
 | 
			
		||||
#latex_appendices = []
 | 
			
		||||
 | 
			
		||||
# If false, no module index is generated.
 | 
			
		||||
#latex_domain_indices = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -- Options for manual page output --------------------------------------------
 | 
			
		||||
 | 
			
		||||
# One entry per manual page. List of tuples
 | 
			
		||||
# (source start file, name, description, authors, manual section).
 | 
			
		||||
man_pages = [
 | 
			
		||||
    ('index', 'sleekxmpp', u'SleekXMPP Documentation',
 | 
			
		||||
     [u'Nathan Fritz, Lance Stout'], 1)
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										677
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										677
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,677 @@
 | 
			
		||||
Creating a SleekXMPP Plugin
 | 
			
		||||
===========================
 | 
			
		||||
 | 
			
		||||
One of the goals of SleekXMPP is to provide support for every draft or final
 | 
			
		||||
XMPP extension (`XEP <http://xmpp.org/extensions/>`_). To do this, SleekXMPP has a
 | 
			
		||||
plugin mechanism for adding the functionalities required by each XEP. But even
 | 
			
		||||
though plugins were made to quickly implement and prototype the official XMPP
 | 
			
		||||
extensions, there is no reason you can't create your own plugin to implement
 | 
			
		||||
your own custom XMPP-based protocol.
 | 
			
		||||
 | 
			
		||||
This guide will help walk you through the steps to
 | 
			
		||||
implement a rudimentary version of `XEP-0077 In-band
 | 
			
		||||
Registration <http://xmpp.org/extensions/xep-0077.html>`_. In-band registration
 | 
			
		||||
was implemented in example 14-6 (page 223) of `XMPP: The Definitive
 | 
			
		||||
Guide <http://oreilly.com/catalog/9780596521271>`_ because there was no SleekXMPP
 | 
			
		||||
plugin for XEP-0077 at the time of writing. We will partially fix that issue
 | 
			
		||||
here by turning the example implementation from *XMPP: The Definitive Guide*
 | 
			
		||||
into a plugin. Again, note that this will not a complete implementation, and a
 | 
			
		||||
different, more robust, official plugin for XEP-0077 may be added to SleekXMPP
 | 
			
		||||
in the future.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    The example plugin created in this guide is for the server side of the
 | 
			
		||||
    registration process only. It will **NOT** be able to register new accounts
 | 
			
		||||
    on an XMPP server.
 | 
			
		||||
 | 
			
		||||
First Steps
 | 
			
		||||
-----------
 | 
			
		||||
Every plugin inherits from the class :mod:`base_plugin <sleekxmpp.plugins.base.base_plugin>`,
 | 
			
		||||
and must include a ``plugin_init`` method. While the
 | 
			
		||||
plugins distributed with SleekXMPP must be placed in the plugins directory
 | 
			
		||||
``sleekxmpp/plugins`` to be loaded, custom plugins may be loaded from any
 | 
			
		||||
module. To do so, use the following form when registering the plugin:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    self.register_plugin('myplugin', module=mod_containing_my_plugin)
 | 
			
		||||
 | 
			
		||||
The plugin name must be the same as the plugin's class name.
 | 
			
		||||
 
 | 
			
		||||
Now, we can open our favorite text editors and create ``xep_0077.py`` in
 | 
			
		||||
``SleekXMPP/sleekxmpp/plugins``. We want to do some basic house-keeping and
 | 
			
		||||
declare the name and description of the XEP we are implementing. If you
 | 
			
		||||
are creating your own custom plugin, you don't need to include the ``xep``
 | 
			
		||||
attribute.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Creating a SleekXMPP Plugin
 | 
			
		||||
 | 
			
		||||
    This is a minimal implementation of XEP-0077 to serve
 | 
			
		||||
    as a tutorial for creating SleekXMPP plugins.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
 | 
			
		||||
    class xep_0077(base_plugin):
 | 
			
		||||
        """
 | 
			
		||||
        XEP-0077 In-Band Registration
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        def plugin_init(self):
 | 
			
		||||
            self.description = "In-Band Registration"
 | 
			
		||||
            self.xep = "0077"
 | 
			
		||||
 | 
			
		||||
Now that we have a basic plugin, we need to edit
 | 
			
		||||
``sleekxmpp/plugins/__init__.py`` to include our new plugin by adding
 | 
			
		||||
``'xep_0077'`` to the ``__all__`` declaration.
 | 
			
		||||
 | 
			
		||||
Interacting with Other Plugins
 | 
			
		||||
------------------------------
 | 
			
		||||
 | 
			
		||||
In-band registration is a feature that should be advertised through `Service
 | 
			
		||||
Discovery <http://xmpp.org/extensions/xep-0030.html>`_. To do that, we tell the
 | 
			
		||||
``xep_0030`` plugin to add the ``"jabber:iq:register"`` feature. We put this
 | 
			
		||||
call in a method named ``post_init`` which will be called once the plugin has
 | 
			
		||||
been loaded; by doing so we advertise that we can do registrations only after we
 | 
			
		||||
finish activating the plugin.
 | 
			
		||||
 | 
			
		||||
The ``post_init`` method needs to call ``base_plugin.post_init(self)``
 | 
			
		||||
which will mark that ``post_init`` has been called for the plugin. Once the
 | 
			
		||||
SleekXMPP object begins processing, ``post_init`` will be called on any plugins
 | 
			
		||||
that have not already run ``post_init``. This allows you to register plugins and
 | 
			
		||||
their dependencies without needing to worry about the order in which you do so.
 | 
			
		||||
 | 
			
		||||
**Note:** by adding this call we have introduced a dependency on the XEP-0030
 | 
			
		||||
plugin. Be sure to register ``'xep_0030'`` as well as ``'xep_0077'``. SleekXMPP
 | 
			
		||||
does not automatically load plugin dependencies for you.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature("jabber:iq:register")
 | 
			
		||||
 | 
			
		||||
Creating Custom Stanza Objects
 | 
			
		||||
------------------------------
 | 
			
		||||
 | 
			
		||||
Now, the IQ stanzas needed to implement our version of XEP-0077 are not very
 | 
			
		||||
complex, and we could just interact with the XML objects directly just like
 | 
			
		||||
in the *XMPP: The Definitive Guide* example. However, creating custom stanza
 | 
			
		||||
objects is good practice.
 | 
			
		||||
 | 
			
		||||
We will create a new ``Registration`` stanza. Following the *XMPP: The
 | 
			
		||||
Definitive Guide* example, we will add support for a username and password
 | 
			
		||||
field. We also need two flags: ``registered`` and ``remove``. The ``registered``
 | 
			
		||||
flag is sent when an already registered user attempts to register, along with
 | 
			
		||||
their registration data. The ``remove`` flag is a request to unregister a user's
 | 
			
		||||
account.
 | 
			
		||||
 | 
			
		||||
Adding additional `fields specified in
 | 
			
		||||
XEP-0077 <http://xmpp.org/extensions/xep-0077.html#registrar-formtypes-register>`_
 | 
			
		||||
will not be difficult and is left as an exercise for the reader.
 | 
			
		||||
 | 
			
		||||
Our ``Registration`` class needs to start with a few descriptions of its
 | 
			
		||||
behaviour:
 | 
			
		||||
 | 
			
		||||
* ``namespace``
 | 
			
		||||
    The namespace our stanza object lives in. In this case,
 | 
			
		||||
    ``"jabber:iq:register"``.
 | 
			
		||||
 | 
			
		||||
* ``name``
 | 
			
		||||
    The name of the root XML element. In this case, the ``query`` element.
 | 
			
		||||
 | 
			
		||||
* ``plugin_attrib``
 | 
			
		||||
    The name to access this type of stanza. In particular, given a
 | 
			
		||||
    registration stanza, the ``Registration`` object can be found using:
 | 
			
		||||
    ``iq_object['register']``.
 | 
			
		||||
 | 
			
		||||
* ``interfaces``
 | 
			
		||||
    A list of dictionary-like keys that can be used with the stanza object.
 | 
			
		||||
    When using ``"key"``, if there exists a method of the form ``getKey``,
 | 
			
		||||
    ``setKey``, or``delKey`` (depending on context) then the result of calling
 | 
			
		||||
    that method will be returned. Otherwise, the value of the attribute ``key``
 | 
			
		||||
    of the main stanza element is returned if one exists.
 | 
			
		||||
 | 
			
		||||
    **Note:** The accessor methods currently use title case, and not camel case.
 | 
			
		||||
    Thus if you need to access an item named ``"methodName"`` you will need to
 | 
			
		||||
    use ``getMethodname``. This naming convention might change to full camel
 | 
			
		||||
    case in a future version of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
* ``sub_interfaces``
 | 
			
		||||
    A subset of ``interfaces``, but these keys map to the text of any
 | 
			
		||||
    subelements that are direct children of the main stanza element. Thus,
 | 
			
		||||
    referencing ``iq_object['register']['username']`` will either execute
 | 
			
		||||
    ``getUsername`` or return the value in the ``username`` element of the
 | 
			
		||||
    query.
 | 
			
		||||
 | 
			
		||||
    If you need to access an element, say ``elem``, that is not a direct child
 | 
			
		||||
    of the main stanza element, you will need to add ``getElem``, ``setElem``,
 | 
			
		||||
    and ``delElem``. See the note above about naming conventions.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
 | 
			
		||||
    from sleekxmpp import Iq
 | 
			
		||||
 | 
			
		||||
    class Registration(ElementBase):
 | 
			
		||||
        namespace = 'jabber:iq:register'
 | 
			
		||||
        name = 'query'
 | 
			
		||||
        plugin_attrib = 'register'
 | 
			
		||||
        interfaces = set(('username', 'password', 'registered', 'remove'))
 | 
			
		||||
        sub_interfaces = interfaces
 | 
			
		||||
 | 
			
		||||
        def getRegistered(self):
 | 
			
		||||
            present = self.xml.find('{%s}registered' % self.namespace)
 | 
			
		||||
            return present is not None
 | 
			
		||||
 | 
			
		||||
        def getRemove(self):
 | 
			
		||||
            present = self.xml.find('{%s}remove' % self.namespace)
 | 
			
		||||
            return present is not None
 | 
			
		||||
 | 
			
		||||
        def setRegistered(self, registered):
 | 
			
		||||
            if registered:
 | 
			
		||||
                self.addField('registered')
 | 
			
		||||
            else:
 | 
			
		||||
                del self['registered']
 | 
			
		||||
 | 
			
		||||
        def setRemove(self, remove):
 | 
			
		||||
            if remove:
 | 
			
		||||
                self.addField('remove')
 | 
			
		||||
            else:
 | 
			
		||||
                del self['remove']
 | 
			
		||||
 | 
			
		||||
        def addField(self, name):
 | 
			
		||||
            itemXML = ET.Element('{%s}%s' % (self.namespace, name))
 | 
			
		||||
            self.xml.append(itemXML)
 | 
			
		||||
 | 
			
		||||
Setting a ``sub_interface`` attribute to ``""`` will remove that subelement.
 | 
			
		||||
Since we want to include empty registration fields in our form, we need the
 | 
			
		||||
``addField`` method to add the empty elements.
 | 
			
		||||
 | 
			
		||||
Since the ``registered`` and ``remove`` elements are just flags, we need to add
 | 
			
		||||
custom logic to enforce the binary behavior.
 | 
			
		||||
 | 
			
		||||
Extracting Stanzas from the XML Stream
 | 
			
		||||
--------------------------------------
 | 
			
		||||
 | 
			
		||||
Now that we have a custom stanza object, we need to be able to detect when we
 | 
			
		||||
receive one. To do this, we register a stream handler that will pattern match
 | 
			
		||||
stanzas off of the XML stream against our stanza object's element name and
 | 
			
		||||
namespace. To do so, we need to create a ``Callback`` object which contains
 | 
			
		||||
an XML fragment that can identify our stanza type. We can add this handler
 | 
			
		||||
registration to our ``plugin_init`` method.
 | 
			
		||||
 | 
			
		||||
Also, we need to associate our ``Registration`` class with IQ stanzas;
 | 
			
		||||
that requires the use of the ``register_stanza_plugin`` function (in
 | 
			
		||||
``sleekxmpp.xmlstream.stanzabase``) which takes the class of a parent stanza
 | 
			
		||||
type followed by the substanza type. In our case, the parent stanza is an IQ
 | 
			
		||||
stanza, and the substanza is our registration query.
 | 
			
		||||
 | 
			
		||||
The ``__handleRegistration`` method referenced in the callback will be our
 | 
			
		||||
handler function to process registration requests.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.description = "In-Band Registration"
 | 
			
		||||
        self.xep = "0077"
 | 
			
		||||
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
          Callback('In-Band Registration',
 | 
			
		||||
            MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
 | 
			
		||||
            self.__handleRegistration))
 | 
			
		||||
        register_stanza_plugin(Iq, Registration)
 | 
			
		||||
 | 
			
		||||
Handling Incoming Stanzas and Triggering Events
 | 
			
		||||
-----------------------------------------------
 | 
			
		||||
There are six situations that we need to handle to finish our implementation of
 | 
			
		||||
XEP-0077.
 | 
			
		||||
 | 
			
		||||
**Registration Form Request from a New User:**
 | 
			
		||||
 | 
			
		||||
    .. code-block:: xml
 | 
			
		||||
 | 
			
		||||
        <iq type="result">
 | 
			
		||||
         <query xmlns="jabber:iq:register">
 | 
			
		||||
          <username />
 | 
			
		||||
          <password />
 | 
			
		||||
         </query>
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
**Registration Form Request from an Existing User:**
 | 
			
		||||
 | 
			
		||||
    .. code-block:: xml
 | 
			
		||||
 | 
			
		||||
        <iq type="result">
 | 
			
		||||
         <query xmlns="jabber:iq:register">
 | 
			
		||||
          <registered />
 | 
			
		||||
          <username>Foo</username>
 | 
			
		||||
          <password>hunter2</password>
 | 
			
		||||
         </query>
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
**Unregister Account:**
 | 
			
		||||
 | 
			
		||||
    .. code-block:: xml
 | 
			
		||||
 | 
			
		||||
        <iq type="result">
 | 
			
		||||
         <query xmlns="jabber:iq:register" />
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
**Incomplete Registration:**
 | 
			
		||||
 | 
			
		||||
    .. code-block:: xml
 | 
			
		||||
 | 
			
		||||
        <iq type="error">
 | 
			
		||||
          <query xmlns="jabber:iq:register">
 | 
			
		||||
            <username>Foo</username>
 | 
			
		||||
          </query>
 | 
			
		||||
         <error code="406" type="modify">
 | 
			
		||||
          <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
 | 
			
		||||
         </error>
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
**Conflicting Registrations:**
 | 
			
		||||
 | 
			
		||||
    .. code-block:: xml
 | 
			
		||||
 | 
			
		||||
        <iq type="error">
 | 
			
		||||
         <query xmlns="jabber:iq:register">
 | 
			
		||||
          <username>Foo</username>
 | 
			
		||||
          <password>hunter2</password>
 | 
			
		||||
         </query>
 | 
			
		||||
         <error code="409" type="cancel">
 | 
			
		||||
          <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
 | 
			
		||||
         </error>
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
**Successful Registration:**
 | 
			
		||||
 | 
			
		||||
    .. code-block:: xml
 | 
			
		||||
 | 
			
		||||
        <iq type="result">
 | 
			
		||||
         <query xmlns="jabber:iq:register" />
 | 
			
		||||
        </iq>
 | 
			
		||||
 | 
			
		||||
Cases 1 and 2: Registration Requests
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
Responding to registration requests depends on if the requesting user already
 | 
			
		||||
has an account. If there is an account, the response should include the
 | 
			
		||||
``registered`` flag and the user's current registration information. Otherwise,
 | 
			
		||||
we just send the fields for our registration form.
 | 
			
		||||
 | 
			
		||||
We will handle both cases by creating a ``sendRegistrationForm`` method that
 | 
			
		||||
will create either an empty of full form depending on if we provide it with
 | 
			
		||||
user data. Since we need to know which form fields to include (especially if we
 | 
			
		||||
add support for the other fields specified in XEP-0077), we will also create a
 | 
			
		||||
method ``setForm`` which will take the names of the fields we wish to include.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.description = "In-Band Registration"
 | 
			
		||||
        self.xep = "0077"
 | 
			
		||||
        self.form_fields = ('username', 'password')
 | 
			
		||||
        ... remainder of plugin_init
 | 
			
		||||
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
    def __handleRegistration(self, iq):
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            # Registration form requested
 | 
			
		||||
            userData = self.backend[iq['from'].bare]
 | 
			
		||||
            self.sendRegistrationForm(iq, userData)
 | 
			
		||||
 | 
			
		||||
    def setForm(self, *fields):
 | 
			
		||||
        self.form_fields = fields
 | 
			
		||||
 | 
			
		||||
    def sendRegistrationForm(self, iq, userData=None):
 | 
			
		||||
        reg = iq['register']
 | 
			
		||||
        if userData is None:
 | 
			
		||||
            userData = {}
 | 
			
		||||
        else:
 | 
			
		||||
            reg['registered'] = True
 | 
			
		||||
 | 
			
		||||
        for field in self.form_fields:
 | 
			
		||||
            data = userData.get(field, '')
 | 
			
		||||
            if data:
 | 
			
		||||
                # Add field with existing data
 | 
			
		||||
                reg[field] = data
 | 
			
		||||
            else:
 | 
			
		||||
                # Add a blank field
 | 
			
		||||
                reg.addField(field)
 | 
			
		||||
 | 
			
		||||
        iq.reply().setPayload(reg.xml)
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
Note how we are able to access our ``Registration`` stanza object with
 | 
			
		||||
``iq['register']``.
 | 
			
		||||
 | 
			
		||||
A User Backend
 | 
			
		||||
++++++++++++++
 | 
			
		||||
You might have noticed the reference to ``self.backend``, which is an object
 | 
			
		||||
that abstracts away storing and retrieving user information. Since it is not
 | 
			
		||||
much more than a dictionary, we will leave the implementation details to the
 | 
			
		||||
final, full source code example.
 | 
			
		||||
 | 
			
		||||
Case 3: Unregister an Account
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
The next simplest case to consider is responding to a request to remove
 | 
			
		||||
an account. If we receive a ``remove`` flag, we instruct the backend to
 | 
			
		||||
remove the user's account. Since your application may need to know about
 | 
			
		||||
when users are registered or unregistered, we trigger an event using
 | 
			
		||||
``self.xmpp.event('unregister_user', iq)``. See the component examples below for
 | 
			
		||||
how to respond to that event.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
     def __handleRegistration(self, iq):
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            # Registration form requested
 | 
			
		||||
            userData = self.backend[iq['from'].bare]
 | 
			
		||||
            self.sendRegistrationForm(iq, userData)
 | 
			
		||||
        elif iq['type'] == 'set':
 | 
			
		||||
            # Remove an account
 | 
			
		||||
            if iq['register']['remove']:
 | 
			
		||||
                self.backend.unregister(iq['from'].bare)
 | 
			
		||||
                self.xmpp.event('unregistered_user', iq)
 | 
			
		||||
                iq.reply().send()
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
Case 4: Incomplete Registration
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
For the next case we need to check the user's registration to ensure it has all
 | 
			
		||||
of the fields we wanted. The simple option that we will use is to loop over the
 | 
			
		||||
field names and check each one; however, this means that all fields we send to
 | 
			
		||||
the user are required. Adding optional fields is left to the reader.
 | 
			
		||||
 | 
			
		||||
Since we have received an incomplete form, we need to send an error message back
 | 
			
		||||
to the user. We have to send a few different types of errors, so we will also
 | 
			
		||||
create a ``_sendError`` method that will add the appropriate ``error`` element
 | 
			
		||||
to the IQ reply.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def __handleRegistration(self, iq):
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            # Registration form requested
 | 
			
		||||
            userData = self.backend[iq['from'].bare]
 | 
			
		||||
            self.sendRegistrationForm(iq, userData)
 | 
			
		||||
        elif iq['type'] == 'set':
 | 
			
		||||
            if iq['register']['remove']:
 | 
			
		||||
                # Remove an account
 | 
			
		||||
                self.backend.unregister(iq['from'].bare)
 | 
			
		||||
                self.xmpp.event('unregistered_user', iq)
 | 
			
		||||
                iq.reply().send()
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            for field in self.form_fields:
 | 
			
		||||
                if not iq['register'][field]:
 | 
			
		||||
                    # Incomplete Registration
 | 
			
		||||
                    self._sendError(iq, '406', 'modify', 'not-acceptable'
 | 
			
		||||
                                    "Please fill in all fields.")
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
    def _sendError(self, iq, code, error_type, name, text=''):
 | 
			
		||||
        iq.reply().setPayload(iq['register'].xml)
 | 
			
		||||
        iq.error()
 | 
			
		||||
        iq['error']['code'] = code
 | 
			
		||||
        iq['error']['type'] = error_type
 | 
			
		||||
        iq['error']['condition'] = name
 | 
			
		||||
        iq['error']['text'] = text
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
Cases 5 and 6: Conflicting and Successful Registration
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
We are down to the final decision on if we have a successful registration. We
 | 
			
		||||
send the user's data to the backend with the ``self.backend.register`` method.
 | 
			
		||||
If it returns ``True``, then registration has been successful. Otherwise,
 | 
			
		||||
there has been a conflict with usernames and registration has failed. Like
 | 
			
		||||
with unregistering an account, we trigger an event indicating that a user has
 | 
			
		||||
been registered by using ``self.xmpp.event('registered_user', iq)``. See the
 | 
			
		||||
component examples below for how to respond to this event.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def __handleRegistration(self, iq):
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            # Registration form requested
 | 
			
		||||
            userData = self.backend[iq['from'].bare]
 | 
			
		||||
            self.sendRegistrationForm(iq, userData)
 | 
			
		||||
        elif iq['type'] == 'set':
 | 
			
		||||
            if iq['register']['remove']:
 | 
			
		||||
                # Remove an account
 | 
			
		||||
                self.backend.unregister(iq['from'].bare)
 | 
			
		||||
                self.xmpp.event('unregistered_user', iq)
 | 
			
		||||
                iq.reply().send()
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            for field in self.form_fields:
 | 
			
		||||
                if not iq['register'][field]:
 | 
			
		||||
                    # Incomplete Registration
 | 
			
		||||
                    self._sendError(iq, '406', 'modify', 'not-acceptable',
 | 
			
		||||
                                    "Please fill in all fields.")
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
            if self.backend.register(iq['from'].bare, iq['register']):
 | 
			
		||||
                # Successful registration
 | 
			
		||||
                self.xmpp.event('registered_user', iq)
 | 
			
		||||
                iq.reply().setPayload(iq['register'].xml)
 | 
			
		||||
                iq.send()
 | 
			
		||||
            else:
 | 
			
		||||
                # Conflicting registration
 | 
			
		||||
                self._sendError(iq, '409', 'cancel', 'conflict',
 | 
			
		||||
                                "That username is already taken.")
 | 
			
		||||
 | 
			
		||||
Example Component Using the XEP-0077 Plugin
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
Alright, the moment we've been working towards - actually using our plugin to
 | 
			
		||||
simplify our other applications. Here is a basic component that simply manages
 | 
			
		||||
user registrations and sends the user a welcoming message when they register,
 | 
			
		||||
and a farewell message when they delete their account.
 | 
			
		||||
 | 
			
		||||
Note that we have to register the ``'xep_0030'`` plugin first,
 | 
			
		||||
and that we specified the form fields we wish to use with
 | 
			
		||||
``self.xmpp.plugin['xep_0077'].setForm('username', 'password')``.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    import sleekxmpp.componentxmpp
 | 
			
		||||
 | 
			
		||||
    class Example(sleekxmpp.componentxmpp.ComponentXMPP):
 | 
			
		||||
 | 
			
		||||
        def __init__(self, jid, password):
 | 
			
		||||
            sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888)
 | 
			
		||||
 | 
			
		||||
            self.registerPlugin('xep_0030')
 | 
			
		||||
            self.registerPlugin('xep_0077')
 | 
			
		||||
            self.plugin['xep_0077'].setForm('username', 'password')
 | 
			
		||||
 | 
			
		||||
            self.add_event_handler("registered_user", self.reg)
 | 
			
		||||
            self.add_event_handler("unregistered_user", self.unreg)
 | 
			
		||||
 | 
			
		||||
        def reg(self, iq):
 | 
			
		||||
            msg = "Welcome! %s" % iq['register']['username']
 | 
			
		||||
            self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
 | 
			
		||||
 | 
			
		||||
        def unreg(self, iq):
 | 
			
		||||
            msg = "Bye! %s" % iq['register']['username']
 | 
			
		||||
            self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
 | 
			
		||||
 | 
			
		||||
**Congratulations!** We now have a basic, functioning implementation of
 | 
			
		||||
XEP-0077.
 | 
			
		||||
 | 
			
		||||
Complete Source Code for XEP-0077 Plugin
 | 
			
		||||
----------------------------------------
 | 
			
		||||
Here is a copy of a more complete implementation of the plugin we created, but
 | 
			
		||||
with some additional registration fields implemented.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Creating a SleekXMPP Plugin
 | 
			
		||||
 | 
			
		||||
    This is a minimal implementation of XEP-0077 to serve
 | 
			
		||||
    as a tutorial for creating SleekXMPP plugins.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
    from sleekxmpp.xmlstream.handler.callback import Callback
 | 
			
		||||
    from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
    from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
 | 
			
		||||
    from sleekxmpp import Iq
 | 
			
		||||
    import copy
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class Registration(ElementBase):
 | 
			
		||||
        namespace = 'jabber:iq:register'
 | 
			
		||||
        name = 'query'
 | 
			
		||||
        plugin_attrib = 'register'
 | 
			
		||||
        interfaces = set(('username', 'password', 'email', 'nick', 'name', 
 | 
			
		||||
                          'first', 'last', 'address', 'city', 'state', 'zip', 
 | 
			
		||||
                          'phone', 'url', 'date', 'misc', 'text', 'key', 
 | 
			
		||||
                          'registered', 'remove', 'instructions'))
 | 
			
		||||
        sub_interfaces = interfaces
 | 
			
		||||
 | 
			
		||||
        def getRegistered(self):
 | 
			
		||||
            present = self.xml.find('{%s}registered' % self.namespace)
 | 
			
		||||
            return present is not None
 | 
			
		||||
 | 
			
		||||
        def getRemove(self):
 | 
			
		||||
            present = self.xml.find('{%s}remove' % self.namespace)
 | 
			
		||||
            return present is not None
 | 
			
		||||
 | 
			
		||||
        def setRegistered(self, registered):
 | 
			
		||||
            if registered:
 | 
			
		||||
                self.addField('registered')
 | 
			
		||||
            else:
 | 
			
		||||
                del self['registered']
 | 
			
		||||
 | 
			
		||||
        def setRemove(self, remove):
 | 
			
		||||
            if remove:
 | 
			
		||||
                self.addField('remove')
 | 
			
		||||
            else:
 | 
			
		||||
                del self['remove']
 | 
			
		||||
 | 
			
		||||
        def addField(self, name):
 | 
			
		||||
            itemXML = ET.Element('{%s}%s' % (self.namespace, name))
 | 
			
		||||
            self.xml.append(itemXML)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class UserStore(object):
 | 
			
		||||
        def __init__(self):
 | 
			
		||||
            self.users = {}
 | 
			
		||||
 | 
			
		||||
        def __getitem__(self, jid):
 | 
			
		||||
            return self.users.get(jid, None)
 | 
			
		||||
 | 
			
		||||
        def register(self, jid, registration):
 | 
			
		||||
            username = registration['username']
 | 
			
		||||
 | 
			
		||||
            def filter_usernames(user):
 | 
			
		||||
                return user != jid and self.users[user]['username'] == username
 | 
			
		||||
 | 
			
		||||
            conflicts = filter(filter_usernames, self.users.keys())
 | 
			
		||||
            if conflicts:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            self.users[jid] = registration
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        def unregister(self, jid):
 | 
			
		||||
            del self.users[jid]
 | 
			
		||||
 | 
			
		||||
    class xep_0077(base_plugin):
 | 
			
		||||
        """
 | 
			
		||||
        XEP-0077 In-Band Registration
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        def plugin_init(self):
 | 
			
		||||
            self.description = "In-Band Registration"
 | 
			
		||||
            self.xep = "0077"
 | 
			
		||||
            self.form_fields = ('username', 'password')
 | 
			
		||||
            self.form_instructions = ""
 | 
			
		||||
            self.backend = UserStore()
 | 
			
		||||
 | 
			
		||||
            self.xmpp.registerHandler(
 | 
			
		||||
                Callback('In-Band Registration',
 | 
			
		||||
                         MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
 | 
			
		||||
                         self.__handleRegistration))
 | 
			
		||||
            register_stanza_plugin(Iq, Registration)
 | 
			
		||||
 | 
			
		||||
        def post_init(self):
 | 
			
		||||
            base_plugin.post_init(self)
 | 
			
		||||
            self.xmpp['xep_0030'].add_feature("jabber:iq:register")
 | 
			
		||||
 | 
			
		||||
        def __handleRegistration(self, iq):
 | 
			
		||||
            if iq['type'] == 'get':
 | 
			
		||||
                # Registration form requested
 | 
			
		||||
                userData = self.backend[iq['from'].bare]
 | 
			
		||||
                self.sendRegistrationForm(iq, userData)
 | 
			
		||||
            elif iq['type'] == 'set':
 | 
			
		||||
                if iq['register']['remove']:
 | 
			
		||||
                    # Remove an account
 | 
			
		||||
                    self.backend.unregister(iq['from'].bare)
 | 
			
		||||
                    self.xmpp.event('unregistered_user', iq)
 | 
			
		||||
                    iq.reply().send()
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
                for field in self.form_fields:
 | 
			
		||||
                    if not iq['register'][field]:
 | 
			
		||||
                        # Incomplete Registration
 | 
			
		||||
                        self._sendError(iq, '406', 'modify', 'not-acceptable',
 | 
			
		||||
                                        "Please fill in all fields.")
 | 
			
		||||
                        return
 | 
			
		||||
 | 
			
		||||
                if self.backend.register(iq['from'].bare, iq['register']):
 | 
			
		||||
                    # Successful registration
 | 
			
		||||
                    self.xmpp.event('registered_user', iq)
 | 
			
		||||
                    iq.reply().setPayload(iq['register'].xml)
 | 
			
		||||
                    iq.send()
 | 
			
		||||
                else:
 | 
			
		||||
                    # Conflicting registration
 | 
			
		||||
                    self._sendError(iq, '409', 'cancel', 'conflict',
 | 
			
		||||
                                    "That username is already taken.")
 | 
			
		||||
 | 
			
		||||
        def setForm(self, *fields):
 | 
			
		||||
            self.form_fields = fields
 | 
			
		||||
 | 
			
		||||
        def setInstructions(self, instructions):
 | 
			
		||||
            self.form_instructions = instructions
 | 
			
		||||
 | 
			
		||||
        def sendRegistrationForm(self, iq, userData=None):
 | 
			
		||||
            reg = iq['register']
 | 
			
		||||
            if userData is None:
 | 
			
		||||
                userData = {}
 | 
			
		||||
            else:
 | 
			
		||||
                reg['registered'] = True
 | 
			
		||||
 | 
			
		||||
            if self.form_instructions:
 | 
			
		||||
                reg['instructions'] = self.form_instructions
 | 
			
		||||
 | 
			
		||||
            for field in self.form_fields:
 | 
			
		||||
                data = userData.get(field, '')
 | 
			
		||||
                if data:
 | 
			
		||||
                    # Add field with existing data
 | 
			
		||||
                    reg[field] = data
 | 
			
		||||
                else:
 | 
			
		||||
                    # Add a blank field
 | 
			
		||||
                    reg.addField(field)
 | 
			
		||||
 | 
			
		||||
            iq.reply().setPayload(reg.xml)
 | 
			
		||||
            iq.send()
 | 
			
		||||
 | 
			
		||||
        def _sendError(self, iq, code, error_type, name, text=''):
 | 
			
		||||
            iq.reply().setPayload(iq['register'].xml)
 | 
			
		||||
            iq.error()
 | 
			
		||||
            iq['error']['code'] = code
 | 
			
		||||
            iq['error']['type'] = error_type
 | 
			
		||||
            iq['error']['condition'] = name
 | 
			
		||||
            iq['error']['text'] = text
 | 
			
		||||
            iq.send()
 | 
			
		||||
							
								
								
									
										271
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,271 @@
 | 
			
		||||
Event Index
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
.. glossary::
 | 
			
		||||
    :sorted:
 | 
			
		||||
 | 
			
		||||
    connected
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP`
 | 
			
		||||
 | 
			
		||||
        Signal that a connection has been made with the XMPP server, but a session
 | 
			
		||||
        has not yet been established.
 | 
			
		||||
 | 
			
		||||
    changed_status
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
 | 
			
		||||
        Triggered when a presence stanza is received from a JID with a show type
 | 
			
		||||
        different than the last presence stanza from the same JID.
 | 
			
		||||
 | 
			
		||||
    changed_subscription
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
 | 
			
		||||
        Triggered whenever a presence stanza with a type of ``subscribe``,
 | 
			
		||||
        ``subscribed``, ``unsubscribe``, or ``unsubscribed`` is received.
 | 
			
		||||
 | 
			
		||||
        Note that if the values ``xmpp.auto_authorize`` and ``xmpp.auto_subscribe``
 | 
			
		||||
        are set to ``True`` or ``False``, and not ``None``, then SleekXMPP will
 | 
			
		||||
        either accept or reject all subscription requests before your event handlers
 | 
			
		||||
        are called. Set these values to ``None`` if you wish to make more complex
 | 
			
		||||
        subscription decisions.
 | 
			
		||||
 | 
			
		||||
    chatstate_active
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    chatstate_composing
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    chatstate_gone
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    chatstate_inactive
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    chatstate_paused
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    disco_info
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoInfo`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
 | 
			
		||||
        
 | 
			
		||||
        Triggered whenever a ``disco#info`` result stanza is received.
 | 
			
		||||
 | 
			
		||||
    disco_items
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoItems`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
 | 
			
		||||
        
 | 
			
		||||
        Triggered whenever a ``disco#items`` result stanza is received.
 | 
			
		||||
 | 
			
		||||
    disconnected
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.ClientXMPP`
 | 
			
		||||
        
 | 
			
		||||
        Signal that the connection with the XMPP server has been lost.
 | 
			
		||||
 | 
			
		||||
    entity_time
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    failed_auth
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.ClientXMPP`, :py:class:`~sleekxmpp.plugins.xep_0078.xep_0078`
 | 
			
		||||
 | 
			
		||||
        Signal that the server has rejected the provided login credentials.
 | 
			
		||||
 | 
			
		||||
    gmail_notify
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
 | 
			
		||||
        
 | 
			
		||||
        Signal that there are unread emails for the Gmail account associated with the current XMPP account.
 | 
			
		||||
 | 
			
		||||
    gmail_messages
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Iq`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
 | 
			
		||||
        
 | 
			
		||||
        Signal that there are unread emails for the Gmail account associated with the current XMPP account.
 | 
			
		||||
 | 
			
		||||
    got_online
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        If a presence stanza is received from a JID which was previously marked as
 | 
			
		||||
        offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
 | 
			
		||||
        or '``xa``', then this event is triggered as well.
 | 
			
		||||
 | 
			
		||||
    got_offline
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        Signal that an unavailable presence stanza has been received from a JID.
 | 
			
		||||
 | 
			
		||||
    groupchat_invite
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    groupchat_direct_invite
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Message` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
 | 
			
		||||
 | 
			
		||||
    groupchat_message
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Message`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
 | 
			
		||||
        
 | 
			
		||||
        Triggered whenever a message is received from a multi-user chat room.
 | 
			
		||||
 | 
			
		||||
    groupchat_presence
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
 | 
			
		||||
        
 | 
			
		||||
        Triggered whenever a presence stanza is received from a user in a multi-user chat room.
 | 
			
		||||
 | 
			
		||||
    groupchat_subject
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Message`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
 | 
			
		||||
        
 | 
			
		||||
        Triggered whenever the subject of a multi-user chat room is changed, or announced when joining a room.
 | 
			
		||||
 | 
			
		||||
    killed
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    last_activity
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    message
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Message`
 | 
			
		||||
        - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
 | 
			
		||||
        
 | 
			
		||||
        Makes the contents of message stanzas available whenever one is received. Be
 | 
			
		||||
        sure to check the message type in order to handle error messages.
 | 
			
		||||
 | 
			
		||||
    message_form
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
			
		||||
        
 | 
			
		||||
        Currently the same as :term:`message_xform`.
 | 
			
		||||
 | 
			
		||||
    message_xform
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
			
		||||
        
 | 
			
		||||
        Triggered whenever a data form is received inside a message.
 | 
			
		||||
 | 
			
		||||
    mucc::[room]::got_offline
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    muc::[room]::got_online
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    muc::[room]::message
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    muc::[room]::presence
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    presence_available
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``available``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_error
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``error``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_form
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
			
		||||
        
 | 
			
		||||
        This event is present in the XEP-0004 plugin code, but is currently not used.
 | 
			
		||||
 | 
			
		||||
    presence_probe
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``probe``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_subscribe
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``subscribe``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_subscribed
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``subscribed``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_unavailable
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``unavailable``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_unsubscribe
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``unsubscribe``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_unsubscribed
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
        
 | 
			
		||||
        A presence stanza with a type of '``unsubscribed``' is received.
 | 
			
		||||
 | 
			
		||||
    roster_update
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.stanza.Roster` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.ClientXMPP` 
 | 
			
		||||
        
 | 
			
		||||
        An IQ result containing roster entries is received.
 | 
			
		||||
 | 
			
		||||
    sent_presence
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
 | 
			
		||||
        
 | 
			
		||||
        Signal that an initial presence stanza has been written to the XML stream.
 | 
			
		||||
 | 
			
		||||
    session_end
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`, 
 | 
			
		||||
          :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
 | 
			
		||||
          :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
 | 
			
		||||
 | 
			
		||||
        Signal that a connection to the XMPP server has been lost and the current
 | 
			
		||||
        stream session has ended. Currently equivalent to :term:`disconnected`, but
 | 
			
		||||
        future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
 | 
			
		||||
        will distinguish the two events.
 | 
			
		||||
 | 
			
		||||
        Plugins that maintain session-based state should clear themselves when
 | 
			
		||||
        this event is fired.
 | 
			
		||||
 | 
			
		||||
    session_start
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`, 
 | 
			
		||||
          :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
 | 
			
		||||
          :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
 | 
			
		||||
 | 
			
		||||
        Signal that a connection to the XMPP server has been made and a session has been established.
 | 
			
		||||
 | 
			
		||||
    socket_error
 | 
			
		||||
        - **Data:** ``Socket`` exception object 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
 | 
			
		||||
 | 
			
		||||
    stream_error
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.stanza.StreamError`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
How to Use Stream Features
 | 
			
		||||
==========================
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Create and Run a Server Component
 | 
			
		||||
=================================
 | 
			
		||||
							
								
								
									
										390
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,390 @@
 | 
			
		||||
.. _echobot:
 | 
			
		||||
 | 
			
		||||
===============================
 | 
			
		||||
SleekXMPP Quickstart - Echo Bot
 | 
			
		||||
===============================
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    
 | 
			
		||||
    If you have any issues working through this quickstart guide
 | 
			
		||||
    or the other tutorials here, please either send a message to the
 | 
			
		||||
    `mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
 | 
			
		||||
    or join the chat room at `sleek@conference.jabber.org
 | 
			
		||||
    <xmpp:sleek@conference.jabber.org?join>`_.
 | 
			
		||||
 | 
			
		||||
If you have not yet installed SleekXMPP, do so now by either checking out a version
 | 
			
		||||
from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip``
 | 
			
		||||
or ``easy_install``.
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
    pip install sleekxmpp  # Or: easy_install sleekxmpp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
As a basic starting project, we will create an echo bot which will reply to any
 | 
			
		||||
messages sent to it. We will also go through adding some basic command line configuration
 | 
			
		||||
for enabling or disabling debug log outputs and setting the username and password
 | 
			
		||||
for the bot.
 | 
			
		||||
 | 
			
		||||
For the command line options processing, we will use the built-in ``optparse``
 | 
			
		||||
module and the ``getpass`` module for reading in passwords.
 | 
			
		||||
 | 
			
		||||
TL;DR Just Give Me the Code
 | 
			
		||||
---------------------------
 | 
			
		||||
As you wish: :ref:`the completed example <echobot_complete>`.
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
To get started, here is a brief outline of the structure that the final project will have:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    #!/usr/bin/env python
 | 
			
		||||
    # -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
    import sys
 | 
			
		||||
    import logging
 | 
			
		||||
    import getpass
 | 
			
		||||
    from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
    import sleekxmpp
 | 
			
		||||
 | 
			
		||||
    '''Here we will create out echo bot class'''
 | 
			
		||||
 | 
			
		||||
    if __name__ == '__main__':
 | 
			
		||||
        '''Here we will configure and read command line options'''
 | 
			
		||||
 | 
			
		||||
        '''Here we will instantiate our echo bot'''
 | 
			
		||||
 | 
			
		||||
        '''Finally, we connect the bot and start listening for messages'''
 | 
			
		||||
 | 
			
		||||
Default Encoding
 | 
			
		||||
----------------
 | 
			
		||||
XMPP requires support for UTF-8 and so SleekXMPP must use UTF-8 as well. In
 | 
			
		||||
Python3 this is simple because Unicode is the default string type. For Python2.6+
 | 
			
		||||
the situation is not as easy because standard strings are simply byte arrays and
 | 
			
		||||
use ASCII. We can get Python to use UTF-8 as the default encoding by including:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    if sys.version_info < (3, 0):
 | 
			
		||||
        reload(sys)
 | 
			
		||||
        sys.setdefaultencoding('utf8')
 | 
			
		||||
 | 
			
		||||
.. warning::
 | 
			
		||||
 | 
			
		||||
    Until we are able to ensure that SleekXMPP will always use Unicode in Python2.6+, this
 | 
			
		||||
    may cause issues embedding SleekXMPP into other applications which assume ASCII encoding.
 | 
			
		||||
 | 
			
		||||
Creating the EchoBot Class
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
There are three main types of entities within XMPP — servers, components, and
 | 
			
		||||
clients. Since our echo bot will only be responding to a few people, and won't need
 | 
			
		||||
to remember thousands of users, we will use a client connection. A client connection
 | 
			
		||||
is the same type that you use with your standard IM client such as Pidgin or Psi.
 | 
			
		||||
 | 
			
		||||
SleekXMPP comes with a :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` class
 | 
			
		||||
which we can extend to add our message echoing feature. :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>`
 | 
			
		||||
requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those
 | 
			
		||||
as well.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        
 | 
			
		||||
        def __init__(self, jid, password):
 | 
			
		||||
            super(EchoBot, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
Handling Session Start
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
The XMPP spec requires clients to broadcast its presence and retrieve its roster (buddy list) once
 | 
			
		||||
it connects and establishes a session with the XMPP server. Until these two tasks are completed,
 | 
			
		||||
some servers may not deliver or send messages or presence notifications to the client. So we now
 | 
			
		||||
need to be sure that we retrieve our roster and send an initial presence once the session has 
 | 
			
		||||
started. To do that, we will register an event handler for the :term:`session_start` event.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
     def __init__(self, jid, password):
 | 
			
		||||
        super(EchoBot, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('session_start', self.start)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Since we want the method ``self.start`` to execute when the :term:`session_start` event is triggered,
 | 
			
		||||
we also need to define the ``self.start`` handler.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
.. warning::
 | 
			
		||||
 | 
			
		||||
    Not sending an initial presence and retrieving the roster when using a client instance can
 | 
			
		||||
    prevent your program from receiving presence notifications or messages depending on the
 | 
			
		||||
    XMPP server you have chosen.
 | 
			
		||||
 | 
			
		||||
Our event handler, like every event handler, accepts a single parameter which typically is the stanza
 | 
			
		||||
that was received that caused the event. In this case, ``event`` will just be an empty dictionary since
 | 
			
		||||
there is no associated data.
 | 
			
		||||
 | 
			
		||||
Our first task of sending an initial presence is done using :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>`.
 | 
			
		||||
Calling :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>` without any arguments will send the simplest
 | 
			
		||||
stanza allowed in XMPP:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <presence />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The second requirement is fulfilled using :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>`, which
 | 
			
		||||
will send an IQ stanza requesting the roster to the server and then wait for the response. You may be wondering
 | 
			
		||||
what :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>` returns since we are not saving any return
 | 
			
		||||
value. The roster data is saved by an internal handler to ``self.roster``, and in the case of a :class:`ClientXMPP
 | 
			
		||||
<sleekxmpp.clientxmpp.ClientXMPP>` instance to ``self.client_roster``. (The difference between ``self.roster`` and
 | 
			
		||||
``self.client_roster`` is that ``self.roster`` supports storing roster information for multiple JIDs, which is useful
 | 
			
		||||
for components, whereas ``self.client_roster`` stores roster data for just the client's JID.)
 | 
			
		||||
 | 
			
		||||
It is possible for a timeout to occur while waiting for the server to respond, which can happen if the
 | 
			
		||||
network is excessively slow or the server is no longer responding. In that case, an :class:`IQTimeout
 | 
			
		||||
<sleekxmpp.exceptions.IQTimeout>` is raised. Similarly, an :class:`IQError <sleekxmpp.exceptions.IQError>` exception can
 | 
			
		||||
be raised if the request contained bad data or requested the roster for the wrong user. In either case, you can wrap the
 | 
			
		||||
``get_roster()`` call in a ``try``/``except`` block to retry the roster retrieval process.
 | 
			
		||||
 | 
			
		||||
The XMPP stanzas from the roster retrieval process could look like this:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <iq type="get">
 | 
			
		||||
      <query xmlns="jabber:iq:roster" />
 | 
			
		||||
    </iq>
 | 
			
		||||
 | 
			
		||||
    <iq type="result" to="echobot@example.com" from="example.com">
 | 
			
		||||
      <query xmlns="jabber:iq:roster">
 | 
			
		||||
        <item jid="friend@example.com" subscription="both" />
 | 
			
		||||
      </query>
 | 
			
		||||
    </iq>
 | 
			
		||||
 | 
			
		||||
Responding to Messages
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
Now that an ``EchoBot`` instance handles :term:`session_start`, we can begin receiving and
 | 
			
		||||
responding to messages. Now we can register a handler for the :term:`message` event that is raised
 | 
			
		||||
whenever a messsage is received.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
     def __init__(self, jid, password):
 | 
			
		||||
        super(EchoBot, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('session_start', self.start)
 | 
			
		||||
        self.add_event_handler('message', self.message)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The :term:`message` event is fired whenever a ``<message />`` stanza is received, including for
 | 
			
		||||
group chat messages, errors, etc. Properly responding to messages thus requires checking the
 | 
			
		||||
``'type'`` interface of the message :term:`stanza object`. For responding to only messages
 | 
			
		||||
addressed to our bot (and not from a chat room), we check that the type is either ``normal``
 | 
			
		||||
or ``chat``. (Other potential types are ``error``, ``headline``, and ``groupchat``.)
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def message(self, msg):
 | 
			
		||||
        if msg['type'] in ('normal', 'chat'):
 | 
			
		||||
            msg.reply("Thanks for sending:\n%s" % msg['body']).send()
 | 
			
		||||
 | 
			
		||||
Let's take a closer look at the ``.reply()`` method used above. For message stanzas,
 | 
			
		||||
``.reply()`` accepts the parameter ``body`` (also as the first positional argument),
 | 
			
		||||
which is then used as the value of the ``<body />`` element of the message. 
 | 
			
		||||
Setting the appropriate ``to`` JID is also handled by ``.reply()``.
 | 
			
		||||
 | 
			
		||||
Another way to have sent the reply message would be to use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`,
 | 
			
		||||
which is a convenience method for generating and sending a message based on the values passed to it. If we were to use
 | 
			
		||||
this method, the above code would look as so:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def message(self, msg):
 | 
			
		||||
        if msg['type'] in ('normal', 'chat'):
 | 
			
		||||
            self.send_message(mto=msg['from'],
 | 
			
		||||
                              mbody='Thanks for sending:\n%s' % msg['body'])
 | 
			
		||||
 | 
			
		||||
Whichever method you choose to use, the results in action will look like this:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <message to="echobot@example.com" from="someuser@example.net" type="chat">
 | 
			
		||||
      <body>Hej!</body>
 | 
			
		||||
    </message>
 | 
			
		||||
 | 
			
		||||
    <message to="someuser@example.net" type="chat">
 | 
			
		||||
      <body>Thanks for sending:
 | 
			
		||||
      Hej!</body>
 | 
			
		||||
    </message>
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    XMPP does not require stanzas sent by a client to include a ``from`` attribute, and
 | 
			
		||||
    leaves that responsibility to the XMPP server. However, if a sent stanza does
 | 
			
		||||
    include a ``from`` attribute, it must match the full JID of the client or some
 | 
			
		||||
    servers will reject it. SleekXMPP thus leaves out the ``from`` attribute when replying
 | 
			
		||||
    using a client connection.
 | 
			
		||||
 | 
			
		||||
Command Line Arguments and Logging
 | 
			
		||||
----------------------------------
 | 
			
		||||
 | 
			
		||||
While this isn't part of SleekXMPP itself, we do want our echo bot program to be able
 | 
			
		||||
to accept a JID and password from the command line instead of hard coding them. We will
 | 
			
		||||
use the ``optparse`` module for this, though there are several alternative methods, including
 | 
			
		||||
the newer ``argparse`` module.
 | 
			
		||||
 | 
			
		||||
We want to accept three parameters: the JID for the echo bot, its password, and a flag for
 | 
			
		||||
displaying the debugging logs. We also want these to be optional parameters, since passing
 | 
			
		||||
a password directly through the command line can be a security risk. 
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    if __name__ == '__main__':
 | 
			
		||||
        optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
        optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                        action='store_const', dest='loglevel',
 | 
			
		||||
                        const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
        optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                        help="JID to use")
 | 
			
		||||
        optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                        help="password to use")
 | 
			
		||||
 | 
			
		||||
        opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
        if opts.jid is None:
 | 
			
		||||
            opts.jid = raw_input("Username: ")
 | 
			
		||||
        if opts.password is None:
 | 
			
		||||
            opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
Since we included a flag for enabling debugging logs, we need to configure the
 | 
			
		||||
``logging`` module to behave accordingly.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
        # .. option parsing from above ..
 | 
			
		||||
 | 
			
		||||
        logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                            format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Connecting to the Server and Processing
 | 
			
		||||
---------------------------------------
 | 
			
		||||
There are three steps remaining until our echo bot is complete:
 | 
			
		||||
    1. We need to instantiate the bot.
 | 
			
		||||
    2. The bot needs to connect to an XMPP server.
 | 
			
		||||
    3. We have to instruct the bot to start running and processing messages.
 | 
			
		||||
 | 
			
		||||
Creating the bot is straightforward, but we can also perform some configuration
 | 
			
		||||
at this stage. For example, let's say we want our bot to support `service discovery
 | 
			
		||||
<http://xmpp.org/extensions/xep-0030.html>`_ and `pings <http://xmpp.org/extensions/xep-0199.html>`_:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
        # .. option parsing and logging steps from above
 | 
			
		||||
 | 
			
		||||
        xmpp = EchoBot(opts.jid, opts.password)
 | 
			
		||||
        xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
        xmpp.register_plugin('xep_0199') # Ping
 | 
			
		||||
 | 
			
		||||
If the ``EchoBot`` class had a hard dependency on a plugin, we could register that plugin in
 | 
			
		||||
the ``EchoBot.__init__`` method instead.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    If you are using the OpenFire server, you will need to include an additional 
 | 
			
		||||
    configuration step. OpenFire supports a different version of SSL than what
 | 
			
		||||
    most servers and SleekXMPP support.
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
    
 | 
			
		||||
        import ssl
 | 
			
		||||
        xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
Now we're ready to connect and begin echoing messages. If you have the package
 | 
			
		||||
``dnspython`` installed, then the :meth:`sleekxmpp.clientxmpp.ClientXMPP` method
 | 
			
		||||
will perform a DNS query to find the appropriate server to connect to for the
 | 
			
		||||
given JID. If you do not have ``dnspython``, then SleekXMPP will attempt to 
 | 
			
		||||
connect to the hostname used by the JID, unless an address tuple is supplied
 | 
			
		||||
to :meth:`sleekxmpp.clientxmpp.ClientXMPP`. 
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
        # .. option parsing & echo bot configuration
 | 
			
		||||
 | 
			
		||||
        if xmpp.connect():
 | 
			
		||||
            xmpp.process(block=True)
 | 
			
		||||
        else:
 | 
			
		||||
            print('Unable to connect')
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    For Google Talk users withouth ``dnspython`` installed, the above code
 | 
			
		||||
    should look like:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
        if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
            # .. option parsing & echo bot configuration
 | 
			
		||||
 | 
			
		||||
            if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
                xmpp.process(block=True)
 | 
			
		||||
            else:
 | 
			
		||||
                print('Unable to connect')
 | 
			
		||||
 | 
			
		||||
To begin responding to messages, you'll see we called :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
 | 
			
		||||
which will start the event handling, send queue, and XML reader threads. It will also call
 | 
			
		||||
the :meth:`sleekxmpp.plugins.base.base_plugin.post_init` method on all registered plugins. By
 | 
			
		||||
passing ``block=True`` to :meth:`sleekxmpp.basexmpp.BaseXMPP.process` we are running the 
 | 
			
		||||
main processing loop in the main thread of execution. The :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
 | 
			
		||||
call will not return until after SleekXMPP disconnects. If you need to run the client in the background
 | 
			
		||||
for another program, use ``block=False`` to spawn the processing loop in its own thread.
 | 
			
		||||
 | 
			
		||||
.. note:: 
 | 
			
		||||
 | 
			
		||||
    Before 1.0, controlling the blocking behaviour of :meth:`sleekxmpp.basexmpp.BaseXMPP.process` was
 | 
			
		||||
    done via the ``threaded`` argument. This arrangement was a source of confusion because some users
 | 
			
		||||
    interpreted that as controlling whether or not SleekXMPP used threads at all, instead of how
 | 
			
		||||
    the processing loop itself was spawned.
 | 
			
		||||
 | 
			
		||||
    The statements ``xmpp.process(threaded=False)`` and ``xmpp.process(block=True)`` are equivalent.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _echobot_complete:
 | 
			
		||||
 | 
			
		||||
The Final Product
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
Here then is what the final result should look like after working through the guide above. The code
 | 
			
		||||
can also be found in the SleekXMPP `examples directory <http://github.com/fritzy/SleekXMPP/tree/master/examples>`_.
 | 
			
		||||
 | 
			
		||||
.. compound::
 | 
			
		||||
 | 
			
		||||
    You can run the code using:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: sh
 | 
			
		||||
 | 
			
		||||
        python echobot.py -d -j echobot@example.com
 | 
			
		||||
 | 
			
		||||
    which will prompt for the password and then begin echoing messages. To test, open
 | 
			
		||||
    your regular IM client and start a chat with the echo bot. Messages you send to it should
 | 
			
		||||
    be mirrored back to you. Be careful if you are using the same JID for the echo bot that
 | 
			
		||||
    you also have logged in with another IM client. Messages could be routed to your IM client instead
 | 
			
		||||
    of the bot.
 | 
			
		||||
 | 
			
		||||
.. include:: ../../examples/echo_client.py
 | 
			
		||||
    :literal:
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Send/Receive IQ Stanzas
 | 
			
		||||
=======================
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Mulit-User Chat (MUC) Bot
 | 
			
		||||
=========================
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/getting_started/presence.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/presence.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Manage Presence Subscriptions
 | 
			
		||||
=============================
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Enable HTTP Proxy Support
 | 
			
		||||
=========================
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/getting_started/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Send a Message Every 5 Minutes
 | 
			
		||||
==============================
 | 
			
		||||
							
								
								
									
										94
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
Sign in, Send a Message, and Disconnect
 | 
			
		||||
=======================================
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    
 | 
			
		||||
    If you have any issues working through this quickstart guide
 | 
			
		||||
    or the other tutorials here, please either send a message to the
 | 
			
		||||
    `mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
 | 
			
		||||
    or join the chat room at `sleek@conference.jabber.org
 | 
			
		||||
    <xmpp:sleek@conference.jabber.org?join>`_.
 | 
			
		||||
 | 
			
		||||
A common use case for SleekXMPP is to send one-off messages from
 | 
			
		||||
time to time. For example, one use case could be sending out a notice when 
 | 
			
		||||
a shell script finishes a task.
 | 
			
		||||
 | 
			
		||||
We will create our one-shot bot based on the pattern explained in :ref:`echobot`. To
 | 
			
		||||
start, we create a client class based on :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` and
 | 
			
		||||
register a handler for the :term:`session_start` event. We will also accept parameters
 | 
			
		||||
for the JID that will receive our message, and the string content of the message.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    import sleekxmpp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class SendMsgBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        
 | 
			
		||||
        def __init__(self, jid, password, recipient, msg):
 | 
			
		||||
            super(SendMsgBot, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
            self.recipient = recipient
 | 
			
		||||
            self.msg = msg
 | 
			
		||||
 | 
			
		||||
            self.add_event_handler('session_start', self.start)
 | 
			
		||||
 | 
			
		||||
        def start(self, event):
 | 
			
		||||
            self.send_presence()
 | 
			
		||||
            self.get_roster()
 | 
			
		||||
 | 
			
		||||
Note that as in :ref:`echobot`, we need to include send an initial presence and request
 | 
			
		||||
the roster. Next, we want to send our message, and to do that we will use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        self.send_message(mto=self.recipient, mbody=self.msg)
 | 
			
		||||
 | 
			
		||||
Finally, we need to disconnect the client using :meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>`.
 | 
			
		||||
Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
 | 
			
		||||
:meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
 | 
			
		||||
for the client to disconnect before the send queue is processed and the message is actually
 | 
			
		||||
sent on the wire. To ensure that our message is processed, we use 
 | 
			
		||||
:meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        self.send_message(mto=self.recipient, mbody=self.msg)
 | 
			
		||||
 | 
			
		||||
        self.disconnect(wait=True)
 | 
			
		||||
 | 
			
		||||
.. warning::
 | 
			
		||||
 | 
			
		||||
    If you happen to be adding stanzas to the send queue faster than the send thread
 | 
			
		||||
    can process them, then :meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`
 | 
			
		||||
    will block and not disconnect.
 | 
			
		||||
 | 
			
		||||
Final Product
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
.. compound::
 | 
			
		||||
 | 
			
		||||
    The final step is to create a small runner script for initialising our ``SendMsgBot`` class and adding some
 | 
			
		||||
    basic configuration options. By following the basic boilerplate pattern in :ref:`echobot`, we arrive
 | 
			
		||||
    at the code below. To experiment with this example, you can use:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: sh
 | 
			
		||||
 | 
			
		||||
            python send_client.py -d -j oneshot@example.com -t someone@example.net -m "This is a message"
 | 
			
		||||
 | 
			
		||||
    which will prompt for the password and then log in, send your message, and then disconnect. To test, open
 | 
			
		||||
    your regular IM client with the account you wish to send messages to. When you run the ``send_client.py``
 | 
			
		||||
    example and instruct it to send your IM client account a message, you should receive the message you
 | 
			
		||||
    gave. If the two JIDs you use also have a mutual presence subscription (they're on each other's buddy lists)
 | 
			
		||||
    then you will also see the ``SendMsgBot`` client come online and then go offline.
 | 
			
		||||
 | 
			
		||||
.. include:: ../../examples/send_client.py
 | 
			
		||||
    :literal:
 | 
			
		||||
							
								
								
									
										35
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
.. _glossary:
 | 
			
		||||
 | 
			
		||||
Glossary
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
.. glossary::
 | 
			
		||||
    :sorted:
 | 
			
		||||
 | 
			
		||||
    stream handler
 | 
			
		||||
        A callback function that accepts stanza objects pulled directly
 | 
			
		||||
        from the XML stream. A stream handler is encapsulated in a
 | 
			
		||||
        object that includes a :term:`Matcher` object, and which provides
 | 
			
		||||
        additional semantics. For example, the ``Waiter`` handler wrapper
 | 
			
		||||
        blocks thread execution until a matching stanza is received.
 | 
			
		||||
 | 
			
		||||
    event handler
 | 
			
		||||
        A callback function that responds to events raised by
 | 
			
		||||
        ``XMLStream.event``. An event handler may be marked as
 | 
			
		||||
        threaded, allowing it to execute outside of the main processing
 | 
			
		||||
        loop.
 | 
			
		||||
 | 
			
		||||
    stanza object
 | 
			
		||||
        Informally may refer both to classes which extend ``ElementBase``
 | 
			
		||||
        or ``StanzaBase``, and to objects of such classes.
 | 
			
		||||
 | 
			
		||||
        A stanza object is a wrapper for an XML object which exposes ``dict``
 | 
			
		||||
        like interfaces which may be assigned to, read from, or deleted.
 | 
			
		||||
 | 
			
		||||
    stanza plugin
 | 
			
		||||
        A :term:`stanza object` which has been registered as a potential child
 | 
			
		||||
        of another stanza object. The plugin stanza may accessed through the
 | 
			
		||||
        parent stanza using the plugin's ``plugin_attrib`` as an interface.
 | 
			
		||||
 | 
			
		||||
    substanza
 | 
			
		||||
        See :term:`stanza plugin`
 | 
			
		||||
							
								
								
									
										201
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
XEP-0030: Working with Service Discovery
 | 
			
		||||
========================================
 | 
			
		||||
 | 
			
		||||
XMPP networks can be composed of many individual clients, components,
 | 
			
		||||
and servers. Determining the JIDs for these entities and the various
 | 
			
		||||
features they may support is the role of `XEP-0030, Service
 | 
			
		||||
Discovery <http://xmpp.org/extensions/xep-0030.html>`_, or "disco" for short.
 | 
			
		||||
 | 
			
		||||
Every XMPP entity may possess what are called nodes. A node is just a name for
 | 
			
		||||
some aspect of an XMPP entity. For example, if an XMPP entity provides `Ad-Hoc
 | 
			
		||||
Commands <http://xmpp.org/extensions/xep-0050.html>`_, then it will have a node
 | 
			
		||||
named ``http://jabber.org/protocol/commands`` which will contain information
 | 
			
		||||
about the commands provided. Other agents using these ad-hoc commands will
 | 
			
		||||
interact with the information provided by this node. Note that the node name is
 | 
			
		||||
just an identifier; there is no inherent meaning.
 | 
			
		||||
 | 
			
		||||
Working with service discovery is about creating and querying these nodes.
 | 
			
		||||
According to XEP-0030, a node may contain three types of information:
 | 
			
		||||
identities, features, and items. (Further, extensible, information types are
 | 
			
		||||
defined in `XEP-0128 <http://xmpp.org/extensions/xep-0128.html>`_, but they are
 | 
			
		||||
not yet implemented by SleekXMPP.) SleekXMPP provides methods to configure each
 | 
			
		||||
of these node attributes.
 | 
			
		||||
 | 
			
		||||
Configuring Service Discovery
 | 
			
		||||
-----------------------------
 | 
			
		||||
The design focus for the XEP-0030 plug-in is handling info and items requests
 | 
			
		||||
in a dynamic fashion, allowing for complex policy decisions of who may receive
 | 
			
		||||
information and how much, or use alternate backend storage mechanisms for all
 | 
			
		||||
of the disco data. To do this, each action that the XEP-0030 plug-in performs
 | 
			
		||||
is handed off to what is called a "node handler," which is just a callback
 | 
			
		||||
function. These handlers are arranged in a hierarchy that allows for a single
 | 
			
		||||
handler to manage an entire domain of JIDs (say for a component), while allowing
 | 
			
		||||
other handler functions to override that global behaviour for certain JIDs, or
 | 
			
		||||
even further limited to only certain JID and node combinations.
 | 
			
		||||
 | 
			
		||||
The Dynamic Handler Hierarchy
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
* ``global``: (JID is None, node is None)
 | 
			
		||||
 | 
			
		||||
  Handlers assigned at this level for an action (such as ``add_feature``) provide a global default
 | 
			
		||||
  behaviour when the action is performed.
 | 
			
		||||
 | 
			
		||||
* ``jid``: (JID assigned, node is None)
 | 
			
		||||
 | 
			
		||||
  At this level, handlers provide a default behaviour for actions affecting any node owned by the
 | 
			
		||||
  JID in question. This level is most useful for component connections; there is effectively no
 | 
			
		||||
  difference between this and the global level when using a client connection.
 | 
			
		||||
 | 
			
		||||
* ``node``: (JID assigned, node assigned)
 | 
			
		||||
 | 
			
		||||
  A handler for this level is responsible for carrying out an action for only one node, and is the
 | 
			
		||||
  most specific handler type available. These types of handlers will be most useful for "special"
 | 
			
		||||
  nodes that require special processing different than others provided by the JID, such as using
 | 
			
		||||
  access control lists, or consolidating data from other nodes.
 | 
			
		||||
 | 
			
		||||
Default Static Handlers
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
The XEP-0030 plug-in provides a default set of handlers that work using in-memory
 | 
			
		||||
disco stanzas. Each handler simply performs the appropriate lookup or storage
 | 
			
		||||
operation using these stanzas without doing any complex operations such as
 | 
			
		||||
checking an ACL, etc.
 | 
			
		||||
 | 
			
		||||
You may find it necessary at some point to revert a particular node or JID to
 | 
			
		||||
using the default, static handlers. To do so, use the method ``make_static()``.
 | 
			
		||||
You may also elect to only convert a given set of actions instead.
 | 
			
		||||
 | 
			
		||||
Creating a Node Handler
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
Every node handler receives three arguments: the JID, the node, and a data
 | 
			
		||||
parameter that will contain the relevant information for carrying out the
 | 
			
		||||
handler's action, typically a dictionary.
 | 
			
		||||
 | 
			
		||||
The JID will always have a value, defaulting to ``xmpp.boundjid.full`` for
 | 
			
		||||
components or ``xmpp.boundjid.bare`` for clients. The node value may be None or
 | 
			
		||||
a string.
 | 
			
		||||
 | 
			
		||||
Only handlers for the actions ``get_info`` and ``get_items`` need to have return
 | 
			
		||||
values. For these actions, DiscoInfo or DiscoItems stanzas are exepected as
 | 
			
		||||
output. It is also acceptable for handlers for these actions to generate an
 | 
			
		||||
XMPPError exception when necessary.
 | 
			
		||||
 | 
			
		||||
Example Node Handler:
 | 
			
		||||
+++++++++++++++++++++
 | 
			
		||||
Here is one of the built-in default handlers as an example:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def add_identity(self, jid, node, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new identity to the JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may provide:
 | 
			
		||||
            category -- The general category to which the agent belongs.
 | 
			
		||||
            itype    -- A more specific designation with the category.
 | 
			
		||||
            name     -- Optional human readable name for this identity.
 | 
			
		||||
            lang     -- Optional standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.nodes[(jid, node)]['info'].add_identity(
 | 
			
		||||
                data.get('category', ''),
 | 
			
		||||
                data.get('itype', ''),
 | 
			
		||||
                data.get('name', None),
 | 
			
		||||
                data.get('lang', None))
 | 
			
		||||
 | 
			
		||||
Adding Identities, Features, and Items
 | 
			
		||||
--------------------------------------
 | 
			
		||||
In order to maintain some backwards compatibility, the methods ``add_identity``,
 | 
			
		||||
``add_feature``, and ``add_item`` do not follow the method signature pattern of
 | 
			
		||||
the other API methods (i.e. jid, node, then other options), but rather retain
 | 
			
		||||
the parameter orders from previous plug-in versions.
 | 
			
		||||
 | 
			
		||||
Adding an Identity
 | 
			
		||||
~~~~~~~~~~~~~~~~~~
 | 
			
		||||
Adding an identity may be done using either the older positional notation, or
 | 
			
		||||
with keyword parameters. The example below uses the keyword arguments, but in
 | 
			
		||||
the same order as expected using positional arguments.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    xmpp['xep_0030'].add_identity(category='client',
 | 
			
		||||
                                  itype='bot',
 | 
			
		||||
                                  name='Sleek',
 | 
			
		||||
                                  node='foo',
 | 
			
		||||
                                  jid=xmpp.boundjid.full,
 | 
			
		||||
                                  lang='no')
 | 
			
		||||
 | 
			
		||||
The JID and node values determine which handler will be used to perform the
 | 
			
		||||
``add_identity`` action.
 | 
			
		||||
 | 
			
		||||
The ``lang`` parameter allows for adding localized versions of identities using
 | 
			
		||||
the ``xml:lang`` attribute.
 | 
			
		||||
 | 
			
		||||
Adding a Feature
 | 
			
		||||
~~~~~~~~~~~~~~~~
 | 
			
		||||
The position ordering for ``add_feature()`` is to include the feature, then
 | 
			
		||||
specify the node and then the JID. The JID and node values determine which
 | 
			
		||||
handler will be used to perform the ``add_feature`` action.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    xmpp['xep_0030'].add_feature(feature='jabber:x:data',
 | 
			
		||||
                                 node='foo',
 | 
			
		||||
                                 jid=xmpp.boundjid.full)
 | 
			
		||||
 | 
			
		||||
Adding an Item
 | 
			
		||||
~~~~~~~~~~~~~~
 | 
			
		||||
The parameters to ``add_item()`` are potentially confusing due to the fact that
 | 
			
		||||
adding an item requires two JID and node combinations: the JID and node of the
 | 
			
		||||
item itself, and the JID and node that will own the item.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    xmpp['xep_0030'].add_item(jid='myitemjid@example.com',
 | 
			
		||||
                              name='An Item!',
 | 
			
		||||
                              node='owner_node',
 | 
			
		||||
                              subnode='item_node',
 | 
			
		||||
                              ijid=xmpp.boundjid.full)
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    In this case, the owning JID and node are provided with the
 | 
			
		||||
    parameters ``ijid`` and ``node``. 
 | 
			
		||||
 | 
			
		||||
Peforming Disco Queries
 | 
			
		||||
-----------------------
 | 
			
		||||
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
 | 
			
		||||
and their nodes for disco information. Since these methods are wrappers for
 | 
			
		||||
sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``
 | 
			
		||||
method. The ``get_items()`` method may also accept the boolean parameter
 | 
			
		||||
``iterator``, which when set to ``True`` will return an iterator object using
 | 
			
		||||
the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    info = self['xep_0030'].get_info(jid='foo@example.com',
 | 
			
		||||
                                     node='bar',
 | 
			
		||||
                                     ifrom='baz@mycomponent.example.com',
 | 
			
		||||
                                     block=True,
 | 
			
		||||
                                     timeout=30)
 | 
			
		||||
 | 
			
		||||
    items = self['xep_0030'].get_info(jid='foo@example.com',
 | 
			
		||||
                                      node='bar',
 | 
			
		||||
                                      iterator=True)
 | 
			
		||||
 | 
			
		||||
For more examples on how to use basic disco queries, check the ``disco_browser.py``
 | 
			
		||||
example in the ``examples`` directory.
 | 
			
		||||
 | 
			
		||||
Local Queries
 | 
			
		||||
~~~~~~~~~~~~~
 | 
			
		||||
In some cases, it may be necessary to query the contents of a node owned by the
 | 
			
		||||
client itself, or one of a component's many JIDs. The same method is used as for
 | 
			
		||||
normal queries, with two differences. First, the parameter ``local=True`` must
 | 
			
		||||
be used. Second, the return value will be a DiscoInfo or DiscoItems stanza, not
 | 
			
		||||
a full Iq stanza.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    info = self['xep_0030'].get_info(node='foo', local=True)
 | 
			
		||||
    items = self['xep_0030'].get_items(jid='somejid@mycomponent.example.com',
 | 
			
		||||
                                       node='bar', 
 | 
			
		||||
                                       local=True)
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Using Stream Handlers and Matchers
 | 
			
		||||
==================================
 | 
			
		||||
							
								
								
									
										155
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
			
		||||
SleekXMPP
 | 
			
		||||
#########
 | 
			
		||||
 | 
			
		||||
.. sidebar:: Get the Code
 | 
			
		||||
 | 
			
		||||
    .. code-block:: sh
 | 
			
		||||
 | 
			
		||||
        pip install sleekxmpp
 | 
			
		||||
 | 
			
		||||
    The latest source code for SleekXMPP may be found on `Github
 | 
			
		||||
    <http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the
 | 
			
		||||
    ``master`` branch, while the latest development version is in the
 | 
			
		||||
    ``develop`` branch.
 | 
			
		||||
 | 
			
		||||
    **Stable Releases**
 | 
			
		||||
        - `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_  
 | 
			
		||||
        - `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_
 | 
			
		||||
        - `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_
 | 
			
		||||
        - `1.0 Beta3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta3>`_
 | 
			
		||||
        - `1.0 Beta2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta2>`_
 | 
			
		||||
        - `1.0 Beta1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta1>`_
 | 
			
		||||
 | 
			
		||||
    **Develop Releases**
 | 
			
		||||
        - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    A mailing list and XMPP chat room are available for discussing and getting
 | 
			
		||||
    help with SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    **Mailing List**
 | 
			
		||||
        `SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_
 | 
			
		||||
 | 
			
		||||
    **Chat**
 | 
			
		||||
        `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SleekXMPP is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+,
 | 
			
		||||
and is featured in examples in
 | 
			
		||||
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ 
 | 
			
		||||
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
 | 
			
		||||
here from reading the Definitive Guide, please see the notes on updating
 | 
			
		||||
the examples to the latest version of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
SleekXMPP's design goals and philosphy are:
 | 
			
		||||
 | 
			
		||||
**Low number of dependencies**
 | 
			
		||||
    Installing and using SleekXMPP should be as simple as possible, without
 | 
			
		||||
    having to deal with long dependency chains.
 | 
			
		||||
 | 
			
		||||
    As part of reducing the number of dependencies, some third party
 | 
			
		||||
    modules are included with SleekXMPP in the ``thirdparty`` directory.
 | 
			
		||||
    Imports from this module first try to import an existing installed
 | 
			
		||||
    version before loading the packaged version, when possible.
 | 
			
		||||
 | 
			
		||||
**Every XEP as a plugin**
 | 
			
		||||
    Following Python's "batteries included" approach, the goal is to
 | 
			
		||||
    provide support for all currently active XEPs (final and draft). Since
 | 
			
		||||
    adding XEP support is done through easy to create plugins, the hope is
 | 
			
		||||
    to also provide a solid base for implementing and creating experimental
 | 
			
		||||
    XEPs.
 | 
			
		||||
 | 
			
		||||
**Rewarding to work with**
 | 
			
		||||
    As much as possible, SleekXMPP should allow things to "just work" using
 | 
			
		||||
    sensible defaults and appropriate abstractions. XML can be ugly to work
 | 
			
		||||
    with, but it doesn't have to be that way.
 | 
			
		||||
 | 
			
		||||
Getting Started (with Examples)
 | 
			
		||||
-------------------------------
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 1
 | 
			
		||||
    
 | 
			
		||||
    getting_started/echobot
 | 
			
		||||
    getting_started/sendlogout
 | 
			
		||||
    getting_started/component
 | 
			
		||||
    getting_started/presence
 | 
			
		||||
    getting_started/muc
 | 
			
		||||
    getting_started/proxy
 | 
			
		||||
    getting_started/scheduler
 | 
			
		||||
    getting_started/iq
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Tutorials, FAQs, and How To Guides
 | 
			
		||||
----------------------------------
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 1
 | 
			
		||||
   
 | 
			
		||||
    xeps
 | 
			
		||||
    xmpp_tdg
 | 
			
		||||
    create_plugin
 | 
			
		||||
    features
 | 
			
		||||
    sasl
 | 
			
		||||
    handlersmatchers
 | 
			
		||||
 | 
			
		||||
Plugin Guides
 | 
			
		||||
~~~~~~~~~~~~~
 | 
			
		||||
.. toctree:: 
 | 
			
		||||
    :maxdepth: 1
 | 
			
		||||
 | 
			
		||||
    guide_xep_0030
 | 
			
		||||
 | 
			
		||||
SleekXMPP Architecture and Design
 | 
			
		||||
---------------------------------
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 3
 | 
			
		||||
 | 
			
		||||
    architecture
 | 
			
		||||
    plugin_arch
 | 
			
		||||
 | 
			
		||||
API Reference
 | 
			
		||||
-------------
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 2
 | 
			
		||||
    
 | 
			
		||||
    event_index
 | 
			
		||||
    api/clientxmpp
 | 
			
		||||
    api/basexmpp
 | 
			
		||||
    api/xmlstream
 | 
			
		||||
 | 
			
		||||
Additional Info
 | 
			
		||||
---------------
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :hidden:
 | 
			
		||||
 | 
			
		||||
    glossary
 | 
			
		||||
    license
 | 
			
		||||
 | 
			
		||||
* :ref:`license`
 | 
			
		||||
* :ref:`glossary`
 | 
			
		||||
* :ref:`genindex`
 | 
			
		||||
* :ref:`modindex`
 | 
			
		||||
* :ref:`search`
 | 
			
		||||
 | 
			
		||||
Credits
 | 
			
		||||
-------
 | 
			
		||||
**Main Author:** Nathan Fritz
 | 
			
		||||
    `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, 
 | 
			
		||||
    `@fritzy <http://twitter.com/fritzy>`_
 | 
			
		||||
 | 
			
		||||
    Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
 | 
			
		||||
    <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP
 | 
			
		||||
    Council.
 | 
			
		||||
 | 
			
		||||
**Co-Author:** Lance Stout
 | 
			
		||||
    `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, 
 | 
			
		||||
    `@lancestout <http://twitter.com/lancestout>`_
 | 
			
		||||
 | 
			
		||||
**Contributors:**
 | 
			
		||||
    - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_)
 | 
			
		||||
    - Dann Martens (`dannmartens <http://github.com/dannmartens>`_)
 | 
			
		||||
    - Florent Le Coz (`louiz <http://github.com/louiz>`_)
 | 
			
		||||
    - Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk)
 | 
			
		||||
    - Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be)
 | 
			
		||||
    - Te-jé Rogers (`te-je <http://github.com/te-je>`_)
 | 
			
		||||
    - Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								docs/license.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/license.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
.. _license: 
 | 
			
		||||
 | 
			
		||||
License (MIT)
 | 
			
		||||
=============
 | 
			
		||||
.. include:: ../LICENSE
 | 
			
		||||
							
								
								
									
										170
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
			
		||||
@ECHO OFF
 | 
			
		||||
 | 
			
		||||
REM Command file for Sphinx documentation
 | 
			
		||||
 | 
			
		||||
if "%SPHINXBUILD%" == "" (
 | 
			
		||||
	set SPHINXBUILD=sphinx-build
 | 
			
		||||
)
 | 
			
		||||
set BUILDDIR=_build
 | 
			
		||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
 | 
			
		||||
if NOT "%PAPER%" == "" (
 | 
			
		||||
	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "" goto help
 | 
			
		||||
 | 
			
		||||
if "%1" == "help" (
 | 
			
		||||
	:help
 | 
			
		||||
	echo.Please use `make ^<target^>` where ^<target^> is one of
 | 
			
		||||
	echo.  html       to make standalone HTML files
 | 
			
		||||
	echo.  dirhtml    to make HTML files named index.html in directories
 | 
			
		||||
	echo.  singlehtml to make a single large HTML file
 | 
			
		||||
	echo.  pickle     to make pickle files
 | 
			
		||||
	echo.  json       to make JSON files
 | 
			
		||||
	echo.  htmlhelp   to make HTML files and a HTML help project
 | 
			
		||||
	echo.  qthelp     to make HTML files and a qthelp project
 | 
			
		||||
	echo.  devhelp    to make HTML files and a Devhelp project
 | 
			
		||||
	echo.  epub       to make an epub
 | 
			
		||||
	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
 | 
			
		||||
	echo.  text       to make text files
 | 
			
		||||
	echo.  man        to make manual pages
 | 
			
		||||
	echo.  changes    to make an overview over all changed/added/deprecated items
 | 
			
		||||
	echo.  linkcheck  to check all external links for integrity
 | 
			
		||||
	echo.  doctest    to run all doctests embedded in the documentation if enabled
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "clean" (
 | 
			
		||||
	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
 | 
			
		||||
	del /q /s %BUILDDIR%\*
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "html" (
 | 
			
		||||
	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "dirhtml" (
 | 
			
		||||
	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "singlehtml" (
 | 
			
		||||
	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "pickle" (
 | 
			
		||||
	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can process the pickle files.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "json" (
 | 
			
		||||
	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can process the JSON files.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "htmlhelp" (
 | 
			
		||||
	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can run HTML Help Workshop with the ^
 | 
			
		||||
.hhp project file in %BUILDDIR%/htmlhelp.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "qthelp" (
 | 
			
		||||
	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; now you can run "qcollectiongenerator" with the ^
 | 
			
		||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
 | 
			
		||||
	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SleekXMPP.qhcp
 | 
			
		||||
	echo.To view the help file:
 | 
			
		||||
	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SleekXMPP.ghc
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "devhelp" (
 | 
			
		||||
	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "epub" (
 | 
			
		||||
	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The epub file is in %BUILDDIR%/epub.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "latex" (
 | 
			
		||||
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "text" (
 | 
			
		||||
	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The text files are in %BUILDDIR%/text.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "man" (
 | 
			
		||||
	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Build finished. The manual pages are in %BUILDDIR%/man.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "changes" (
 | 
			
		||||
	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.The overview file is in %BUILDDIR%/changes.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "linkcheck" (
 | 
			
		||||
	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Link check complete; look for any errors in the above output ^
 | 
			
		||||
or in %BUILDDIR%/linkcheck/output.txt.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if "%1" == "doctest" (
 | 
			
		||||
	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
 | 
			
		||||
	if errorlevel 1 exit /b 1
 | 
			
		||||
	echo.
 | 
			
		||||
	echo.Testing of doctests in the sources finished, look at the ^
 | 
			
		||||
results in %BUILDDIR%/doctest/output.txt.
 | 
			
		||||
	goto end
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
:end
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/plugin_arch.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/plugin_arch.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Plugin Architecture
 | 
			
		||||
===================
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
How SASL Authentication Works
 | 
			
		||||
=============================
 | 
			
		||||
							
								
								
									
										2
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
Supported XEPS
 | 
			
		||||
==============
 | 
			
		||||
							
								
								
									
										249
									
								
								docs/xmpp_tdg.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								docs/xmpp_tdg.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,249 @@
 | 
			
		||||
Following *XMPP: The Definitive Guide*
 | 
			
		||||
======================================
 | 
			
		||||
 | 
			
		||||
SleekXMPP was featured in the first edition of the O'Reilly book 
 | 
			
		||||
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271/>`_
 | 
			
		||||
by Peter Saint-Andre, Kevin Smith, and Remko Tronçon. The original source code
 | 
			
		||||
for the book's examples can be found at http://github.com/remko/xmpp-tdg. An
 | 
			
		||||
updated version of the source code, maintained to stay current with the latest
 | 
			
		||||
SleekXMPP release, is available at http://github.com/legastero/xmpp-tdg.
 | 
			
		||||
 | 
			
		||||
However, since publication, SleekXMPP has advanced from version 0.2.1 to version
 | 
			
		||||
1.0 and there have been several major API changes. The most notable is the
 | 
			
		||||
introduction of :term:`stanza objects <stanza object>` which have simplified and
 | 
			
		||||
standardized interactions with the XMPP XML stream.
 | 
			
		||||
 | 
			
		||||
What follows is a walk-through of *The Definitive Guide* highlighting the
 | 
			
		||||
changes needed to make the code examples work with version 1.0 of SleekXMPP.
 | 
			
		||||
These changes have been kept to a minimum to preserve the correlation with
 | 
			
		||||
the book's explanations, so be aware that some code may not use current best
 | 
			
		||||
practices.
 | 
			
		||||
 | 
			
		||||
Example 2-2. (Page 26)
 | 
			
		||||
----------------------
 | 
			
		||||
 | 
			
		||||
**Implementation of a basic bot that echoes all incoming messages back to its sender.**
 | 
			
		||||
 | 
			
		||||
The echo bot example requires a change to the ``handleIncomingMessage`` method
 | 
			
		||||
to reflect the use of the ``Message`` :term:`stanza object`. The
 | 
			
		||||
``"jid"`` field of the message object should now be ``"from"`` to match the
 | 
			
		||||
``from`` attribute of the actual XML message stanza. Likewise, ``"message"``
 | 
			
		||||
changes to ``"body"`` to match the ``body`` element of the message stanza.
 | 
			
		||||
 | 
			
		||||
Updated Code
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def handleIncomingMessage(self, message):
 | 
			
		||||
        self.xmpp.sendMessage(message["from"], message["body"])
 | 
			
		||||
 | 
			
		||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_ |
 | 
			
		||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_
 | 
			
		||||
 | 
			
		||||
Example 14-1. (Page 215)
 | 
			
		||||
------------------------
 | 
			
		||||
 | 
			
		||||
**CheshiR IM bot implementation.**
 | 
			
		||||
 | 
			
		||||
The main event handling method in the Bot class is meant to process both message
 | 
			
		||||
events and presence update events. With the new changes in SleekXMPP 1.0,
 | 
			
		||||
extracting a CheshiR status "message" from both types of stanzas
 | 
			
		||||
requires accessing different attributes. In the case of a message stanza, the
 | 
			
		||||
``"body"`` attribute would contain the CheshiR message. For a presence event,
 | 
			
		||||
the information is stored in the ``"status"`` attribute. To handle both cases,
 | 
			
		||||
we can test the type of the given event object and look up the proper attribute
 | 
			
		||||
based on the type.
 | 
			
		||||
 | 
			
		||||
Like in the EchoBot example, the expression ``event["jid"]`` needs to change
 | 
			
		||||
to ``event["from"]`` in order to get a JID object for the stanza's sender.
 | 
			
		||||
Because other functions in CheshiR assume that the JID is a string, the ``jid``
 | 
			
		||||
attribute is used to access the string version of the JID. A check is also added
 | 
			
		||||
in case ``user`` is ``None``, but the check could (and probably should) be
 | 
			
		||||
placed in ``addMessageFromUser``.
 | 
			
		||||
 | 
			
		||||
Another change is needed in ``handleMessageAddedToBackend`` where
 | 
			
		||||
an HTML-IM response is created. The HTML content should be enclosed in a single
 | 
			
		||||
element, such as a ``<p>`` tag.
 | 
			
		||||
 | 
			
		||||
Updated Code
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
  def handleIncomingXMPPEvent(self, event):
 | 
			
		||||
    msgLocations = {sleekxmpp.stanza.presence.Presence: "status",
 | 
			
		||||
                    sleekxmpp.stanza.message.Message: "body"}
 | 
			
		||||
 | 
			
		||||
    message = event[msgLocations[type(event)]]
 | 
			
		||||
    user = self.backend.getUserFromJID(event["from"].jid)
 | 
			
		||||
    if user is not None:
 | 
			
		||||
      self.backend.addMessageFromUser(message, user)
 | 
			
		||||
  
 | 
			
		||||
  def handleMessageAddedToBackend(self, message) :
 | 
			
		||||
    body = message.user + ": " + message.text
 | 
			
		||||
    htmlBody = "<p><a href='%(uri)s'>%(user)s</a>: %(message)s</p>" % {
 | 
			
		||||
      "uri": self.url + "/" + message.user,
 | 
			
		||||
      "user" : message.user, "message" : message.text }
 | 
			
		||||
    for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
 | 
			
		||||
      self.xmpp.sendMessage(subscriberJID, body, mhtml=htmlBody)
 | 
			
		||||
 | 
			
		||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_ |
 | 
			
		||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Example 14-3. (Page 217)
 | 
			
		||||
------------------------
 | 
			
		||||
**Configurable CheshiR IM bot implementation.**
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    Since the CheshiR examples build on each other, see previous sections for
 | 
			
		||||
    corrections to code that is not marked as new in the book example.
 | 
			
		||||
 | 
			
		||||
The main difference for the configurable IM bot is the handling for the
 | 
			
		||||
data form in ``handleConfigurationCommand``. The test for equality
 | 
			
		||||
with the string ``"1"`` is no longer required; SleekXMPP converts
 | 
			
		||||
boolean data form fields to the values ``True`` and ``False``
 | 
			
		||||
automatically.
 | 
			
		||||
 | 
			
		||||
For the method ``handleIncomingXMPPPresence``, the attribute
 | 
			
		||||
``"jid"`` is again converted to ``"from"`` to get a JID
 | 
			
		||||
object for the presence stanza's sender, and the ``jid`` attribute is
 | 
			
		||||
used to access the string version of that JID object. A check is also added in
 | 
			
		||||
case ``user`` is ``None``, but the check could (and probably
 | 
			
		||||
should) be placed in ``getShouldMonitorPresenceFromUser``.
 | 
			
		||||
 | 
			
		||||
Updated Code
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
  def handleConfigurationCommand(self, form, sessionId):
 | 
			
		||||
    values = form.getValues()
 | 
			
		||||
    monitorPresence =values["monitorPresence"]
 | 
			
		||||
    jid = self.xmpp.plugin["xep_0050"].sessions[sessionId]["jid"]
 | 
			
		||||
    user = self.backend.getUserFromJID(jid)
 | 
			
		||||
    self.backend.setShouldMonitorPresenceFromUser(user, monitorPresence)
 | 
			
		||||
 | 
			
		||||
  def handleIncomingXMPPPresence(self, event):
 | 
			
		||||
    user = self.backend.getUserFromJID(event["from"].jid)
 | 
			
		||||
    if user is not None:
 | 
			
		||||
      if self.backend.getShouldMonitorPresenceFromUser(user):
 | 
			
		||||
        self.handleIncomingXMPPEvent(event)
 | 
			
		||||
 | 
			
		||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_ |
 | 
			
		||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Example 14-4. (Page 220)
 | 
			
		||||
------------------------
 | 
			
		||||
**CheshiR IM server component implementation.**
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    Since the CheshiR examples build on each other, see previous sections for
 | 
			
		||||
    corrections to code that is not marked as new in the book example.
 | 
			
		||||
 | 
			
		||||
Like several previous examples, a needed change is to replace
 | 
			
		||||
``subscription["from"]`` with ``subscription["from"].jid`` because the
 | 
			
		||||
``BaseXMPP`` method ``makePresence`` requires the JID to be a string.
 | 
			
		||||
 | 
			
		||||
A correction needs to be made in ``handleXMPPPresenceProbe`` because a line was
 | 
			
		||||
left out of the original implementation; the variable ``user`` is undefined. The
 | 
			
		||||
JID of the user can be extracted from the presence stanza's ``from`` attribute.
 | 
			
		||||
 | 
			
		||||
Since this implementation of CheshiR uses an XMPP component, it must
 | 
			
		||||
include a ``from`` attribute in all messages that it sends. Adding the
 | 
			
		||||
``from`` attribute is done by including ``mfrom=self.xmpp.jid`` in calls to
 | 
			
		||||
``self.xmpp.sendMessage``.
 | 
			
		||||
 | 
			
		||||
Updated Code
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
  def handleXMPPPresenceProbe(self, event) :
 | 
			
		||||
    self.xmpp.sendPresence(pto = event["from"])
 | 
			
		||||
 | 
			
		||||
  def handleXMPPPresenceSubscription(self, subscription) :
 | 
			
		||||
    if subscription["type"] == "subscribe" :
 | 
			
		||||
      userJID = subscription["from"].jid
 | 
			
		||||
      self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribed")
 | 
			
		||||
      self.xmpp.sendPresence(pto = userJID)
 | 
			
		||||
      self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribe")
 | 
			
		||||
 | 
			
		||||
  def handleMessageAddedToBackend(self, message) :
 | 
			
		||||
    body = message.user + ": " + message.text
 | 
			
		||||
    for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
 | 
			
		||||
      self.xmpp.sendMessage(subscriberJID, body, mfrom=self.xmpp.jid)
 | 
			
		||||
 | 
			
		||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_ |
 | 
			
		||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Example 14-6. (Page 223)
 | 
			
		||||
------------------------
 | 
			
		||||
**CheshiR IM server component with in-band registration support.**
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    Since the CheshiR examples build on each other, see previous sections for
 | 
			
		||||
    corrections to code that is not marked as new in the book example.
 | 
			
		||||
 | 
			
		||||
After applying the changes from Example 14-4 above, the registrable component
 | 
			
		||||
implementation should work correctly.
 | 
			
		||||
 | 
			
		||||
.. tip::
 | 
			
		||||
    To see how to implement in-band registration as a SleekXMPP plugin,
 | 
			
		||||
    see the tutorial :ref:`tutorial-create-plugin`.
 | 
			
		||||
 | 
			
		||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_ |
 | 
			
		||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_
 | 
			
		||||
 | 
			
		||||
Example 14-7. (Page 225)
 | 
			
		||||
------------------------
 | 
			
		||||
**Extended CheshiR IM server component implementation.**
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    Since the CheshiR examples build on each other, see previous 
 | 
			
		||||
    sections for corrections to code that is not marked as new in the book
 | 
			
		||||
    example.
 | 
			
		||||
 | 
			
		||||
While the final code example can look daunting with all of the changes
 | 
			
		||||
made, it requires very few modifications to work with the latest version of
 | 
			
		||||
SleekXMPP. Most differences are the result of CheshiR's backend functions
 | 
			
		||||
expecting JIDs to be strings so that they can be stripped to bare JIDs. To
 | 
			
		||||
resolve these, use the ``jid`` attribute of the JID objects. Also,
 | 
			
		||||
references to ``"message"`` and ``"jid"`` attributes need to
 | 
			
		||||
be changed to either ``"body"`` or ``"status"``, and either
 | 
			
		||||
``"from"`` or ``"to"`` depending on if the object is a message
 | 
			
		||||
or presence stanza and which of the JIDs from the stanza is needed.
 | 
			
		||||
 | 
			
		||||
Updated Code
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
  def handleIncomingXMPPMessage(self, event) :
 | 
			
		||||
    message = self.addRecipientToMessage(event["body"], event["to"].jid)
 | 
			
		||||
    user = self.backend.getUserFromJID(event["from"].jid)
 | 
			
		||||
    self.backend.addMessageFromUser(message, user)
 | 
			
		||||
 | 
			
		||||
  def handleIncomingXMPPPresence(self, event) :
 | 
			
		||||
    if event["to"].jid == self.componentDomain :
 | 
			
		||||
      user = self.backend.getUserFromJID(event["from"].jid)
 | 
			
		||||
      self.backend.addMessageFromUser(event["status"], user)
 | 
			
		||||
 | 
			
		||||
  ...
 | 
			
		||||
 | 
			
		||||
  def handleXMPPPresenceSubscription(self, subscription) :
 | 
			
		||||
    if subscription["type"] == "subscribe" :
 | 
			
		||||
      userJID = subscription["from"].jid
 | 
			
		||||
      user = self.backend.getUserFromJID(userJID)
 | 
			
		||||
      contactJID = subscription["to"]
 | 
			
		||||
      self.xmpp.sendPresenceSubscription(
 | 
			
		||||
          pfrom=contactJID, pto=userJID, ptype="subscribed", pnick=user)
 | 
			
		||||
      self.sendPresenceOfContactToUser(contactJID=contactJID, userJID=userJID)
 | 
			
		||||
      if contactJID == self.componentDomain :
 | 
			
		||||
        self.sendAllContactSubscriptionRequestsToUser(userJID)
 | 
			
		||||
 | 
			
		||||
`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_ |
 | 
			
		||||
`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_ 
 | 
			
		||||
@@ -24,6 +24,8 @@ import sleekxmpp
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
@@ -79,6 +81,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
                       here to persist across handler callbacks.
 | 
			
		||||
        """
 | 
			
		||||
        form = self['xep_0004'].makeForm('form', 'Greeting')
 | 
			
		||||
        form['instructions'] = 'Send a custom greeting to a JID'
 | 
			
		||||
        form.addField(var='greeting',
 | 
			
		||||
                      ftype='text-single',
 | 
			
		||||
                      label='Your greeting')
 | 
			
		||||
@@ -123,8 +126,10 @@ class CommandBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        form = payload
 | 
			
		||||
 | 
			
		||||
        greeting = form['values']['greeting']
 | 
			
		||||
 | 
			
		||||
        self.send_message(mto=session['from'],
 | 
			
		||||
                          mbody="%s, World!" % greeting)
 | 
			
		||||
                          mbody="%s, World!" % greeting,
 | 
			
		||||
                          mtype='chat')
 | 
			
		||||
 | 
			
		||||
        # Having no return statement is the same as unsetting the 'payload'
 | 
			
		||||
        # and 'next' session values and returning the session.
 | 
			
		||||
@@ -176,6 +181,7 @@ if __name__ == '__main__':
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0050') # Adhoc Commands
 | 
			
		||||
    xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency':15})
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@ import sleekxmpp
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandUserBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
@@ -136,6 +138,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # The session will automatically be cleared if no error
 | 
			
		||||
        # handler is provided.
 | 
			
		||||
        self['xep_0050'].terminate_command(session)
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
@@ -176,7 +179,7 @@ if __name__ == '__main__':
 | 
			
		||||
    if opts.other is None:
 | 
			
		||||
        opts.other = raw_input("JID Providing Commands: ")
 | 
			
		||||
    if opts.greeting is None:
 | 
			
		||||
        opts.other = raw_input("Greeting: ")
 | 
			
		||||
        opts.greeting = raw_input("Greeting: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the CommandBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
 
 | 
			
		||||
@@ -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.")
 | 
			
		||||
@@ -25,6 +25,8 @@ import sleekxmpp
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Disco(sleekxmpp.ClientXMPP):
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@ import sleekxmpp
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
@@ -76,7 +78,8 @@ class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
                   for stanza objects and the Message stanza to see
 | 
			
		||||
                   how it may be used.
 | 
			
		||||
        """
 | 
			
		||||
        msg.reply("Thanks for sending\n%(body)s" % msg).send()
 | 
			
		||||
        if msg['type'] in ('chat', 'normal'):
 | 
			
		||||
            msg.reply("Thanks for sending\n%(body)s" % msg).send()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 time
 | 
			
		||||
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(threaded=False)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
@@ -23,6 +23,8 @@ import sleekxmpp
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
@@ -59,7 +61,7 @@ class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # muc::room@server::got_online, or muc::room@server::got_offline.
 | 
			
		||||
        self.add_event_handler("muc::%s::got_online" % self.room,
 | 
			
		||||
                               self.muc_online)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
@@ -76,15 +78,15 @@ class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        """
 | 
			
		||||
        self.getRoster()
 | 
			
		||||
        self.sendPresence()
 | 
			
		||||
        self.plugin['xep_0045'].joinMUC(self.room, 
 | 
			
		||||
                                        self.nick, 
 | 
			
		||||
        self.plugin['xep_0045'].joinMUC(self.room,
 | 
			
		||||
                                        self.nick,
 | 
			
		||||
                                        # If a room password is needed, use:
 | 
			
		||||
                                        # password=the_room_password,
 | 
			
		||||
                                        wait=True)
 | 
			
		||||
 | 
			
		||||
    def muc_message(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming message stanzas from any chat room. Be aware 
 | 
			
		||||
        Process incoming message stanzas from any chat room. Be aware
 | 
			
		||||
        that if you also have any handlers for the 'message' event,
 | 
			
		||||
        message stanzas may be processed by both handlers, so check
 | 
			
		||||
        the 'type' attribute when using a 'message' event handler.
 | 
			
		||||
@@ -96,7 +98,7 @@ class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
                   otherwise you will create an infinite loop responding
 | 
			
		||||
                   to your own messages.
 | 
			
		||||
 | 
			
		||||
        This handler will reply to messages that mention 
 | 
			
		||||
        This handler will reply to messages that mention
 | 
			
		||||
        the bot's nickname.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -112,12 +114,12 @@ class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
    def muc_online(self, presence):
 | 
			
		||||
        """
 | 
			
		||||
        Process a presence stanza from a chat room. In this case,
 | 
			
		||||
        presences from users that have just come online are 
 | 
			
		||||
        presences from users that have just come online are
 | 
			
		||||
        handled by sending a welcome message that includes
 | 
			
		||||
        the user's nickname and role in the room.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            presence -- The received presence stanza. See the 
 | 
			
		||||
            presence -- The received presence stanza. See the
 | 
			
		||||
                        documentation for the Presence stanza
 | 
			
		||||
                        to see how else it may be used.
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@ import sleekxmpp
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PingTest(sleekxmpp.ClientXMPP):
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@ import sleekxmpp
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										173
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
import threading
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RosterBrowser(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A basic script for dumping a client's roster to
 | 
			
		||||
    the command line.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can intialize
 | 
			
		||||
        # our roster. We need threaded=True so that the
 | 
			
		||||
        # session_start handler doesn't block event processing
 | 
			
		||||
        # while we wait for presence stanzas to arrive.
 | 
			
		||||
        self.add_event_handler("session_start", self.start, threaded=True)
 | 
			
		||||
        self.add_event_handler("changed_status", self.wait_for_presences)
 | 
			
		||||
 | 
			
		||||
        self.received = set()
 | 
			
		||||
        self.presences_received = threading.Event()
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            self.get_roster()
 | 
			
		||||
        except IqError as err:
 | 
			
		||||
            print('Error: %' % err.iq['error']['condition'])
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            print('Error: Request timed out')
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        print('Waiting for presence updates...\n')
 | 
			
		||||
        self.presences_received.wait(5)
 | 
			
		||||
 | 
			
		||||
        print('Roster for %s' % self.boundjid.bare)
 | 
			
		||||
        groups = self.client_roster.groups()
 | 
			
		||||
        for group in groups:
 | 
			
		||||
            print('\n%s' % group)
 | 
			
		||||
            print('-' * 72)
 | 
			
		||||
            for jid in groups[group]:
 | 
			
		||||
                sub = self.client_roster[jid]['subscription']
 | 
			
		||||
                name = self.client_roster[jid]['name']
 | 
			
		||||
                if self.client_roster[jid]['name']:
 | 
			
		||||
                    print(' %s (%s) [%s]' % (name, jid, sub))
 | 
			
		||||
                else:
 | 
			
		||||
                    print(' %s [%s]' % (jid, sub))
 | 
			
		||||
 | 
			
		||||
                connections = self.client_roster.presence(jid)
 | 
			
		||||
                for res, pres in connections.items():
 | 
			
		||||
                    show = 'available'
 | 
			
		||||
                    if pres['show']:
 | 
			
		||||
                        show = pres['show']
 | 
			
		||||
                    print('   - %s (%s)' % (res, show))
 | 
			
		||||
                    if pres['status']:
 | 
			
		||||
                        print('       %s' % pres['status'])
 | 
			
		||||
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
    def wait_for_presences(self, pres):
 | 
			
		||||
        """
 | 
			
		||||
        Track how many roster entries have received presence updates.
 | 
			
		||||
        """
 | 
			
		||||
        self.received.add(pres['from'].bare)
 | 
			
		||||
        if len(self.received) >= len(self.client_roster.keys()):
 | 
			
		||||
            self.presences_received.set()
 | 
			
		||||
        else:
 | 
			
		||||
            self.presences_received.clear()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
    optp.add_option('-q','--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
    optp.add_option('-d','--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
    optp.add_option('-v','--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=5,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
    opts,args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    xmpp = RosterBrowser(opts.jid, opts.password)
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										144
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										144
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SendMsgBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A basic SleekXMPP bot that will log in, send a message,
 | 
			
		||||
    and then log out.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, recipient, message):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        # The message we wish to send, and the JID that
 | 
			
		||||
        # will receive it.
 | 
			
		||||
        self.recipient = recipient
 | 
			
		||||
        self.msg = message
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can intialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        self.send_message(mto=self.recipient,
 | 
			
		||||
                          mbody=self.msg,
 | 
			
		||||
                          mtype='chat')
 | 
			
		||||
 | 
			
		||||
        # Using wait=True ensures that the send queue will be
 | 
			
		||||
        # emptied before ending the session.
 | 
			
		||||
        self.disconnect(wait=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
    optp.add_option("-t", "--to", dest="to",
 | 
			
		||||
                    help="JID to send the message to")
 | 
			
		||||
    optp.add_option("-m", "--message", dest="message",
 | 
			
		||||
                    help="message to send")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
    if opts.to is None:
 | 
			
		||||
        opts.to = raw_input("Send To: ")
 | 
			
		||||
    if opts.message is None:
 | 
			
		||||
        opts.message = raw_input("Message: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the EchoBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = SendMsgBot(opts.jid, opts.password, opts.to, opts.message)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										195
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										195
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,94 +1,101 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2007-2011 Nathanael C. Fritz
 | 
			
		||||
# All Rights Reserved
 | 
			
		||||
#
 | 
			
		||||
# This software is licensed as described in the README file,
 | 
			
		||||
# which you should have received as part of this distribution.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# from ez_setup import use_setuptools
 | 
			
		||||
from distutils.core import setup
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
# if 'cygwin' in sys.platform.lower():
 | 
			
		||||
#     min_version = '0.6c6'
 | 
			
		||||
# else:
 | 
			
		||||
#     min_version = '0.6a9'
 | 
			
		||||
#
 | 
			
		||||
# try:
 | 
			
		||||
#     use_setuptools(min_version=min_version)
 | 
			
		||||
# except TypeError:
 | 
			
		||||
#     # locally installed ez_setup won't have min_version
 | 
			
		||||
#     use_setuptools()
 | 
			
		||||
#
 | 
			
		||||
# from setuptools import setup, find_packages, Extension, Feature
 | 
			
		||||
 | 
			
		||||
VERSION          = sleekxmpp.__version__
 | 
			
		||||
DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
 | 
			
		||||
with open('README') as readme:
 | 
			
		||||
    LONG_DESCRIPTION = '\n'.join(readme)
 | 
			
		||||
 | 
			
		||||
CLASSIFIERS      = [ 'Intended Audience :: Developers',
 | 
			
		||||
                     'License :: OSI Approved :: MIT License',
 | 
			
		||||
                     'Programming Language :: Python',
 | 
			
		||||
                     'Programming Language :: Python 2.6',
 | 
			
		||||
                     'Programming Language :: Python 2.7',
 | 
			
		||||
                     'Programming Language :: Python 3.1',
 | 
			
		||||
                     'Programming Language :: Python 3.2',
 | 
			
		||||
                     'Topic :: Software Development :: Libraries :: Python Modules',
 | 
			
		||||
                   ]
 | 
			
		||||
 | 
			
		||||
packages     = [ 'sleekxmpp',
 | 
			
		||||
                 'sleekxmpp/stanza',
 | 
			
		||||
                 'sleekxmpp/test',
 | 
			
		||||
                 'sleekxmpp/xmlstream',
 | 
			
		||||
                 'sleekxmpp/xmlstream/matcher',
 | 
			
		||||
                 'sleekxmpp/xmlstream/handler',
 | 
			
		||||
                 'sleekxmpp/plugins',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0050',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0059',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0060',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0060/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0066',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0085',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0086',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0092',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0128',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0199',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0202',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0203',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0224',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0249',
 | 
			
		||||
                 'sleekxmpp/features',
 | 
			
		||||
                 'sleekxmpp/features/feature_mechanisms',
 | 
			
		||||
                 'sleekxmpp/features/feature_mechanisms/stanza',
 | 
			
		||||
                 'sleekxmpp/features/feature_starttls',
 | 
			
		||||
                 'sleekxmpp/features/feature_bind',
 | 
			
		||||
                 'sleekxmpp/features/feature_session',
 | 
			
		||||
                 'sleekxmpp/thirdparty',
 | 
			
		||||
                 'sleekxmpp/thirdparty/suelta',
 | 
			
		||||
                 'sleekxmpp/thirdparty/suelta/mechanisms',
 | 
			
		||||
                 ]
 | 
			
		||||
 | 
			
		||||
setup(
 | 
			
		||||
    name             = "sleekxmpp",
 | 
			
		||||
    version          = VERSION,
 | 
			
		||||
    description      = DESCRIPTION,
 | 
			
		||||
    long_description = LONG_DESCRIPTION,
 | 
			
		||||
    author       = 'Nathanael Fritz',
 | 
			
		||||
    author_email = 'fritzy [at] netflint.net',
 | 
			
		||||
    url          = 'http://github.com/fritzy/SleekXMPP',
 | 
			
		||||
    license      = 'MIT',
 | 
			
		||||
    platforms    = [ 'any' ],
 | 
			
		||||
    packages     = packages,
 | 
			
		||||
    requires     = [ 'tlslite', 'pythondns' ],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2007-2011 Nathanael C. Fritz
 | 
			
		||||
# All Rights Reserved
 | 
			
		||||
#
 | 
			
		||||
# This software is licensed as described in the README.rst and LICENSE
 | 
			
		||||
# file, which you should have received as part of this distribution.
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
try:
 | 
			
		||||
    from setuptools import setup, Command
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from distutils.core import setup, Command
 | 
			
		||||
# from ez_setup import use_setuptools
 | 
			
		||||
 | 
			
		||||
from testall import TestCommand
 | 
			
		||||
from sleekxmpp.version import __version__
 | 
			
		||||
# if 'cygwin' in sys.platform.lower():
 | 
			
		||||
#     min_version = '0.6c6'
 | 
			
		||||
# else:
 | 
			
		||||
#     min_version = '0.6a9'
 | 
			
		||||
#
 | 
			
		||||
# try:
 | 
			
		||||
#     use_setuptools(min_version=min_version)
 | 
			
		||||
# except TypeError:
 | 
			
		||||
#     # locally installed ez_setup won't have min_version
 | 
			
		||||
#     use_setuptools()
 | 
			
		||||
#
 | 
			
		||||
# from setuptools import setup, find_packages, Extension, Feature
 | 
			
		||||
 | 
			
		||||
VERSION          = __version__
 | 
			
		||||
DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
 | 
			
		||||
with open('README.rst') as readme:
 | 
			
		||||
    LONG_DESCRIPTION = ''.join(readme)
 | 
			
		||||
 | 
			
		||||
CLASSIFIERS      = [ 'Intended Audience :: Developers',
 | 
			
		||||
                     'License :: OSI Approved :: MIT License',
 | 
			
		||||
                     'Programming Language :: Python',
 | 
			
		||||
                     'Programming Language :: Python :: 2.6',
 | 
			
		||||
                     'Programming Language :: Python :: 2.7',
 | 
			
		||||
                     'Programming Language :: Python :: 3.1',
 | 
			
		||||
                     'Programming Language :: Python :: 3.2',
 | 
			
		||||
                     'Topic :: Software Development :: Libraries :: Python Modules',
 | 
			
		||||
                   ]
 | 
			
		||||
 | 
			
		||||
packages     = [ 'sleekxmpp',
 | 
			
		||||
                 'sleekxmpp/stanza',
 | 
			
		||||
                 'sleekxmpp/test',
 | 
			
		||||
                 'sleekxmpp/roster',
 | 
			
		||||
                 'sleekxmpp/xmlstream',
 | 
			
		||||
                 'sleekxmpp/xmlstream/matcher',
 | 
			
		||||
                 'sleekxmpp/xmlstream/handler',
 | 
			
		||||
                 'sleekxmpp/plugins',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0004',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0004/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0050',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0059',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0060',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0060/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0066',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0078',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0085',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0086',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0092',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0128',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0199',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0202',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0203',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0224',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0249',
 | 
			
		||||
                 'sleekxmpp/features',
 | 
			
		||||
                 'sleekxmpp/features/feature_mechanisms',
 | 
			
		||||
                 'sleekxmpp/features/feature_mechanisms/stanza',
 | 
			
		||||
                 'sleekxmpp/features/feature_starttls',
 | 
			
		||||
                 'sleekxmpp/features/feature_bind',
 | 
			
		||||
                 'sleekxmpp/features/feature_session',
 | 
			
		||||
                 'sleekxmpp/thirdparty',
 | 
			
		||||
                 'sleekxmpp/thirdparty/suelta',
 | 
			
		||||
                 'sleekxmpp/thirdparty/suelta/mechanisms',
 | 
			
		||||
                 ]
 | 
			
		||||
 | 
			
		||||
setup(
 | 
			
		||||
    name             = "sleekxmpp",
 | 
			
		||||
    version          = VERSION,
 | 
			
		||||
    description      = DESCRIPTION,
 | 
			
		||||
    long_description = LONG_DESCRIPTION,
 | 
			
		||||
    author       = 'Nathanael Fritz',
 | 
			
		||||
    author_email = 'fritzy [at] netflint.net',
 | 
			
		||||
    url          = 'http://github.com/fritzy/SleekXMPP',
 | 
			
		||||
    license      = 'MIT',
 | 
			
		||||
    platforms    = [ 'any' ],
 | 
			
		||||
    packages     = packages,
 | 
			
		||||
    requires     = [ 'dnspython' ],
 | 
			
		||||
    classifiers  = CLASSIFIERS,
 | 
			
		||||
    cmdclass     = {'test': TestCommand}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -15,5 +15,4 @@ from sleekxmpp.xmlstream import XMLStream, RestartStream
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
 | 
			
		||||
 | 
			
		||||
__version__ = '1.0beta6.1'
 | 
			
		||||
__version_info__ = (1, 0, 0, 'beta6', 1)
 | 
			
		||||
from sleekxmpp.version import __version__, __version_info__
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,8 @@ import copy
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import plugins
 | 
			
		||||
from sleekxmpp import plugins, roster
 | 
			
		||||
from sleekxmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError
 | 
			
		||||
from sleekxmpp.stanza.roster import Roster
 | 
			
		||||
@@ -78,7 +79,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
       send_presence_subscribe -- Send a subscription request.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, default_ns='jabber:client'):
 | 
			
		||||
    def __init__(self, jid='', default_ns='jabber:client'):
 | 
			
		||||
        """
 | 
			
		||||
        Adapt an XML stream for use with XMPP.
 | 
			
		||||
 | 
			
		||||
@@ -94,16 +95,17 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        self.stream_ns = 'http://etherx.jabber.org/streams'
 | 
			
		||||
        self.namespace_map[self.stream_ns] = 'stream'
 | 
			
		||||
 | 
			
		||||
        self.boundjid = JID("")
 | 
			
		||||
        self.boundjid = JID(jid)
 | 
			
		||||
 | 
			
		||||
        self.plugin = {}
 | 
			
		||||
        self.plugin_config = {}
 | 
			
		||||
        self.plugin_whitelist = []
 | 
			
		||||
        self.roster = {}
 | 
			
		||||
        self.is_component = False
 | 
			
		||||
        self.auto_authorize = True
 | 
			
		||||
        self.auto_subscribe = True
 | 
			
		||||
 | 
			
		||||
        self.roster = roster.Roster(self)
 | 
			
		||||
        self.roster.add(self.boundjid.bare)
 | 
			
		||||
        self.client_roster = self.roster[self.boundjid.bare]
 | 
			
		||||
 | 
			
		||||
        self.is_component = False
 | 
			
		||||
        self.sentpresence = False
 | 
			
		||||
 | 
			
		||||
        self.stanza = sleekxmpp.stanza
 | 
			
		||||
@@ -122,10 +124,30 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
                     MatchXPath("{%s}error" % self.stream_ns),
 | 
			
		||||
                     self._handle_stream_error))
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('presence_subscribe',
 | 
			
		||||
                               self._handle_subscribe)
 | 
			
		||||
        self.add_event_handler('disconnected',
 | 
			
		||||
                               self._handle_disconnected)
 | 
			
		||||
        self.add_event_handler('presence_available',
 | 
			
		||||
                               self._handle_available)
 | 
			
		||||
        self.add_event_handler('presence_dnd',
 | 
			
		||||
                               self._handle_available)
 | 
			
		||||
        self.add_event_handler('presence_xa',
 | 
			
		||||
                               self._handle_available)
 | 
			
		||||
        self.add_event_handler('presence_chat',
 | 
			
		||||
                               self._handle_available)
 | 
			
		||||
        self.add_event_handler('presence_away',
 | 
			
		||||
                               self._handle_available)
 | 
			
		||||
        self.add_event_handler('presence_unavailable',
 | 
			
		||||
                               self._handle_unavailable)
 | 
			
		||||
        self.add_event_handler('presence_subscribe',
 | 
			
		||||
                               self._handle_subscribe)
 | 
			
		||||
        self.add_event_handler('presence_subscribed',
 | 
			
		||||
                               self._handle_subscribed)
 | 
			
		||||
        self.add_event_handler('presence_unsubscribe',
 | 
			
		||||
                               self._handle_unsubscribe)
 | 
			
		||||
        self.add_event_handler('presence_unsubscribed',
 | 
			
		||||
                               self._handle_unsubscribed)
 | 
			
		||||
        self.add_event_handler('roster_subscription_request',
 | 
			
		||||
                               self._handle_new_subscription)
 | 
			
		||||
 | 
			
		||||
        # Set up the XML stream with XMPP's root stanzas.
 | 
			
		||||
        self.register_stanza(Message)
 | 
			
		||||
@@ -138,6 +160,17 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        register_stanza_plugin(Message, Nick)
 | 
			
		||||
        register_stanza_plugin(Message, HTMLIM)
 | 
			
		||||
 | 
			
		||||
    def start_stream_handler(self, xml):
 | 
			
		||||
        """
 | 
			
		||||
        Save the stream ID once the streams have been established.
 | 
			
		||||
 | 
			
		||||
        Overrides XMLStream.start_stream_handler.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The incoming stream's root element.
 | 
			
		||||
        """
 | 
			
		||||
        self.stream_id = xml.get('id', '')
 | 
			
		||||
 | 
			
		||||
    def process(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Overrides XMLStream.process.
 | 
			
		||||
@@ -198,6 +231,10 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
                # the sleekxmpp package, so leave out the globals().
 | 
			
		||||
                module = __import__(module, fromlist=[plugin])
 | 
			
		||||
 | 
			
		||||
            # Use the global plugin config cache, if applicable
 | 
			
		||||
            if not pconfig:
 | 
			
		||||
                pconfig = self.plugin_config.get(plugin, {})
 | 
			
		||||
 | 
			
		||||
            # Load the plugin class from the module.
 | 
			
		||||
            self.plugin[plugin] = getattr(module, plugin)(self, pconfig)
 | 
			
		||||
 | 
			
		||||
@@ -431,7 +468,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            msubject -- Optional subject for the message.
 | 
			
		||||
            mtype    -- The message's type, such as 'chat' or 'groupchat'.
 | 
			
		||||
            mhtml    -- Optional HTML body content.
 | 
			
		||||
            mfrom    -- The sender of the message. If sending from a client,
 | 
			
		||||
            mfrom    -- The sender of the message. if sending from a client,
 | 
			
		||||
                        be aware that some servers require that the full JID
 | 
			
		||||
                        of the sender be used.
 | 
			
		||||
            mnick    -- Optional nickname of the sender.
 | 
			
		||||
@@ -446,7 +483,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        return message
 | 
			
		||||
 | 
			
		||||
    def make_presence(self, pshow=None, pstatus=None, ppriority=None,
 | 
			
		||||
                      pto=None, ptype=None, pfrom=None):
 | 
			
		||||
                      pto=None, ptype=None, pfrom=None, pnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create and initialize a new Presence stanza.
 | 
			
		||||
 | 
			
		||||
@@ -457,14 +494,16 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            pto       -- The recipient of a directed presence.
 | 
			
		||||
            ptype     -- The type of presence, such as 'subscribe'.
 | 
			
		||||
            pfrom     -- The sender of the presence.
 | 
			
		||||
            pnick     -- Optional nickname of the presence's sender.
 | 
			
		||||
        """
 | 
			
		||||
        presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
 | 
			
		||||
        if pshow is not None:
 | 
			
		||||
            presence['type'] = pshow
 | 
			
		||||
        if pfrom is None:
 | 
			
		||||
        if pfrom is None and self.is_component:
 | 
			
		||||
            presence['from'] = self.boundjid.full
 | 
			
		||||
        presence['priority'] = ppriority
 | 
			
		||||
        presence['status'] = pstatus
 | 
			
		||||
        presence['nick'] = pnick
 | 
			
		||||
        return presence
 | 
			
		||||
 | 
			
		||||
    def send_message(self, mto, mbody, msubject=None, mtype=None,
 | 
			
		||||
@@ -472,13 +511,22 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a Message stanza.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            mto      -- The recipient of the message.
 | 
			
		||||
            mbody    -- The main contents of the message.
 | 
			
		||||
            msubject -- Optional subject for the message.
 | 
			
		||||
            mtype    -- The message's type, such as 'chat' or 'groupchat'.
 | 
			
		||||
            mhtml    -- Optional HTML body content.
 | 
			
		||||
            mfrom    -- The sender of the message. if sending from a client,
 | 
			
		||||
                        be aware that some servers require that the full JID
 | 
			
		||||
                        of the sender be used.
 | 
			
		||||
            mnick    -- Optional nickname of the sender.
 | 
			
		||||
        """
 | 
			
		||||
        self.makeMessage(mto, mbody, msubject, mtype,
 | 
			
		||||
                         mhtml, mfrom, mnick).send()
 | 
			
		||||
        self.make_message(mto, mbody, msubject, mtype,
 | 
			
		||||
                          mhtml, mfrom, mnick).send()
 | 
			
		||||
 | 
			
		||||
    def send_presence(self, pshow=None, pstatus=None, ppriority=None,
 | 
			
		||||
                      pto=None, pfrom=None, ptype=None):
 | 
			
		||||
                      pto=None, pfrom=None, ptype=None, pnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a Presence stanza.
 | 
			
		||||
 | 
			
		||||
@@ -489,13 +537,20 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            pto       -- The recipient of a directed presence.
 | 
			
		||||
            ptype     -- The type of presence, such as 'subscribe'.
 | 
			
		||||
            pfrom     -- The sender of the presence.
 | 
			
		||||
            pnick     -- Optional nickname of the presence's sender.
 | 
			
		||||
        """
 | 
			
		||||
        self.makePresence(pshow, pstatus, ppriority, pto,
 | 
			
		||||
                          ptype=ptype, pfrom=pfrom).send()
 | 
			
		||||
        # Unexpected errors may occur if
 | 
			
		||||
        if not self.sentpresence:
 | 
			
		||||
            self.event('sent_presence')
 | 
			
		||||
            self.sentpresence = True
 | 
			
		||||
        # Python2.6 chokes on Unicode strings for dict keys.
 | 
			
		||||
        args = {str('pto'): pto,
 | 
			
		||||
                str('ptype'): ptype,
 | 
			
		||||
                str('pshow'): pshow,
 | 
			
		||||
                str('pstatus'): pstatus,
 | 
			
		||||
                str('ppriority'): ppriority,
 | 
			
		||||
                str('pnick'): pnick}
 | 
			
		||||
 | 
			
		||||
        if self.is_component:
 | 
			
		||||
            self.roster[pfrom].send_presence(**args)
 | 
			
		||||
        else:
 | 
			
		||||
            self.client_roster.send_presence(**args)
 | 
			
		||||
 | 
			
		||||
    def send_presence_subscription(self, pto, pfrom=None,
 | 
			
		||||
                                   ptype='subscribe', pnick=None):
 | 
			
		||||
@@ -582,6 +637,46 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        log.warning("server property deprecated. Use boundjid.host")
 | 
			
		||||
        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):
 | 
			
		||||
        """
 | 
			
		||||
        Auto accept or deny subscription requests.
 | 
			
		||||
 | 
			
		||||
        If True, auto accept subscription requests.
 | 
			
		||||
        If False, auto deny subscription requests.
 | 
			
		||||
        If None, don't automatically respond.
 | 
			
		||||
        """
 | 
			
		||||
        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):
 | 
			
		||||
        """
 | 
			
		||||
        Auto send requests for mutual subscriptions.
 | 
			
		||||
 | 
			
		||||
        If True, auto send mutual subscription requests.
 | 
			
		||||
        """
 | 
			
		||||
        self.roster.auto_subscribe = value
 | 
			
		||||
 | 
			
		||||
    def set_jid(self, jid):
 | 
			
		||||
        """Rip a JID apart and claim it as our own."""
 | 
			
		||||
        log.debug("setting jid to %s" % jid)
 | 
			
		||||
@@ -598,7 +693,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    def _handle_disconnected(self, event):
 | 
			
		||||
        """When disconnected, reset the roster"""
 | 
			
		||||
        self.roster = {}
 | 
			
		||||
        self.roster.reset()
 | 
			
		||||
 | 
			
		||||
    def _handle_stream_error(self, error):
 | 
			
		||||
        self.event('stream_error', error)
 | 
			
		||||
@@ -607,6 +702,65 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        """Process incoming message stanzas."""
 | 
			
		||||
        self.event('message', msg)
 | 
			
		||||
 | 
			
		||||
    def _handle_available(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].handle_available(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_unavailable(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].handle_unavailable(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_new_subscription(self, stanza):
 | 
			
		||||
        """
 | 
			
		||||
        Attempt to automatically handle subscription requests.
 | 
			
		||||
 | 
			
		||||
        Subscriptions will be approved if the request is from
 | 
			
		||||
        a whitelisted JID, of self.auto_authorize is True. They
 | 
			
		||||
        will be rejected if self.auto_authorize is False. Setting
 | 
			
		||||
        self.auto_authorize to None will disable automatic
 | 
			
		||||
        subscription handling (except for whitelisted JIDs).
 | 
			
		||||
 | 
			
		||||
        If a subscription is accepted, a request for a mutual
 | 
			
		||||
        subscription will be sent if self.auto_subscribe is True.
 | 
			
		||||
        """
 | 
			
		||||
        roster = self.roster[stanza['to'].bare]
 | 
			
		||||
        item = self.roster[stanza['to'].bare][stanza['from'].bare]
 | 
			
		||||
        if item['whitelisted']:
 | 
			
		||||
            item.authorize()
 | 
			
		||||
        elif roster.auto_authorize:
 | 
			
		||||
            item.authorize()
 | 
			
		||||
            if roster.auto_subscribe:
 | 
			
		||||
                item.subscribe()
 | 
			
		||||
        elif roster.auto_authorize == False:
 | 
			
		||||
            item.unauthorize()
 | 
			
		||||
 | 
			
		||||
    def _handle_removed_subscription(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].unauthorize()
 | 
			
		||||
 | 
			
		||||
    def _handle_subscribe(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].handle_subscribe(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_subscribed(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].handle_subscribed(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_unsubscribe(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].handle_unsubscribe(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_unsubscribed(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].handle_unsubscribed(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_presence(self, presence):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming presence stanzas.
 | 
			
		||||
@@ -624,97 +778,28 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
             not presence['type'] in presence.showtypes:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Strip the information from the stanza.
 | 
			
		||||
        jid = presence['from'].bare
 | 
			
		||||
        resource = presence['from'].resource
 | 
			
		||||
        show = presence['type']
 | 
			
		||||
        status = presence['status']
 | 
			
		||||
        priority = presence['priority']
 | 
			
		||||
 | 
			
		||||
        was_offline = False
 | 
			
		||||
        got_online = False
 | 
			
		||||
        old_roster = self.roster.get(jid, {}).get(resource, {})
 | 
			
		||||
 | 
			
		||||
        # Create a new roster entry if needed.
 | 
			
		||||
        if not jid in self.roster:
 | 
			
		||||
            self.roster[jid] = {'groups': [],
 | 
			
		||||
                                'name': '',
 | 
			
		||||
                                'subscription': 'none',
 | 
			
		||||
                                'presence': {},
 | 
			
		||||
                                'in_roster': False}
 | 
			
		||||
 | 
			
		||||
        # Alias to simplify some references.
 | 
			
		||||
        connections = self.roster[jid].get('presence', {})
 | 
			
		||||
 | 
			
		||||
        # Determine if the user has just come online.
 | 
			
		||||
        if not resource in connections:
 | 
			
		||||
            if show == 'available' or show in presence.showtypes:
 | 
			
		||||
                got_online = True
 | 
			
		||||
            was_offline = True
 | 
			
		||||
            connections[resource] = {}
 | 
			
		||||
 | 
			
		||||
        if connections[resource].get('show', 'unavailable') == 'unavailable':
 | 
			
		||||
            was_offline = True
 | 
			
		||||
 | 
			
		||||
        # Update the roster's state for this JID's resource.
 | 
			
		||||
        connections[resource] = {'show': show,
 | 
			
		||||
                                'status': status,
 | 
			
		||||
                                'priority': priority}
 | 
			
		||||
 | 
			
		||||
        name = self.roster[jid].get('name', '')
 | 
			
		||||
 | 
			
		||||
        # Remove unneeded state information after a resource
 | 
			
		||||
        # disconnects. Determine if this was the last connection
 | 
			
		||||
        # for the JID.
 | 
			
		||||
        if show == 'unavailable':
 | 
			
		||||
            log.debug("%s %s got offline" % (jid, resource))
 | 
			
		||||
            del connections[resource]
 | 
			
		||||
 | 
			
		||||
            if not connections and \
 | 
			
		||||
               not self.roster[jid].get('in_roster', False):
 | 
			
		||||
                del self.roster[jid]
 | 
			
		||||
            if not was_offline:
 | 
			
		||||
                self.event("got_offline", presence)
 | 
			
		||||
            else:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
        name = '(%s) ' % name if name else ''
 | 
			
		||||
 | 
			
		||||
        # Presence state has changed.
 | 
			
		||||
        self.event("changed_status", presence)
 | 
			
		||||
        if got_online:
 | 
			
		||||
            self.event("got_online", presence)
 | 
			
		||||
        log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
 | 
			
		||||
                                                   show, status))
 | 
			
		||||
 | 
			
		||||
    def _handle_subscribe(self, presence):
 | 
			
		||||
    def exception(self, exception):
 | 
			
		||||
        """
 | 
			
		||||
        Automatically managage subscription requests.
 | 
			
		||||
        Process any uncaught exceptions, notably IqError and
 | 
			
		||||
        IqTimeout exceptions.
 | 
			
		||||
 | 
			
		||||
        Subscription behavior is controlled by the settings
 | 
			
		||||
        self.auto_authorize and self.auto_subscribe.
 | 
			
		||||
        Overrides XMLStream.exception.
 | 
			
		||||
 | 
			
		||||
        auto_auth  auto_sub   Result:
 | 
			
		||||
        True       True       Create bi-directional subsriptions.
 | 
			
		||||
        True       False      Create only directed subscriptions.
 | 
			
		||||
        False      *          Decline all subscriptions.
 | 
			
		||||
        None       *          Disable automatic handling and use
 | 
			
		||||
                              a custom handler.
 | 
			
		||||
        Arguments:
 | 
			
		||||
            exception -- An unhandled exception object.
 | 
			
		||||
        """
 | 
			
		||||
        presence.reply()
 | 
			
		||||
        presence['to'] = presence['to'].bare
 | 
			
		||||
        if isinstance(exception, IqError):
 | 
			
		||||
            iq = exception.iq
 | 
			
		||||
            log.error('%s: %s' % (iq['error']['condition'],
 | 
			
		||||
                                  iq['error']['text']))
 | 
			
		||||
            log.warning('You should catch IqError exceptions')
 | 
			
		||||
        elif isinstance(exception, IqTimeout):
 | 
			
		||||
            iq = exception.iq
 | 
			
		||||
            log.error('Request timed out: %s' % iq)
 | 
			
		||||
            log.warning('You should catch IqTimeout exceptions')
 | 
			
		||||
        else:
 | 
			
		||||
            log.exception(exception)
 | 
			
		||||
 | 
			
		||||
        # We are using trinary logic, so conditions have to be
 | 
			
		||||
        # more explicit than usual.
 | 
			
		||||
        if self.auto_authorize == True:
 | 
			
		||||
            presence['type'] = 'subscribed'
 | 
			
		||||
            presence.send()
 | 
			
		||||
            if self.auto_subscribe:
 | 
			
		||||
                presence['type'] = 'subscribe'
 | 
			
		||||
                presence.send()
 | 
			
		||||
        elif self.auto_authorize == False:
 | 
			
		||||
            presence['type'] = 'unsubscribed'
 | 
			
		||||
            presence.send()
 | 
			
		||||
 | 
			
		||||
# Restore the old, lowercased name for backwards compatibility.
 | 
			
		||||
basexmpp = BaseXMPP
 | 
			
		||||
 
 | 
			
		||||
@@ -27,11 +27,12 @@ from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
 | 
			
		||||
# Flag indicating if DNS SRV records are available for use.
 | 
			
		||||
SRV_SUPPORT = True
 | 
			
		||||
try:
 | 
			
		||||
    import dns.resolver
 | 
			
		||||
except:
 | 
			
		||||
    SRV_SUPPORT = False
 | 
			
		||||
except ImportError:
 | 
			
		||||
    DNSPYTHON = False
 | 
			
		||||
else:
 | 
			
		||||
    DNSPYTHON = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
@@ -58,7 +59,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, ssl=False, plugin_config={},
 | 
			
		||||
                 plugin_whitelist=[], escape_quotes=True):
 | 
			
		||||
                 plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new SleekXMPP client.
 | 
			
		||||
 | 
			
		||||
@@ -71,14 +72,14 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                                when calling register_plugins.
 | 
			
		||||
            escape_quotes    -- Deprecated.
 | 
			
		||||
        """
 | 
			
		||||
        BaseXMPP.__init__(self, 'jabber:client')
 | 
			
		||||
        BaseXMPP.__init__(self, jid, 'jabber:client')
 | 
			
		||||
 | 
			
		||||
        self.set_jid(jid)
 | 
			
		||||
        self.password = password
 | 
			
		||||
        self.escape_quotes = escape_quotes
 | 
			
		||||
        self.plugin_config = plugin_config
 | 
			
		||||
        self.plugin_whitelist = plugin_whitelist
 | 
			
		||||
        self.srv_support = SRV_SUPPORT
 | 
			
		||||
        self.default_port = 5222
 | 
			
		||||
 | 
			
		||||
        self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
 | 
			
		||||
                self.boundjid.host,
 | 
			
		||||
@@ -113,11 +114,13 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
 | 
			
		||||
        # Setup default stream features
 | 
			
		||||
        self.register_plugin('feature_starttls')
 | 
			
		||||
        self.register_plugin('feature_mechanisms')
 | 
			
		||||
        self.register_plugin('feature_bind')
 | 
			
		||||
        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.
 | 
			
		||||
 | 
			
		||||
@@ -131,56 +134,47 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                         error occurs. Defaults to True.
 | 
			
		||||
            use_tls   -- Indicates if TLS should be used for the
 | 
			
		||||
                         connection. Defaults to True.
 | 
			
		||||
            use_ssl   -- Indicates if the older SSL connection method
 | 
			
		||||
                         should be used. Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        self.session_started_event.clear()
 | 
			
		||||
        if not address or len(address) < 2:
 | 
			
		||||
            if not self.srv_support:
 | 
			
		||||
                log.debug("Did not supply (address, port) to connect" + \
 | 
			
		||||
                              " to and no SRV support is installed" + \
 | 
			
		||||
                              " (http://www.dnspython.org)." + \
 | 
			
		||||
                              " Continuing to attempt connection, using" + \
 | 
			
		||||
                              " server hostname from JID.")
 | 
			
		||||
            else:
 | 
			
		||||
                log.debug("Since no address is supplied," + \
 | 
			
		||||
                              "attempting SRV lookup.")
 | 
			
		||||
                try:
 | 
			
		||||
                    xmpp_srv = "_xmpp-client._tcp.%s" % self.boundjid.host
 | 
			
		||||
                    answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV)
 | 
			
		||||
                except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
 | 
			
		||||
                    log.debug("No appropriate SRV record found." + \
 | 
			
		||||
                                  " Using JID server name.")
 | 
			
		||||
                except (dns.exception.Timeout,):
 | 
			
		||||
                    log.debug("DNS resolution timed out.")
 | 
			
		||||
                else:
 | 
			
		||||
                    # Pick a random server, weighted by priority.
 | 
			
		||||
 | 
			
		||||
                    addresses = {}
 | 
			
		||||
                    intmax = 0
 | 
			
		||||
                    topprio = 65535
 | 
			
		||||
                    for answer in answers:
 | 
			
		||||
                        topprio = min(topprio, answer.priority)
 | 
			
		||||
                    for answer in answers:
 | 
			
		||||
                        if answer.priority == topprio:
 | 
			
		||||
                            intmax += answer.weight
 | 
			
		||||
                            addresses[intmax] = (answer.target.to_text()[:-1],
 | 
			
		||||
                                             answer.port)
 | 
			
		||||
 | 
			
		||||
                    #python3 returns a generator for dictionary keys
 | 
			
		||||
                    items = [x for x in addresses.keys()]
 | 
			
		||||
                    items.sort()
 | 
			
		||||
 | 
			
		||||
                    picked = random.randint(0, intmax)
 | 
			
		||||
                    for item in items:
 | 
			
		||||
                        if picked <= item:
 | 
			
		||||
                            address = addresses[item]
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
        if not address:
 | 
			
		||||
            # If all else fails, use the server from the JID.
 | 
			
		||||
            address = (self.boundjid.host, 5222)
 | 
			
		||||
 | 
			
		||||
        return XMLStream.connect(self, address[0], address[1],
 | 
			
		||||
                                 use_tls=use_tls, reattempt=reattempt)
 | 
			
		||||
                                 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.
 | 
			
		||||
        Overriddes XMLStream.get_dns_records to use SRV.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            domain -- The domain in question.
 | 
			
		||||
            port   -- If the results don't include a port, use this one.
 | 
			
		||||
        """
 | 
			
		||||
        if port is None:
 | 
			
		||||
            port = self.default_port
 | 
			
		||||
        if DNSPYTHON:
 | 
			
		||||
            try:
 | 
			
		||||
                record = "_xmpp-client._tcp.%s" % domain
 | 
			
		||||
                answers = []
 | 
			
		||||
                for answer in dns.resolver.query(record, dns.rdatatype.SRV):
 | 
			
		||||
                    address = (answer.target.to_text()[:-1], answer.port)
 | 
			
		||||
                    answers.append((address, answer.priority, answer.weight))
 | 
			
		||||
            except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
 | 
			
		||||
                log.warning("No SRV records for %s" % domain)
 | 
			
		||||
                answers = super(ClientXMPP, self).get_dns_records(domain, port)
 | 
			
		||||
            except dns.exception.Timeout:
 | 
			
		||||
                log.warning("DNS resolution timed out " + \
 | 
			
		||||
                            "for SRV record of %s" % domain)
 | 
			
		||||
                answers = super(ClientXMPP, self).get_dns_records(domain, port)
 | 
			
		||||
            return answers
 | 
			
		||||
        else:
 | 
			
		||||
            log.warning("dnspython is not installed -- " + \
 | 
			
		||||
                        "relying on OS A record resolution")
 | 
			
		||||
            return [((domain, port), 0, 0)]
 | 
			
		||||
 | 
			
		||||
    def register_feature(self, name, handler, restart=False, order=5000):
 | 
			
		||||
        """
 | 
			
		||||
@@ -221,15 +215,8 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                            Will be executed when the roster is received.
 | 
			
		||||
                            Implies block=False.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['roster']['items'] = {jid: {'name': name,
 | 
			
		||||
                                       'subscription': subscription,
 | 
			
		||||
                                       'groups': groups}}
 | 
			
		||||
        response = iq.send(block, timeout, callback)
 | 
			
		||||
        if response in [False, None] or not isinstance(response, Iq):
 | 
			
		||||
            return response
 | 
			
		||||
        return response['type'] == 'result'
 | 
			
		||||
        return self.client_roster.update(jid, name, subscription, groups,
 | 
			
		||||
                                         block, timeout, callback)
 | 
			
		||||
 | 
			
		||||
    def del_roster_item(self, jid):
 | 
			
		||||
        """
 | 
			
		||||
@@ -239,7 +226,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid -- The JID of the item to remove.
 | 
			
		||||
        """
 | 
			
		||||
        return self.update_roster(jid, subscription='remove')
 | 
			
		||||
        return self.client_roster.remove(jid)
 | 
			
		||||
 | 
			
		||||
    def get_roster(self, block=True, timeout=None, callback=None):
 | 
			
		||||
        """
 | 
			
		||||
@@ -261,12 +248,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        iq.enable('roster')
 | 
			
		||||
        response = iq.send(block, timeout, callback)
 | 
			
		||||
 | 
			
		||||
        if response == False:
 | 
			
		||||
            self.event('roster_timeout')
 | 
			
		||||
 | 
			
		||||
        if response in [False, None] or not isinstance(response, Iq):
 | 
			
		||||
            return response
 | 
			
		||||
        else:
 | 
			
		||||
        if callback is None:
 | 
			
		||||
            return self._handle_roster(response, request=True)
 | 
			
		||||
 | 
			
		||||
    def _handle_connected(self, event=None):
 | 
			
		||||
@@ -277,13 +259,6 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        self.bindfail = False
 | 
			
		||||
        self.features = set()
 | 
			
		||||
 | 
			
		||||
        def session_timeout():
 | 
			
		||||
            if not self.session_started_event.isSet():
 | 
			
		||||
                log.debug("Session start has taken more than 15 seconds")
 | 
			
		||||
                self.disconnect(reconnect=self.auto_reconnect)
 | 
			
		||||
 | 
			
		||||
        self.schedule("session timeout checker", 15, session_timeout)
 | 
			
		||||
 | 
			
		||||
    def _handle_stream_features(self, features):
 | 
			
		||||
        """
 | 
			
		||||
        Process the received stream features.
 | 
			
		||||
@@ -310,13 +285,13 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        """
 | 
			
		||||
        if iq['type'] == 'set' or (iq['type'] == 'result' and request):
 | 
			
		||||
            for jid in iq['roster']['items']:
 | 
			
		||||
                if not jid in self.roster:
 | 
			
		||||
                    self.roster[jid] = {'groups': [],
 | 
			
		||||
                                        'name': '',
 | 
			
		||||
                                        'subscription': 'none',
 | 
			
		||||
                                        'presence': {},
 | 
			
		||||
                                        'in_roster': True}
 | 
			
		||||
                self.roster[jid].update(iq['roster']['items'][jid])
 | 
			
		||||
                item = iq['roster']['items'][jid]
 | 
			
		||||
                roster = self.roster[iq['to'].bare]
 | 
			
		||||
                roster[jid]['name'] = item['name']
 | 
			
		||||
                roster[jid]['groups'] = item['groups']
 | 
			
		||||
                roster[jid]['from'] = item['subscription'] in ['from', 'both']
 | 
			
		||||
                roster[jid]['to'] = item['subscription'] in ['to', 'both']
 | 
			
		||||
                roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
 | 
			
		||||
            self.event('roster_received', iq)
 | 
			
		||||
 | 
			
		||||
        self.event("roster_update", iq)
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
            default_ns = 'jabber:client'
 | 
			
		||||
        else:
 | 
			
		||||
            default_ns = 'jabber:component:accept'
 | 
			
		||||
        BaseXMPP.__init__(self, default_ns)
 | 
			
		||||
        BaseXMPP.__init__(self, jid, default_ns)
 | 
			
		||||
 | 
			
		||||
        self.auto_authorize = None
 | 
			
		||||
        self.stream_header = "<stream:stream %s %s to='%s'>" % (
 | 
			
		||||
@@ -68,8 +68,8 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        self.stream_footer = "</stream:stream>"
 | 
			
		||||
        self.server_host = host
 | 
			
		||||
        self.server_port = port
 | 
			
		||||
        self.set_jid(jid)
 | 
			
		||||
        self.secret = secret
 | 
			
		||||
 | 
			
		||||
        self.plugin_config = plugin_config
 | 
			
		||||
        self.plugin_whitelist = plugin_whitelist
 | 
			
		||||
        self.is_component = True
 | 
			
		||||
@@ -78,6 +78,8 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
                Callback('Handshake',
 | 
			
		||||
                         MatchXPath('{jabber:component:accept}handshake'),
 | 
			
		||||
                         self._handle_handshake))
 | 
			
		||||
        self.add_event_handler('presence_probe',
 | 
			
		||||
                               self._handle_probe)
 | 
			
		||||
 | 
			
		||||
    def connect(self):
 | 
			
		||||
        """
 | 
			
		||||
@@ -115,11 +117,13 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        Once the streams are established, attempt to handshake
 | 
			
		||||
        with the server to be accepted as a component.
 | 
			
		||||
 | 
			
		||||
        Overrides XMLStream.start_stream_handler.
 | 
			
		||||
        Overrides BaseXMPP.start_stream_handler.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The incoming stream's root element.
 | 
			
		||||
        """
 | 
			
		||||
        BaseXMPP.start_stream_handler(self, xml)
 | 
			
		||||
 | 
			
		||||
        # Construct a hash of the stream ID and the component secret.
 | 
			
		||||
        sid = xml.get('id', '')
 | 
			
		||||
        pre_hash = '%s%s' % (sid, self.secret)
 | 
			
		||||
@@ -140,3 +144,8 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        """
 | 
			
		||||
        self.session_started_event.set()
 | 
			
		||||
        self.event("session_start")
 | 
			
		||||
 | 
			
		||||
    def _handle_probe(self, presence):
 | 
			
		||||
        pto = presence['to'].bare
 | 
			
		||||
        pfrom = presence['from'].bare
 | 
			
		||||
        self.roster[pto][pfrom].handle_probe(presence)
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,9 @@ class XMPPError(Exception):
 | 
			
		||||
    Meant for use in SleekXMPP plugins and applications using SleekXMPP.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, condition='undefined-condition', text=None, etype=None,
 | 
			
		||||
                 extension=None, extension_ns=None, extension_args=None,
 | 
			
		||||
                 clear=True):
 | 
			
		||||
    def __init__(self, condition='undefined-condition', text=None,
 | 
			
		||||
                etype='cancel', extension=None, extension_ns=None,
 | 
			
		||||
                extension_args=None, clear=True):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new XMPPError exception.
 | 
			
		||||
 | 
			
		||||
@@ -31,8 +31,10 @@ class XMPPError(Exception):
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            condition      -- The XMPP defined error condition.
 | 
			
		||||
                              Defaults to 'undefined-condition'.
 | 
			
		||||
            text           -- Human readable text describing the error.
 | 
			
		||||
            etype          -- The XMPP error type, such as cancel or modify.
 | 
			
		||||
                              Defaults to 'cancel'.
 | 
			
		||||
            extension      -- Tag name of the extension's XML content.
 | 
			
		||||
            extension_ns   -- XML namespace of the extensions' XML content.
 | 
			
		||||
            extension_args -- Content and attributes for the extension
 | 
			
		||||
@@ -52,3 +54,33 @@ class XMPPError(Exception):
 | 
			
		||||
        self.extension = extension
 | 
			
		||||
        self.extension_ns = extension_ns
 | 
			
		||||
        self.extension_args = extension_args
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IqTimeout(XMPPError):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    An exception which indicates that an IQ request response has not been
 | 
			
		||||
    received within the alloted time window.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, iq):
 | 
			
		||||
        super(IqTimeout, self).__init__(
 | 
			
		||||
                condition='remote-server-timeout',
 | 
			
		||||
                etype='cancel')
 | 
			
		||||
 | 
			
		||||
        self.iq = iq
 | 
			
		||||
 | 
			
		||||
class IqError(XMPPError):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    An exception raised when an Iq stanza of type 'error' is received
 | 
			
		||||
    after making a blocking send call.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, iq):
 | 
			
		||||
        super(IqError, self).__init__(
 | 
			
		||||
                condition=iq['error']['condition'],
 | 
			
		||||
                text=iq['error']['text'],
 | 
			
		||||
                etype=iq['error']['type'])
 | 
			
		||||
 | 
			
		||||
        self.iq = iq
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,8 @@ class feature_mechanisms(base_plugin):
 | 
			
		||||
        self.description = "SASL Stream Feature"
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.use_mech = self.config.get('use_mech', None)
 | 
			
		||||
 | 
			
		||||
        def tls_active():
 | 
			
		||||
            return 'starttls' in self.xmpp.features
 | 
			
		||||
 | 
			
		||||
@@ -48,7 +50,8 @@ class feature_mechanisms(base_plugin):
 | 
			
		||||
                                username=self.xmpp.boundjid.user,
 | 
			
		||||
                                sec_query=suelta.sec_query_allow,
 | 
			
		||||
                                request_values=sasl_callback,
 | 
			
		||||
                                tls_active=tls_active)
 | 
			
		||||
                                tls_active=tls_active,
 | 
			
		||||
                                mech=self.use_mech)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(StreamFeatures, stanza.Mechanisms)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199',
 | 
			
		||||
           'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify']
 | 
			
		||||
 | 
			
		||||
# Don't automatically load xep_0078
 | 
			
		||||
 
 | 
			
		||||
@@ -1,395 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
	SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
	Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
	This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
	See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import copy
 | 
			
		||||
from . import base
 | 
			
		||||
from .. xmlstream.handler.callback import Callback
 | 
			
		||||
from .. xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from .. stanza.message import Message
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Form(ElementBase):
 | 
			
		||||
	namespace = 'jabber:x:data'
 | 
			
		||||
	name = 'x'
 | 
			
		||||
	plugin_attrib = 'form'
 | 
			
		||||
	interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values'))
 | 
			
		||||
	sub_interfaces = set(('title',))
 | 
			
		||||
	form_types = set(('cancel', 'form', 'result', 'submit'))
 | 
			
		||||
 | 
			
		||||
	def __init__(self, *args, **kwargs):
 | 
			
		||||
		title = None
 | 
			
		||||
		if 'title' in kwargs:
 | 
			
		||||
			title = kwargs['title']
 | 
			
		||||
			del kwargs['title']
 | 
			
		||||
		ElementBase.__init__(self, *args, **kwargs)
 | 
			
		||||
		if title is not None:
 | 
			
		||||
			self['title'] = title
 | 
			
		||||
		self.field = FieldAccessor(self)
 | 
			
		||||
 | 
			
		||||
	def setup(self, xml=None):
 | 
			
		||||
		if ElementBase.setup(self, xml): #if we had to generate xml
 | 
			
		||||
			self['type'] = 'form'
 | 
			
		||||
 | 
			
		||||
	def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs):
 | 
			
		||||
		kwtype = kwargs.get('type', None)
 | 
			
		||||
		if kwtype is None:
 | 
			
		||||
			kwtype = ftype
 | 
			
		||||
 | 
			
		||||
		field = FormField(parent=self)
 | 
			
		||||
		field['var'] = var
 | 
			
		||||
		field['type'] = kwtype
 | 
			
		||||
		field['label'] = label
 | 
			
		||||
		field['desc'] = desc
 | 
			
		||||
		field['required'] = required
 | 
			
		||||
		field['value'] = value
 | 
			
		||||
		if options is not None:
 | 
			
		||||
			field['options'] = options
 | 
			
		||||
		return field
 | 
			
		||||
 | 
			
		||||
	def getXML(self, type='submit'):
 | 
			
		||||
		self['type'] = type
 | 
			
		||||
		log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
 | 
			
		||||
		return self.xml
 | 
			
		||||
 | 
			
		||||
	def fromXML(self, xml):
 | 
			
		||||
		log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
 | 
			
		||||
		n = Form(xml=xml)
 | 
			
		||||
		return n
 | 
			
		||||
 | 
			
		||||
	def addItem(self, values):
 | 
			
		||||
		itemXML = ET.Element('{%s}item' % self.namespace)
 | 
			
		||||
		self.xml.append(itemXML)
 | 
			
		||||
		reported_vars = self['reported'].keys()
 | 
			
		||||
		for var in reported_vars:
 | 
			
		||||
			fieldXML = ET.Element('{%s}field' % FormField.namespace)
 | 
			
		||||
			itemXML.append(fieldXML)
 | 
			
		||||
			field = FormField(xml=fieldXML)
 | 
			
		||||
			field['var'] = var
 | 
			
		||||
			field['value'] = values.get(var, None)
 | 
			
		||||
 | 
			
		||||
	def addReported(self, var, ftype=None, label='', desc='', **kwargs):
 | 
			
		||||
		kwtype = kwargs.get('type', None)
 | 
			
		||||
		if kwtype is None:
 | 
			
		||||
			kwtype = ftype
 | 
			
		||||
		reported = self.xml.find('{%s}reported' % self.namespace)
 | 
			
		||||
		if reported is None:
 | 
			
		||||
			reported = ET.Element('{%s}reported' % self.namespace)
 | 
			
		||||
			self.xml.append(reported)
 | 
			
		||||
		fieldXML = ET.Element('{%s}field' % FormField.namespace)
 | 
			
		||||
		reported.append(fieldXML)
 | 
			
		||||
		field = FormField(xml=fieldXML)
 | 
			
		||||
		field['var'] = var
 | 
			
		||||
		field['type'] = kwtype
 | 
			
		||||
		field['label'] = label
 | 
			
		||||
		field['desc'] = desc
 | 
			
		||||
		return field
 | 
			
		||||
 | 
			
		||||
	def cancel(self):
 | 
			
		||||
		self['type'] = 'cancel'
 | 
			
		||||
 | 
			
		||||
	def delFields(self):
 | 
			
		||||
		fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
 | 
			
		||||
		for fieldXML in fieldsXML:
 | 
			
		||||
			self.xml.remove(fieldXML)
 | 
			
		||||
 | 
			
		||||
	def delInstructions(self):
 | 
			
		||||
		instsXML = self.xml.findall('{%s}instructions')
 | 
			
		||||
		for instXML in instsXML:
 | 
			
		||||
			self.xml.remove(instXML)
 | 
			
		||||
 | 
			
		||||
	def delItems(self):
 | 
			
		||||
		itemsXML = self.xml.find('{%s}item' % self.namespace)
 | 
			
		||||
		for itemXML in itemsXML:
 | 
			
		||||
			self.xml.remove(itemXML)
 | 
			
		||||
 | 
			
		||||
	def delReported(self):
 | 
			
		||||
		reportedXML = self.xml.find('{%s}reported' % self.namespace)
 | 
			
		||||
		if reportedXML is not None:
 | 
			
		||||
			self.xml.remove(reportedXML)
 | 
			
		||||
 | 
			
		||||
	def getFields(self, use_dict=False):
 | 
			
		||||
		fields = {} if use_dict else []
 | 
			
		||||
		fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
 | 
			
		||||
		for fieldXML in fieldsXML:
 | 
			
		||||
			field = FormField(xml=fieldXML)
 | 
			
		||||
			if use_dict:
 | 
			
		||||
				fields[field['var']] = field
 | 
			
		||||
			else:
 | 
			
		||||
				fields.append((field['var'], field))
 | 
			
		||||
		return fields
 | 
			
		||||
 | 
			
		||||
	def getInstructions(self):
 | 
			
		||||
		instructions = ''
 | 
			
		||||
		instsXML = self.xml.findall('{%s}instructions' % self.namespace)
 | 
			
		||||
		return "\n".join([instXML.text for instXML in instsXML])
 | 
			
		||||
 | 
			
		||||
	def getItems(self):
 | 
			
		||||
		items = []
 | 
			
		||||
		itemsXML = self.xml.findall('{%s}item' % self.namespace)
 | 
			
		||||
		for itemXML in itemsXML:
 | 
			
		||||
			item = {}
 | 
			
		||||
			fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
 | 
			
		||||
			for fieldXML in fieldsXML:
 | 
			
		||||
				field = FormField(xml=fieldXML)
 | 
			
		||||
				item[field['var']] = field['value']
 | 
			
		||||
			items.append(item)
 | 
			
		||||
		return items
 | 
			
		||||
 | 
			
		||||
	def getReported(self):
 | 
			
		||||
		fields = {}
 | 
			
		||||
		fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
 | 
			
		||||
									 FormField.namespace))
 | 
			
		||||
		for fieldXML in fieldsXML:
 | 
			
		||||
			field = FormField(xml=fieldXML)
 | 
			
		||||
			fields[field['var']] = field
 | 
			
		||||
		return fields
 | 
			
		||||
 | 
			
		||||
	def getValues(self):
 | 
			
		||||
		values = {}
 | 
			
		||||
		fields = self.getFields(use_dict=True)
 | 
			
		||||
		for var in fields:
 | 
			
		||||
			values[var] = fields[var]['value']
 | 
			
		||||
		return values
 | 
			
		||||
 | 
			
		||||
	def reply(self):
 | 
			
		||||
		if self['type'] == 'form':
 | 
			
		||||
			self['type'] = 'submit'
 | 
			
		||||
		elif self['type'] == 'submit':
 | 
			
		||||
			self['type'] = 'result'
 | 
			
		||||
 | 
			
		||||
	def setFields(self, fields, default=None):
 | 
			
		||||
		del self['fields']
 | 
			
		||||
		for field_data in fields:
 | 
			
		||||
			var = field_data[0]
 | 
			
		||||
			field = field_data[1]
 | 
			
		||||
			field['var'] = var
 | 
			
		||||
 | 
			
		||||
			self.addField(**field)
 | 
			
		||||
 | 
			
		||||
	def setInstructions(self, instructions):
 | 
			
		||||
		del self['instructions']
 | 
			
		||||
		if instructions in [None, '']:
 | 
			
		||||
			return
 | 
			
		||||
		instructions = instructions.split('\n')
 | 
			
		||||
		for instruction in instructions:
 | 
			
		||||
			inst = ET.Element('{%s}instructions' % self.namespace)
 | 
			
		||||
			inst.text = instruction
 | 
			
		||||
			self.xml.append(inst)
 | 
			
		||||
 | 
			
		||||
	def setItems(self, items):
 | 
			
		||||
		for item in items:
 | 
			
		||||
			self.addItem(item)
 | 
			
		||||
 | 
			
		||||
	def setReported(self, reported, default=None):
 | 
			
		||||
		for var in reported:
 | 
			
		||||
			field = reported[var]
 | 
			
		||||
			field['var'] = var
 | 
			
		||||
			self.addReported(var, **field)
 | 
			
		||||
 | 
			
		||||
	def setValues(self, values):
 | 
			
		||||
		fields = self.getFields(use_dict=True)
 | 
			
		||||
		for field in values:
 | 
			
		||||
			fields[field]['value'] = values[field]
 | 
			
		||||
 | 
			
		||||
	def merge(self, other):
 | 
			
		||||
		new = copy.copy(self)
 | 
			
		||||
		if type(other) == dict:
 | 
			
		||||
			new.setValues(other)
 | 
			
		||||
			return new
 | 
			
		||||
		nfields = new.getFields(use_dict=True)
 | 
			
		||||
		ofields = other.getFields(use_dict=True)
 | 
			
		||||
		nfields.update(ofields)
 | 
			
		||||
		new.setFields([(x, nfields[x]) for x in nfields])
 | 
			
		||||
		return new
 | 
			
		||||
 | 
			
		||||
class FieldAccessor(object):
 | 
			
		||||
	def __init__(self, form):
 | 
			
		||||
		self.form = form
 | 
			
		||||
 | 
			
		||||
	def __getitem__(self, key):
 | 
			
		||||
		return self.form.getFields(use_dict=True)[key]
 | 
			
		||||
 | 
			
		||||
	def __contains__(self, key):
 | 
			
		||||
		return key in self.form.getFields(use_dict=True)
 | 
			
		||||
 | 
			
		||||
	def has_key(self, key):
 | 
			
		||||
		return key in self.form.getFields(use_dict=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FormField(ElementBase):
 | 
			
		||||
	namespace = 'jabber:x:data'
 | 
			
		||||
	name = 'field'
 | 
			
		||||
	plugin_attrib = 'field'
 | 
			
		||||
	interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var'))
 | 
			
		||||
	sub_interfaces = set(('desc',))
 | 
			
		||||
	field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
 | 
			
		||||
			   'list-single', 'text-multi', 'text-private', 'text-single'))
 | 
			
		||||
	multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi'))
 | 
			
		||||
	multi_line_types = set(('hidden', 'text-multi'))
 | 
			
		||||
	option_types = set(('list-multi', 'list-single'))
 | 
			
		||||
	true_values = set((True, '1', 'true'))
 | 
			
		||||
 | 
			
		||||
	def addOption(self, label='', value=''):
 | 
			
		||||
		if self['type'] in self.option_types:
 | 
			
		||||
			opt = FieldOption(parent=self)
 | 
			
		||||
			opt['label'] = label
 | 
			
		||||
			opt['value'] = value
 | 
			
		||||
		else:
 | 
			
		||||
			raise ValueError("Cannot add options to a %s field." % self['type'])
 | 
			
		||||
 | 
			
		||||
	def delOptions(self):
 | 
			
		||||
		optsXML = self.xml.findall('{%s}option' % self.namespace)
 | 
			
		||||
		for optXML in optsXML:
 | 
			
		||||
			self.xml.remove(optXML)
 | 
			
		||||
 | 
			
		||||
	def delRequired(self):
 | 
			
		||||
		reqXML = self.xml.find('{%s}required' % self.namespace)
 | 
			
		||||
		if reqXML is not None:
 | 
			
		||||
			self.xml.remove(reqXML)
 | 
			
		||||
 | 
			
		||||
	def delValue(self):
 | 
			
		||||
		valsXML = self.xml.findall('{%s}value' % self.namespace)
 | 
			
		||||
		for valXML in valsXML:
 | 
			
		||||
			self.xml.remove(valXML)
 | 
			
		||||
 | 
			
		||||
	def getAnswer(self):
 | 
			
		||||
		return self.getValue()
 | 
			
		||||
 | 
			
		||||
	def getOptions(self):
 | 
			
		||||
		options = []
 | 
			
		||||
		optsXML = self.xml.findall('{%s}option' % self.namespace)
 | 
			
		||||
		for optXML in optsXML:
 | 
			
		||||
			opt = FieldOption(xml=optXML)
 | 
			
		||||
			options.append({'label': opt['label'], 'value':opt['value']})
 | 
			
		||||
		return options
 | 
			
		||||
 | 
			
		||||
	def getRequired(self):
 | 
			
		||||
		reqXML = self.xml.find('{%s}required' % self.namespace)
 | 
			
		||||
		return reqXML is not None
 | 
			
		||||
 | 
			
		||||
	def getValue(self):
 | 
			
		||||
		valsXML = self.xml.findall('{%s}value' % self.namespace)
 | 
			
		||||
		if len(valsXML) == 0:
 | 
			
		||||
			return None
 | 
			
		||||
		elif self['type'] == 'boolean':
 | 
			
		||||
			return valsXML[0].text in self.true_values
 | 
			
		||||
		elif self['type'] in self.multi_value_types:
 | 
			
		||||
			values = []
 | 
			
		||||
			for valXML in valsXML:
 | 
			
		||||
				if valXML.text is None:
 | 
			
		||||
					valXML.text = ''
 | 
			
		||||
				values.append(valXML.text)
 | 
			
		||||
			if self['type'] == 'text-multi':
 | 
			
		||||
				values = "\n".join(values)
 | 
			
		||||
			return values
 | 
			
		||||
		else:
 | 
			
		||||
			return valsXML[0].text
 | 
			
		||||
 | 
			
		||||
	def setAnswer(self, answer):
 | 
			
		||||
		self.setValue(answer)
 | 
			
		||||
 | 
			
		||||
	def setFalse(self):
 | 
			
		||||
		self.setValue(False)
 | 
			
		||||
 | 
			
		||||
	def setOptions(self, options):
 | 
			
		||||
		for value in options:
 | 
			
		||||
			if isinstance(value, dict):
 | 
			
		||||
				self.addOption(**value)
 | 
			
		||||
			else:
 | 
			
		||||
				self.addOption(value=value)
 | 
			
		||||
 | 
			
		||||
	def setRequired(self, required):
 | 
			
		||||
		exists = self.getRequired()
 | 
			
		||||
		if not exists and required:
 | 
			
		||||
			self.xml.append(ET.Element('{%s}required' % self.namespace))
 | 
			
		||||
		elif exists and not required:
 | 
			
		||||
			self.delRequired()
 | 
			
		||||
 | 
			
		||||
	def setTrue(self):
 | 
			
		||||
		self.setValue(True)
 | 
			
		||||
 | 
			
		||||
	def setValue(self, value):
 | 
			
		||||
		self.delValue()
 | 
			
		||||
		valXMLName = '{%s}value' % self.namespace
 | 
			
		||||
 | 
			
		||||
		if self['type'] == 'boolean':
 | 
			
		||||
			if value in self.true_values:
 | 
			
		||||
				valXML = ET.Element(valXMLName)
 | 
			
		||||
				valXML.text = '1'
 | 
			
		||||
				self.xml.append(valXML)
 | 
			
		||||
			else:
 | 
			
		||||
				valXML = ET.Element(valXMLName)
 | 
			
		||||
				valXML.text = '0'
 | 
			
		||||
				self.xml.append(valXML)
 | 
			
		||||
		elif self['type'] in self.multi_value_types or self['type'] in ['', None]:
 | 
			
		||||
			if self['type'] in self.multi_line_types and isinstance(value, str):
 | 
			
		||||
				value = value.split('\n')
 | 
			
		||||
			if not isinstance(value, list):
 | 
			
		||||
				value = [value]
 | 
			
		||||
			for val in value:
 | 
			
		||||
				if self['type'] in ['', None] and val in self.true_values:
 | 
			
		||||
					val = '1'
 | 
			
		||||
				valXML = ET.Element(valXMLName)
 | 
			
		||||
				valXML.text = val
 | 
			
		||||
				self.xml.append(valXML)
 | 
			
		||||
		else:
 | 
			
		||||
			if isinstance(value, list):
 | 
			
		||||
				raise ValueError("Cannot add multiple values to a %s field." % self['type'])
 | 
			
		||||
			valXML = ET.Element(valXMLName)
 | 
			
		||||
			valXML.text = value
 | 
			
		||||
			self.xml.append(valXML)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FieldOption(ElementBase):
 | 
			
		||||
	namespace = 'jabber:x:data'
 | 
			
		||||
	name = 'option'
 | 
			
		||||
	plugin_attrib = 'option'
 | 
			
		||||
	interfaces = set(('label', 'value'))
 | 
			
		||||
	sub_interfaces = set(('value',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0004(base.base_plugin):
 | 
			
		||||
	"""
 | 
			
		||||
	XEP-0004: Data Forms
 | 
			
		||||
	"""
 | 
			
		||||
 | 
			
		||||
	def plugin_init(self):
 | 
			
		||||
		self.xep = '0004'
 | 
			
		||||
		self.description = 'Data Forms'
 | 
			
		||||
 | 
			
		||||
		self.xmpp.registerHandler(
 | 
			
		||||
			Callback('Data Form',
 | 
			
		||||
				 MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns,
 | 
			
		||||
								   Form.namespace)),
 | 
			
		||||
				 self.handle_form))
 | 
			
		||||
 | 
			
		||||
		registerStanzaPlugin(FormField, FieldOption)
 | 
			
		||||
		registerStanzaPlugin(Form, FormField)
 | 
			
		||||
		registerStanzaPlugin(Message, Form)
 | 
			
		||||
 | 
			
		||||
	def makeForm(self, ftype='form', title='', instructions=''):
 | 
			
		||||
		f = Form()
 | 
			
		||||
		f['type'] = ftype
 | 
			
		||||
		f['title'] = title
 | 
			
		||||
		f['instructions'] = instructions
 | 
			
		||||
		return f
 | 
			
		||||
 | 
			
		||||
	def post_init(self):
 | 
			
		||||
		base.base_plugin.post_init(self)
 | 
			
		||||
		self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
 | 
			
		||||
 | 
			
		||||
	def handle_form(self, message):
 | 
			
		||||
		self.xmpp.event("message_xform", message)
 | 
			
		||||
 | 
			
		||||
	def buildForm(self, xml):
 | 
			
		||||
		return Form(xml=xml)
 | 
			
		||||
							
								
								
									
										11
									
								
								sleekxmpp/plugins/xep_0004/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								sleekxmpp/plugins/xep_0004/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza import Form
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.dataforms import xep_0004
 | 
			
		||||
							
								
								
									
										60
									
								
								sleekxmpp/plugins/xep_0004/dataforms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								sleekxmpp/plugins/xep_0004/dataforms.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty import OrderedDict
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Message
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0004 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0004(base_plugin):
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0004: Data Forms
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0004'
 | 
			
		||||
        self.description = 'Data Forms'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
            Callback('Data Form',
 | 
			
		||||
                 StanzaPath('message/form'),
 | 
			
		||||
                 self.handle_form))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(FormField, FieldOption, iterable=True)
 | 
			
		||||
        register_stanza_plugin(Form, FormField, iterable=True)
 | 
			
		||||
        register_stanza_plugin(Message, Form)
 | 
			
		||||
 | 
			
		||||
    def make_form(self, ftype='form', title='', instructions=''):
 | 
			
		||||
        f = Form()
 | 
			
		||||
        f['type'] = ftype
 | 
			
		||||
        f['title'] = title
 | 
			
		||||
        f['instructions'] = instructions
 | 
			
		||||
        return f
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
 | 
			
		||||
 | 
			
		||||
    def handle_form(self, message):
 | 
			
		||||
        self.xmpp.event("message_xform", message)
 | 
			
		||||
 | 
			
		||||
    def build_form(self, xml):
 | 
			
		||||
        return Form(xml=xml)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
xep_0004.makeForm = xep_0004.make_form
 | 
			
		||||
xep_0004.buildForm = xep_0004.build_form
 | 
			
		||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/xep_0004/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/xep_0004/stanza/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza.field import FormField, FieldOption
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza.form import Form
 | 
			
		||||
							
								
								
									
										178
									
								
								sleekxmpp/plugins/xep_0004/stanza/field.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								sleekxmpp/plugins/xep_0004/stanza/field.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FormField(ElementBase):
 | 
			
		||||
    namespace = 'jabber:x:data'
 | 
			
		||||
    name = 'field'
 | 
			
		||||
    plugin_attrib = 'field'
 | 
			
		||||
    interfaces = set(('answer', 'desc', 'required', 'value',
 | 
			
		||||
                      'options', 'label', 'type', 'var'))
 | 
			
		||||
    sub_interfaces = set(('desc',))
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
 | 
			
		||||
    field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi',
 | 
			
		||||
                       'jid-single', 'list-multi', 'list-single',
 | 
			
		||||
                       'text-multi', 'text-private', 'text-single'))
 | 
			
		||||
 | 
			
		||||
    true_values = set((True, '1', 'true'))
 | 
			
		||||
    option_types = set(('list-multi', 'list-single'))
 | 
			
		||||
    multi_line_types = set(('hidden', 'text-multi'))
 | 
			
		||||
    multi_value_types = set(('hidden', 'jid-multi',
 | 
			
		||||
                             'list-multi', 'text-multi'))
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        if ElementBase.setup(self, xml):
 | 
			
		||||
            self._type = None
 | 
			
		||||
        else:
 | 
			
		||||
            self._type = self['type']
 | 
			
		||||
 | 
			
		||||
    def set_type(self, value):
 | 
			
		||||
        self._set_attr('type', value)
 | 
			
		||||
        if value:
 | 
			
		||||
            self._type = value
 | 
			
		||||
 | 
			
		||||
    def add_option(self, label='', value=''):
 | 
			
		||||
        if self._type in self.option_types:
 | 
			
		||||
            opt = FieldOption(parent=self)
 | 
			
		||||
            opt['label'] = label
 | 
			
		||||
            opt['value'] = value
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Cannot add options to " + \
 | 
			
		||||
                             "a %s field." % self['type'])
 | 
			
		||||
 | 
			
		||||
    def del_options(self):
 | 
			
		||||
        optsXML = self.xml.findall('{%s}option' % self.namespace)
 | 
			
		||||
        for optXML in optsXML:
 | 
			
		||||
            self.xml.remove(optXML)
 | 
			
		||||
 | 
			
		||||
    def del_required(self):
 | 
			
		||||
        reqXML = self.xml.find('{%s}required' % self.namespace)
 | 
			
		||||
        if reqXML is not None:
 | 
			
		||||
            self.xml.remove(reqXML)
 | 
			
		||||
 | 
			
		||||
    def del_value(self):
 | 
			
		||||
        valsXML = self.xml.findall('{%s}value' % self.namespace)
 | 
			
		||||
        for valXML in valsXML:
 | 
			
		||||
            self.xml.remove(valXML)
 | 
			
		||||
 | 
			
		||||
    def get_answer(self):
 | 
			
		||||
        return self['value']
 | 
			
		||||
 | 
			
		||||
    def get_options(self):
 | 
			
		||||
        options = []
 | 
			
		||||
        optsXML = self.xml.findall('{%s}option' % self.namespace)
 | 
			
		||||
        for optXML in optsXML:
 | 
			
		||||
            opt = FieldOption(xml=optXML)
 | 
			
		||||
            options.append({'label': opt['label'], 'value': opt['value']})
 | 
			
		||||
        return options
 | 
			
		||||
 | 
			
		||||
    def get_required(self):
 | 
			
		||||
        reqXML = self.xml.find('{%s}required' % self.namespace)
 | 
			
		||||
        return reqXML is not None
 | 
			
		||||
 | 
			
		||||
    def get_value(self):
 | 
			
		||||
        valsXML = self.xml.findall('{%s}value' % self.namespace)
 | 
			
		||||
        if len(valsXML) == 0:
 | 
			
		||||
            return None
 | 
			
		||||
        elif self._type == 'boolean':
 | 
			
		||||
            return valsXML[0].text in self.true_values
 | 
			
		||||
        elif self._type in self.multi_value_types or len(valsXML) > 1:
 | 
			
		||||
            values = []
 | 
			
		||||
            for valXML in valsXML:
 | 
			
		||||
                if valXML.text is None:
 | 
			
		||||
                    valXML.text = ''
 | 
			
		||||
                values.append(valXML.text)
 | 
			
		||||
            if self._type == 'text-multi':
 | 
			
		||||
                values = "\n".join(values)
 | 
			
		||||
            return values
 | 
			
		||||
        else:
 | 
			
		||||
            if valsXML[0].text is None:
 | 
			
		||||
                return ''
 | 
			
		||||
            return valsXML[0].text
 | 
			
		||||
 | 
			
		||||
    def set_answer(self, answer):
 | 
			
		||||
        self['value'] = answer
 | 
			
		||||
 | 
			
		||||
    def set_false(self):
 | 
			
		||||
        self['value'] = False
 | 
			
		||||
 | 
			
		||||
    def set_options(self, options):
 | 
			
		||||
        for value in options:
 | 
			
		||||
            if isinstance(value, dict):
 | 
			
		||||
                self.add_option(**value)
 | 
			
		||||
            else:
 | 
			
		||||
                self.add_option(value=value)
 | 
			
		||||
 | 
			
		||||
    def set_required(self, required):
 | 
			
		||||
        exists = self['required']
 | 
			
		||||
        if not exists and required:
 | 
			
		||||
            self.xml.append(ET.Element('{%s}required' % self.namespace))
 | 
			
		||||
        elif exists and not required:
 | 
			
		||||
            del self['required']
 | 
			
		||||
 | 
			
		||||
    def set_true(self):
 | 
			
		||||
        self['value'] = True
 | 
			
		||||
 | 
			
		||||
    def set_value(self, value):
 | 
			
		||||
        del self['value']
 | 
			
		||||
        valXMLName = '{%s}value' % self.namespace
 | 
			
		||||
 | 
			
		||||
        if self._type == 'boolean':
 | 
			
		||||
            if value in self.true_values:
 | 
			
		||||
                valXML = ET.Element(valXMLName)
 | 
			
		||||
                valXML.text = '1'
 | 
			
		||||
                self.xml.append(valXML)
 | 
			
		||||
            else:
 | 
			
		||||
                valXML = ET.Element(valXMLName)
 | 
			
		||||
                valXML.text = '0'
 | 
			
		||||
                self.xml.append(valXML)
 | 
			
		||||
        elif self._type in self.multi_value_types or self._type in ('', None):
 | 
			
		||||
            if not isinstance(value, list):
 | 
			
		||||
                value = value.replace('\r', '')
 | 
			
		||||
                value = value.split('\n')
 | 
			
		||||
            for val in value:
 | 
			
		||||
                if self._type in ('', None) and val in self.true_values:
 | 
			
		||||
                    val = '1'
 | 
			
		||||
                valXML = ET.Element(valXMLName)
 | 
			
		||||
                valXML.text = val
 | 
			
		||||
                self.xml.append(valXML)
 | 
			
		||||
        else:
 | 
			
		||||
            if isinstance(value, list):
 | 
			
		||||
                raise ValueError("Cannot add multiple values " + \
 | 
			
		||||
                                 "to a %s field." % self._type)
 | 
			
		||||
            valXML = ET.Element(valXMLName)
 | 
			
		||||
            valXML.text = value
 | 
			
		||||
            self.xml.append(valXML)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FieldOption(ElementBase):
 | 
			
		||||
    namespace = 'jabber:x:data'
 | 
			
		||||
    name = 'option'
 | 
			
		||||
    plugin_attrib = 'option'
 | 
			
		||||
    interfaces = set(('label', 'value'))
 | 
			
		||||
    sub_interfaces = set(('value',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FormField.addOption = FormField.add_option
 | 
			
		||||
FormField.delOptions = FormField.del_options
 | 
			
		||||
FormField.delRequired = FormField.del_required
 | 
			
		||||
FormField.delValue = FormField.del_value
 | 
			
		||||
FormField.getAnswer = FormField.get_answer
 | 
			
		||||
FormField.getOptions = FormField.get_options
 | 
			
		||||
FormField.getRequired = FormField.get_required
 | 
			
		||||
FormField.getValue = FormField.get_value
 | 
			
		||||
FormField.setAnswer = FormField.set_answer
 | 
			
		||||
FormField.setFalse = FormField.set_false
 | 
			
		||||
FormField.setOptions = FormField.set_options
 | 
			
		||||
FormField.setRequired = FormField.set_required
 | 
			
		||||
FormField.setTrue = FormField.set_true
 | 
			
		||||
FormField.setValue = FormField.set_value
 | 
			
		||||
							
								
								
									
										254
									
								
								sleekxmpp/plugins/xep_0004/stanza/form.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								sleekxmpp/plugins/xep_0004/stanza/form.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,254 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty import OrderedDict
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza import FormField
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Form(ElementBase):
 | 
			
		||||
    namespace = 'jabber:x:data'
 | 
			
		||||
    name = 'x'
 | 
			
		||||
    plugin_attrib = 'form'
 | 
			
		||||
    interfaces = set(('fields', 'instructions', 'items',
 | 
			
		||||
                      'reported', 'title', 'type', 'values'))
 | 
			
		||||
    sub_interfaces = set(('title',))
 | 
			
		||||
    form_types = set(('cancel', 'form', 'result', 'submit'))
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        title = None
 | 
			
		||||
        if 'title' in kwargs:
 | 
			
		||||
            title = kwargs['title']
 | 
			
		||||
            del kwargs['title']
 | 
			
		||||
        ElementBase.__init__(self, *args, **kwargs)
 | 
			
		||||
        if title is not None:
 | 
			
		||||
            self['title'] = title
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        if ElementBase.setup(self, xml):
 | 
			
		||||
            # If we had to generate xml
 | 
			
		||||
            self['type'] = 'form'
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def field(self):
 | 
			
		||||
        return self['fields']
 | 
			
		||||
 | 
			
		||||
    def set_type(self, ftype):
 | 
			
		||||
        self._set_attr('type', ftype)
 | 
			
		||||
        if ftype == 'submit':
 | 
			
		||||
            fields = self['fields']
 | 
			
		||||
            for var in fields:
 | 
			
		||||
                field = fields[var]
 | 
			
		||||
                del field['type']
 | 
			
		||||
                del field['label']
 | 
			
		||||
                del field['desc']
 | 
			
		||||
                del field['required']
 | 
			
		||||
                del field['options']
 | 
			
		||||
        elif ftype == 'cancel':
 | 
			
		||||
            del self['fields']
 | 
			
		||||
 | 
			
		||||
    def add_field(self, var='', ftype=None, label='', desc='',
 | 
			
		||||
                  required=False, value=None, options=None, **kwargs):
 | 
			
		||||
        kwtype = kwargs.get('type', None)
 | 
			
		||||
        if kwtype is None:
 | 
			
		||||
            kwtype = ftype
 | 
			
		||||
 | 
			
		||||
        field = FormField(parent=self)
 | 
			
		||||
        field['var'] = var
 | 
			
		||||
        field['type'] = kwtype
 | 
			
		||||
        field['value'] = value
 | 
			
		||||
        if self['type'] in ('form', 'result'):
 | 
			
		||||
            field['label'] = label
 | 
			
		||||
            field['desc'] = desc
 | 
			
		||||
            field['required'] = required
 | 
			
		||||
            if options is not None:
 | 
			
		||||
                field['options'] = options
 | 
			
		||||
        else:
 | 
			
		||||
            del field['type']
 | 
			
		||||
        return field
 | 
			
		||||
 | 
			
		||||
    def getXML(self, type='submit'):
 | 
			
		||||
        self['type'] = type
 | 
			
		||||
        log.warning("Form.getXML() is deprecated API compatibility " + \
 | 
			
		||||
                    "with plugins/old_0004.py")
 | 
			
		||||
        return self.xml
 | 
			
		||||
 | 
			
		||||
    def fromXML(self, xml):
 | 
			
		||||
        log.warning("Form.fromXML() is deprecated API compatibility " + \
 | 
			
		||||
                    "with plugins/old_0004.py")
 | 
			
		||||
        n = Form(xml=xml)
 | 
			
		||||
        return n
 | 
			
		||||
 | 
			
		||||
    def add_item(self, values):
 | 
			
		||||
        itemXML = ET.Element('{%s}item' % self.namespace)
 | 
			
		||||
        self.xml.append(itemXML)
 | 
			
		||||
        reported_vars = self['reported'].keys()
 | 
			
		||||
        for var in reported_vars:
 | 
			
		||||
            fieldXML = ET.Element('{%s}field' % FormField.namespace)
 | 
			
		||||
            itemXML.append(fieldXML)
 | 
			
		||||
            field = FormField(xml=fieldXML)
 | 
			
		||||
            field['var'] = var
 | 
			
		||||
            field['value'] = values.get(var, None)
 | 
			
		||||
 | 
			
		||||
    def add_reported(self, var, ftype=None, label='', desc='', **kwargs):
 | 
			
		||||
        kwtype = kwargs.get('type', None)
 | 
			
		||||
        if kwtype is None:
 | 
			
		||||
            kwtype = ftype
 | 
			
		||||
        reported = self.xml.find('{%s}reported' % self.namespace)
 | 
			
		||||
        if reported is None:
 | 
			
		||||
            reported = ET.Element('{%s}reported' % self.namespace)
 | 
			
		||||
            self.xml.append(reported)
 | 
			
		||||
        fieldXML = ET.Element('{%s}field' % FormField.namespace)
 | 
			
		||||
        reported.append(fieldXML)
 | 
			
		||||
        field = FormField(xml=fieldXML)
 | 
			
		||||
        field['var'] = var
 | 
			
		||||
        field['type'] = kwtype
 | 
			
		||||
        field['label'] = label
 | 
			
		||||
        field['desc'] = desc
 | 
			
		||||
        return field
 | 
			
		||||
 | 
			
		||||
    def cancel(self):
 | 
			
		||||
        self['type'] = 'cancel'
 | 
			
		||||
 | 
			
		||||
    def del_fields(self):
 | 
			
		||||
        fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
 | 
			
		||||
        for fieldXML in fieldsXML:
 | 
			
		||||
            self.xml.remove(fieldXML)
 | 
			
		||||
 | 
			
		||||
    def del_instructions(self):
 | 
			
		||||
        instsXML = self.xml.findall('{%s}instructions')
 | 
			
		||||
        for instXML in instsXML:
 | 
			
		||||
            self.xml.remove(instXML)
 | 
			
		||||
 | 
			
		||||
    def del_items(self):
 | 
			
		||||
        itemsXML = self.xml.find('{%s}item' % self.namespace)
 | 
			
		||||
        for itemXML in itemsXML:
 | 
			
		||||
            self.xml.remove(itemXML)
 | 
			
		||||
 | 
			
		||||
    def del_reported(self):
 | 
			
		||||
        reportedXML = self.xml.find('{%s}reported' % self.namespace)
 | 
			
		||||
        if reportedXML is not None:
 | 
			
		||||
            self.xml.remove(reportedXML)
 | 
			
		||||
 | 
			
		||||
    def get_fields(self, use_dict=False):
 | 
			
		||||
        fields = OrderedDict()
 | 
			
		||||
        fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
 | 
			
		||||
        for fieldXML in fieldsXML:
 | 
			
		||||
            field = FormField(xml=fieldXML)
 | 
			
		||||
            fields[field['var']] = field
 | 
			
		||||
        return fields
 | 
			
		||||
 | 
			
		||||
    def get_instructions(self):
 | 
			
		||||
        instructions = ''
 | 
			
		||||
        instsXML = self.xml.findall('{%s}instructions' % self.namespace)
 | 
			
		||||
        return "\n".join([instXML.text for instXML in instsXML])
 | 
			
		||||
 | 
			
		||||
    def get_items(self):
 | 
			
		||||
        items = []
 | 
			
		||||
        itemsXML = self.xml.findall('{%s}item' % self.namespace)
 | 
			
		||||
        for itemXML in itemsXML:
 | 
			
		||||
            item = {}
 | 
			
		||||
            fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
 | 
			
		||||
            for fieldXML in fieldsXML:
 | 
			
		||||
                field = FormField(xml=fieldXML)
 | 
			
		||||
                item[field['var']] = field['value']
 | 
			
		||||
            items.append(item)
 | 
			
		||||
        return items
 | 
			
		||||
 | 
			
		||||
    def get_reported(self):
 | 
			
		||||
        fields = {}
 | 
			
		||||
        xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
 | 
			
		||||
                                     FormField.namespace))
 | 
			
		||||
        for field in xml:
 | 
			
		||||
            field = FormField(xml=field)
 | 
			
		||||
            fields[field['var']] = field
 | 
			
		||||
        return fields
 | 
			
		||||
 | 
			
		||||
    def get_values(self):
 | 
			
		||||
        values = {}
 | 
			
		||||
        fields = self['fields']
 | 
			
		||||
        for var in fields:
 | 
			
		||||
            values[var] = fields[var]['value']
 | 
			
		||||
        return values
 | 
			
		||||
 | 
			
		||||
    def reply(self):
 | 
			
		||||
        if self['type'] == 'form':
 | 
			
		||||
            self['type'] = 'submit'
 | 
			
		||||
        elif self['type'] == 'submit':
 | 
			
		||||
            self['type'] = 'result'
 | 
			
		||||
 | 
			
		||||
    def set_fields(self, fields):
 | 
			
		||||
        del self['fields']
 | 
			
		||||
        if not isinstance(fields, list):
 | 
			
		||||
            fields = fields.items()
 | 
			
		||||
        for var, field in fields:
 | 
			
		||||
            field['var'] = var
 | 
			
		||||
            self.add_field(**field)
 | 
			
		||||
 | 
			
		||||
    def set_instructions(self, instructions):
 | 
			
		||||
        del self['instructions']
 | 
			
		||||
        if instructions in [None, '']:
 | 
			
		||||
            return
 | 
			
		||||
        instructions = instructions.split('\n')
 | 
			
		||||
        for instruction in instructions:
 | 
			
		||||
            inst = ET.Element('{%s}instructions' % self.namespace)
 | 
			
		||||
            inst.text = instruction
 | 
			
		||||
            self.xml.append(inst)
 | 
			
		||||
 | 
			
		||||
    def set_items(self, items):
 | 
			
		||||
        for item in items:
 | 
			
		||||
            self.add_item(item)
 | 
			
		||||
 | 
			
		||||
    def set_reported(self, reported):
 | 
			
		||||
        for var in reported:
 | 
			
		||||
            field = reported[var]
 | 
			
		||||
            field['var'] = var
 | 
			
		||||
            self.add_reported(var, **field)
 | 
			
		||||
 | 
			
		||||
    def set_values(self, values):
 | 
			
		||||
        fields = self['fields']
 | 
			
		||||
        for field in values:
 | 
			
		||||
            fields[field]['value'] = values[field]
 | 
			
		||||
 | 
			
		||||
    def merge(self, other):
 | 
			
		||||
        new = copy.copy(self)
 | 
			
		||||
        if type(other) == dict:
 | 
			
		||||
            new['values'] = other
 | 
			
		||||
            return new
 | 
			
		||||
        nfields = new['fields']
 | 
			
		||||
        ofields = other['fields']
 | 
			
		||||
        nfields.update(ofields)
 | 
			
		||||
        new['fields'] = nfields
 | 
			
		||||
        return new
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Form.setType = Form.set_type
 | 
			
		||||
Form.addField = Form.add_field
 | 
			
		||||
Form.addItem = Form.add_item
 | 
			
		||||
Form.addReported = Form.add_reported
 | 
			
		||||
Form.delFields = Form.del_fields
 | 
			
		||||
Form.delInstructions = Form.del_instructions
 | 
			
		||||
Form.delItems = Form.del_items
 | 
			
		||||
Form.delReported = Form.del_reported
 | 
			
		||||
Form.getFields = Form.get_fields
 | 
			
		||||
Form.getInstructions = Form.get_instructions
 | 
			
		||||
Form.getItems = Form.get_items
 | 
			
		||||
Form.getReported = Form.get_reported
 | 
			
		||||
Form.getValues = Form.get_values
 | 
			
		||||
Form.setFields = Form.set_fields
 | 
			
		||||
Form.setInstructions = Form.set_instructions
 | 
			
		||||
Form.setItems = Form.set_items
 | 
			
		||||
Form.setReported = Form.set_reported
 | 
			
		||||
Form.setValues = Form.set_values
 | 
			
		||||
@@ -63,7 +63,7 @@ def _py2xml(*args):
 | 
			
		||||
            double.text = str(x)
 | 
			
		||||
            val.append(double)
 | 
			
		||||
        elif type(x) is rpcbase64:
 | 
			
		||||
            b64 = ET.Element("Base64")
 | 
			
		||||
            b64 = ET.Element("base64")
 | 
			
		||||
            b64.text = x.encoded()
 | 
			
		||||
            val.append(b64)
 | 
			
		||||
        elif type(x) is rpctime:
 | 
			
		||||
@@ -110,7 +110,10 @@ def _xml2py(value):
 | 
			
		||||
        return value.find('{%s}string' % namespace).text
 | 
			
		||||
    if value.find('{%s}double' % namespace) is not None:
 | 
			
		||||
        return float(value.find('{%s}double' % namespace).text)
 | 
			
		||||
    if value.find('{%s}base64') is not None:
 | 
			
		||||
        return rpcbase64(value.find('base64' % namespace).text)
 | 
			
		||||
    if value.find('{%s}Base64') is not None:
 | 
			
		||||
        # Older versions of XEP-0009 used Base64
 | 
			
		||||
        return rpcbase64(value.find('Base64' % namespace).text)
 | 
			
		||||
    if value.find('{%s}dateTime.iso8601') is not None:
 | 
			
		||||
        return rpctime(value.find('{%s}dateTime.iso8601'))
 | 
			
		||||
 
 | 
			
		||||
@@ -699,10 +699,10 @@ class Remote(object):
 | 
			
		||||
            with Remote._lock:
 | 
			
		||||
                del cls._sessions[client.boundjid.bare]
 | 
			
		||||
        result = RemoteSession(client, _session_close_callback)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault)
 | 
			
		||||
        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error)
 | 
			
		||||
        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, threaded=True)
 | 
			
		||||
        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, threaded=True)
 | 
			
		||||
        if callback is None:
 | 
			
		||||
            start_event_handler = result._notify
 | 
			
		||||
        else:
 | 
			
		||||
 
 | 
			
		||||
@@ -112,7 +112,4 @@ class xep_0012(base.base_plugin):
 | 
			
		||||
        iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
        id = iq.get('id')
 | 
			
		||||
        result = iq.send()
 | 
			
		||||
        if result and result is not None and result.get('type', 'error') != 'error':
 | 
			
		||||
            return result['last_activity']['seconds']
 | 
			
		||||
        else:
 | 
			
		||||
            return False
 | 
			
		||||
        return result['last_activity']['seconds']
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ from .. stanza.presence import Presence
 | 
			
		||||
from .. xmlstream.handler.callback import Callback
 | 
			
		||||
from .. xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from .. xmlstream.matcher.xmlmask import MatchXMLMask
 | 
			
		||||
from sleekxmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
@@ -188,8 +189,12 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
 | 
			
		||||
        iq.append(query)
 | 
			
		||||
        result = iq.send()
 | 
			
		||||
        if result['type'] == 'error':
 | 
			
		||||
        # For now, swallow errors to preserve existing API
 | 
			
		||||
        try:
 | 
			
		||||
            result = iq.send()
 | 
			
		||||
        except IqError:
 | 
			
		||||
            return False
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            return False
 | 
			
		||||
        xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
 | 
			
		||||
        if xform is None: return False
 | 
			
		||||
@@ -209,15 +214,19 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
        form = form.getXML('submit')
 | 
			
		||||
        query.append(form)
 | 
			
		||||
        iq.append(query)
 | 
			
		||||
        result = iq.send()
 | 
			
		||||
        if result['type'] == 'error':
 | 
			
		||||
        # For now, swallow errors to preserve existing API
 | 
			
		||||
        try:
 | 
			
		||||
            result = iq.send()
 | 
			
		||||
        except IqError:
 | 
			
		||||
            return False
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            return False
 | 
			
		||||
        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.
 | 
			
		||||
        """
 | 
			
		||||
        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')
 | 
			
		||||
        if password:
 | 
			
		||||
            passelement = ET.Element('password')
 | 
			
		||||
@@ -254,12 +263,16 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
        destroy.append(xreason)
 | 
			
		||||
        query.append(destroy)
 | 
			
		||||
        iq.append(query)
 | 
			
		||||
        r = iq.send()
 | 
			
		||||
        if r is False or r['type'] == 'error':
 | 
			
		||||
        # For now, swallow errors to preserve existing API
 | 
			
		||||
        try:
 | 
			
		||||
            r = iq.send()
 | 
			
		||||
        except IqError:
 | 
			
		||||
            return False
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            return False
 | 
			
		||||
        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."""
 | 
			
		||||
        if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
 | 
			
		||||
            raise TypeError
 | 
			
		||||
@@ -271,9 +284,14 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
        query.append(item)
 | 
			
		||||
        iq = self.xmpp.makeIqSet(query)
 | 
			
		||||
        iq['to'] = room
 | 
			
		||||
        result = iq.send()
 | 
			
		||||
        if result is False or result['type'] != 'result':
 | 
			
		||||
            raise ValueError
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        # For now, swallow errors to preserve existing API
 | 
			
		||||
        try:
 | 
			
		||||
            result = iq.send()
 | 
			
		||||
        except IqError:
 | 
			
		||||
            return False
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def invite(self, room, jid, reason='', mfrom=''):
 | 
			
		||||
@@ -290,33 +308,38 @@ class xep_0045(base.base_plugin):
 | 
			
		||||
        msg.append(x)
 | 
			
		||||
        self.xmpp.send(msg)
 | 
			
		||||
 | 
			
		||||
    def leaveMUC(self, room, nick, msg=''):
 | 
			
		||||
    def leaveMUC(self, room, nick, msg='', pfrom=None):
 | 
			
		||||
        """ Leave the specified room.
 | 
			
		||||
        """
 | 
			
		||||
        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:
 | 
			
		||||
            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]
 | 
			
		||||
 | 
			
		||||
    def getRoomConfig(self, room, ifrom=''):
 | 
			
		||||
        iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner')
 | 
			
		||||
        iq['to'] = room
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        result = iq.send()
 | 
			
		||||
        if result is None or result['type'] != 'result':
 | 
			
		||||
        # For now, swallow errors to preserve existing API
 | 
			
		||||
        try:
 | 
			
		||||
            result = iq.send()
 | 
			
		||||
        except IqError:
 | 
			
		||||
            raise ValueError
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            raise ValueError
 | 
			
		||||
        form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
 | 
			
		||||
        if form is None:
 | 
			
		||||
            raise ValueError
 | 
			
		||||
        return self.xmpp.plugin['xep_0004'].buildForm(form)
 | 
			
		||||
 | 
			
		||||
    def cancelConfig(self, room):
 | 
			
		||||
    def cancelConfig(self, room, ifrom=None):
 | 
			
		||||
        query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
 | 
			
		||||
        x = ET.Element('{jabber:x:data}x', type='cancel')
 | 
			
		||||
        query.append(x)
 | 
			
		||||
        iq = self.xmpp.makeIqSet(query)
 | 
			
		||||
        iq['to'] = room
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
    def setRoomConfig(self, room, config, ifrom=''):
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import logging
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.exceptions import IqError
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, JID
 | 
			
		||||
@@ -91,16 +92,6 @@ class xep_0050(base_plugin):
 | 
			
		||||
                         StanzaPath('iq@type=set/command'),
 | 
			
		||||
                         self._handle_command))
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback("Ad-Hoc Result",
 | 
			
		||||
                         StanzaPath('iq@type=result/command'),
 | 
			
		||||
                         self._handle_command_result))
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback("Ad-Hoc Error",
 | 
			
		||||
                         StanzaPath('iq@type=error/command'),
 | 
			
		||||
                         self._handle_command_result))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.Command)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.add_event_handler('command_execute',
 | 
			
		||||
@@ -408,7 +399,7 @@ class xep_0050(base_plugin):
 | 
			
		||||
                                               **kwargs)
 | 
			
		||||
 | 
			
		||||
    def send_command(self, jid, node, ifrom=None, action='execute',
 | 
			
		||||
                    payload=None, sessionid=None, **kwargs):
 | 
			
		||||
                    payload=None, sessionid=None, flow=False, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Create and send a command stanza, without using the provided
 | 
			
		||||
        workflow management APIs.
 | 
			
		||||
@@ -422,6 +413,10 @@ class xep_0050(base_plugin):
 | 
			
		||||
            payload   -- Either a list of payload items, or a single
 | 
			
		||||
                         payload item such as a data form.
 | 
			
		||||
            sessionid -- The current session's ID value.
 | 
			
		||||
            flow      -- If True, process the Iq result using the
 | 
			
		||||
                         command workflow methods contained in the
 | 
			
		||||
                         session instead of returning the response
 | 
			
		||||
                         stanza itself. Defaults to False.
 | 
			
		||||
            block     -- Specify if the send call will block until a
 | 
			
		||||
                         response is received, or a timeout occurs.
 | 
			
		||||
                         Defaults to True.
 | 
			
		||||
@@ -431,13 +426,12 @@ class xep_0050(base_plugin):
 | 
			
		||||
                         sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback  -- Optional reference to a stream handler
 | 
			
		||||
                         function. Will be executed when a reply
 | 
			
		||||
                         stanza is received.
 | 
			
		||||
                         stanza is received if flow=False.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq['command']['node'] = node
 | 
			
		||||
        iq['command']['action'] = action
 | 
			
		||||
        if sessionid is not None:
 | 
			
		||||
@@ -447,13 +441,24 @@ class xep_0050(base_plugin):
 | 
			
		||||
                payload = [payload]
 | 
			
		||||
            for item in payload:
 | 
			
		||||
                iq['command'].append(item)
 | 
			
		||||
        return iq.send(**kwargs)
 | 
			
		||||
        if not flow:
 | 
			
		||||
            return iq.send(**kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
            if kwargs.get('block', True):
 | 
			
		||||
                try:
 | 
			
		||||
                    result = iq.send(**kwargs)
 | 
			
		||||
                except IqError as err:
 | 
			
		||||
                    result = err.iq
 | 
			
		||||
                self._handle_command_result(result)
 | 
			
		||||
            else:
 | 
			
		||||
                iq.send(block=False, callback=self._handle_command_result)
 | 
			
		||||
 | 
			
		||||
    def start_command(self, jid, node, session, ifrom=None):
 | 
			
		||||
    def start_command(self, jid, node, session, ifrom=None, block=False):
 | 
			
		||||
        """
 | 
			
		||||
        Initiate executing a command provided by a remote agent.
 | 
			
		||||
 | 
			
		||||
        The workflow provided is always non-blocking.
 | 
			
		||||
        The default workflow provided is non-blocking, but a blocking
 | 
			
		||||
        version may be used with block=True.
 | 
			
		||||
 | 
			
		||||
        The provided session dictionary should contain:
 | 
			
		||||
            next  -- A handler for processing the command result.
 | 
			
		||||
@@ -465,23 +470,32 @@ class xep_0050(base_plugin):
 | 
			
		||||
            node    -- The node for the desired command.
 | 
			
		||||
            session -- A dictionary of relevant session data.
 | 
			
		||||
            ifrom   -- Optionally specify the sender's JID.
 | 
			
		||||
            block   -- If True, block execution until a result
 | 
			
		||||
                       is received. Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        session['jid'] = jid
 | 
			
		||||
        session['node'] = node
 | 
			
		||||
        session['timestamp'] = time.time()
 | 
			
		||||
        session['payload'] = None
 | 
			
		||||
        session['block'] = block
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
            session['from'] = ifrom
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        session['from'] = ifrom
 | 
			
		||||
        iq['command']['node'] = node
 | 
			
		||||
        iq['command']['action'] = 'execute'
 | 
			
		||||
        sessionid = 'client:pending_' + iq['id']
 | 
			
		||||
        session['id'] = sessionid
 | 
			
		||||
        self.sessions[sessionid] = session
 | 
			
		||||
        iq.send(block=False)
 | 
			
		||||
        if session['block']:
 | 
			
		||||
            try:
 | 
			
		||||
                result = iq.send(block=True)
 | 
			
		||||
            except IqError as err:
 | 
			
		||||
                result = err.iq
 | 
			
		||||
            self._handle_command_result(result)
 | 
			
		||||
        else:
 | 
			
		||||
            iq.send(block=False, callback=self._handle_command_result)
 | 
			
		||||
 | 
			
		||||
    def continue_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
@@ -499,7 +513,9 @@ class xep_0050(base_plugin):
 | 
			
		||||
                          ifrom=session.get('from', None),
 | 
			
		||||
                          action='next',
 | 
			
		||||
                          payload=session.get('payload', None),
 | 
			
		||||
                          sessionid=session['id'])
 | 
			
		||||
                          sessionid=session['id'],
 | 
			
		||||
                          flow=True,
 | 
			
		||||
                          block=session['block'])
 | 
			
		||||
 | 
			
		||||
    def cancel_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
@@ -517,7 +533,9 @@ class xep_0050(base_plugin):
 | 
			
		||||
                          ifrom=session.get('from', None),
 | 
			
		||||
                          action='cancel',
 | 
			
		||||
                          payload=session.get('payload', None),
 | 
			
		||||
                          sessionid=session['id'])
 | 
			
		||||
                          sessionid=session['id'],
 | 
			
		||||
                          flow=True,
 | 
			
		||||
                          block=session['block'])
 | 
			
		||||
 | 
			
		||||
    def complete_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
@@ -535,7 +553,9 @@ class xep_0050(base_plugin):
 | 
			
		||||
                          ifrom=session.get('from', None),
 | 
			
		||||
                          action='complete',
 | 
			
		||||
                          payload=session.get('payload', None),
 | 
			
		||||
                          sessionid=session['id'])
 | 
			
		||||
                          sessionid=session['id'],
 | 
			
		||||
                          flow=True,
 | 
			
		||||
                          block=session['block'])
 | 
			
		||||
 | 
			
		||||
    def terminate_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
@@ -1,313 +1,450 @@
 | 
			
		||||
from __future__ import with_statement
 | 
			
		||||
from sleekxmpp.plugins import base
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
#from xml.etree import cElementTree as ET
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import JID
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0060 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0004 import Form
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0060(base.base_plugin):
 | 
			
		||||
	"""
 | 
			
		||||
	XEP-0060 Publish Subscribe
 | 
			
		||||
	"""
 | 
			
		||||
class xep_0060(base_plugin):
 | 
			
		||||
 | 
			
		||||
	def plugin_init(self):
 | 
			
		||||
		self.xep = '0060'
 | 
			
		||||
		self.description = 'Publish-Subscribe'
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0060 Publish Subscribe
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
	def create_node(self, jid, node, config=None, collection=False, ntype=None):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
 | 
			
		||||
		create = ET.Element('create')
 | 
			
		||||
		create.set('node', node)
 | 
			
		||||
		pubsub.append(create)
 | 
			
		||||
		configure = ET.Element('configure')
 | 
			
		||||
		if collection:
 | 
			
		||||
			ntype = 'collection'
 | 
			
		||||
		#if config is None:
 | 
			
		||||
		#	submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
 | 
			
		||||
		#else:
 | 
			
		||||
		if config is not None:
 | 
			
		||||
			submitform = config
 | 
			
		||||
			if 'FORM_TYPE' in submitform.field:
 | 
			
		||||
				submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
 | 
			
		||||
			else:
 | 
			
		||||
				submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
 | 
			
		||||
			if ntype:
 | 
			
		||||
				if 'pubsub#node_type' in submitform.field:
 | 
			
		||||
					submitform.field['pubsub#node_type'].setValue(ntype)
 | 
			
		||||
				else:
 | 
			
		||||
					submitform.addField('pubsub#node_type', value=ntype)
 | 
			
		||||
			else:
 | 
			
		||||
				if 'pubsub#node_type' in submitform.field:
 | 
			
		||||
					submitform.field['pubsub#node_type'].setValue('leaf')
 | 
			
		||||
				else:
 | 
			
		||||
					submitform.addField('pubsub#node_type', value='leaf')
 | 
			
		||||
			submitform['type'] = 'submit'
 | 
			
		||||
			configure.append(submitform.xml)
 | 
			
		||||
		pubsub.append(configure)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is False or result is None or result['type'] == 'error': return False
 | 
			
		||||
		return True
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0060'
 | 
			
		||||
        self.description = 'Publish-Subscribe'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
	def subscribe(self, jid, node, bare=True, subscribee=None):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
 | 
			
		||||
		subscribe = ET.Element('subscribe')
 | 
			
		||||
		subscribe.attrib['node'] = node
 | 
			
		||||
		if subscribee is None:
 | 
			
		||||
			if bare:
 | 
			
		||||
				subscribe.attrib['jid'] = self.xmpp.boundjid.bare
 | 
			
		||||
			else:
 | 
			
		||||
				subscribe.attrib['jid'] = self.xmpp.boundjid.full
 | 
			
		||||
		else:
 | 
			
		||||
			subscribe.attrib['jid'] = subscribee
 | 
			
		||||
		pubsub.append(subscribe)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is False or result is None or result['type'] == 'error': return False
 | 
			
		||||
		return True
 | 
			
		||||
    def create_node(self, jid, node, config=None, ntype=None, ifrom=None,
 | 
			
		||||
                    block=True, callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create and configure a new pubsub node.
 | 
			
		||||
 | 
			
		||||
	def unsubscribe(self, jid, node, bare=True, subscribee=None):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
 | 
			
		||||
		unsubscribe = ET.Element('unsubscribe')
 | 
			
		||||
		unsubscribe.attrib['node'] = node
 | 
			
		||||
		if subscribee is None:
 | 
			
		||||
			if bare:
 | 
			
		||||
				unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
 | 
			
		||||
			else:
 | 
			
		||||
				unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
 | 
			
		||||
		else:
 | 
			
		||||
			unsubscribe.attrib['jid'] = subscribee
 | 
			
		||||
		pubsub.append(unsubscribe)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is False or result is None or result['type'] == 'error': return False
 | 
			
		||||
		return True
 | 
			
		||||
        A server MAY use a different name for the node than the one provided,
 | 
			
		||||
        so be sure to check the result stanza for a server assigned name.
 | 
			
		||||
 | 
			
		||||
	def getNodeConfig(self, jid, node=None): # if no node, then grab default
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
 | 
			
		||||
		if node is not None:
 | 
			
		||||
			configure = ET.Element('configure')
 | 
			
		||||
			configure.attrib['node'] = node
 | 
			
		||||
		else:
 | 
			
		||||
			configure = ET.Element('default')
 | 
			
		||||
		pubsub.append(configure)
 | 
			
		||||
		#TODO: Add configure support.
 | 
			
		||||
		iq = self.xmpp.makeIqGet()
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result == False or result['type'] == 'error':
 | 
			
		||||
			log.warning("got error instead of config")
 | 
			
		||||
			return False
 | 
			
		||||
		if node is not None:
 | 
			
		||||
			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
 | 
			
		||||
		else:
 | 
			
		||||
			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
 | 
			
		||||
		if not form or form is None:
 | 
			
		||||
			log.error("No form found.")
 | 
			
		||||
			return False
 | 
			
		||||
		return Form(xml=form)
 | 
			
		||||
        If no configuration form is provided, the node will be created using
 | 
			
		||||
        the server's default configuration. To get the default configuration
 | 
			
		||||
        use get_node_config().
 | 
			
		||||
 | 
			
		||||
	def getNodeSubscriptions(self, jid, node):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
 | 
			
		||||
		subscriptions = ET.Element('subscriptions')
 | 
			
		||||
		subscriptions.attrib['node'] = node
 | 
			
		||||
		pubsub.append(subscriptions)
 | 
			
		||||
		iq = self.xmpp.makeIqGet()
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result == False or result['type'] == 'error':
 | 
			
		||||
			log.warning("got error instead of config")
 | 
			
		||||
			return False
 | 
			
		||||
		else:
 | 
			
		||||
			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
 | 
			
		||||
			if results is None:
 | 
			
		||||
				return False
 | 
			
		||||
			subs = {}
 | 
			
		||||
			for sub in results:
 | 
			
		||||
				subs[sub.get('jid')] = sub.get('subscription')
 | 
			
		||||
			return subs
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID of the pubsub service.
 | 
			
		||||
            node     -- Optional name of the node to create. If no name is
 | 
			
		||||
                        provided, the server MAY generate a node ID for you.
 | 
			
		||||
                        The server can also assign a different name than the
 | 
			
		||||
                        one you provide; check the result stanza to see if
 | 
			
		||||
                        the server assigned a name.
 | 
			
		||||
            config   -- Optional XEP-0004 data form of configuration settings.
 | 
			
		||||
            ntype    -- The type of node to create. Servers typically default
 | 
			
		||||
                        to using 'leaf' if no type is provided.
 | 
			
		||||
            ifrom    -- Specify the sender's JID.
 | 
			
		||||
            block    -- Specify if the send call will block until a response
 | 
			
		||||
                        is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before exiting the send call if blocking is used.
 | 
			
		||||
                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub']['create']['node'] = node
 | 
			
		||||
 | 
			
		||||
	def getNodeAffiliations(self, jid, node):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
 | 
			
		||||
		affiliations = ET.Element('affiliations')
 | 
			
		||||
		affiliations.attrib['node'] = node
 | 
			
		||||
		pubsub.append(affiliations)
 | 
			
		||||
		iq = self.xmpp.makeIqGet()
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result == False or result['type'] == 'error':
 | 
			
		||||
			log.warning("got error instead of config")
 | 
			
		||||
			return False
 | 
			
		||||
		else:
 | 
			
		||||
			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
 | 
			
		||||
			if results is None:
 | 
			
		||||
				return False
 | 
			
		||||
			subs = {}
 | 
			
		||||
			for sub in results:
 | 
			
		||||
				subs[sub.get('jid')] = sub.get('affiliation')
 | 
			
		||||
			return subs
 | 
			
		||||
        if config is not None:
 | 
			
		||||
            form_type = 'http://jabber.org/protocol/pubsub#node_config'
 | 
			
		||||
            if 'FORM_TYPE' in config['fields']:
 | 
			
		||||
                config.field['FORM_TYPE']['value'] = form_type
 | 
			
		||||
            else:
 | 
			
		||||
                config.add_field(var='FORM_TYPE',
 | 
			
		||||
                                 ftype='hidden',
 | 
			
		||||
                                 value=form_type)
 | 
			
		||||
            if ntype:
 | 
			
		||||
                if 'pubsub#node_type' in config['fields']:
 | 
			
		||||
                    config.field['pubsub#node_type']['value'] = ntype
 | 
			
		||||
                else:
 | 
			
		||||
                    config.add_field(var='pubsub#node_type', value=ntype)
 | 
			
		||||
            iq['pubsub']['configure'].append(config)
 | 
			
		||||
 | 
			
		||||
	def deleteNode(self, jid, node):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
 | 
			
		||||
		iq = self.xmpp.makeIqSet()
 | 
			
		||||
		delete = ET.Element('delete')
 | 
			
		||||
		delete.attrib['node'] = node
 | 
			
		||||
		pubsub.append(delete)
 | 
			
		||||
		iq.append(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is not None and result is not False and result['type'] != 'error':
 | 
			
		||||
			return True
 | 
			
		||||
		else:
 | 
			
		||||
			return False
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def subscribe(self, jid, node, bare=True, subscribee=None, options=None,
 | 
			
		||||
                  ifrom=None, block=True, callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Subscribe to updates from a pubsub node.
 | 
			
		||||
 | 
			
		||||
	def setNodeConfig(self, jid, node, config):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
 | 
			
		||||
		configure = ET.Element('configure')
 | 
			
		||||
		configure.attrib['node'] = node
 | 
			
		||||
		config = config.getXML('submit')
 | 
			
		||||
		configure.append(config)
 | 
			
		||||
		pubsub.append(configure)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result['type'] == 'error':
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
        The rules for determining the JID that is subscribing to the node are:
 | 
			
		||||
            1. If subscribee is given, use that as provided.
 | 
			
		||||
            2. If ifrom was given, use the bare or full version based on bare.
 | 
			
		||||
            3. Otherwise, use self.xmpp.boundjid based on bare.
 | 
			
		||||
 | 
			
		||||
	def setItem(self, jid, node, items=[]):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
 | 
			
		||||
		publish = ET.Element('publish')
 | 
			
		||||
		publish.attrib['node'] = node
 | 
			
		||||
		for pub_item in items:
 | 
			
		||||
			id, payload = pub_item
 | 
			
		||||
			item = ET.Element('item')
 | 
			
		||||
			if id is not None:
 | 
			
		||||
				item.attrib['id'] = id
 | 
			
		||||
			item.append(payload)
 | 
			
		||||
			publish.append(item)
 | 
			
		||||
		pubsub.append(publish)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result is False or result['type'] == 'error': return False
 | 
			
		||||
		return True
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid        -- The pubsub service JID.
 | 
			
		||||
            node       -- The node to subscribe to.
 | 
			
		||||
            bare       -- Indicates if the subscribee is a bare or full JID.
 | 
			
		||||
                          Defaults to True for a bare JID.
 | 
			
		||||
            subscribee -- The JID that is subscribing to the node.
 | 
			
		||||
            options    --
 | 
			
		||||
            ifrom      -- Specify the sender's JID.
 | 
			
		||||
            block      -- Specify if the send call will block until a response
 | 
			
		||||
                          is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout    -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                          before exiting the send call if blocking is used.
 | 
			
		||||
                          Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback   -- Optional reference to a stream handler function. Will
 | 
			
		||||
                          be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub']['subscribe']['node'] = node
 | 
			
		||||
 | 
			
		||||
	def addItem(self, jid, node, items=[]):
 | 
			
		||||
		return self.setItem(jid, node, items)
 | 
			
		||||
        if subscribee is None:
 | 
			
		||||
            if ifrom:
 | 
			
		||||
                if bare:
 | 
			
		||||
                    subscribee = JID(ifrom).bare
 | 
			
		||||
                else:
 | 
			
		||||
                    subscribee = ifrom
 | 
			
		||||
            else:
 | 
			
		||||
                if bare:
 | 
			
		||||
                    subscribee = self.xmpp.boundjid.bare
 | 
			
		||||
                else:
 | 
			
		||||
                    subscribee = self.xmpp.boundjid
 | 
			
		||||
 | 
			
		||||
	def deleteItem(self, jid, node, item):
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
 | 
			
		||||
		retract = ET.Element('retract')
 | 
			
		||||
		retract.attrib['node'] = node
 | 
			
		||||
		itemn = ET.Element('item')
 | 
			
		||||
		itemn.attrib['id'] = item
 | 
			
		||||
		retract.append(itemn)
 | 
			
		||||
		pubsub.append(retract)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result is False or result['type'] == 'error': return False
 | 
			
		||||
		return True
 | 
			
		||||
        iq['pubsub']['subscribe']['jid'] = subscribee
 | 
			
		||||
        if options is not None:
 | 
			
		||||
            iq['pubsub']['options'].append(options)
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
	def getNodes(self, jid):
 | 
			
		||||
		response = self.xmpp.plugin['xep_0030'].getItems(jid)
 | 
			
		||||
		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
 | 
			
		||||
		nodes = {}
 | 
			
		||||
		if items is not None and items is not False:
 | 
			
		||||
			for item in items:
 | 
			
		||||
				nodes[item.get('node')] = item.get('name')
 | 
			
		||||
		return nodes
 | 
			
		||||
    def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None,
 | 
			
		||||
                    ifrom=None, block=True, callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Unubscribe from updates from a pubsub node.
 | 
			
		||||
 | 
			
		||||
	def getItems(self, jid, node):
 | 
			
		||||
		response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
 | 
			
		||||
		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
 | 
			
		||||
		nodeitems = []
 | 
			
		||||
		if items is not None and items is not False:
 | 
			
		||||
			for item in items:
 | 
			
		||||
				nodeitems.append(item.get('node'))
 | 
			
		||||
		return nodeitems
 | 
			
		||||
        The rules for determining the JID that is unsubscribing
 | 
			
		||||
        from the node are:
 | 
			
		||||
            1. If subscribee is given, use that as provided.
 | 
			
		||||
            2. If ifrom was given, use the bare or full version based on bare.
 | 
			
		||||
            3. Otherwise, use self.xmpp.boundjid based on bare.
 | 
			
		||||
 | 
			
		||||
	def addNodeToCollection(self, jid, child, parent=''):
 | 
			
		||||
		config = self.getNodeConfig(jid, child)
 | 
			
		||||
		if not config or config is None:
 | 
			
		||||
			self.lasterror = "Config Error"
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			config.field['pubsub#collection'].setValue(parent)
 | 
			
		||||
		except KeyError:
 | 
			
		||||
			log.warning("pubsub#collection doesn't exist in config, trying to add it")
 | 
			
		||||
			config.addField('pubsub#collection', value=parent)
 | 
			
		||||
		if not self.setNodeConfig(jid, child, config):
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid        -- The pubsub service JID.
 | 
			
		||||
            node       -- The node to subscribe to.
 | 
			
		||||
            subid      -- The specific subscription, if multiple subscriptions
 | 
			
		||||
                          exist for this JID/node combination.
 | 
			
		||||
            bare       -- Indicates if the subscribee is a bare or full JID.
 | 
			
		||||
                          Defaults to True for a bare JID.
 | 
			
		||||
            subscribee -- The JID that is subscribing to the node.
 | 
			
		||||
            ifrom      -- Specify the sender's JID.
 | 
			
		||||
            block      -- Specify if the send call will block until a response
 | 
			
		||||
                          is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout    -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                          before exiting the send call if blocking is used.
 | 
			
		||||
                          Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback   -- Optional reference to a stream handler function. Will
 | 
			
		||||
                          be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub']['unsubscribe']['node'] = node
 | 
			
		||||
 | 
			
		||||
	def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
 | 
			
		||||
		if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
 | 
			
		||||
			raise TypeError
 | 
			
		||||
		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
 | 
			
		||||
		affs = ET.Element('affiliations')
 | 
			
		||||
		affs.attrib['node'] = node
 | 
			
		||||
		aff = ET.Element('affiliation')
 | 
			
		||||
		aff.attrib['jid'] = user_jid
 | 
			
		||||
		aff.attrib['affiliation'] = affiliation
 | 
			
		||||
		affs.append(aff)
 | 
			
		||||
		pubsub.append(affs)
 | 
			
		||||
		iq = self.xmpp.makeIqSet(pubsub)
 | 
			
		||||
		iq.attrib['to'] = ps_jid
 | 
			
		||||
		iq.attrib['from'] = self.xmpp.boundjid.full
 | 
			
		||||
		id = iq['id']
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or result is False or result['type'] == 'error':
 | 
			
		||||
		    return False
 | 
			
		||||
		return True
 | 
			
		||||
        if subscribee is None:
 | 
			
		||||
            if ifrom:
 | 
			
		||||
                if bare:
 | 
			
		||||
                    subscribee = JID(ifrom).bare
 | 
			
		||||
                else:
 | 
			
		||||
                    subscribee = ifrom
 | 
			
		||||
            else:
 | 
			
		||||
                if bare:
 | 
			
		||||
                    subscribee = self.xmpp.boundjid.bare
 | 
			
		||||
                else:
 | 
			
		||||
                    subscribee = self.xmpp.boundjid
 | 
			
		||||
 | 
			
		||||
	def addNodeToCollection(self, jid, child, parent=''):
 | 
			
		||||
		config = self.getNodeConfig(jid, child)
 | 
			
		||||
		if not config or config is None:
 | 
			
		||||
			self.lasterror = "Config Error"
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			config.field['pubsub#collection'].setValue(parent)
 | 
			
		||||
		except KeyError:
 | 
			
		||||
			log.warning("pubsub#collection doesn't exist in config, trying to add it")
 | 
			
		||||
			config.addField('pubsub#collection', value=parent)
 | 
			
		||||
		if not self.setNodeConfig(jid, child, config):
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
        iq['pubsub']['unsubscribe']['jid'] = subscribee
 | 
			
		||||
        iq['pubsub']['unsubscribe']['subid'] = subid
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
	def removeNodeFromCollection(self, jid, child):
 | 
			
		||||
		self.addNodeToCollection(jid, child, '')
 | 
			
		||||
    def get_subscriptions(self, jid, node=None, ifrom=None, block=True,
 | 
			
		||||
                          callback=None, timeout=None):
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        iq['pubsub']['subscriptions']['node'] = node
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_affiliations(self, jid, node=None, ifrom=None, block=True,
 | 
			
		||||
                         callback=None, timeout=None):
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        iq['pubsub']['affiliations']['node'] = node
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_subscription_options(self, jid, node=None, user_jid=None, ifrom=None,
 | 
			
		||||
                                 block=True, callback=None, timeout=None):
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        if user_jid is None:
 | 
			
		||||
            iq['pubsub']['default']['node'] = node
 | 
			
		||||
        else:
 | 
			
		||||
            iq['pubsub']['options']['node'] = node
 | 
			
		||||
            iq['pubsub']['options']['jid'] = user_jid
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def set_subscription_options(self, jid, node, user_jid, options,
 | 
			
		||||
                                 ifrom=None, block=True, callback=None,
 | 
			
		||||
                                 timeout=None):
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        iq['pubsub']['options']['node'] = node
 | 
			
		||||
        iq['pubsub']['options']['jid'] = user_jid
 | 
			
		||||
        iq['pubsub']['options'].append(options)
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_node_config(self, jid, node=None, ifrom=None, block=True,
 | 
			
		||||
                        callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the configuration for a node, or the pubsub service's
 | 
			
		||||
        default configuration for new nodes.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID of the pubsub service.
 | 
			
		||||
            node     -- The node to retrieve the configuration for. If None,
 | 
			
		||||
                        the default configuration for new nodes will be
 | 
			
		||||
                        requested. Defaults to None.
 | 
			
		||||
            ifrom    -- Specify the sender's JID.
 | 
			
		||||
            block    -- Specify if the send call will block until a response
 | 
			
		||||
                        is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before exiting the send call if blocking is used.
 | 
			
		||||
                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        if node is None:
 | 
			
		||||
            iq['pubsub_owner']['default']
 | 
			
		||||
        else:
 | 
			
		||||
            iq['pubsub_owner']['configure']['node'] = node
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_node_subscriptions(self, jid, node, ifrom=None, block=True,
 | 
			
		||||
                               callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the subscriptions associated with a given node.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID of the pubsub service.
 | 
			
		||||
            node     -- The node to retrieve subscriptions from.
 | 
			
		||||
            ifrom    -- Specify the sender's JID.
 | 
			
		||||
            block    -- Specify if the send call will block until a response
 | 
			
		||||
                        is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before exiting the send call if blocking is used.
 | 
			
		||||
                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        iq['pubsub_owner']['subscriptions']['node'] = node
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_node_affiliations(self, jid, node, ifrom=None, block=True,
 | 
			
		||||
                              callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the affiliations associated with a given node.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID of the pubsub service.
 | 
			
		||||
            node     -- The node to retrieve affiliations from.
 | 
			
		||||
            ifrom    -- Specify the sender's JID.
 | 
			
		||||
            block    -- Specify if the send call will block until a response
 | 
			
		||||
                        is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before exiting the send call if blocking is used.
 | 
			
		||||
                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        iq['pubsub_owner']['affiliations']['node'] = node
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def delete_node(self, jid, node, ifrom=None, block=True,
 | 
			
		||||
                    callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Delete a a pubsub node.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID of the pubsub service.
 | 
			
		||||
            node     -- The node to delete.
 | 
			
		||||
            ifrom    -- Specify the sender's JID.
 | 
			
		||||
            block    -- Specify if the send call will block until a response
 | 
			
		||||
                        is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before exiting the send call if blocking is used.
 | 
			
		||||
                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub_owner']['delete']['node'] = node
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def set_node_config(self, jid, node, config, ifrom=None, block=True,
 | 
			
		||||
                        callback=None, timeout=None):
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub_owner']['configure']['node'] = node
 | 
			
		||||
        iq['pubsub_owner']['configure']['form'].values = config.values
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def publish(self, jid, node, id=None, payload=None, options=None,
 | 
			
		||||
                ifrom=None, block=True, callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new item to a node, or edit an existing item.
 | 
			
		||||
 | 
			
		||||
        For services that support it, you can use the publish command
 | 
			
		||||
        as an event signal by not including an ID or payload.
 | 
			
		||||
 | 
			
		||||
        When including a payload and you do not provide an ID then
 | 
			
		||||
        the service will generally create an ID for you.
 | 
			
		||||
 | 
			
		||||
        Publish options may be specified, and how those options
 | 
			
		||||
        are processed is left to the service, such as treating
 | 
			
		||||
        the options as preconditions that the node's settings
 | 
			
		||||
        must match.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid      -- The JID of the pubsub service.
 | 
			
		||||
            node     -- The node to publish the item to.
 | 
			
		||||
            id       -- Optionally specify the ID of the item.
 | 
			
		||||
            payload  -- The item content to publish.
 | 
			
		||||
            options  -- A form of publish options.
 | 
			
		||||
            ifrom    -- Specify the sender's JID.
 | 
			
		||||
            block    -- Specify if the send call will block until a response
 | 
			
		||||
                        is received, or a timeout occurs. Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before exiting the send call if blocking is used.
 | 
			
		||||
                        Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when a reply stanza is received.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub']['publish']['node'] = node
 | 
			
		||||
        if id is not None:
 | 
			
		||||
            iq['pubsub']['publish']['item']['id'] = id
 | 
			
		||||
        if payload is not None:
 | 
			
		||||
            iq['pubsub']['publish']['item']['payload'] = payload
 | 
			
		||||
        iq['pubsub']['publish_options'] = options
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def retract(self, jid, node, id, notify=None, ifrom=None, block=True,
 | 
			
		||||
                callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Delete a single item from a node.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
 | 
			
		||||
        iq['pubsub']['retract']['node'] = node
 | 
			
		||||
        iq['pubsub']['retract']['notify'] = notify
 | 
			
		||||
        iq['pubsub']['retract']['item']['id'] = id
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def purge(self, jid, node, ifrom=None, block=True, callback=None,
 | 
			
		||||
              timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Remove all items from a node.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub_owner']['purge']['node'] = node
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_nodes(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Discover the nodes provided by a Pubsub service, using disco.
 | 
			
		||||
        """
 | 
			
		||||
        return self.xmpp.plugin['xep_0030'].get_items(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_item(self, jid, node, item_id, ifrom=None, block=True,
 | 
			
		||||
                 callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the content of an individual item.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        item = self.stanza.Item()
 | 
			
		||||
        item['id'] = item_id
 | 
			
		||||
        iq['pubsub']['items']['node'] = node
 | 
			
		||||
        iq['pubsub']['items'].append(item)
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_items(self, jid, node, item_ids=None, max_items=None,
 | 
			
		||||
                  iterator=False, ifrom=None, block=False,
 | 
			
		||||
                  callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
        Request the contents of a node's items.
 | 
			
		||||
 | 
			
		||||
        The desired items can be specified, or a query for the last
 | 
			
		||||
        few published items can be used.
 | 
			
		||||
 | 
			
		||||
        Pubsub services may use result set management for nodes with
 | 
			
		||||
        many items, so an iterator can be returned if needed.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
 | 
			
		||||
        iq['pubsub']['items']['node'] = node
 | 
			
		||||
        iq['pubsub']['items']['max_items'] = max_items
 | 
			
		||||
 | 
			
		||||
        if item_ids is not None:
 | 
			
		||||
            for item_id in item_ids:
 | 
			
		||||
                item = self.stanza.Item()
 | 
			
		||||
                item['id'] = item_id
 | 
			
		||||
                iq['pubsub']['items'].append(item)
 | 
			
		||||
 | 
			
		||||
        if iterator:
 | 
			
		||||
            return self.xmpp['xep_0059'].iterate(iq, 'pubsub')
 | 
			
		||||
        else:
 | 
			
		||||
            return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def get_item_ids(self, jid, node, ifrom=None, block=True,
 | 
			
		||||
                     callback=None, timeout=None, iterator=False):
 | 
			
		||||
        """
 | 
			
		||||
        Retrieve the ItemIDs hosted by a given node, using disco.
 | 
			
		||||
        """
 | 
			
		||||
        return self.xmpp.plugin['xep_0030'].get_items(jid, node,
 | 
			
		||||
                                                      ifrom=ifrom,
 | 
			
		||||
                                                      block=block,
 | 
			
		||||
                                                      callback=callback,
 | 
			
		||||
                                                      timeout=timeout,
 | 
			
		||||
                                                      iterator=iterator)
 | 
			
		||||
 | 
			
		||||
    def modify_affiliations(self, jid, node, affiliations=None, ifrom=None,
 | 
			
		||||
                            block=True, callback=None, timeout=None):
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub_owner']['affiliations']['node'] = node
 | 
			
		||||
 | 
			
		||||
        if affiliations is None:
 | 
			
		||||
            affiliations = []
 | 
			
		||||
 | 
			
		||||
        for jid, affiliation in affiliations:
 | 
			
		||||
            aff = self.stanza.OwnerAffiliation()
 | 
			
		||||
            aff['jid'] = jid
 | 
			
		||||
            aff['affiliation'] = affiliation
 | 
			
		||||
            iq['pubsub_owner']['affiliations'].append(aff)
 | 
			
		||||
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def modify_subscriptions(self, jid, node, subscriptions=None, ifrom=None,
 | 
			
		||||
                             block=True, callback=None, timeout=None):
 | 
			
		||||
        iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
 | 
			
		||||
        iq['pubsub_owner']['subscriptions']['node'] = node
 | 
			
		||||
 | 
			
		||||
        if subscriptions is None:
 | 
			
		||||
            subscriptions = []
 | 
			
		||||
 | 
			
		||||
        for jid, subscription in subscriptions:
 | 
			
		||||
            sub = self.stanza.OwnerSubscription()
 | 
			
		||||
            sub['jid'] = jid
 | 
			
		||||
            sub['subscription'] = subscription
 | 
			
		||||
            iq['pubsub_owner']['subscriptions'].append(sub)
 | 
			
		||||
 | 
			
		||||
        return iq.send(block=block, callback=callback, timeout=timeout)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,12 @@
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Pubsub, Affiliation, Affiliations, Subscription, Subscriptions, SubscribeOptions, Item, Items, Create, Publish, Retract, Unsubscribe, Subscribe, Configure, Options, PubsubState, PubsubStateEvent
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import PubsubOwner, DefaultConfig, OwnerAffiliations, OwnerAffiliation, OwnerConfigure, OwnerDefault, OwnerDelete, OwnerPurge, OwnerRedirect, OwnerSubscriptions, OwnerSubscription
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import Event, EventItem, EventRetract, EventItems, EventCollection, EventAssociate, EventDisassociate, EventConfiguration, EventPurge, EventSubscription
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import *
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import *
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import *
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_errors import *
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,29 @@
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OptionalSetting(object):
 | 
			
		||||
	interfaces = set(('required',))
 | 
			
		||||
 | 
			
		||||
	def setRequired(self, value):
 | 
			
		||||
		value = bool(value)
 | 
			
		||||
		if value and not self['required']:
 | 
			
		||||
			self.xml.append(ET.Element("{%s}required" % self.namespace))
 | 
			
		||||
		elif not value and self['required']:
 | 
			
		||||
			self.delRequired()
 | 
			
		||||
	
 | 
			
		||||
	def getRequired(self):
 | 
			
		||||
		required = self.xml.find("{%s}required" % self.namespace)
 | 
			
		||||
		if required is not None:
 | 
			
		||||
			return True
 | 
			
		||||
		else:
 | 
			
		||||
			return False
 | 
			
		||||
	
 | 
			
		||||
	def delRequired(self):
 | 
			
		||||
		required = self.xml.find("{%s}required" % self.namespace)
 | 
			
		||||
		if required is not None:
 | 
			
		||||
			self.xml.remove(required)
 | 
			
		||||
    interfaces = set(('required',))
 | 
			
		||||
 | 
			
		||||
    def set_required(self, value):
 | 
			
		||||
        if value in (True, 'true', 'True', '1'):
 | 
			
		||||
            self.xml.append(ET.Element("{%s}required" % self.namespace))
 | 
			
		||||
        elif self['required']:
 | 
			
		||||
            self.del_required()
 | 
			
		||||
 | 
			
		||||
    def get_required(self):
 | 
			
		||||
        required = self.xml.find("{%s}required" % self.namespace)
 | 
			
		||||
        return required is not None
 | 
			
		||||
 | 
			
		||||
    def del_required(self):
 | 
			
		||||
        required = self.xml.find("{%s}required" % self.namespace)
 | 
			
		||||
        if required is not None:
 | 
			
		||||
            self.xml.remove(required)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,277 +1,300 @@
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.stanza.iq import Iq
 | 
			
		||||
from sleekxmpp.stanza.message import Message
 | 
			
		||||
from sleekxmpp.basexmpp import basexmpp
 | 
			
		||||
from sleekxmpp.xmlstream.xmlstream import XMLStream
 | 
			
		||||
import logging
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Iq, Message
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.plugins import xep_0004
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Pubsub(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'pubsub'
 | 
			
		||||
	plugin_attrib = 'pubsub'
 | 
			
		||||
	interfaces = set(tuple())
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'pubsub'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(tuple())
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Iq, Pubsub)
 | 
			
		||||
 | 
			
		||||
class Affiliations(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'affiliations'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Affiliation(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'affiliation'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node', 'affiliation'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'affiliation'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'affiliation', 'jid'))
 | 
			
		||||
 | 
			
		||||
class Affiliations(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'affiliations'
 | 
			
		||||
	plugin_attrib = 'affiliations'
 | 
			
		||||
	interfaces = set(tuple())
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	subitem = (Affiliation,)
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
	def append(self, affiliation):
 | 
			
		||||
		if not isinstance(affiliation, Affiliation):
 | 
			
		||||
			raise TypeError
 | 
			
		||||
		self.xml.append(affiliation.xml)
 | 
			
		||||
		return self.iterables.append(affiliation)
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Affiliations)
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Subscription(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'subscription'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('jid', 'node', 'subscription', 'subid'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'subscription'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('jid', 'node', 'subscription', 'subid'))
 | 
			
		||||
 | 
			
		||||
	def setjid(self, value):
 | 
			
		||||
		self._setattr('jid', str(value))
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
	def getjid(self):
 | 
			
		||||
		return jid(self._getattr('jid'))
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Subscription)
 | 
			
		||||
 | 
			
		||||
class Subscriptions(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'subscriptions'
 | 
			
		||||
	plugin_attrib = 'subscriptions'
 | 
			
		||||
	interfaces = set(tuple())
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	subitem = (Subscription,)
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Subscriptions)
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'subscriptions'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SubscribeOptions(ElementBase, OptionalSetting):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'subscribe-options'
 | 
			
		||||
	plugin_attrib = 'suboptions'
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	interfaces = set(('required',))
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'subscribe-options'
 | 
			
		||||
    plugin_attrib = 'suboptions'
 | 
			
		||||
    interfaces = set(('required',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Subscription, SubscribeOptions)
 | 
			
		||||
 | 
			
		||||
class Item(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'item'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('id', 'payload'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'item'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('id', 'payload'))
 | 
			
		||||
 | 
			
		||||
	def setPayload(self, value):
 | 
			
		||||
		self.xml.append(value)
 | 
			
		||||
    def set_payload(self, value):
 | 
			
		||||
        del self['payload']
 | 
			
		||||
        self.append(value)
 | 
			
		||||
 | 
			
		||||
	def getPayload(self):
 | 
			
		||||
		childs = self.xml.getchildren()
 | 
			
		||||
		if len(childs) > 0:
 | 
			
		||||
			return childs[0]
 | 
			
		||||
 | 
			
		||||
	def delPayload(self):
 | 
			
		||||
		for child in self.xml.getchildren():
 | 
			
		||||
			self.xml.remove(child)
 | 
			
		||||
 | 
			
		||||
class Items(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'items'
 | 
			
		||||
	plugin_attrib = 'items'
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	subitem = (Item,)
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Items)
 | 
			
		||||
 | 
			
		||||
class Create(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'create'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Create)
 | 
			
		||||
 | 
			
		||||
#class Default(ElementBase):
 | 
			
		||||
#	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
#	name = 'default'
 | 
			
		||||
#	plugin_attrib = name
 | 
			
		||||
#	interfaces = set(('node', 'type'))
 | 
			
		||||
#	plugin_attrib_map = {}
 | 
			
		||||
#	plugin_tag_map = {}
 | 
			
		||||
#
 | 
			
		||||
#	def getType(self):
 | 
			
		||||
#		t = self._getAttr('type')
 | 
			
		||||
#		if not t: t == 'leaf'
 | 
			
		||||
#		return t
 | 
			
		||||
#
 | 
			
		||||
#registerStanzaPlugin(Pubsub, Default)
 | 
			
		||||
 | 
			
		||||
class Publish(Items):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'publish'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	subitem = (Item,)
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Publish)
 | 
			
		||||
 | 
			
		||||
class Retract(Items):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'retract'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node', 'notify'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Retract)
 | 
			
		||||
 | 
			
		||||
class Unsubscribe(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'unsubscribe'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node', 'jid'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
	def setJid(self, value):
 | 
			
		||||
		self._setAttr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
	def getJid(self):
 | 
			
		||||
		return JID(self._getAttr('jid'))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Unsubscribe)
 | 
			
		||||
 | 
			
		||||
class Subscribe(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'subscribe'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node', 'jid'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
	def setJid(self, value):
 | 
			
		||||
		self._setAttr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
	def getJid(self):
 | 
			
		||||
		return JID(self._getAttr('jid'))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Subscribe)
 | 
			
		||||
 | 
			
		||||
class Configure(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'configure'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node', 'type'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
	def getType(self):
 | 
			
		||||
		t = self._getAttr('type')
 | 
			
		||||
		if not t: t == 'leaf'
 | 
			
		||||
		return t
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Configure)
 | 
			
		||||
registerStanzaPlugin(Configure, xep_0004.Form)
 | 
			
		||||
 | 
			
		||||
class Options(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
	name = 'options'
 | 
			
		||||
	plugin_attrib = 'options'
 | 
			
		||||
	interfaces = set(('jid', 'node', 'options'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
	def __init__(self, *args, **kwargs):
 | 
			
		||||
		ElementBase.__init__(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
	def getOptions(self):
 | 
			
		||||
		config = self.xml.find('{jabber:x:data}x')
 | 
			
		||||
		form = xep_0004.Form()
 | 
			
		||||
		if config is not None:
 | 
			
		||||
			form.fromXML(config)
 | 
			
		||||
		return form
 | 
			
		||||
 | 
			
		||||
	def setOptions(self, value):
 | 
			
		||||
		self.xml.append(value.getXML())
 | 
			
		||||
		return self
 | 
			
		||||
 | 
			
		||||
	def delOptions(self):
 | 
			
		||||
		config = self.xml.find('{jabber:x:data}x')
 | 
			
		||||
		self.xml.remove(config)
 | 
			
		||||
 | 
			
		||||
	def setJid(self, value):
 | 
			
		||||
		self._setAttr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
	def getJid(self):
 | 
			
		||||
		return JID(self._getAttr('jid'))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Pubsub, Options)
 | 
			
		||||
registerStanzaPlugin(Subscribe, Options)
 | 
			
		||||
 | 
			
		||||
class PubsubState(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/psstate'
 | 
			
		||||
    name = 'state'
 | 
			
		||||
    plugin_attrib = 'psstate'
 | 
			
		||||
    interfaces = set(('node', 'item', 'payload'))
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
    def setPayload(self, value):
 | 
			
		||||
        self.xml.append(value)
 | 
			
		||||
 | 
			
		||||
    def getPayload(self):
 | 
			
		||||
    def get_payload(self):
 | 
			
		||||
        childs = self.xml.getchildren()
 | 
			
		||||
        if len(childs) > 0:
 | 
			
		||||
            return childs[0]
 | 
			
		||||
 | 
			
		||||
    def delPayload(self):
 | 
			
		||||
    def del_payload(self):
 | 
			
		||||
        for child in self.xml.getchildren():
 | 
			
		||||
            self.xml.remove(child)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Items(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'items'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'max_items'))
 | 
			
		||||
 | 
			
		||||
    def set_max_items(self, value):
 | 
			
		||||
        self._set_attr('max_items', str(value))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Create(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'create'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Default(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'default'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'type'))
 | 
			
		||||
 | 
			
		||||
    def get_type(self):
 | 
			
		||||
        t = self._get_attr('type')
 | 
			
		||||
        if not t:
 | 
			
		||||
            return 'leaf'
 | 
			
		||||
        return t
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Publish(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'publish'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Retract(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'retract'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'notify'))
 | 
			
		||||
 | 
			
		||||
    def get_notify(self):
 | 
			
		||||
        notify = self._get_attr('notify')
 | 
			
		||||
        if notify in ('0', 'false'):
 | 
			
		||||
            return False
 | 
			
		||||
        elif notify in ('1', 'true'):
 | 
			
		||||
            return True
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def set_notify(self, value):
 | 
			
		||||
        del self['notify']
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return
 | 
			
		||||
        elif value in (True, '1', 'true', 'True'):
 | 
			
		||||
            self._set_attr('notify', 'true')
 | 
			
		||||
        else:
 | 
			
		||||
            self._set_attr('notify', 'false')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Unsubscribe(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'unsubscribe'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'jid', 'subid'))
 | 
			
		||||
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Subscribe(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'subscribe'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'jid'))
 | 
			
		||||
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Configure(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'configure'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'type'))
 | 
			
		||||
 | 
			
		||||
    def getType(self):
 | 
			
		||||
        t = self._get_attr('type')
 | 
			
		||||
        if not t:
 | 
			
		||||
            t == 'leaf'
 | 
			
		||||
        return t
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Options(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'options'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('jid', 'node', 'options'))
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        ElementBase.__init__(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_options(self):
 | 
			
		||||
        config = self.xml.find('{jabber:x:data}x')
 | 
			
		||||
        form = xep_0004.Form(xml=config)
 | 
			
		||||
        return form
 | 
			
		||||
 | 
			
		||||
    def set_options(self, value):
 | 
			
		||||
        self.xml.append(value.getXML())
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def del_options(self):
 | 
			
		||||
        config = self.xml.find('{jabber:x:data}x')
 | 
			
		||||
        self.xml.remove(config)
 | 
			
		||||
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PublishOptions(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub'
 | 
			
		||||
    name = 'publish-options'
 | 
			
		||||
    plugin_attrib = 'publish_options'
 | 
			
		||||
    interfaces = set(('publish_options',))
 | 
			
		||||
    is_extension = True
 | 
			
		||||
 | 
			
		||||
    def get_publish_options(self):
 | 
			
		||||
        config = self.xml.find('{jabber:x:data}x')
 | 
			
		||||
        if config is None:
 | 
			
		||||
            return None
 | 
			
		||||
        form = xep_0004.Form(xml=config)
 | 
			
		||||
        return form
 | 
			
		||||
 | 
			
		||||
    def set_publish_options(self, value):
 | 
			
		||||
        if value is None:
 | 
			
		||||
            self.del_publish_options()
 | 
			
		||||
        else:
 | 
			
		||||
            self.xml.append(value.getXML())
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def del_publish_options(self):
 | 
			
		||||
        config = self.xml.find('{jabber:x:data}x')
 | 
			
		||||
        if config is not None:
 | 
			
		||||
            self.xml.remove(config)
 | 
			
		||||
        self.parent().xml.remove(self.xml)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PubsubState(ElementBase):
 | 
			
		||||
    """This is an experimental pubsub extension."""
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/psstate'
 | 
			
		||||
    name = 'state'
 | 
			
		||||
    plugin_attrib = 'psstate'
 | 
			
		||||
    interfaces = set(('node', 'item', 'payload'))
 | 
			
		||||
 | 
			
		||||
    def set_payload(self, value):
 | 
			
		||||
        self.xml.append(value)
 | 
			
		||||
 | 
			
		||||
    def get_payload(self):
 | 
			
		||||
        childs = self.xml.getchildren()
 | 
			
		||||
        if len(childs) > 0:
 | 
			
		||||
            return childs[0]
 | 
			
		||||
 | 
			
		||||
    def del_payload(self):
 | 
			
		||||
        for child in self.xml.getchildren():
 | 
			
		||||
            self.xml.remove(child)
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Iq, PubsubState)
 | 
			
		||||
 | 
			
		||||
class PubsubStateEvent(ElementBase):
 | 
			
		||||
    """This is an experimental pubsub extension."""
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/psstate#event'
 | 
			
		||||
    name = 'event'
 | 
			
		||||
    plugin_attrib = 'psstate_event'
 | 
			
		||||
    intefaces = set(tuple())
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Message, PubsubStateEvent)
 | 
			
		||||
registerStanzaPlugin(PubsubStateEvent, PubsubState)
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Iq, PubsubState)
 | 
			
		||||
register_stanza_plugin(Message, PubsubStateEvent)
 | 
			
		||||
register_stanza_plugin(PubsubStateEvent, PubsubState)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Iq, Pubsub)
 | 
			
		||||
register_stanza_plugin(Pubsub, Affiliations)
 | 
			
		||||
register_stanza_plugin(Pubsub, Configure)
 | 
			
		||||
register_stanza_plugin(Pubsub, Create)
 | 
			
		||||
register_stanza_plugin(Pubsub, Default)
 | 
			
		||||
register_stanza_plugin(Pubsub, Items)
 | 
			
		||||
register_stanza_plugin(Pubsub, Options)
 | 
			
		||||
register_stanza_plugin(Pubsub, Publish)
 | 
			
		||||
register_stanza_plugin(Pubsub, PublishOptions)
 | 
			
		||||
register_stanza_plugin(Pubsub, Retract)
 | 
			
		||||
register_stanza_plugin(Pubsub, Subscribe)
 | 
			
		||||
register_stanza_plugin(Pubsub, Subscription)
 | 
			
		||||
register_stanza_plugin(Pubsub, Subscriptions)
 | 
			
		||||
register_stanza_plugin(Pubsub, Unsubscribe)
 | 
			
		||||
register_stanza_plugin(Affiliations, Affiliation, iterable=True)
 | 
			
		||||
register_stanza_plugin(Configure, xep_0004.Form)
 | 
			
		||||
register_stanza_plugin(Items, Item, iterable=True)
 | 
			
		||||
register_stanza_plugin(Publish, Item, iterable=True)
 | 
			
		||||
register_stanza_plugin(Retract, Item)
 | 
			
		||||
register_stanza_plugin(Subscribe, Options)
 | 
			
		||||
register_stanza_plugin(Subscription, SubscribeOptions)
 | 
			
		||||
register_stanza_plugin(Subscriptions, Subscription, iterable=True)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Error
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PubsubErrorCondition(ElementBase):
 | 
			
		||||
 | 
			
		||||
    plugin_attrib = 'pubsub'
 | 
			
		||||
    interfaces = set(('condition', 'unsupported'))
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
    conditions = set(('closed-node', 'configuration-required', 'invalid-jid',
 | 
			
		||||
                      'invalid-options', 'invalid-payload', 'invalid-subid',
 | 
			
		||||
                      'item-forbidden', 'item-required', 'jid-required',
 | 
			
		||||
                      'max-items-exceeded', 'max-nodes-exceeded',
 | 
			
		||||
                      'nodeid-required', 'not-in-roster-group',
 | 
			
		||||
                      'not-subscribed', 'payload-too-big',
 | 
			
		||||
                      'payload-required' 'pending-subscription',
 | 
			
		||||
                      'presence-subscription-required', 'subid-required',
 | 
			
		||||
                      'too-many-subscriptions', 'unsupported'))
 | 
			
		||||
    condition_ns = 'http://jabber.org/protocol/pubsub#errors'
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml):
 | 
			
		||||
        """Don't create XML for the plugin."""
 | 
			
		||||
        self.xml = ET.Element('')
 | 
			
		||||
 | 
			
		||||
    def get_condition(self):
 | 
			
		||||
        """Return the condition element's name."""
 | 
			
		||||
        for child in self.parent().xml.getchildren():
 | 
			
		||||
            if "{%s}" % self.condition_ns in child.tag:
 | 
			
		||||
                cond = child.tag.split('}', 1)[-1]
 | 
			
		||||
                if cond in self.conditions:
 | 
			
		||||
                    return cond
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    def set_condition(self, value):
 | 
			
		||||
        """
 | 
			
		||||
        Set the tag name of the condition element.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
           value -- The tag name of the condition element.
 | 
			
		||||
        """
 | 
			
		||||
        if value in self.conditions:
 | 
			
		||||
            del self['condition']
 | 
			
		||||
            cond = ET.Element("{%s}%s" % (self.condition_ns, value))
 | 
			
		||||
            self.parent().xml.append(cond)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def del_condition(self):
 | 
			
		||||
        """Remove the condition element."""
 | 
			
		||||
        for child in self.parent().xml.getchildren():
 | 
			
		||||
            if "{%s}" % self.condition_ns in child.tag:
 | 
			
		||||
                tag = child.tag.split('}', 1)[-1]
 | 
			
		||||
                if tag in self.conditions:
 | 
			
		||||
                    self.parent().xml.remove(child)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def get_unsupported(self):
 | 
			
		||||
        """Return the name of an unsupported feature"""
 | 
			
		||||
        xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
 | 
			
		||||
        if xml is not None:
 | 
			
		||||
            return xml.attrib.get('feature', '')
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    def set_unsupported(self, value):
 | 
			
		||||
        """Mark a feature as unsupported"""
 | 
			
		||||
        self.del_unsupported()
 | 
			
		||||
        xml = ET.Element('{%s}unsupported' % self.condition_ns)
 | 
			
		||||
        xml.attrib['feature'] = value
 | 
			
		||||
        self.parent().xml.append(xml)
 | 
			
		||||
 | 
			
		||||
    def del_unsupported(self):
 | 
			
		||||
        """Delete an unsupported feature condition."""
 | 
			
		||||
        xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
 | 
			
		||||
        if xml is not None:
 | 
			
		||||
            self.parent().xml.remove(xml)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Error, PubsubErrorCondition)
 | 
			
		||||
@@ -1,124 +1,112 @@
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.stanza.iq import Iq
 | 
			
		||||
from sleekxmpp.stanza.message import Message
 | 
			
		||||
from sleekxmpp.basexmpp import basexmpp
 | 
			
		||||
from sleekxmpp.xmlstream.xmlstream import XMLStream
 | 
			
		||||
import logging
 | 
			
		||||
from sleekxmpp.plugins import xep_0004
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Message
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.plugins.xep_0004 import Form
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Event(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'event'
 | 
			
		||||
	plugin_attrib = 'pubsub_event'
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'event'
 | 
			
		||||
    plugin_attrib = 'pubsub_event'
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Message, Event)
 | 
			
		||||
 | 
			
		||||
class EventItem(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'item'
 | 
			
		||||
	plugin_attrib = 'item'
 | 
			
		||||
	interfaces = set(('id', 'payload'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'item'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('id', 'payload'))
 | 
			
		||||
 | 
			
		||||
	def setPayload(self, value):
 | 
			
		||||
		self.xml.append(value)
 | 
			
		||||
	
 | 
			
		||||
	def getPayload(self):
 | 
			
		||||
		childs = self.xml.getchildren()
 | 
			
		||||
		if len(childs) > 0:
 | 
			
		||||
			return childs[0]
 | 
			
		||||
	
 | 
			
		||||
	def delPayload(self):
 | 
			
		||||
		for child in self.xml.getchildren():
 | 
			
		||||
			self.xml.remove(child)
 | 
			
		||||
    def set_payload(self, value):
 | 
			
		||||
        self.xml.append(value)
 | 
			
		||||
 | 
			
		||||
    def get_payload(self):
 | 
			
		||||
        childs = self.xml.getchildren()
 | 
			
		||||
        if len(childs) > 0:
 | 
			
		||||
            return childs[0]
 | 
			
		||||
 | 
			
		||||
    def del_payload(self):
 | 
			
		||||
        for child in self.xml.getchildren():
 | 
			
		||||
            self.xml.remove(child)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventRetract(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'retract'
 | 
			
		||||
	plugin_attrib = 'retract'
 | 
			
		||||
	interfaces = set(('id',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'retract'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('id',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventItems(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'items'
 | 
			
		||||
	plugin_attrib = 'items'
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	subitem = (EventItem, EventRetract)
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'items'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Event, EventItems)
 | 
			
		||||
 | 
			
		||||
class EventCollection(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'collection'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'collection'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Event, EventCollection)
 | 
			
		||||
 | 
			
		||||
class EventAssociate(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'associate'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'associate'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(EventCollection, EventAssociate)
 | 
			
		||||
 | 
			
		||||
class EventDisassociate(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'disassociate'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'disassociate'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(EventCollection, EventDisassociate)
 | 
			
		||||
 | 
			
		||||
class EventConfiguration(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'configuration'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node', 'config'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	
 | 
			
		||||
registerStanzaPlugin(Event, EventConfiguration)
 | 
			
		||||
registerStanzaPlugin(EventConfiguration, xep_0004.Form)
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'configuration'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'config'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventPurge(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'purge'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'purge'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Event, EventPurge)
 | 
			
		||||
 | 
			
		||||
class EventSubscription(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
	name = 'subscription'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node','expiry', 'jid', 'subid', 'subscription'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	
 | 
			
		||||
	def setJid(self, value):
 | 
			
		||||
		self._setAttr('jid', str(value))
 | 
			
		||||
	
 | 
			
		||||
	def getJid(self):
 | 
			
		||||
		return JID(self._getAttr('jid'))
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#event'
 | 
			
		||||
    name = 'subscription'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'expiry', 'jid', 'subid', 'subscription'))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Event, EventSubscription)
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Message, Event)
 | 
			
		||||
register_stanza_plugin(Event, EventCollection)
 | 
			
		||||
register_stanza_plugin(Event, EventConfiguration)
 | 
			
		||||
register_stanza_plugin(Event, EventItems)
 | 
			
		||||
register_stanza_plugin(Event, EventPurge)
 | 
			
		||||
register_stanza_plugin(Event, EventSubscription)
 | 
			
		||||
register_stanza_plugin(EventCollection, EventAssociate)
 | 
			
		||||
register_stanza_plugin(EventCollection, EventDisassociate)
 | 
			
		||||
register_stanza_plugin(EventConfiguration, Form)
 | 
			
		||||
register_stanza_plugin(EventItems, EventItem, iterable=True)
 | 
			
		||||
register_stanza_plugin(EventItems, EventRetract, iterable=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,152 +1,131 @@
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.stanza.iq import Iq
 | 
			
		||||
from sleekxmpp.stanza.message import Message
 | 
			
		||||
from sleekxmpp.basexmpp import basexmpp
 | 
			
		||||
from sleekxmpp.xmlstream.xmlstream import XMLStream
 | 
			
		||||
import logging
 | 
			
		||||
from sleekxmpp.plugins import xep_0004
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
 | 
			
		||||
from sleekxmpp.plugins.xep_0004 import Form
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation, Configure, Subscriptions
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation
 | 
			
		||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Configure, Subscriptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PubsubOwner(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	name = 'pubsub'
 | 
			
		||||
	plugin_attrib = 'pubsub_owner'
 | 
			
		||||
	interfaces = set(tuple())
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    name = 'pubsub'
 | 
			
		||||
    plugin_attrib = 'pubsub_owner'
 | 
			
		||||
    interfaces = set(tuple())
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(Iq, PubsubOwner)
 | 
			
		||||
 | 
			
		||||
class DefaultConfig(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	name = 'default'
 | 
			
		||||
	plugin_attrib = 'default'
 | 
			
		||||
	interfaces = set(('node', 'type', 'config'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    name = 'default'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'config'))
 | 
			
		||||
 | 
			
		||||
	def __init__(self, *args, **kwargs):
 | 
			
		||||
		ElementBase.__init__(self, *args, **kwargs)
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        ElementBase.__init__(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
	def getType(self):
 | 
			
		||||
		t = self._getAttr('type')
 | 
			
		||||
		if not t: t = 'leaf'
 | 
			
		||||
		return t
 | 
			
		||||
    def get_config(self):
 | 
			
		||||
        return self['form']
 | 
			
		||||
 | 
			
		||||
	def getConfig(self):
 | 
			
		||||
		return self['form']
 | 
			
		||||
    def set_config(self, value):
 | 
			
		||||
        self['form'].values = value.values
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
	def setConfig(self, value):
 | 
			
		||||
		self['form'].setStanzaValues(value.getStanzaValues())
 | 
			
		||||
		return self
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(PubsubOwner, DefaultConfig)
 | 
			
		||||
registerStanzaPlugin(DefaultConfig, xep_0004.Form)
 | 
			
		||||
 | 
			
		||||
class OwnerAffiliations(Affiliations):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	interfaces = set(('node'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
	def append(self, affiliation):
 | 
			
		||||
		if not isinstance(affiliation, OwnerAffiliation):
 | 
			
		||||
			raise TypeError
 | 
			
		||||
		self.xml.append(affiliation.xml)
 | 
			
		||||
		return self.affiliations.append(affiliation)
 | 
			
		||||
    def append(self, affiliation):
 | 
			
		||||
        if not isinstance(affiliation, OwnerAffiliation):
 | 
			
		||||
            raise TypeError
 | 
			
		||||
        self.xml.append(affiliation.xml)
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
 | 
			
		||||
 | 
			
		||||
class OwnerAffiliation(Affiliation):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	interfaces = set(('affiliation', 'jid'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    interfaces = set(('affiliation', 'jid'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OwnerConfigure(Configure):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	interfaces = set(('node', 'config'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    name = 'configure'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(PubsubOwner, OwnerConfigure)
 | 
			
		||||
 | 
			
		||||
class OwnerDefault(OwnerConfigure):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	interfaces = set(('node', 'config'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
	def getConfig(self):
 | 
			
		||||
		return self['form']
 | 
			
		||||
 | 
			
		||||
	def setConfig(self, value):
 | 
			
		||||
		self['form'].setStanzaValues(value.getStanzaValues())
 | 
			
		||||
		return self
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(PubsubOwner, OwnerDefault)
 | 
			
		||||
registerStanzaPlugin(OwnerDefault, xep_0004.Form)
 | 
			
		||||
 | 
			
		||||
class OwnerDelete(ElementBase, OptionalSetting):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	name = 'delete'
 | 
			
		||||
	plugin_attrib = 'delete'
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    name = 'delete'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(PubsubOwner, OwnerDelete)
 | 
			
		||||
 | 
			
		||||
class OwnerPurge(ElementBase, OptionalSetting):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	name = 'purge'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    name = 'purge'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(PubsubOwner, OwnerPurge)
 | 
			
		||||
 | 
			
		||||
class OwnerRedirect(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	name = 'redirect'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('node', 'jid'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    name = 'redirect'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('node', 'jid'))
 | 
			
		||||
 | 
			
		||||
	def setJid(self, value):
 | 
			
		||||
		self._setAttr('jid', str(value))
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
	def getJid(self):
 | 
			
		||||
		return JID(self._getAttr('jid'))
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(OwnerDelete, OwnerRedirect)
 | 
			
		||||
 | 
			
		||||
class OwnerSubscriptions(Subscriptions):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	interfaces = set(('node',))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    interfaces = set(('node',))
 | 
			
		||||
 | 
			
		||||
	def append(self, subscription):
 | 
			
		||||
		if not isinstance(subscription, OwnerSubscription):
 | 
			
		||||
			raise TypeError
 | 
			
		||||
		self.xml.append(subscription.xml)
 | 
			
		||||
		return self.subscriptions.append(subscription)
 | 
			
		||||
    def append(self, subscription):
 | 
			
		||||
        if not isinstance(subscription, OwnerSubscription):
 | 
			
		||||
            raise TypeError
 | 
			
		||||
        self.xml.append(subscription.xml)
 | 
			
		||||
 | 
			
		||||
registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
 | 
			
		||||
 | 
			
		||||
class OwnerSubscription(ElementBase):
 | 
			
		||||
	namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
	name = 'subscription'
 | 
			
		||||
	plugin_attrib = name
 | 
			
		||||
	interfaces = set(('jid', 'subscription'))
 | 
			
		||||
	plugin_attrib_map = {}
 | 
			
		||||
	plugin_tag_map = {}
 | 
			
		||||
    namespace = 'http://jabber.org/protocol/pubsub#owner'
 | 
			
		||||
    name = 'subscription'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(('jid', 'subscription'))
 | 
			
		||||
 | 
			
		||||
	def setJid(self, value):
 | 
			
		||||
		self._setAttr('jid', str(value))
 | 
			
		||||
    def set_jid(self, value):
 | 
			
		||||
        self._set_attr('jid', str(value))
 | 
			
		||||
 | 
			
		||||
	def getJid(self):
 | 
			
		||||
		return JID(self._getAttr('from'))
 | 
			
		||||
    def get_jid(self):
 | 
			
		||||
        return JID(self._get_attr('jid'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Iq, PubsubOwner)
 | 
			
		||||
register_stanza_plugin(PubsubOwner, DefaultConfig)
 | 
			
		||||
register_stanza_plugin(PubsubOwner, OwnerAffiliations)
 | 
			
		||||
register_stanza_plugin(PubsubOwner, OwnerConfigure)
 | 
			
		||||
register_stanza_plugin(PubsubOwner, OwnerDefault)
 | 
			
		||||
register_stanza_plugin(PubsubOwner, OwnerDelete)
 | 
			
		||||
register_stanza_plugin(PubsubOwner, OwnerPurge)
 | 
			
		||||
register_stanza_plugin(PubsubOwner, OwnerSubscriptions)
 | 
			
		||||
register_stanza_plugin(DefaultConfig, Form)
 | 
			
		||||
register_stanza_plugin(OwnerAffiliations, OwnerAffiliation, iterable=True)
 | 
			
		||||
register_stanza_plugin(OwnerConfigure, Form)
 | 
			
		||||
register_stanza_plugin(OwnerDefault, Form)
 | 
			
		||||
register_stanza_plugin(OwnerDelete, OwnerRedirect)
 | 
			
		||||
register_stanza_plugin(OwnerSubscriptions, OwnerSubscription, iterable=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -108,8 +108,7 @@ class xep_0066(base_plugin):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['to'] = to
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq['oob_transfer']['url'] = url
 | 
			
		||||
        iq['oob_transfer']['desc'] = desc
 | 
			
		||||
        return iq.send(**iqargs)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,72 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
from __future__ import with_statement
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import logging
 | 
			
		||||
import hashlib
 | 
			
		||||
from . import base
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0078(base.base_plugin):
 | 
			
		||||
	"""
 | 
			
		||||
	XEP-0078 NON-SASL Authentication
 | 
			
		||||
	"""
 | 
			
		||||
	def plugin_init(self):
 | 
			
		||||
		self.description = "Non-SASL Authentication (broken)"
 | 
			
		||||
		self.xep = "0078"
 | 
			
		||||
		self.xmpp.add_event_handler("session_start", self.check_stream)
 | 
			
		||||
		#disabling until I fix conflict with PLAIN
 | 
			
		||||
		#self.xmpp.registerFeature("<auth xmlns='http://jabber.org/features/iq-auth'/>", self.auth)
 | 
			
		||||
		self.streamid = ''
 | 
			
		||||
 | 
			
		||||
	def check_stream(self, xml):
 | 
			
		||||
		self.streamid = xml.attrib['id']
 | 
			
		||||
		if xml.get('version', '0') != '1.0':
 | 
			
		||||
			self.auth()
 | 
			
		||||
 | 
			
		||||
	def auth(self, xml=None):
 | 
			
		||||
		log.debug("Starting jabber:iq:auth Authentication")
 | 
			
		||||
		auth_request = self.xmpp.makeIqGet()
 | 
			
		||||
		auth_request_query = ET.Element('{jabber:iq:auth}query')
 | 
			
		||||
		auth_request.attrib['to'] = self.xmpp.boundjid.host
 | 
			
		||||
		username = ET.Element('username')
 | 
			
		||||
		username.text = self.xmpp.username
 | 
			
		||||
		auth_request_query.append(username)
 | 
			
		||||
		auth_request.append(auth_request_query)
 | 
			
		||||
		result = auth_request.send()
 | 
			
		||||
		rquery = result.find('{jabber:iq:auth}query')
 | 
			
		||||
		attempt = self.xmpp.makeIqSet()
 | 
			
		||||
		query = ET.Element('{jabber:iq:auth}query')
 | 
			
		||||
		resource = ET.Element('resource')
 | 
			
		||||
		resource.text = self.xmpp.resource
 | 
			
		||||
		query.append(username)
 | 
			
		||||
		query.append(resource)
 | 
			
		||||
		if rquery.find('{jabber:iq:auth}digest') is None:
 | 
			
		||||
			log.warning("Authenticating via jabber:iq:auth Plain.")
 | 
			
		||||
			password = ET.Element('password')
 | 
			
		||||
			password.text = self.xmpp.password
 | 
			
		||||
			query.append(password)
 | 
			
		||||
		else:
 | 
			
		||||
			log.debug("Authenticating via jabber:iq:auth Digest")
 | 
			
		||||
			digest = ET.Element('digest')
 | 
			
		||||
			digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest()
 | 
			
		||||
			query.append(digest)
 | 
			
		||||
		attempt.append(query)
 | 
			
		||||
		result = attempt.send()
 | 
			
		||||
		if result.attrib['type'] == 'result':
 | 
			
		||||
			with self.xmpp.lock:
 | 
			
		||||
				self.xmpp.authenticated = True
 | 
			
		||||
				self.xmpp.sessionstarted = True
 | 
			
		||||
			self.xmpp.event("session_start")
 | 
			
		||||
		else:
 | 
			
		||||
			log.info("Authentication failed")
 | 
			
		||||
			self.xmpp.disconnect()
 | 
			
		||||
			self.xmpp.event("failed_auth")
 | 
			
		||||
							
								
								
									
										12
									
								
								sleekxmpp/plugins/xep_0078/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								sleekxmpp/plugins/xep_0078/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
"""
 | 
			
		||||
    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_0078 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0078.stanza import IqAuth, AuthFeature
 | 
			
		||||
from sleekxmpp.plugins.xep_0078.legacyauth import xep_0078
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										119
									
								
								sleekxmpp/plugins/xep_0078/legacyauth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								sleekxmpp/plugins/xep_0078/legacyauth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import hashlib
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Iq, StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0078 import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0078(base_plugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0078 NON-SASL Authentication
 | 
			
		||||
 | 
			
		||||
    This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin
 | 
			
		||||
    unless you are forced to use an old XMPP server implementation.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = "0078"
 | 
			
		||||
        self.description = "Non-SASL Authentication"
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_feature('auth',
 | 
			
		||||
                self._handle_auth,
 | 
			
		||||
                restart=False,
 | 
			
		||||
                order=self.config.get('order', 15))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.IqAuth)
 | 
			
		||||
        register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _handle_auth(self, features):
 | 
			
		||||
        # If we can or have already authenticated with SASL, do nothing.
 | 
			
		||||
        if 'mechanisms' in features['features']:
 | 
			
		||||
            return False
 | 
			
		||||
        if self.xmpp.authenticated:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        log.debug("Starting jabber:iq:auth Authentication")
 | 
			
		||||
 | 
			
		||||
        # Step 1: Request the auth form
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = self.xmpp.boundjid.host
 | 
			
		||||
        iq['auth']['username'] = self.xmpp.boundjid.user
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            resp = iq.send(now=True)
 | 
			
		||||
        except IqError:
 | 
			
		||||
            log.info("Authentication failed: %s" % resp['error']['condition'])
 | 
			
		||||
            self.xmpp.event('failed_auth', direct=True)
 | 
			
		||||
            self.xmpp.disconnect()
 | 
			
		||||
            return True
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            log.info("Authentication failed: %s" % 'timeout')
 | 
			
		||||
            self.xmpp.event('failed_auth', direct=True)
 | 
			
		||||
            self.xmpp.disconnect()
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        # Step 2: Fill out auth form for either password or digest auth
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['auth']['username'] = self.xmpp.boundjid.user
 | 
			
		||||
 | 
			
		||||
        # A resource is required, so create a random one if necessary
 | 
			
		||||
        if self.xmpp.boundjid.resource:
 | 
			
		||||
            iq['auth']['resource'] = self.xmpp.boundjid.resource
 | 
			
		||||
        else:
 | 
			
		||||
            iq['auth']['resource'] = '%s' % random.random()
 | 
			
		||||
 | 
			
		||||
        if 'digest' in resp['auth']['fields']:
 | 
			
		||||
            log.debug('Authenticating via jabber:iq:auth Digest')
 | 
			
		||||
            if sys.version_info < (3, 0):
 | 
			
		||||
                stream_id = bytes(self.xmpp.stream_id)
 | 
			
		||||
                password = bytes(self.xmpp.password)
 | 
			
		||||
            else:
 | 
			
		||||
                stream_id = bytes(self.xmpp.stream_id, encoding='utf-8')
 | 
			
		||||
                password = bytes(self.xmpp.password, encoding='utf-8')
 | 
			
		||||
 | 
			
		||||
            digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest()
 | 
			
		||||
            iq['auth']['digest'] = digest
 | 
			
		||||
        else:
 | 
			
		||||
            log.warning('Authenticating via jabber:iq:auth Plain.')
 | 
			
		||||
            iq['auth']['password'] = self.xmpp.password
 | 
			
		||||
 | 
			
		||||
        # Step 3: Send credentials
 | 
			
		||||
        try:
 | 
			
		||||
            result = iq.send(now=True)
 | 
			
		||||
        except IqError as err:
 | 
			
		||||
            log.info("Authentication failed")
 | 
			
		||||
            self.xmpp.disconnect()
 | 
			
		||||
            self.xmpp.event("failed_auth", direct=True)
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            log.info("Authentication failed")
 | 
			
		||||
            self.xmpp.disconnect()
 | 
			
		||||
            self.xmpp.event("failed_auth", direct=True)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.features.add('auth')
 | 
			
		||||
 | 
			
		||||
        self.xmpp.authenticated = True
 | 
			
		||||
        log.debug("Established Session")
 | 
			
		||||
        self.xmpp.sessionstarted = True
 | 
			
		||||
        self.xmpp.session_started_event.set()
 | 
			
		||||
        self.xmpp.event('session_start')
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
							
								
								
									
										43
									
								
								sleekxmpp/plugins/xep_0078/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								sleekxmpp/plugins/xep_0078/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IqAuth(ElementBase):
 | 
			
		||||
    namespace = 'jabber:iq:auth'
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    plugin_attrib = 'auth'
 | 
			
		||||
    interfaces = set(('fields', 'username', 'password', 'resource', 'digest'))
 | 
			
		||||
    sub_interfaces = set(('username', 'password', 'resource', 'digest'))
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
 | 
			
		||||
    def get_fields(self):
 | 
			
		||||
        fields = set()
 | 
			
		||||
        for field in self.sub_interfaces:
 | 
			
		||||
            if self.xml.find('{%s}%s' % (self.namespace, field)) is not None:
 | 
			
		||||
                fields.add(field)
 | 
			
		||||
        return fields
 | 
			
		||||
 | 
			
		||||
    def set_resource(self, value):
 | 
			
		||||
        self._set_sub_text('resource', value, keep=True)
 | 
			
		||||
 | 
			
		||||
    def set_password(self, value):
 | 
			
		||||
        self._set_sub_text('password', value, keep=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthFeature(ElementBase):
 | 
			
		||||
    namespace = 'http://jabber.org/features/iq-auth'
 | 
			
		||||
    name = 'auth'
 | 
			
		||||
    plugin_attrib = 'auth'
 | 
			
		||||
    interfaces = set()
 | 
			
		||||
    plugin_tag_map = {}
 | 
			
		||||
    plugin_attrib_map = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -76,8 +76,7 @@ class xep_0092(base_plugin):
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['query'] = Version.namespace
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
@@ -69,6 +70,8 @@ class xep_0199(base_plugin):
 | 
			
		||||
            self.xmpp.add_event_handler('session_start',
 | 
			
		||||
                                        self._handle_keepalive,
 | 
			
		||||
                                        threaded=True)
 | 
			
		||||
            self.xmpp.add_event_handler('session_end',
 | 
			
		||||
                                        self._handle_session_end)
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """Handle cross-plugin dependencies."""
 | 
			
		||||
@@ -89,8 +92,13 @@ class xep_0199(base_plugin):
 | 
			
		||||
        def scheduled_ping():
 | 
			
		||||
            """Send ping request to the server."""
 | 
			
		||||
            log.debug("Pinging...")
 | 
			
		||||
            resp = self.send_ping(self.xmpp.boundjid.host, self.timeout)
 | 
			
		||||
            if resp is None or resp is False:
 | 
			
		||||
            try:
 | 
			
		||||
                self.send_ping(self.xmpp.boundjid.host, self.timeout)
 | 
			
		||||
            except IqError:
 | 
			
		||||
                log.debug("Ping response was an error." + \
 | 
			
		||||
                          "Requesting Reconnect.")
 | 
			
		||||
                self.xmpp.reconnect()
 | 
			
		||||
            except IqTimeout:
 | 
			
		||||
                log.debug("Did not recieve ping back in time." + \
 | 
			
		||||
                          "Requesting Reconnect.")
 | 
			
		||||
                self.xmpp.reconnect()
 | 
			
		||||
@@ -100,6 +108,9 @@ class xep_0199(base_plugin):
 | 
			
		||||
                           scheduled_ping,
 | 
			
		||||
                           repeat=True)
 | 
			
		||||
 | 
			
		||||
    def _handle_session_end(self, event):
 | 
			
		||||
        self.xmpp.scheduler.remove('Ping Keep Alive')
 | 
			
		||||
 | 
			
		||||
    def _handle_ping(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Automatically reply to ping requests.
 | 
			
		||||
@@ -108,7 +119,7 @@ class xep_0199(base_plugin):
 | 
			
		||||
            iq -- The ping request.
 | 
			
		||||
        """
 | 
			
		||||
        log.debug("Pinged by %s" % iq['from'])
 | 
			
		||||
        iq.reply().enable('ping').send()
 | 
			
		||||
        iq.reply().send()
 | 
			
		||||
 | 
			
		||||
    def send_ping(self, jid, timeout=None, errorfalse=False,
 | 
			
		||||
                  ifrom=None, block=True, callback=None):
 | 
			
		||||
@@ -137,14 +148,18 @@ class xep_0199(base_plugin):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = ifrom
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq.enable('ping')
 | 
			
		||||
 | 
			
		||||
        start_time = time.clock()
 | 
			
		||||
        resp = iq.send(block=block,
 | 
			
		||||
                       timeout=timeout,
 | 
			
		||||
                       callback=callback)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            resp = iq.send(block=block,
 | 
			
		||||
                           timeout=timeout,
 | 
			
		||||
                           callback=callback)
 | 
			
		||||
        except IqError as err:
 | 
			
		||||
            resp = err.iq
 | 
			
		||||
 | 
			
		||||
        end_time = time.clock()
 | 
			
		||||
 | 
			
		||||
        delay = end_time - start_time
 | 
			
		||||
@@ -152,9 +167,6 @@ class xep_0199(base_plugin):
 | 
			
		||||
        if not block:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        if not resp or resp['type'] == 'error':
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        log.debug("Pong: %s %f" % (jid, delay))
 | 
			
		||||
        return delay
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -85,8 +85,7 @@ class xep_0202(base_plugin):
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = 'to'
 | 
			
		||||
        if ifrom:
 | 
			
		||||
            iq['from'] = 'ifrom'
 | 
			
		||||
        iq['to'] = to
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq.enable('entity_time')
 | 
			
		||||
        return iq.send(**iqargs)
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user