Compare commits
	
		
			135 Commits
		
	
	
		
			slix-1.2.2
			...
			sleek-0.9-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 494e3ef449 | ||
|   | be5688007b | ||
|   | ad7c1b06f4 | ||
|   | 083ac3faaf | ||
|   | a909731b03 | ||
|   | 4864197d46 | ||
|   | 92a5ac2ba9 | ||
|   | 02ca5f0e42 | ||
|   | 1e009513ee | ||
|   | 55f83e8ab0 | ||
|   | d43fba3c8f | ||
|   | 9c5285987d | ||
|   | d09cbef9a7 | ||
|   | 9c850f080d | ||
|   | 879dd11daa | ||
|   | 969c4652a4 | ||
|   | 9506970042 | ||
|   | 3c6b07353d | ||
|   | 66c6c21ad8 | ||
|   | c5b5cc4af1 | ||
|   | e835843aab | ||
|   | d6681f16d2 | ||
|   | fc952efae9 | ||
|   | f7273affc5 | ||
|   | 34eb88f199 | ||
|   | f3cf5f6080 | ||
|   | 85d8b9270f | ||
|   | 259dffeb6e | ||
|   | 0a30e6c017 | ||
|   | d381ab320a | ||
|   | 6e93982fdf | ||
|   | 33602f232c | ||
|   | 7968ca2892 | ||
|   | 661cdd2018 | ||
|   | 4b00baab1e | ||
|   | fe1d3004cc | ||
|   | 62da57a6c2 | ||
|   | ba9633f8f7 | ||
|   | 065a164223 | ||
|   | cd2017b8b0 | ||
|   | dd9f33b7d9 | ||
|   | 0a23f84ec3 | ||
|   | f477ccf533 | ||
|   | d62a30b0f8 | ||
|   | d763795b2c | ||
|   | fff54eaf2f | ||
|   | 488d5b29d4 | ||
|   | 9bdb297fe2 | ||
|   | fa7f72d0af | ||
|   | c538ffae79 | ||
|   | 5d87a54913 | ||
|   | 8bdfa77024 | ||
|   | 15ac3e9fba | ||
|   | e8d37b409c | ||
|   | 898f96f265 | ||
|   | bbf1cb8ba2 | ||
|   | d22f6a2aa5 | ||
|   | c0a6291fea | ||
|   | f5d0466462 | ||
|   | f659e3081e | ||
|   | 4fccd77685 | ||
|   | bf2bf29fc6 | ||
|   | 34dc236126 | ||
|   | 9464736551 | ||
|   | 47f1fb1690 | ||
|   | 66cf0c2021 | ||
|   | e7c37c4ec5 | ||
|   | 1aa34cb0fc | ||
|   | 919c8c5633 | ||
|   | f54501a346 | ||
|   | d20cd6b3e6 | ||
|   | 3f96226e29 | ||
|   | 71d72f431f | ||
|   | da6e1e47dc | ||
|   | 2f0f18a8c6 | ||
|   | 1c32668e18 | ||
|   | 77bff9cce7 | ||
|   | 1f3cfb98f1 | ||
|   | 4295a66c70 | ||
|   | 8227affd7f | ||
|   | 3a2f989c5e | ||
|   | 85a2715c7d | ||
|   | b03e6168a8 | ||
|   | 2a43f59a58 | ||
|   | 184f7cb8a4 | ||
|   | e1aa4d0b93 | ||
|   | db4989c66d | ||
|   | 7930ed22f2 | ||
|   | b0066f3ef4 | ||
|   | c0457cf5d0 | ||
|   | 59b8406573 | ||
|   | 686943a2ec | ||
|   | 060b4c3938 | ||
|   | 49f5767aea | ||
|   | 4eb210bff5 | ||
|   | 1780ca900a | ||
|   | e6c2fde283 | ||
|   | ecf902bf16 | ||
|   | d76c0931ef | ||
|   | e18793152f | ||
|   | e388680269 | ||
|   | bee42e4a2f | ||
|   | 8e3227ae5e | ||
|   | 257bcadd96 | ||
|   | 3e5cdc8664 | ||
|   | 194e6bcb51 | ||
|   | 2e7024419a | ||
|   | 5235313aab | ||
|   | a2719b0bb0 | ||
|   | 71ad715caa | ||
|   | d452085049 | ||
|   | 8b3b8aca9e | ||
|   | e00dea7c0c | ||
|   | 520bf72e11 | ||
|   | 040f426f1a | ||
|   | 226b0e4297 | ||
|   | 0b2cd176b1 | ||
|   | 56b5cbe5b1 | ||
|   | 3e83b16a58 | ||
|   | de4d611d30 | ||
|   | e8d0fc37dc | ||
|   | dda3e733b5 | ||
|   | 4b322720b3 | ||
|   | 3f41fdd231 | ||
|   | 8e95ae2948 | ||
|   | 341c110b6a | ||
|   | 7522839141 | ||
|   | 4c410dd48a | ||
|   | 2d89954412 | ||
|   | a92075a659 | ||
|   | 7552efee5c | ||
|   | 6bc6ebb95d | ||
|   | e0c32b6d9b | ||
|   | 1521a8b5c9 | ||
|   | 70f69c180c | 
							
								
								
									
										17
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,15 +1,6 @@ | |||||||
| *.py[co] | *.pyc | ||||||
|  | .project | ||||||
| build/ | build/ | ||||||
| dist/ |  | ||||||
| MANIFEST |  | ||||||
| docs/_build/ |  | ||||||
| *.swp | *.swp | ||||||
| .tox/ | .pydevproject | ||||||
| .coverage | .settings | ||||||
| slixmpp.egg-info/ |  | ||||||
| .ropeproject/ |  | ||||||
| 4913 |  | ||||||
| *~ |  | ||||||
| .baboon/ |  | ||||||
| .DS_STORE |  | ||||||
| .idea/ |  | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								.pylintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								.pylintrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | # Pylint configuration file. | ||||||
|  | # run `pylint --generate-rcfile` to see the default configuration | ||||||
|  | # run `pylint --rcfile=.pylintrc smallfoot` to perform analysis | ||||||
|  |  | ||||||
|  | # Brain-dead errors regarding standard language features | ||||||
|  | #   W0142 = *args and **kwargs support | ||||||
|  | #   W0403 = Relative imports | ||||||
|  |  | ||||||
|  | # Pointless whining | ||||||
|  | #   R0201 = Method could be a function | ||||||
|  | #   W0212 = Accessing protected attribute of client class | ||||||
|  | #   W0613 = Unused argument | ||||||
|  | #   W0232 = Class has no __init__ method | ||||||
|  | #   R0903 = Too few public methods | ||||||
|  | #   C0301 = Line too long | ||||||
|  | #   R0913 = Too many arguments | ||||||
|  | #   C0103 = Invalid name | ||||||
|  | #   R0914 = Too many local variables | ||||||
|  |  | ||||||
|  | # PyLint's module importation is unreliable | ||||||
|  | #   F0401 = Unable to import module | ||||||
|  | #   W0402 = Uses of a deprecated module | ||||||
|  |  | ||||||
|  | # Already an error when wildcard imports are used | ||||||
|  | #   W0614 = Unused import from wildcard | ||||||
|  |  | ||||||
|  | # Sometimes disabled depending on how bad a module is | ||||||
|  | #   C0111 = Missing docstring | ||||||
|  |  | ||||||
|  | # Convention Errors related to whitespace: | ||||||
|  | #   C0321,C0322,C0323,C0324 | ||||||
|  |  | ||||||
|  | # Comments that we've put in the code: | ||||||
|  | #   W0511 | ||||||
|  |  | ||||||
|  | [MESSAGES CONTROL] | ||||||
|  |  | ||||||
|  | # Disable the message(s) with the given id(s). | ||||||
|  | disable=W0142,W0403,R0201,W0212,W0613,W0232,R0903,W0614,C0103,C0111,C0301,C0321,C0322,C0323,C0324,R0913,F0401,W0402,R0914,W0511,W0312 | ||||||
|  |  | ||||||
|  | [REPORTS] | ||||||
|  |  | ||||||
|  | include-ids=y | ||||||
|  | reports=y | ||||||
|  | # Set the output format. Available formats are text, parseable, colorized, msvs | ||||||
|  | # (visual studio) and html | ||||||
|  | output-format=text | ||||||
|  | # Put messages in a separate file for each module / package specified on the | ||||||
|  | # command line instead of printing them on stdout. Reports (if any) will be | ||||||
|  | # written in a file name "pylint_global.[txt|html]". | ||||||
|  | files-output=no | ||||||
|  |  | ||||||
|  | [VARIABLES] | ||||||
|  |    | ||||||
|  | # Tells whether we should check for unused import in __init__ files. | ||||||
|  | init-import=yes | ||||||
|  |  | ||||||
|  | [TYPECHECK] | ||||||
|  |  | ||||||
|  | # List of classes names for which member attributes should not be checked | ||||||
|  | # (useful for classes with attributes dynamically set). | ||||||
|  | #ignored-classes=Message | ||||||
|  |  | ||||||
|  |  | ||||||
|  | [MISCELLANEOUS] | ||||||
|  |  | ||||||
|  | # List of note tags to take in consideration, separated by a comma. | ||||||
|  | notes=FIXME,XXX,TODO | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,10 +0,0 @@ | |||||||
| language: python |  | ||||||
| python: |  | ||||||
|   - "2.6" |  | ||||||
|   - "2.7" |  | ||||||
|   - "3.2" |  | ||||||
|   - "3.3" |  | ||||||
|   - "3.4" |  | ||||||
| install: |  | ||||||
|   - "pip install ." |  | ||||||
| script: testall.py |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| Contributing to the Slixmpp project |  | ||||||
| =================================== |  | ||||||
|  |  | ||||||
| To contribute, the preferred way is to commit your changes on some |  | ||||||
| publicly-available git repository (on a fork `on github |  | ||||||
| <https://github.com/poezio/slixmpp>`_ or on your own repository) and to |  | ||||||
| notify the developers with either: |  | ||||||
|  - a ticket `on the bug tracker <https://dev.poez.io/new>`_ |  | ||||||
|  - a pull request on github |  | ||||||
|  - a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_ |  | ||||||
|  |  | ||||||
| Even though Slixmpp’s github repository is just a read-only mirror, we can |  | ||||||
| still be notified of the pull requests and fetch your mirror manually to |  | ||||||
| integrate your changes. |  | ||||||
							
								
								
									
										11
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,13 +1,8 @@ | |||||||
| Pre-requisites: | Pre-requisites: | ||||||
| - Python 3.4 | Python 3.1 or 2.6 | ||||||
| - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) |  | ||||||
|  |  | ||||||
| Install: | Install: | ||||||
| > python3 setup.py install | python3 setup.py install | ||||||
|  |  | ||||||
| Root install: | Root install: | ||||||
| > sudo python3 setup.py install | sudo python3 setup.py install | ||||||
|  |  | ||||||
| To test: |  | ||||||
| > cd examples |  | ||||||
| > python3 echo_client.py -d -j [USER@example.com] -p [PASSWORD] |  | ||||||
|   | |||||||
							
								
								
									
										177
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										177
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| Copyright (c) 2010 Nathanael C. Fritz | Copyright (c) 2010 ICRL | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||||
| @@ -17,178 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
| THE SOFTWARE. | THE SOFTWARE. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Licenses of Bundled Third Party Code |  | ||||||
| ------------------------------------ |  | ||||||
|  |  | ||||||
| dateutil - Extensions to the standard python 2.3+ datetime module. |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net> |  | ||||||
|  |  | ||||||
| All rights reserved. |  | ||||||
|  |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are met: |  | ||||||
|  |  | ||||||
|     * Redistributions of source code must retain the above copyright notice, |  | ||||||
|       this list of conditions and the following disclaimer. |  | ||||||
|     * Redistributions in binary form must reproduce the above copyright notice, |  | ||||||
|       this list of conditions and the following disclaimer in the documentation |  | ||||||
|       and/or other materials provided with the distribution. |  | ||||||
|     * Neither the name of the copyright holder nor the names of its |  | ||||||
|       contributors may be used to endorse or promote products derived from |  | ||||||
|       this software without specific prior written permission. |  | ||||||
|  |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |  | ||||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |  | ||||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |  | ||||||
| CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |  | ||||||
| EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |  | ||||||
| PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |  | ||||||
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |  | ||||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |  | ||||||
| NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |  | ||||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fixed_datetime |  | ||||||
| ~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Copyright (c) 2008, Red Innovation Ltd., Finland |  | ||||||
| All rights reserved. |  | ||||||
|  |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are met: |  | ||||||
|     * Redistributions of source code must retain the above copyright |  | ||||||
|       notice, this list of conditions and the following disclaimer. |  | ||||||
|     * Redistributions in binary form must reproduce the above copyright |  | ||||||
|       notice, this list of conditions and the following disclaimer in the |  | ||||||
|       documentation and/or other materials provided with the distribution. |  | ||||||
|     * Neither the name of Red Innovation nor the names of its contributors |  | ||||||
|       may be used to endorse or promote products derived from this software |  | ||||||
|       without specific prior written permission. |  | ||||||
|  |  | ||||||
| THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY |  | ||||||
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |  | ||||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |  | ||||||
| DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY |  | ||||||
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |  | ||||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |  | ||||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |  | ||||||
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |  | ||||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |  | ||||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| OrderedDict - A port of the Python 2.7+ OrderedDict to Python 2.6 |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Copyright (c) 2009 Raymond Hettinger |  | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person |  | ||||||
| obtaining a copy of this software and associated documentation files |  | ||||||
| (the "Software"), to deal in the Software without restriction, |  | ||||||
| including without limitation the rights to use, copy, modify, merge, |  | ||||||
| publish, distribute, sublicense, and/or sell copies of the Software, |  | ||||||
| and to permit persons to whom the Software is furnished to do so, |  | ||||||
| subject to the following conditions: |  | ||||||
|  |  | ||||||
|     The above copyright notice and this permission notice shall be |  | ||||||
|     included in all copies or substantial portions of the Software. |  | ||||||
|  |  | ||||||
|     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |  | ||||||
|     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |  | ||||||
|     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |  | ||||||
|     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |  | ||||||
|     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |  | ||||||
|     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |  | ||||||
|     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |  | ||||||
|     OTHER DEALINGS IN THE SOFTWARE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| This software is subject to "The MIT License" |  | ||||||
|  |  | ||||||
| Copyright 2004-2013 David Alan Cridland |  | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy |  | ||||||
| of this software and associated documentation files (the "Software"), to deal |  | ||||||
| in the Software without restriction, including without limitation the rights |  | ||||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |  | ||||||
| copies of the Software, and to permit persons to whom the Software is |  | ||||||
| furnished to do so, subject to the following conditions: |  | ||||||
|  |  | ||||||
| The above copyright notice and this permission notice shall be included in |  | ||||||
| all copies or substantial portions of the Software. |  | ||||||
|  |  | ||||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |  | ||||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |  | ||||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |  | ||||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |  | ||||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |  | ||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |  | ||||||
| THE SOFTWARE. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| python-gnupg: A Python wrapper for the GNU Privacy Guard |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
| Copyright (c) 2008-2012 by Vinay Sajip. |  | ||||||
| All rights reserved. |  | ||||||
|  |  | ||||||
| Redistribution and use in source and binary forms, with or without |  | ||||||
| modification, are permitted provided that the following conditions are met: |  | ||||||
|  |  | ||||||
|     * Redistributions of source code must retain the above copyright notice, |  | ||||||
|       this list of conditions and the following disclaimer. |  | ||||||
|     * Redistributions in binary form must reproduce the above copyright notice, |  | ||||||
|       this list of conditions and the following disclaimer in the documentation |  | ||||||
|       and/or other materials provided with the distribution. |  | ||||||
|     * The name(s) of the copyright holder(s) may not be used to endorse or |  | ||||||
|       promote products derived from this software without specific prior |  | ||||||
|       written permission. |  | ||||||
|  |  | ||||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR |  | ||||||
| IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |  | ||||||
| MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |  | ||||||
| EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT, |  | ||||||
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |  | ||||||
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |  | ||||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |  | ||||||
| OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |  | ||||||
| ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | ||||||
|  |  | ||||||
| socksipy: A Python SOCKS client module. |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
| Copyright 2006 Dan-Haim. All rights reserved. |  | ||||||
|  |  | ||||||
| Redistribution and use in source and binary forms, with or without modification, |  | ||||||
| are permitted provided that the following conditions are met: |  | ||||||
| 1. Redistributions of source code must retain the above copyright notice, this |  | ||||||
|    list of conditions and the following disclaimer. |  | ||||||
| 2. Redistributions in binary form must reproduce the above copyright notice, |  | ||||||
|    this list of conditions and the following disclaimer in the documentation |  | ||||||
|    and/or other materials provided with the distribution. |  | ||||||
| 3. Neither the name of Dan Haim nor the names of his contributors may be used |  | ||||||
|    to endorse or promote products derived from this software without specific |  | ||||||
|    prior written permission. |  | ||||||
|  |  | ||||||
| THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED |  | ||||||
| WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |  | ||||||
| MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |  | ||||||
| EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |  | ||||||
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |  | ||||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA |  | ||||||
| OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |  | ||||||
| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |  | ||||||
| OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. |  | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								MANIFEST
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								MANIFEST
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | setup.py | ||||||
|  | sleekxmpp/__init__.py | ||||||
|  | sleekxmpp/basexmpp.py | ||||||
|  | sleekxmpp/clientxmpp.py | ||||||
|  | sleekxmpp/example.py | ||||||
|  | sleekxmpp/plugins/__init__.py | ||||||
|  | sleekxmpp/plugins/base.py | ||||||
|  | sleekxmpp/plugins/gmail_notify.py | ||||||
|  | sleekxmpp/plugins/xep_0004.py | ||||||
|  | sleekxmpp/plugins/xep_0009.py | ||||||
|  | sleekxmpp/plugins/xep_0030.py | ||||||
|  | sleekxmpp/plugins/xep_0045.py | ||||||
|  | sleekxmpp/plugins/xep_0050.py | ||||||
|  | sleekxmpp/plugins/xep_0060.py | ||||||
|  | sleekxmpp/plugins/xep_0078.py | ||||||
|  | sleekxmpp/plugins/xep_0086.py | ||||||
|  | sleekxmpp/plugins/xep_0092.py | ||||||
|  | sleekxmpp/plugins/xep_0199.py | ||||||
|  | sleekxmpp/stanza/__init__.py | ||||||
|  | sleekxmpp/stanza/iq.py | ||||||
|  | sleekxmpp/stanza/message.py | ||||||
|  | sleekxmpp/stanza/presence.py | ||||||
|  | sleekxmpp/xmlstream/__init__.py | ||||||
|  | sleekxmpp/xmlstream/stanzabase.py | ||||||
|  | sleekxmpp/xmlstream/statemachine.py | ||||||
|  | sleekxmpp/xmlstream/test.py | ||||||
|  | sleekxmpp/xmlstream/testclient.py | ||||||
|  | sleekxmpp/xmlstream/xmlstream.py | ||||||
|  | sleekxmpp/xmlstream/handler/__init__.py | ||||||
|  | sleekxmpp/xmlstream/handler/base.py | ||||||
|  | sleekxmpp/xmlstream/handler/callback.py | ||||||
|  | sleekxmpp/xmlstream/handler/waiter.py | ||||||
|  | sleekxmpp/xmlstream/handler/xmlcallback.py | ||||||
|  | sleekxmpp/xmlstream/handler/xmlwaiter.py | ||||||
|  | sleekxmpp/xmlstream/matcher/__init__.py | ||||||
|  | sleekxmpp/xmlstream/matcher/base.py | ||||||
|  | sleekxmpp/xmlstream/matcher/many.py | ||||||
|  | sleekxmpp/xmlstream/matcher/xmlmask.py | ||||||
|  | sleekxmpp/xmlstream/matcher/xpath.py | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| include README.rst |  | ||||||
| include LICENSE |  | ||||||
| include run_tests.py |  | ||||||
| include slixmpp/stringprep.pyx |  | ||||||
| recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png |  | ||||||
| recursive-include examples *.py |  | ||||||
| recursive-include tests *.py |  | ||||||
							
								
								
									
										42
									
								
								README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | 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 | ||||||
|  |  | ||||||
|  | SleekXMPP has several design goals/philosophies: | ||||||
|  | - Low number of dependencies. | ||||||
|  | - Every XEP as a plugin. | ||||||
|  | - Rewarding to work with. | ||||||
|  |  | ||||||
|  | The goals for 1.0 include (and we're getting close): | ||||||
|  | - Nearly Full test coverage of stanzas. | ||||||
|  | - Wide range of functional tests. | ||||||
|  | - Stanza objects for all interaction with the stream | ||||||
|  | - Documentation on using and extending SleekXMPP. | ||||||
|  | - Complete documentation on all implemented stanza objects | ||||||
|  | - Documentation on all examples used in XMPP: The Definitive Guide | ||||||
|  |  | ||||||
|  | 1.1 will include: | ||||||
|  | - More functional and unit tests | ||||||
|  | - PEP-8 compliance | ||||||
|  | - XEP-225 support | ||||||
|  |  | ||||||
|  | Since 0.2, here's the Changelog: | ||||||
|  | - MANY bugfixes | ||||||
|  | - Re-implementation of handlers/threading to greatly simplify and remove bugs (no more spawning threads in handlers) | ||||||
|  | - Stanza objects for jabber:client and all implemented XEPs | ||||||
|  | - Raising XMPPError for jabber:client and extended errors in handlers | ||||||
|  | - Robust error handling and better insurance of iq responses | ||||||
|  | - Stanza objects have made life a lot easier! | ||||||
|  | - Massive audit/cleanup. | ||||||
|  |  | ||||||
|  | Credits | ||||||
|  | ---------------- | ||||||
|  | Main Author: Nathan Fritz fritz@netflint.net | ||||||
|  | Contributors: Kevin Smith & Lance Stout | ||||||
|  | Patches: Remko Tronçon | ||||||
|  |  | ||||||
|  | Feel free to add fritzy@netflint.net to your roster for direct support and comments. | ||||||
|  | Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion. | ||||||
|  | Join sleek@conference.jabber.org for groupchat discussion. | ||||||
							
								
								
									
										139
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								README.rst
									
									
									
									
									
								
							| @@ -1,139 +0,0 @@ | |||||||
| Slixmpp |  | ||||||
| ######### |  | ||||||
|  |  | ||||||
| Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of |  | ||||||
| SleekXMPP. |  | ||||||
|  |  | ||||||
| Slixmpp's goals is to only rewrite the core of the library (the low level |  | ||||||
| socket handling, the timers, the events dispatching) in order to remove all |  | ||||||
| threads. |  | ||||||
|  |  | ||||||
| Building |  | ||||||
| -------- |  | ||||||
|  |  | ||||||
| Slixmpp can make use of cython to improve performance on critical modules. |  | ||||||
| To do that, **cython3** is necessary along with **libidn** headers. |  | ||||||
| Otherwise, no compilation is needed. Building is done by running setup.py:: |  | ||||||
|  |  | ||||||
|     python3 setup.py build_ext --inplace |  | ||||||
|  |  | ||||||
| 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 Slixmpp:: |  | ||||||
|  |  | ||||||
|     python run_tests.py |  | ||||||
|  |  | ||||||
|  |  | ||||||
| The Slixmpp Boilerplate |  | ||||||
| ------------------------- |  | ||||||
| Projects using Slixmpp tend to follow a basic pattern for setting up client/component |  | ||||||
| connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp |  | ||||||
| based project. See the documetation or examples directory for more detailed archetypes for |  | ||||||
| Slixmpp projects:: |  | ||||||
|  |  | ||||||
|     import logging |  | ||||||
|  |  | ||||||
|     from slixmpp import ClientXMPP |  | ||||||
|     from slixmpp.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) |  | ||||||
|  |  | ||||||
|             # If you wanted more functionality, here's how to register plugins: |  | ||||||
|             # self.register_plugin('xep_0030') # Service Discovery |  | ||||||
|             # self.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|             # Here's how to access plugins once you've registered them: |  | ||||||
|             # self['xep_0030'].add_feature('echo_demo') |  | ||||||
|  |  | ||||||
|             # 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() |  | ||||||
|             self.get_roster() |  | ||||||
|  |  | ||||||
|             # 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(forever=True) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Slixmpp Credits |  | ||||||
| --------------- |  | ||||||
|  |  | ||||||
| **Maintainers:** |  | ||||||
|     - Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_), |  | ||||||
|     - Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_), |  | ||||||
|  |  | ||||||
| **Contributors:** |  | ||||||
|     - Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_) |  | ||||||
|     - Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_) |  | ||||||
|     - Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_) |  | ||||||
|     - Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_) |  | ||||||
|     - Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_) |  | ||||||
|     - Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_) |  | ||||||
|  |  | ||||||
| Credits (SleekXMPP) |  | ||||||
| ------------------- |  | ||||||
|  |  | ||||||
| **Main Author:** Nathan Fritz |  | ||||||
|     `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, |  | ||||||
|     `@fritzy <http://twitter.com/fritzy>`_ |  | ||||||
|  |  | ||||||
|     Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP |  | ||||||
|     <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of |  | ||||||
|     the XMPP Council. |  | ||||||
|  |  | ||||||
| **Co-Author:** Lance Stout |  | ||||||
|     `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, |  | ||||||
|     `@lancestout <http://twitter.com/lancestout>`_ |  | ||||||
|  |  | ||||||
| **Contributors:** |  | ||||||
|     - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_) |  | ||||||
|     - Dann Martens (`dannmartens <http://github.com/dannmartens>`_) |  | ||||||
|     - Florent Le Coz (`louiz <http://github.com/louiz>`_) |  | ||||||
|     - Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk) |  | ||||||
|     - Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be) |  | ||||||
|     - Te-jé Rogers (`te-je <http://github.com/te-je>`_) |  | ||||||
|     - Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_) |  | ||||||
							
								
								
									
										171
									
								
								conn_tests/test_pubsubjobs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								conn_tests/test_pubsubjobs.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | |||||||
|  | import logging | ||||||
|  | import sleekxmpp | ||||||
|  | from optparse import OptionParser | ||||||
|  | from xml.etree import cElementTree as ET | ||||||
|  | import os | ||||||
|  | import time | ||||||
|  | import sys | ||||||
|  | import unittest | ||||||
|  | import sleekxmpp.plugins.xep_0004 | ||||||
|  | from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream.handler.waiter import Waiter | ||||||
|  | try: | ||||||
|  | 	import configparser | ||||||
|  | except ImportError: | ||||||
|  | 	import ConfigParser as configparser | ||||||
|  | try: | ||||||
|  | 	import queue | ||||||
|  | except ImportError: | ||||||
|  | 	import Queue as queue | ||||||
|  |  | ||||||
|  | class TestClient(sleekxmpp.ClientXMPP): | ||||||
|  | 	def __init__(self, jid, password): | ||||||
|  | 		sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  | 		self.add_event_handler("session_start", self.start) | ||||||
|  | 		#self.add_event_handler("message", self.message) | ||||||
|  | 		self.waitforstart = queue.Queue() | ||||||
|  | 	 | ||||||
|  | 	def start(self, event): | ||||||
|  | 		self.getRoster() | ||||||
|  | 		self.sendPresence() | ||||||
|  | 		self.waitforstart.put(True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestPubsubServer(unittest.TestCase): | ||||||
|  | 	statev = {} | ||||||
|  |  | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		unittest.TestCase.__init__(self, *args, **kwargs) | ||||||
|  |  | ||||||
|  | 	def setUp(self): | ||||||
|  | 		pass | ||||||
|  |  | ||||||
|  | 	def test001getdefaultconfig(self): | ||||||
|  | 		"""Get the default node config""" | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2') | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3') | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4') | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5') | ||||||
|  | 		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) | ||||||
|  | 		self.statev['defaultconfig'] = result | ||||||
|  | 		self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form)) | ||||||
|  | 	 | ||||||
|  | 	def test002createdefaultnode(self): | ||||||
|  | 		"""Create a node without config""" | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1')) | ||||||
|  |  | ||||||
|  | 	def test003deletenode(self): | ||||||
|  | 		"""Delete recently created node""" | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1')) | ||||||
|  | 	 | ||||||
|  | 	def test004createnode(self): | ||||||
|  | 		"""Create a node with a config""" | ||||||
|  | 		self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open') | ||||||
|  | 		self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True) | ||||||
|  | 		self.statev['defaultconfig'].field['pubsub#persist_items'].setValue(True) | ||||||
|  | 		self.statev['defaultconfig'].field['pubsub#presence_based_delivery'].setValue(True) | ||||||
|  | 		p = self.xmpp2.Presence() | ||||||
|  | 		p['to'] = self.pshost | ||||||
|  | 		p.send() | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig'], ntype='job')) | ||||||
|  | 	 | ||||||
|  | 	def test005reconfigure(self): | ||||||
|  | 		"""Retrieving node config and reconfiguring""" | ||||||
|  | 		nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2') | ||||||
|  | 		self.failUnless(nconfig, "No configuration returned") | ||||||
|  | 		#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues())) | ||||||
|  | 		self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match") | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig)) | ||||||
|  |  | ||||||
|  | 	def test006subscribetonode(self): | ||||||
|  | 		"""Subscribe to node from account 2""" | ||||||
|  | 		self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2")) | ||||||
|  | 	 | ||||||
|  | 	def test007publishitem(self): | ||||||
|  | 		"""Publishing item""" | ||||||
|  | 		item = ET.Element('{http://netflint.net/protocol/test}test') | ||||||
|  | 		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items')) | ||||||
|  | 		self.xmpp2.registerHandler(w) | ||||||
|  | 		#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),)) | ||||||
|  | 		result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test1', item) | ||||||
|  | 		msg = w.wait(5) # got to get a result in 5 seconds | ||||||
|  | 		self.failUnless(msg != False, "Account #2 did not get message event") | ||||||
|  | 		#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),)) | ||||||
|  | 		result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test2', item) | ||||||
|  | 		w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items')) | ||||||
|  | 		self.xmpp2.registerHandler(w) | ||||||
|  | 		self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test1') | ||||||
|  | 		msg = w.wait(5) # got to get a result in 5 seconds | ||||||
|  | 		self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test2') | ||||||
|  | 		self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test1') | ||||||
|  | 		self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test2') | ||||||
|  | 		print result | ||||||
|  | 		#need to add check for update | ||||||
|  |  | ||||||
|  | 	def test900cleanup(self): | ||||||
|  | 		"Cleaning up" | ||||||
|  | 		#self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.") | ||||||
|  | 		time.sleep(10) | ||||||
|  | 	 | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  | 	#parse command line arguements | ||||||
|  | 	optp = OptionParser() | ||||||
|  | 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||||
|  | 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||||
|  | 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||||
|  | 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||||
|  | 	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use") | ||||||
|  | 	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use") | ||||||
|  | 	opts,args = optp.parse_args() | ||||||
|  | 	 | ||||||
|  | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  | 	#load xml config | ||||||
|  | 	logging.info("Loading config file: %s" % opts.configfile) | ||||||
|  | 	config = configparser.RawConfigParser() | ||||||
|  | 	config.read(opts.configfile) | ||||||
|  | 	 | ||||||
|  | 	#init | ||||||
|  | 	logging.info("Account 1 is %s" % config.get('account1', 'jid')) | ||||||
|  | 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | ||||||
|  | 	logging.info("Account 2 is %s" % config.get('account2', 'jid')) | ||||||
|  | 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | ||||||
|  | 	 | ||||||
|  | 	xmpp1.registerPlugin('xep_0004') | ||||||
|  | 	xmpp1.registerPlugin('xep_0030') | ||||||
|  | 	xmpp1.registerPlugin('xep_0060') | ||||||
|  | 	xmpp1.registerPlugin('xep_0199') | ||||||
|  | 	xmpp1.registerPlugin('jobs') | ||||||
|  | 	xmpp2.registerPlugin('xep_0004') | ||||||
|  | 	xmpp2.registerPlugin('xep_0030') | ||||||
|  | 	xmpp2.registerPlugin('xep_0060') | ||||||
|  | 	xmpp2.registerPlugin('xep_0199') | ||||||
|  | 	xmpp2.registerPlugin('jobs') | ||||||
|  |  | ||||||
|  | 	if not config.get('account1', 'server'): | ||||||
|  | 		# we don't know the server, but the lib can probably figure it out | ||||||
|  | 		xmpp1.connect()  | ||||||
|  | 	else: | ||||||
|  | 		xmpp1.connect((config.get('account1', 'server'), 5222)) | ||||||
|  | 	xmpp1.process(threaded=True) | ||||||
|  | 	 | ||||||
|  | 	#init | ||||||
|  | 	if not config.get('account2', 'server'): | ||||||
|  | 		# we don't know the server, but the lib can probably figure it out | ||||||
|  | 		xmpp2.connect()  | ||||||
|  | 	else: | ||||||
|  | 		xmpp2.connect((config.get('account2', 'server'), 5222)) | ||||||
|  | 	xmpp2.process(threaded=True) | ||||||
|  |  | ||||||
|  | 	TestPubsubServer.xmpp1 = xmpp1 | ||||||
|  | 	TestPubsubServer.xmpp2 = xmpp2 | ||||||
|  | 	TestPubsubServer.pshost = config.get('settings', 'pubsub') | ||||||
|  | 	xmpp1.waitforstart.get(True) | ||||||
|  | 	xmpp2.waitforstart.get(True) | ||||||
|  | 	testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer) | ||||||
|  |  | ||||||
|  | 	alltests_suite = unittest.TestSuite([testsuite]) | ||||||
|  | 	result = unittest.TextTestRunner(verbosity=2).run(alltests_suite) | ||||||
|  | 	xmpp1.disconnect() | ||||||
|  | 	xmpp2.disconnect() | ||||||
							
								
								
									
										233
									
								
								conn_tests/test_pubsubserver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								conn_tests/test_pubsubserver.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | |||||||
|  | import logging | ||||||
|  | import sleekxmpp | ||||||
|  | from optparse import OptionParser | ||||||
|  | from xml.etree import cElementTree as ET | ||||||
|  | import os | ||||||
|  | import time | ||||||
|  | import sys | ||||||
|  | import unittest | ||||||
|  | import sleekxmpp.plugins.xep_0004 | ||||||
|  | from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream.handler.waiter import Waiter | ||||||
|  | try: | ||||||
|  | 	import configparser | ||||||
|  | except ImportError: | ||||||
|  | 	import ConfigParser as configparser | ||||||
|  | try: | ||||||
|  | 	import queue | ||||||
|  | except ImportError: | ||||||
|  | 	import Queue as queue | ||||||
|  |  | ||||||
|  | class TestClient(sleekxmpp.ClientXMPP): | ||||||
|  | 	def __init__(self, jid, password): | ||||||
|  | 		sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  | 		self.add_event_handler("session_start", self.start) | ||||||
|  | 		#self.add_event_handler("message", self.message) | ||||||
|  | 		self.waitforstart = queue.Queue() | ||||||
|  | 	 | ||||||
|  | 	def start(self, event): | ||||||
|  | 		self.getRoster() | ||||||
|  | 		self.sendPresence() | ||||||
|  | 		self.waitforstart.put(True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestPubsubServer(unittest.TestCase): | ||||||
|  | 	statev = {} | ||||||
|  |  | ||||||
|  | 	def __init__(self, *args, **kwargs): | ||||||
|  | 		unittest.TestCase.__init__(self, *args, **kwargs) | ||||||
|  |  | ||||||
|  | 	def setUp(self): | ||||||
|  | 		pass | ||||||
|  |  | ||||||
|  | 	def test001getdefaultconfig(self): | ||||||
|  | 		"""Get the default node config""" | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2') | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3') | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4') | ||||||
|  | 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5') | ||||||
|  | 		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) | ||||||
|  | 		self.statev['defaultconfig'] = result | ||||||
|  | 		self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form)) | ||||||
|  | 	 | ||||||
|  | 	def test002createdefaultnode(self): | ||||||
|  | 		"""Create a node without config""" | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1')) | ||||||
|  |  | ||||||
|  | 	def test003deletenode(self): | ||||||
|  | 		"""Delete recently created node""" | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1')) | ||||||
|  | 	 | ||||||
|  | 	def test004createnode(self): | ||||||
|  | 		"""Create a node with a config""" | ||||||
|  | 		self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open') | ||||||
|  | 		self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True) | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig'])) | ||||||
|  | 	 | ||||||
|  | 	def test005reconfigure(self): | ||||||
|  | 		"""Retrieving node config and reconfiguring""" | ||||||
|  | 		nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2') | ||||||
|  | 		self.failUnless(nconfig, "No configuration returned") | ||||||
|  | 		#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues())) | ||||||
|  | 		self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match") | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig)) | ||||||
|  |  | ||||||
|  | 	def test006subscribetonode(self): | ||||||
|  | 		"""Subscribe to node from account 2""" | ||||||
|  | 		self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2")) | ||||||
|  | 	 | ||||||
|  | 	def test007publishitem(self): | ||||||
|  | 		"""Publishing item""" | ||||||
|  | 		item = ET.Element('{http://netflint.net/protocol/test}test') | ||||||
|  | 		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items')) | ||||||
|  | 		self.xmpp2.registerHandler(w) | ||||||
|  | 		result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),)) | ||||||
|  | 		msg = w.wait(5) # got to get a result in 5 seconds | ||||||
|  | 		self.failUnless(msg != False, "Account #2 did not get message event") | ||||||
|  | 		self.failUnless(result) | ||||||
|  | 		#need to add check for update | ||||||
|  | 	 | ||||||
|  | 	def test008updateitem(self): | ||||||
|  | 		"""Updating item""" | ||||||
|  | 		item = ET.Element('{http://netflint.net/protocol/test}test', {'someattr': 'hi there'}) | ||||||
|  | 		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items')) | ||||||
|  | 		self.xmpp2.registerHandler(w) | ||||||
|  | 		result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),)) | ||||||
|  | 		msg = w.wait(5) # got to get a result in 5 seconds | ||||||
|  | 		self.failUnless(msg != False, "Account #2 did not get message event") | ||||||
|  | 		self.failUnless(result) | ||||||
|  | 		#need to add check for update | ||||||
|  | 	 | ||||||
|  | 	def test009deleteitem(self): | ||||||
|  | 		"""Deleting item""" | ||||||
|  | 		w = Waiter('wait retract', StanzaPath('message/pubsub_event/items@node=testnode2')) | ||||||
|  | 		self.xmpp2.registerHandler(w) | ||||||
|  | 		result = self.xmpp1['xep_0060'].deleteItem(self.pshost, "testnode2", "test1") | ||||||
|  | 		self.failUnless(result, "Got error when deleting item.") | ||||||
|  | 		msg = w.wait(1) | ||||||
|  | 		self.failUnless(msg != False, "Did not get retract notice.") | ||||||
|  | 	 | ||||||
|  | 	def test010unsubscribenode(self): | ||||||
|  | 		"Unsubscribing Account #2" | ||||||
|  | 		self.failUnless(self.xmpp2['xep_0060'].unsubscribe(self.pshost, "testnode2"), "Got error response when unsubscribing.") | ||||||
|  | 	 | ||||||
|  | 	def test011createcollectionnode(self): | ||||||
|  | 		"Create a collection node w/ Account #2" | ||||||
|  | 		self.failUnless(self.xmpp2['xep_0060'].create_node(self.pshost, "testnode3", self.statev['defaultconfig'], True), "Could not create collection node") | ||||||
|  | 	 | ||||||
|  | 	def test012subscribecollection(self): | ||||||
|  | 		"Subscribe Account #1 to collection" | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode3")) | ||||||
|  | 	 | ||||||
|  | 	def test013assignnodetocollection(self): | ||||||
|  | 		"Assign node to collection" | ||||||
|  | 		self.failUnless(self.xmpp2['xep_0060'].addNodeToCollection(self.pshost, 'testnode2', 'testnode3')) | ||||||
|  | 	 | ||||||
|  | 	def test014publishcollection(self): | ||||||
|  | 		"""Publishing item to collection child""" | ||||||
|  | 		item = ET.Element('{http://netflint.net/protocol/test}test') | ||||||
|  | 		w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items@node=testnode2')) | ||||||
|  | 		self.xmpp1.registerHandler(w) | ||||||
|  | 		result = self.xmpp2['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),)) | ||||||
|  | 		msg = w.wait(5) # got to get a result in 5 seconds | ||||||
|  | 		self.failUnless(msg != False, "Account #1 did not get message event: perhaps node was advertised incorrectly?") | ||||||
|  | 		self.failUnless(result) | ||||||
|  |  | ||||||
|  | #	def test016speedtest(self): | ||||||
|  | #		"Uncached speed test" | ||||||
|  | #		import time | ||||||
|  | #		start = time.time() | ||||||
|  | #		for y in range(0, 50000, 1000): | ||||||
|  | #			start2 = time.time() | ||||||
|  | #			for x in range(y, y+1000): | ||||||
|  | #				self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode4", subscribee="testuser%s@whatever" % x)) | ||||||
|  | #			print time.time() - start2 | ||||||
|  | #		seconds = time.time() - start | ||||||
|  | #		print "--", seconds | ||||||
|  | #		print "---------" | ||||||
|  | #		time.sleep(15) | ||||||
|  | #		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4'), "Could not delete non-cached test node") | ||||||
|  | 	 | ||||||
|  | #	def test015speedtest(self): | ||||||
|  | #		"cached speed test" | ||||||
|  | #		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) | ||||||
|  | #		self.statev['defaultconfig'] = result | ||||||
|  | #		self.statev['defaultconfig'].field['pubsub#node_type'].setValue("leaf") | ||||||
|  | #		self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(True) | ||||||
|  | #		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode4', self.statev['defaultconfig'])) | ||||||
|  | #		self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(False) | ||||||
|  | #		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode5', self.statev['defaultconfig'])) | ||||||
|  | #		start = time.time() | ||||||
|  | #		for y in range(0, 50000, 1000): | ||||||
|  | #			start2 = time.time() | ||||||
|  | #			for x in range(y, y+1000): | ||||||
|  | #				self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode5", subscribee="testuser%s@whatever" % x)) | ||||||
|  | #			print time.time() - start2 | ||||||
|  | #		seconds = time.time() - start | ||||||
|  | #		print "--", seconds | ||||||
|  |  | ||||||
|  | 	def test900cleanup(self): | ||||||
|  | 		"Cleaning up" | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.") | ||||||
|  | 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3'), "Could not delete collection node") | ||||||
|  | 	 | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  | 	#parse command line arguements | ||||||
|  | 	optp = OptionParser() | ||||||
|  | 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||||
|  | 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||||
|  | 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||||
|  | 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||||
|  | 	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use") | ||||||
|  | 	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use") | ||||||
|  | 	opts,args = optp.parse_args() | ||||||
|  | 	 | ||||||
|  | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  | 	#load xml config | ||||||
|  | 	logging.info("Loading config file: %s" % opts.configfile) | ||||||
|  | 	config = configparser.RawConfigParser() | ||||||
|  | 	config.read(opts.configfile) | ||||||
|  | 	 | ||||||
|  | 	#init | ||||||
|  | 	logging.info("Account 1 is %s" % config.get('account1', 'jid')) | ||||||
|  | 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | ||||||
|  | 	logging.info("Account 2 is %s" % config.get('account2', 'jid')) | ||||||
|  | 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | ||||||
|  | 	 | ||||||
|  | 	xmpp1.registerPlugin('xep_0004') | ||||||
|  | 	xmpp1.registerPlugin('xep_0030') | ||||||
|  | 	xmpp1.registerPlugin('xep_0060') | ||||||
|  | 	xmpp1.registerPlugin('xep_0199') | ||||||
|  | 	xmpp2.registerPlugin('xep_0004') | ||||||
|  | 	xmpp2.registerPlugin('xep_0030') | ||||||
|  | 	xmpp2.registerPlugin('xep_0060') | ||||||
|  | 	xmpp2.registerPlugin('xep_0199') | ||||||
|  |  | ||||||
|  | 	if not config.get('account1', 'server'): | ||||||
|  | 		# we don't know the server, but the lib can probably figure it out | ||||||
|  | 		xmpp1.connect()  | ||||||
|  | 	else: | ||||||
|  | 		xmpp1.connect((config.get('account1', 'server'), 5222)) | ||||||
|  | 	xmpp1.process(threaded=True) | ||||||
|  | 	 | ||||||
|  | 	#init | ||||||
|  | 	if not config.get('account2', 'server'): | ||||||
|  | 		# we don't know the server, but the lib can probably figure it out | ||||||
|  | 		xmpp2.connect()  | ||||||
|  | 	else: | ||||||
|  | 		xmpp2.connect((config.get('account2', 'server'), 5222)) | ||||||
|  | 	xmpp2.process(threaded=True) | ||||||
|  |  | ||||||
|  | 	TestPubsubServer.xmpp1 = xmpp1 | ||||||
|  | 	TestPubsubServer.xmpp2 = xmpp2 | ||||||
|  | 	TestPubsubServer.pshost = config.get('settings', 'pubsub') | ||||||
|  | 	xmpp1.waitforstart.get(True) | ||||||
|  | 	xmpp2.waitforstart.get(True) | ||||||
|  | 	testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer) | ||||||
|  |  | ||||||
|  | 	alltests_suite = unittest.TestSuite([testsuite]) | ||||||
|  | 	result = unittest.TextTestRunner(verbosity=2).run(alltests_suite) | ||||||
|  | 	xmpp1.disconnect() | ||||||
|  | 	xmpp2.disconnect() | ||||||
							
								
								
									
										13
									
								
								conn_tests/testconfig.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								conn_tests/testconfig.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | [settings] | ||||||
|  | enabled=true | ||||||
|  | pubsub=pubsub.recon | ||||||
|  |  | ||||||
|  | [account1] | ||||||
|  | jid=fritzy@recon | ||||||
|  | pass=testing123 | ||||||
|  | server= | ||||||
|  |  | ||||||
|  | [account2] | ||||||
|  | jid=fritzy2@recon | ||||||
|  | pass=testing123 | ||||||
|  | server= | ||||||
							
								
								
									
										1
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | |||||||
| _build/* |  | ||||||
							
								
								
									
										130
									
								
								docs/Makefile
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								docs/Makefile
									
									
									
									
									
								
							| @@ -1,130 +0,0 @@ | |||||||
| # 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/Slixmpp.qhcp" |  | ||||||
| 	@echo "To view the help file:" |  | ||||||
| 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Slixmpp.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/Slixmpp" |  | ||||||
| 	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Slixmpp" |  | ||||||
| 	@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
									
									
								
							
							
						
						
									
										452
									
								
								docs/_static/agogo.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,452 +0,0 @@ | |||||||
| /* |  | ||||||
|  * 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
									
									
								
							
							
						
						
									
										532
									
								
								docs/_static/basic.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,532 +0,0 @@ | |||||||
| /* |  | ||||||
|  * 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
									
									
								
							
							
						
						
									
										256
									
								
								docs/_static/default.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,256 +0,0 @@ | |||||||
| /* |  | ||||||
|  * 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
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500.otf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										431
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										431
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,431 +0,0 @@ | |||||||
| /* |  | ||||||
|  * 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: 30em; |  | ||||||
|     max-width: 70em; |  | ||||||
|     color: #444; |  | ||||||
|     text-align: center; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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: auto; |  | ||||||
|     margin-top: 20px; |  | ||||||
|     margin-bottom: 50px; |  | ||||||
|     font-size: 0.9em; |  | ||||||
|     width: 700px; |  | ||||||
|     text-align: left; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* heading and navigation */ |  | ||||||
|  |  | ||||||
| div.header { |  | ||||||
|     position: relative; |  | ||||||
|     margin: auto; |  | ||||||
|     margin-top: 125px; |  | ||||||
|     height: 85px; |  | ||||||
|     padding: 0 40px; |  | ||||||
|     font-family: "Yanone Kaffeesatz"; |  | ||||||
|     text-align: left; |  | ||||||
|     width: 750px; |  | ||||||
| } |  | ||||||
| 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: auto; |  | ||||||
|     margin-top: 0; |  | ||||||
|     margin-bottom: 0px; |  | ||||||
|     text-align: right; |  | ||||||
|     font-size: 0.8em; |  | ||||||
|     width: 750px; |  | ||||||
| } |  | ||||||
| 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; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #from_andyet { |  | ||||||
|     -webkit-box-shadow: #CCC 0px 0px 3px; |  | ||||||
|     background: rgba(255, 255, 255, 1); |  | ||||||
|     bottom: 0px; |  | ||||||
|     right: 17px; |  | ||||||
|     padding: 3px 10px; |  | ||||||
|     position: fixed; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #from_andyet h2 { |  | ||||||
|     background-image: url("images/from_&yet.png"); |  | ||||||
|     background-repeat: no-repeat; |  | ||||||
|     height: 29px; |  | ||||||
|     line-height: 0; |  | ||||||
|     text-indent: -9999em; |  | ||||||
|     width: 79px; |  | ||||||
|     margin-top: 0; |  | ||||||
|     margin: 0px; |  | ||||||
|     padding: 0px; |  | ||||||
| } |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 16 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 27 KiB | 
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,70 +0,0 @@ | |||||||
| .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
									
									
								
							
							
						
						
									
										245
									
								
								docs/_static/nature.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,245 +0,0 @@ | |||||||
| /* |  | ||||||
|  * 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
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/noise_dk.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 22 KiB | 
							
								
								
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,70 +0,0 @@ | |||||||
| .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
									
									
								
							
							
						
						
									
										339
									
								
								docs/_static/sphinxdoc.css
									
									
									
									
										vendored
									
									
								
							| @@ -1,339 +0,0 @@ | |||||||
| /* |  | ||||||
|  * 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; |  | ||||||
| } |  | ||||||
							
								
								
									
										70
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							| @@ -1,70 +0,0 @@ | |||||||
| {# |  | ||||||
|     haiku/layout.html |  | ||||||
|     ~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
|     Sphinx layout template for the haiku theme. |  | ||||||
|  |  | ||||||
|     :copyright: Copyright 2007-2010 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>{{ title|striptags }}</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> |  | ||||||
|       <a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| ======== |  | ||||||
| BaseXMPP |  | ||||||
| ======== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.basexmpp |  | ||||||
|  |  | ||||||
| .. autoclass:: BaseXMPP |  | ||||||
|     :members: |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| ========== |  | ||||||
| ClientXMPP |  | ||||||
| ========== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.clientxmpp |  | ||||||
|  |  | ||||||
| .. autoclass:: ClientXMPP |  | ||||||
|     :members: |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| ============= |  | ||||||
| ComponentXMPP |  | ||||||
| ============= |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.componentxmpp |  | ||||||
|  |  | ||||||
| .. autoclass:: ComponentXMPP |  | ||||||
|     :members: |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| Exceptions |  | ||||||
| ========== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.exceptions |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. autoexception:: XMPPError |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| .. autoexception:: IqError |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| .. autoexception:: IqTimeout |  | ||||||
|     :members: |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| IQ Stanza |  | ||||||
| ========= |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.stanza |  | ||||||
|  |  | ||||||
| .. autoclass:: Iq |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| Message Stanza |  | ||||||
| ============== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.stanza |  | ||||||
|  |  | ||||||
| .. autoclass:: Message |  | ||||||
|     :members: |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| Presence Stanza |  | ||||||
| =============== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.stanza |  | ||||||
|  |  | ||||||
| .. autoclass:: Presence |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| Root Stanza |  | ||||||
| =========== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.stanza.rootstanza |  | ||||||
|  |  | ||||||
| .. autoclass:: RootStanza |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| Stanza Handlers |  | ||||||
| =============== |  | ||||||
|  |  | ||||||
| The Basic Handler |  | ||||||
| ----------------- |  | ||||||
| .. module:: slixmpp.xmlstream.handler.base |  | ||||||
|  |  | ||||||
| .. autoclass:: BaseHandler |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| Callback |  | ||||||
| -------- |  | ||||||
| .. module:: slixmpp.xmlstream.handler |  | ||||||
|  |  | ||||||
| .. autoclass:: Callback |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| CoroutineCallback |  | ||||||
| ----------------- |  | ||||||
|  |  | ||||||
| .. autoclass:: CoroutineCallback |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
| Waiter |  | ||||||
| ------ |  | ||||||
|  |  | ||||||
| .. autoclass:: Waiter |  | ||||||
|     :members: |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| Jabber IDs (JID) |  | ||||||
| ================= |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.jid |  | ||||||
|  |  | ||||||
| .. autoclass:: JID |  | ||||||
|     :members: |  | ||||||
| @@ -1,41 +0,0 @@ | |||||||
| Stanza Matchers |  | ||||||
| =============== |  | ||||||
|  |  | ||||||
| The Basic Matcher |  | ||||||
| ----------------- |  | ||||||
| .. module:: slixmpp.xmlstream.matcher.base |  | ||||||
|  |  | ||||||
| .. autoclass:: MatcherBase |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ID Matching |  | ||||||
| ----------- |  | ||||||
| .. module:: slixmpp.xmlstream.matcher.id |  | ||||||
|  |  | ||||||
| .. autoclass:: MatcherId |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Stanza Path Matching |  | ||||||
| -------------------- |  | ||||||
| .. module:: slixmpp.xmlstream.matcher.stanzapath |  | ||||||
|  |  | ||||||
| .. autoclass:: StanzaPath |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| XPath |  | ||||||
| ----- |  | ||||||
| .. module:: slixmpp.xmlstream.matcher.xpath |  | ||||||
|  |  | ||||||
| .. autoclass:: MatchXPath |  | ||||||
|     :members: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| XMLMask |  | ||||||
| ------- |  | ||||||
| .. module:: slixmpp.xmlstream.matcher.xmlmask |  | ||||||
|  |  | ||||||
| .. autoclass:: MatchXMLMask |  | ||||||
|     :members: |  | ||||||
| @@ -1,123 +0,0 @@ | |||||||
| .. _stanzabase: |  | ||||||
|  |  | ||||||
| ============== |  | ||||||
| Stanza Objects |  | ||||||
| ============== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.xmlstream.stanzabase |  | ||||||
|  |  | ||||||
| The :mod:`~slixmpp.xmlstream.stanzabase` module provides a wrapper for the |  | ||||||
| standard :mod:`~xml.etree.ElementTree` module that makes working with XML |  | ||||||
| less painful. Instead of having to manually move up and down an element |  | ||||||
| tree and insert subelements and attributes, you can interact with an object |  | ||||||
| that behaves like a normal dictionary or JSON object, which silently maps |  | ||||||
| keys to XML attributes and elements behind the scenes. |  | ||||||
|  |  | ||||||
| Overview |  | ||||||
| -------- |  | ||||||
|  |  | ||||||
| The usefulness of this layer grows as the XML you have to work with |  | ||||||
| becomes nested. The base unit here, :class:`ElementBase`, can map to a |  | ||||||
| single XML element, or several depending on how advanced of a mapping |  | ||||||
| is desired from interface keys to XML structures. For example, a single |  | ||||||
| :class:`ElementBase` derived class could easily describe: |  | ||||||
|  |  | ||||||
| .. code-block:: xml |  | ||||||
|  |  | ||||||
|     <message to="user@example.com" from="friend@example.com"> |  | ||||||
|       <body>Hi!</body> |  | ||||||
|       <x:extra> |  | ||||||
|         <x:item>Custom item 1</x:item> |  | ||||||
|         <x:item>Custom item 2</x:item> |  | ||||||
|         <x:item>Custom item 3</x:item> |  | ||||||
|       </x:extra> |  | ||||||
|     </message> |  | ||||||
|  |  | ||||||
| If that chunk of XML were put in the :class:`ElementBase` instance |  | ||||||
| ``msg``, we could extract the data from the XML using:: |  | ||||||
|  |  | ||||||
|     >>> msg['extra'] |  | ||||||
|     ['Custom item 1', 'Custom item 2', 'Custom item 3'] |  | ||||||
|  |  | ||||||
| Provided we set up the handler for the ``'extra'`` interface to load the |  | ||||||
| ``<x:item>`` element content into a list. |  | ||||||
|  |  | ||||||
| The key concept is that given an XML structure that will be repeatedly |  | ||||||
| used, we can define a set of :term:`interfaces` which when we read from, |  | ||||||
| write to, or delete, will automatically manipulate the underlying XML |  | ||||||
| as needed. In addition, some of these interfaces may in turn reference |  | ||||||
| child objects which expose interfaces for particularly complex child |  | ||||||
| elements of the original XML chunk. |  | ||||||
|  |  | ||||||
| .. seealso:: |  | ||||||
|     :ref:`create-stanza-interfaces`. |  | ||||||
|  |  | ||||||
| Because the :mod:`~slixmpp.xmlstream.stanzabase` module was developed |  | ||||||
| as part of an `XMPP <http://xmpp.org>`_ library, these chunks of XML are |  | ||||||
| referred to as :term:`stanzas <stanza>`, and in Slixmpp we refer to a |  | ||||||
| subclass of :class:`ElementBase` which defines the interfaces needed for |  | ||||||
| interacting with a given :term:`stanza` a :term:`stanza object`. |  | ||||||
|  |  | ||||||
| To make dealing with more complicated and nested :term:`stanzas <stanza>` |  | ||||||
| or XML chunks easier, :term:`stanza objects <stanza object>` can be |  | ||||||
| composed in two ways: as iterable child objects or as plugins. Iterable |  | ||||||
| child stanzas, or :term:`substanzas <substanza>`, are accessible through a |  | ||||||
| special ``'substanzas'`` interface. This option is useful for stanzas which |  | ||||||
| may contain more than one of the same kind of element. When there is |  | ||||||
| only one child element, the plugin method is more useful. For plugins, |  | ||||||
| a parent stanza object delegates one of its XML child elements to the |  | ||||||
| plugin stanza object. Here is an example: |  | ||||||
|  |  | ||||||
| .. code-block:: xml |  | ||||||
|  |  | ||||||
|     <iq type="result"> |  | ||||||
|       <query xmlns="http://jabber.org/protocol/disco#info"> |  | ||||||
|         <identity category="client" type="bot" name="Slixmpp Bot" /> |  | ||||||
|       </query> |  | ||||||
|     </iq> |  | ||||||
|  |  | ||||||
| We can can arrange this stanza into two objects: an outer, wrapper object for |  | ||||||
| dealing with the ``<iq />`` element and its attributes, and a plugin object to |  | ||||||
| control the ``<query />`` payload element. If we give the plugin object the |  | ||||||
| name ``'disco_info'`` (using its :attr:`ElementBase.plugin_attrib` value), then |  | ||||||
| we can access the plugin as so:: |  | ||||||
|  |  | ||||||
|     >>> iq['disco_info'] |  | ||||||
|     '<query xmlns="http://jabber.org/protocol/disco#info"> |  | ||||||
|       <identity category="client" type="bot" name="Slixmpp Bot" /> |  | ||||||
|     </query>' |  | ||||||
|  |  | ||||||
| We can then drill down through the plugin object's interfaces as desired:: |  | ||||||
|  |  | ||||||
|     >>> iq['disco_info']['identities'] |  | ||||||
|     [('client', 'bot', 'Slixmpp Bot')] |  | ||||||
|  |  | ||||||
| Plugins may also add new interfaces to the parent stanza object as if they |  | ||||||
| had been defined by the parent directly, and can also override the behaviour |  | ||||||
| of an interface defined by the parent. |  | ||||||
|  |  | ||||||
| .. seealso:: |  | ||||||
|  |  | ||||||
|     - :ref:`create-stanza-plugins` |  | ||||||
|     - :ref:`create-extension-plugins` |  | ||||||
|     - :ref:`override-parent-interfaces` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Registering Stanza Plugins |  | ||||||
| -------------------------- |  | ||||||
|  |  | ||||||
| .. autofunction:: register_stanza_plugin |  | ||||||
|  |  | ||||||
| ElementBase |  | ||||||
| ----------- |  | ||||||
|  |  | ||||||
| .. autoclass:: ElementBase |  | ||||||
|     :members: |  | ||||||
|     :private-members: |  | ||||||
|     :special-members: |  | ||||||
|  |  | ||||||
| StanzaBase |  | ||||||
| ---------- |  | ||||||
|  |  | ||||||
| .. autoclass:: StanzaBase |  | ||||||
|     :members: |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| .. module:: slixmpp.xmlstream.tostring |  | ||||||
|  |  | ||||||
| .. _tostring: |  | ||||||
|  |  | ||||||
| XML Serialization |  | ||||||
| ================= |  | ||||||
|  |  | ||||||
| Since the XML layer of Slixmpp is based on :mod:`~xml.etree.ElementTree`, |  | ||||||
| why not just use the built-in :func:`~xml.etree.ElementTree.tostring` |  | ||||||
| method? The answer is that using that method produces ugly results when |  | ||||||
| using namespaces. The :func:`tostring()` method used here intelligently |  | ||||||
| hides namespaces when able and does not introduce excessive namespace |  | ||||||
| prefixes:: |  | ||||||
|  |  | ||||||
|     >>> from slixmpp.xmlstream.tostring import tostring |  | ||||||
|     >>> from xml.etree import cElementTree as ET |  | ||||||
|     >>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>') |  | ||||||
|     >>> ET.tostring(xml) |  | ||||||
|     '<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>' |  | ||||||
|     >>> tostring(xml) |  | ||||||
|     '<foo xmlns="bar"><baz /></foo>' |  | ||||||
|  |  | ||||||
| As a side effect of this namespace hiding, using :func:`tostring()` may |  | ||||||
| produce unexpected results depending on how the :func:`tostring()` method |  | ||||||
| is invoked. For example, when sending XML on the wire, the main XMPP |  | ||||||
| stanzas with their namespace of ``jabber:client`` will not include the |  | ||||||
| namespace because that is already declared by the stream header. But, if |  | ||||||
| you create a :class:`~slixmpp.stanza.message.Message` instance and dump |  | ||||||
| it to the terminal, the ``jabber:client`` namespace will appear. |  | ||||||
|  |  | ||||||
| .. autofunction:: slixmpp.xmlstream.tostring |  | ||||||
|  |  | ||||||
| Escaping Special Characters |  | ||||||
| --------------------------- |  | ||||||
|  |  | ||||||
| In order to prevent errors when sending arbitrary text as the textual |  | ||||||
| content of an XML element, certain characters must be escaped. These |  | ||||||
| are: ``&``, ``<``, ``>``, ``"``, and ``'``. The default escaping |  | ||||||
| mechanism is to replace those characters with their equivalent escape |  | ||||||
| entities: ``&``, ``<``, ``>``, ``'``, and ``"``. |  | ||||||
|  |  | ||||||
| In the future, the use of CDATA sections may be allowed to reduce the |  | ||||||
| size of escaped text or for when other XMPP processing agents do not |  | ||||||
| undertand these entities. |  | ||||||
|  |  | ||||||
| .. |  | ||||||
|     autofunction:: xml_escape |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| ========== |  | ||||||
| XML Stream |  | ||||||
| ========== |  | ||||||
|  |  | ||||||
| .. module:: slixmpp.xmlstream.xmlstream |  | ||||||
|  |  | ||||||
| .. autoclass:: XMLStream |  | ||||||
|     :members: |  | ||||||
| @@ -1,150 +0,0 @@ | |||||||
| .. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP |  | ||||||
|  |  | ||||||
| Slixmpp Architecture |  | ||||||
| ====================== |  | ||||||
|  |  | ||||||
| The core of Slixmpp is contained in four classes: ``XMLStream``, |  | ||||||
| ``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this |  | ||||||
| stack is a library for working with XML objects that eliminates most |  | ||||||
| of the tedium of creating/manipulating XML. |  | ||||||
|  |  | ||||||
| .. image:: _static/images/arch_layers.png |  | ||||||
|     :height: 300px |  | ||||||
|     :align: center |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. index:: XMLStream |  | ||||||
|  |  | ||||||
| The Foundation: XMLStream |  | ||||||
| ------------------------- |  | ||||||
| :class:`~slixmpp.xmlstream.xmlstream.XMLStream` is a mostly XMPP-agnostic |  | ||||||
| class whose purpose is to read and write from a bi-directional XML stream. |  | ||||||
| It also allows for callback functions to execute when XML matching given |  | ||||||
| patterns is received; these callbacks are also referred to as :term:`stream |  | ||||||
| handlers <stream handler>`. The class also provides a basic eventing system |  | ||||||
| which can be triggered either manually or on a timed schedule. |  | ||||||
|  |  | ||||||
| The event loop |  | ||||||
| ~~~~~~~~~~~~~~ |  | ||||||
| :class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the |  | ||||||
| :class:`asyncio.BaseProtocol` class, and therefore do not have to handle |  | ||||||
| reads and writes directly, but receive data through |  | ||||||
| :meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write |  | ||||||
| data in the socket transport. |  | ||||||
|  |  | ||||||
| Upon receiving data, :term:`stream handlers <stream handler>` are run |  | ||||||
| immediately, except if they are coroutines, in which case they are |  | ||||||
| scheduled using :meth:`asyncio.async`. |  | ||||||
|  |  | ||||||
| :term:`Event handlers <event handler>` (which are called inside |  | ||||||
| :term:`stream handlers <stream handler>`) work the same way. |  | ||||||
|  |  | ||||||
| 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> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #. **Convert XML strings into objects.** |  | ||||||
|  |  | ||||||
|    Incoming text is parsed and converted into XML objects (using |  | ||||||
|    ElementTree) which are then wrapped into what are referred to as |  | ||||||
|    :term:`Stanza objects <stanza object>`. The appropriate class for the |  | ||||||
|    new object is determined using a map of namespaced element names to |  | ||||||
|    classes. |  | ||||||
|  |  | ||||||
|    Our incoming XML is thus turned into a :class:`~slixmpp.stanza.Message` |  | ||||||
|    :term:`stanza object` because the namespaced element name |  | ||||||
|    ``{jabber:client}message`` is associated with the class |  | ||||||
|    :class:`~slixmpp.stanza.Message`. |  | ||||||
|  |  | ||||||
| #. **Match stanza objects to callbacks.** |  | ||||||
|  |  | ||||||
|    These objects are then compared against the stored patterns associated |  | ||||||
|    with the registered callback handlers. |  | ||||||
|  |  | ||||||
|    Each handler matching our :term:`stanza object` is then added to a list. |  | ||||||
|  |  | ||||||
| #. **Processing callbacks** |  | ||||||
|  |  | ||||||
|    Every handler in the list is then called with the :term:`stanza object` |  | ||||||
|    as a parameter; if the handler is a |  | ||||||
|    :class:`~slixmpp.xmlstream.handler.CoroutineCallback` |  | ||||||
|    then it will be scheduled in the event loop using :meth:`asyncio.async` |  | ||||||
|    instead of run. |  | ||||||
|  |  | ||||||
| #. **Raise Custom Events** |  | ||||||
|  |  | ||||||
|    Since a :term:`stream handler` shouldn't block, if extensive processing |  | ||||||
|    for a stanza is required (such as needing to send and receive an |  | ||||||
|    :class:`~slixmpp.stanza.Iq` stanza), then custom events must be used. |  | ||||||
|    These events are not explicitly tied to the incoming XML stream and may |  | ||||||
|    be raised at any time. |  | ||||||
|  |  | ||||||
|    In contrast to :term:`stream handlers <stream handler>`, these functions |  | ||||||
|    are referred to as :term:`event handlers <event handler>`. |  | ||||||
|  |  | ||||||
|    The code for :meth:`BaseXMPP._handle_message` follows this pattern, and |  | ||||||
|    raises a ``'message'`` event |  | ||||||
|  |  | ||||||
|    .. code-block:: python |  | ||||||
|  |  | ||||||
|         self.event('message', msg) |  | ||||||
|  |  | ||||||
| #. **Process Custom Events** |  | ||||||
|  |  | ||||||
|    The :term:`event handlers <event handler>` are then executed, passing |  | ||||||
|    the stanza as the only argument. |  | ||||||
|  |  | ||||||
|    .. 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:: |  | ||||||
|  |  | ||||||
|        reply = msg.reply() |  | ||||||
|        reply['body'] = "Hey! This is awesome!" |  | ||||||
|        reply.send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. index:: BaseXMPP, XMLStream |  | ||||||
|  |  | ||||||
| Raising XMPP Awareness: BaseXMPP |  | ||||||
| -------------------------------- |  | ||||||
| While :class:`~slixmpp.xmlstream.xmlstream.XMLStream` attempts to shy away |  | ||||||
| from anything too XMPP specific, :class:`~slixmpp.basexmpp.BaseXMPP`'s |  | ||||||
| sole purpose is to provide foundational support for sending and receiving |  | ||||||
| XMPP stanzas. This support includes registering the basic message, |  | ||||||
| presence, and iq stanzas, methods for creating and sending stanzas, and |  | ||||||
| default handlers for incoming messages and keeping track of presence |  | ||||||
| notifications. |  | ||||||
|  |  | ||||||
| The plugin system for adding new XEP support is also maintained by |  | ||||||
| :class:`~slixmpp.basexmpp.BaseXMPP`. |  | ||||||
|  |  | ||||||
| .. index:: ClientXMPP, BaseXMPP |  | ||||||
|  |  | ||||||
| ClientXMPP |  | ||||||
| ---------- |  | ||||||
| :class:`~slixmpp.clientxmpp.ClientXMPP` extends |  | ||||||
| :class:`~slixmpp.clientxmpp.BaseXMPP` with additional logic for connecting |  | ||||||
| to an XMPP server by performing DNS lookups. It also adds support for stream |  | ||||||
| features such as STARTTLS and SASL. |  | ||||||
|  |  | ||||||
| .. index:: ComponentXMPP, BaseXMPP |  | ||||||
|  |  | ||||||
| ComponentXMPP |  | ||||||
| ------------- |  | ||||||
| :class:`~slixmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of |  | ||||||
| :class:`~slixmpp.basexmpp.BaseXMPP` that implements the component handshake |  | ||||||
| protocol. |  | ||||||
							
								
								
									
										222
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								docs/conf.py
									
									
									
									
									
								
							| @@ -1,222 +0,0 @@ | |||||||
| # -*- coding: utf-8 -*- |  | ||||||
| # |  | ||||||
| # Slixmpp documentation build configuration file, created by |  | ||||||
| # sphinx-quickstart on Tue Aug  9 22:27:06 2011. |  | ||||||
| # |  | ||||||
| # This file is execfile()d with the current directory set to its containing dir. |  | ||||||
| # |  | ||||||
| # Note that not all possible configuration values are present in this |  | ||||||
| # autogenerated file. |  | ||||||
| # |  | ||||||
| # All configuration values have a default; values that are commented out |  | ||||||
| # serve to show the default. |  | ||||||
|  |  | ||||||
| import sys, os |  | ||||||
|  |  | ||||||
| # If extensions (or modules to document with autodoc) are in another directory, |  | ||||||
| # add these directories to sys.path here. If the directory is relative to the |  | ||||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. |  | ||||||
| sys.path.insert(0, os.path.abspath('..')) |  | ||||||
|  |  | ||||||
| # -- General configuration ----------------------------------------------------- |  | ||||||
|  |  | ||||||
| # If your documentation needs a minimal Sphinx version, state it here. |  | ||||||
| #needs_sphinx = '1.0' |  | ||||||
|  |  | ||||||
| # Add any Sphinx extension module names here, as strings. They can be extensions |  | ||||||
| # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |  | ||||||
| extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx'] |  | ||||||
|  |  | ||||||
| # Add any paths that contain templates here, relative to this directory. |  | ||||||
| templates_path = ['_templates'] |  | ||||||
|  |  | ||||||
| # The suffix of source filenames. |  | ||||||
| source_suffix = '.rst' |  | ||||||
|  |  | ||||||
| # The encoding of source files. |  | ||||||
| #source_encoding = 'utf-8-sig' |  | ||||||
|  |  | ||||||
| # The master toctree document. |  | ||||||
| master_doc = 'index' |  | ||||||
|  |  | ||||||
| # General information about the project. |  | ||||||
| project = u'Slixmpp' |  | ||||||
| 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.1' |  | ||||||
| # The full version, including alpha/beta/rc tags. |  | ||||||
| release = '1.1' |  | ||||||
|  |  | ||||||
| # The language for content autogenerated by Sphinx. Refer to documentation |  | ||||||
| # for a list of supported languages. |  | ||||||
| #language = None |  | ||||||
|  |  | ||||||
| # There are two options for replacing |today|: either, you set today to some |  | ||||||
| # non-false value, then it is used: |  | ||||||
| #today = '' |  | ||||||
| # Else, today_fmt is used as the format for a strftime call. |  | ||||||
| #today_fmt = '%B %d, %Y' |  | ||||||
|  |  | ||||||
| # List of patterns, relative to source directory, that match files and |  | ||||||
| # directories to ignore when looking for source files. |  | ||||||
| exclude_patterns = ['_build'] |  | ||||||
|  |  | ||||||
| # The reST default role (used for this markup: `text`) to use for all documents. |  | ||||||
| #default_role = None |  | ||||||
|  |  | ||||||
| # If true, '()' will be appended to :func: etc. cross-reference text. |  | ||||||
| #add_function_parentheses = True |  | ||||||
|  |  | ||||||
| # If true, the current module name will be prepended to all description |  | ||||||
| # unit titles (such as .. function::). |  | ||||||
| #add_module_names = True |  | ||||||
|  |  | ||||||
| # If true, sectionauthor and moduleauthor directives will be shown in the |  | ||||||
| # output. They are ignored by default. |  | ||||||
| #show_authors = False |  | ||||||
|  |  | ||||||
| # The name of the Pygments (syntax highlighting) style to use. |  | ||||||
| pygments_style = 'tango' |  | ||||||
|  |  | ||||||
| # A list of ignored prefixes for module index sorting. |  | ||||||
| #modindex_common_prefix = [] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- Options for HTML output --------------------------------------------------- |  | ||||||
|  |  | ||||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for |  | ||||||
| # a list of builtin themes. |  | ||||||
| html_theme = '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 = 'slixmpp' |  | ||||||
|  |  | ||||||
| # 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 = 'Slixmppdoc' |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # -- 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', 'Slixmpp.tex', u'Slixmpp 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', 'slixmpp', u'Slixmpp Documentation', |  | ||||||
|      [u'Nathan Fritz, Lance Stout'], 1) |  | ||||||
| ] |  | ||||||
|  |  | ||||||
| intersphinx_mapping = {'python': ('http://docs.python.org/3.4', 'python-objects.inv')} |  | ||||||
| @@ -1,682 +0,0 @@ | |||||||
| .. _create-plugin: |  | ||||||
|  |  | ||||||
| Creating a Slixmpp Plugin |  | ||||||
| =========================== |  | ||||||
|  |  | ||||||
| One of the goals of Slixmpp is to provide support for every draft or final |  | ||||||
| XMPP extension (`XEP <http://xmpp.org/extensions/>`_). To do this, Slixmpp 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 Slixmpp |  | ||||||
| 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 Slixmpp |  | ||||||
| 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:`BasePlugin <slixmpp.plugins.base.BasePlugin`, |  | ||||||
| and must include a ``plugin_init`` method. While the |  | ||||||
| plugins distributed with Slixmpp must be placed in the plugins directory |  | ||||||
| ``slixmpp/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 |  | ||||||
| ``Slixmpp/slixmpp/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 Slixmpp Plugin |  | ||||||
|  |  | ||||||
|     This is a minimal implementation of XEP-0077 to serve |  | ||||||
|     as a tutorial for creating Slixmpp plugins. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     from slixmpp.plugins.base import BasePlugin |  | ||||||
|  |  | ||||||
|     class xep_0077(BasePlugin): |  | ||||||
|         """ |  | ||||||
|         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 |  | ||||||
| ``slixmpp/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 ``BasePlugin.post_init(self)`` |  | ||||||
| which will mark that ``post_init`` has been called for the plugin. Once the |  | ||||||
| Slixmpp 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'``. Slixmpp |  | ||||||
| does not automatically load plugin dependencies for you. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     def post_init(self): |  | ||||||
|         BasePlugin.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 Slixmpp. |  | ||||||
|  |  | ||||||
| * ``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 slixmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin |  | ||||||
|     from slixmpp import Iq |  | ||||||
|  |  | ||||||
|     class Registration(ElementBase): |  | ||||||
|         namespace = 'jabber:iq:register' |  | ||||||
|         name = 'query' |  | ||||||
|         plugin_attrib = 'register' |  | ||||||
|         interfaces = {'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 |  | ||||||
| ``slixmpp.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.register_handler( |  | ||||||
|           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().set_payload(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().set_payload(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().set_payload(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 slixmpp.componentxmpp |  | ||||||
|  |  | ||||||
|     class Example(slixmpp.componentxmpp.ComponentXMPP): |  | ||||||
|  |  | ||||||
|         def __init__(self, jid, password): |  | ||||||
|             slixmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888) |  | ||||||
|  |  | ||||||
|             self.register_plugin('xep_0030') |  | ||||||
|             self.register_plugin('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.send_message(iq['from'], msg, mfrom=self.fulljid) |  | ||||||
|  |  | ||||||
|         def unreg(self, iq): |  | ||||||
|             msg = "Bye! %s" % iq['register']['username'] |  | ||||||
|             self.send_message(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 Slixmpp Plugin |  | ||||||
|  |  | ||||||
|     This is a minimal implementation of XEP-0077 to serve |  | ||||||
|     as a tutorial for creating Slixmpp plugins. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     from slixmpp.plugins.base import BasePlugin |  | ||||||
|     from slixmpp.xmlstream.handler.callback import Callback |  | ||||||
|     from slixmpp.xmlstream.matcher.xpath import MatchXPath |  | ||||||
|     from slixmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin |  | ||||||
|     from slixmpp import Iq |  | ||||||
|     import copy |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     class Registration(ElementBase): |  | ||||||
|         namespace = 'jabber:iq:register' |  | ||||||
|         name = 'query' |  | ||||||
|         plugin_attrib = 'register' |  | ||||||
|         interfaces = {'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(BasePlugin): |  | ||||||
|         """ |  | ||||||
|         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.register_handler( |  | ||||||
|                 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): |  | ||||||
|             BasePlugin.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) |  | ||||||
|                     reply = iq.reply() |  | ||||||
|                     reply.set_payload(iq['register'].xml) |  | ||||||
|                     reply.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) |  | ||||||
|  |  | ||||||
|             reply = iq.reply() |  | ||||||
|             reply.set_payload(reg.xml) |  | ||||||
|             reply.send() |  | ||||||
|  |  | ||||||
|         def _sendError(self, iq, code, error_type, name, text=''): |  | ||||||
|             reply = iq.reply() |  | ||||||
|             reply.set_payload(iq['register'].xml) |  | ||||||
|             reply.error() |  | ||||||
|             reply['error']['code'] = code |  | ||||||
|             reply['error']['type'] = error_type |  | ||||||
|             reply['error']['condition'] = name |  | ||||||
|             reply['error']['text'] = text |  | ||||||
|             reply.send() |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| .. _differences: |  | ||||||
|  |  | ||||||
| Differences from SleekXMPP |  | ||||||
| ========================== |  | ||||||
|  |  | ||||||
| **Python 3.4+ only** |  | ||||||
|     slixmpp will only work on python 3.4 and above. |  | ||||||
|  |  | ||||||
| **Stanza copies** |  | ||||||
|     The same stanza object is given through all the handlers; a handler that |  | ||||||
|     edits the stanza object should make its own copy. |  | ||||||
|  |  | ||||||
| **Replies** |  | ||||||
|     Because stanzas are not copied anymore, |  | ||||||
|     :meth:`Stanza.reply() <.StanzaBase.reply>` calls |  | ||||||
|     (for :class:`IQs <.Iq>`, :class:`Messages <.Message>`, etc) |  | ||||||
|     now return a new object instead of editing the stanza object |  | ||||||
|     in-place. |  | ||||||
|  |  | ||||||
| **Block and threaded arguments** |  | ||||||
|     All the functions that had a ``threaded=`` or ``block=`` argument |  | ||||||
|     do not have it anymore. Also, :meth:`.Iq.send` **does not block |  | ||||||
|     anymore**. |  | ||||||
|  |  | ||||||
| **Coroutine facilities** |  | ||||||
|     **See** :ref:`using_asyncio` |  | ||||||
|  |  | ||||||
|     If an event handler is a coroutine, it will be called asynchronously |  | ||||||
|     in the event loop instead of inside the event caller. |  | ||||||
|  |  | ||||||
|     A CoroutineCallback class has been added to create coroutine stream |  | ||||||
|     handlers, which will be also handled in the event loop. |  | ||||||
|  |  | ||||||
|     The :class:`~.slixmpp.stanza.Iq` object’s :meth:`~.slixmpp.stanza.Iq.send` |  | ||||||
|     method now **always** return a :class:`~.asyncio.Future` which result will be set |  | ||||||
|     to the IQ reply when it is received, or to ``None`` if the IQ is not of |  | ||||||
|     type ``get`` or ``set``. |  | ||||||
|  |  | ||||||
|     Many plugins (WIP) calls which retrieve information also return the same |  | ||||||
|     future. |  | ||||||
|  |  | ||||||
| **Architectural differences** |  | ||||||
|     slixmpp does not have an event queue anymore, and instead processes |  | ||||||
|     handlers directly after receiving the XML stanza. |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|     If you find something that doesn’t work but should, please report it. |  | ||||||
| @@ -1,282 +0,0 @@ | |||||||
| Event Index |  | ||||||
| =========== |  | ||||||
|  |  | ||||||
| .. glossary:: |  | ||||||
|     :sorted: |  | ||||||
|  |  | ||||||
|     connected |  | ||||||
|         - **Data:** ``{}`` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` |  | ||||||
|  |  | ||||||
|         Signal that a connection has been made with the XMPP server, but a session |  | ||||||
|         has not yet been established. |  | ||||||
|  |  | ||||||
|     connection_failed |  | ||||||
|         - **Data:** ``{}`` or ``Failure Stanza`` if available |  | ||||||
|         - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` |  | ||||||
|  |  | ||||||
|         Signal that a connection can not be established after number of attempts. |  | ||||||
|  |  | ||||||
|     changed_status |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.roster.item.RosterItem` |  | ||||||
|  |  | ||||||
|         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:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.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 Slixmpp 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:`~slixmpp.plugins.xep_0030.stanza.DiscoInfo` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` |  | ||||||
|  |  | ||||||
|         Triggered whenever a ``disco#info`` result stanza is received. |  | ||||||
|  |  | ||||||
|     disco_items |  | ||||||
|         - **Data:** :py:class:`~slixmpp.plugins.xep_0030.stanza.DiscoItems` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` |  | ||||||
|  |  | ||||||
|         Triggered whenever a ``disco#items`` result stanza is received. |  | ||||||
|  |  | ||||||
|     disconnected |  | ||||||
|         - **Data:** ``{}`` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` |  | ||||||
|  |  | ||||||
|         Signal that the connection with the XMPP server has been lost. |  | ||||||
|  |  | ||||||
|     entity_time |  | ||||||
|         - **Data:** |  | ||||||
|         - **Source:** |  | ||||||
|  |  | ||||||
|     failed_auth |  | ||||||
|         - **Data:** ``{}`` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.ClientXMPP`, :py:class:`~slixmpp.plugins.xep_0078.xep_0078` |  | ||||||
|  |  | ||||||
|         Signal that the server has rejected the provided login credentials. |  | ||||||
|  |  | ||||||
|     gmail_notify |  | ||||||
|         - **Data:** ``{}`` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Iq` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.roster.item.RosterItem` |  | ||||||
|  |  | ||||||
|         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:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.roster.item.RosterItem` |  | ||||||
|  |  | ||||||
|         Signal that an unavailable presence stanza has been received from a JID. |  | ||||||
|  |  | ||||||
|     groupchat_invite |  | ||||||
|         - **Data:** |  | ||||||
|         - **Source:** |  | ||||||
|  |  | ||||||
|     groupchat_direct_invite |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Message` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.plugins.xep_0249.direct` |  | ||||||
|  |  | ||||||
|     groupchat_message |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Message` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.plugins.xep_0045.xep_0045` |  | ||||||
|  |  | ||||||
|         Triggered whenever a message is received from a multi-user chat room. |  | ||||||
|  |  | ||||||
|     groupchat_presence |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Message` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Message` |  | ||||||
|         - **Source:** :py:class:`BaseXMPP <slixmpp.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_error |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Message` |  | ||||||
|         - **Source:** :py:class:`BaseXMPP <slixmpp.BaseXMPP>` |  | ||||||
|  |  | ||||||
|         Makes the contents of message stanzas available whenever one is received. |  | ||||||
|         Only handler messages with an ``error`` type. |  | ||||||
|  |  | ||||||
|     message_form |  | ||||||
|         - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` |  | ||||||
|  |  | ||||||
|         Currently the same as :term:`message_xform`. |  | ||||||
|  |  | ||||||
|     message_xform |  | ||||||
|         - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` |  | ||||||
|  |  | ||||||
|         Triggered whenever a data form is received inside a message. |  | ||||||
|  |  | ||||||
|     muc::[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:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``available``' is received. |  | ||||||
|  |  | ||||||
|     presence_error |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``error``' is received. |  | ||||||
|  |  | ||||||
|     presence_form |  | ||||||
|         - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``probe``' is received. |  | ||||||
|  |  | ||||||
|     presence_subscribe |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``subscribe``' is received. |  | ||||||
|  |  | ||||||
|     presence_subscribed |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``subscribed``' is received. |  | ||||||
|  |  | ||||||
|     presence_unavailable |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``unavailable``' is received. |  | ||||||
|  |  | ||||||
|     presence_unsubscribe |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``unsubscribe``' is received. |  | ||||||
|  |  | ||||||
|     presence_unsubscribed |  | ||||||
|         - **Data:** :py:class:`~slixmpp.Presence` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
|  |  | ||||||
|         A presence stanza with a type of '``unsubscribed``' is received. |  | ||||||
|  |  | ||||||
|     roster_update |  | ||||||
|         - **Data:** :py:class:`~slixmpp.stanza.Roster` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.ClientXMPP` |  | ||||||
|  |  | ||||||
|         An IQ result containing roster entries is received. |  | ||||||
|  |  | ||||||
|     sent_presence |  | ||||||
|         - **Data:** ``{}`` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.roster.multi.Roster` |  | ||||||
|  |  | ||||||
|         Signal that an initial presence stanza has been written to the XML stream. |  | ||||||
|  |  | ||||||
|     session_end |  | ||||||
|         - **Data:** ``{}`` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` |  | ||||||
|  |  | ||||||
|         Signal that a connection to the XMPP server has been lost and the current |  | ||||||
|         stream session has ended. Currently equivalent to :term:`disconnected`, but |  | ||||||
|         implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ |  | ||||||
|         distinguish between the two events. |  | ||||||
|  |  | ||||||
|         Plugins that maintain session-based state should clear themselves when |  | ||||||
|         this event is fired. |  | ||||||
|  |  | ||||||
|     session_start |  | ||||||
|         - **Data:** ``{}`` |  | ||||||
|         - **Source:** :py:class:`ClientXMPP <slixmpp.ClientXMPP>`, |  | ||||||
|           :py:class:`ComponentXMPP <slixmpp.ComponentXMPP>` |  | ||||||
|           :py:class:`XEP-0078 <slixmpp.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:`~slixmpp.xmlstream.XMLstream` |  | ||||||
|  |  | ||||||
|     stream_error |  | ||||||
|         - **Data:** :py:class:`~slixmpp.stanza.StreamError` |  | ||||||
|         - **Source:** :py:class:`~slixmpp.BaseXMPP` |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| How to Use Stream Features |  | ||||||
| ========================== |  | ||||||
| @@ -1,67 +0,0 @@ | |||||||
| .. _echocomponent: |  | ||||||
|  |  | ||||||
| ================================= |  | ||||||
| Create and Run a Server Component |  | ||||||
| ================================= |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     If you have any issues working through this quickstart guide |  | ||||||
|     join the chat room at `slixmpp@muc.poez.io |  | ||||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. |  | ||||||
|  |  | ||||||
| If you have not yet installed Slixmpp, do so now by either checking out a version |  | ||||||
| with `Git <http://git.poez.io/slixmpp>`_. |  | ||||||
|  |  | ||||||
| Many XMPP applications eventually graduate to requiring to run as a server |  | ||||||
| component in order to meet scalability requirements. To demonstrate how to |  | ||||||
| turn an XMPP client bot into a component, we'll turn the echobot example |  | ||||||
| (:ref:`echobot`) into a component version. |  | ||||||
|  |  | ||||||
| The first difference is that we will add an additional import statement: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     from slixmpp.componentxmpp import ComponentXMPP |  | ||||||
|  |  | ||||||
| Likewise, we will change the bot's class definition to match: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     class EchoComponent(ComponentXMPP): |  | ||||||
|  |  | ||||||
|         def __init__(self, jid, secret, server, port): |  | ||||||
|             ComponentXMPP.__init__(self, jid, secret, server, port) |  | ||||||
|  |  | ||||||
| A component instance requires two extra parameters compared to a client |  | ||||||
| instance: ``server`` and ``port``. These specifiy the name and port of |  | ||||||
| the XMPP server that will be accepting the component. For example, for |  | ||||||
| a MUC component, the following could be used: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     muc = ComponentXMPP('muc.slixmpp.com', '******', 'slixmpp.com', 5555) |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     The ``server`` value is **NOT** derived from the provided JID for the |  | ||||||
|     component, unlike with client connections. |  | ||||||
|  |  | ||||||
| One difference with the component version is that we do not have |  | ||||||
| to handle the :term:`session_start` event if we don't wish to deal |  | ||||||
| with presence. |  | ||||||
|  |  | ||||||
| The other, main difference with components is that the |  | ||||||
| ``'from'`` value for every stanza must be explicitly set, since |  | ||||||
| components may send stanzas from multiple JIDs. To do so, |  | ||||||
| the :meth:`~slixmpp.basexmpp.BaseXMPP.send_message()` and |  | ||||||
| :meth:`~slixmpp.basexmpp.BaseXMPP.send_presence()` accept the parameters |  | ||||||
| ``mfrom`` and ``pfrom``, respectively. For any method that uses |  | ||||||
| :class:`~slixmpp.stanza.iq.Iq` stanzas, ``ifrom`` may be used. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Final Product |  | ||||||
| ------------- |  | ||||||
|  |  | ||||||
| .. include:: ../../examples/echo_component.py |  | ||||||
|     :literal: |  | ||||||
| @@ -1,349 +0,0 @@ | |||||||
| .. _echobot: |  | ||||||
|  |  | ||||||
| =============================== |  | ||||||
| Slixmpp Quickstart - Echo Bot |  | ||||||
| =============================== |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     If you have any issues working through this quickstart guide |  | ||||||
|     join the chat room at `slixmpp@muc.poez.io |  | ||||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. |  | ||||||
|  |  | ||||||
| If you have not yet installed Slixmpp, do so now by either checking out a version |  | ||||||
| with `Git <http://git.poez.io/slixmpp>`_. |  | ||||||
|  |  | ||||||
| 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 asyncio |  | ||||||
|     import logging |  | ||||||
|     import getpass |  | ||||||
|     from optparse import OptionParser |  | ||||||
|  |  | ||||||
|     import slixmpp |  | ||||||
|  |  | ||||||
|     '''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''' |  | ||||||
|  |  | ||||||
| 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. |  | ||||||
|  |  | ||||||
| Slixmpp comes with a :class:`ClientXMPP <slixmpp.clientxmpp.ClientXMPP>` class |  | ||||||
| which we can extend to add our message echoing feature. :class:`ClientXMPP <slixmpp.clientxmpp.ClientXMPP>` |  | ||||||
| requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those |  | ||||||
| as well. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     class EchoBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|         def __init__(self, jid, password): |  | ||||||
|             super().__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().__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 <slixmpp.basexmpp.BaseXMPP.send_presence>`. |  | ||||||
| Calling :meth:`send_presence <slixmpp.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 <slixmpp.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 <slixmpp.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 |  | ||||||
| <slixmpp.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 |  | ||||||
| <slixmpp.exceptions.IQTimeout>` is raised. Similarly, an :class:`IQError <slixmpp.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().__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 <slixmpp.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. Slixmpp thus leaves out the ``from`` attribute when replying |  | ||||||
|     using a client connection. |  | ||||||
|  |  | ||||||
| Command Line Arguments and Logging |  | ||||||
| ---------------------------------- |  | ||||||
|  |  | ||||||
| While this isn't part of Slixmpp 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 Slixmpp 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 |  | ||||||
| ``aiodns`` installed, then the :meth:`slixmpp.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 ``aiodns``, then Slixmpp will attempt to |  | ||||||
| connect to the hostname used by the JID, unless an address tuple is supplied |  | ||||||
| to :meth:`slixmpp.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') |  | ||||||
|  |  | ||||||
| To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process` |  | ||||||
| which will start the event handling, send queue, and XML reader threads. It will also call |  | ||||||
| the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By |  | ||||||
| passing ``block=True`` to :meth:`slixmpp.basexmpp.BaseXMPP.process` we are running the |  | ||||||
| main processing loop in the main thread of execution. The :meth:`slixmpp.basexmpp.BaseXMPP.process` |  | ||||||
| call will not return until after Slixmpp 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:`slixmpp.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 Slixmpp 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 Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/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: |  | ||||||
| @@ -1,182 +0,0 @@ | |||||||
| Send/Receive IQ Stanzas |  | ||||||
| ======================= |  | ||||||
|  |  | ||||||
| Unlike :class:`~slixmpp.stanza.message.Message` and |  | ||||||
| :class:`~slixmpp.stanza.presence.Presence` stanzas which only use |  | ||||||
| text data for basic usage, :class:`~slixmpp.stanza.iq.Iq` stanzas |  | ||||||
| require using XML payloads, and generally entail creating a new |  | ||||||
| Slixmpp plugin to provide the necessary convenience methods to |  | ||||||
| make working with them easier. |  | ||||||
|  |  | ||||||
| Basic Use |  | ||||||
| --------- |  | ||||||
|  |  | ||||||
| XMPP's use of :class:`~slixmpp.stanza.iq.Iq` stanzas is built around |  | ||||||
| namespaced ``<query />`` elements. For clients, just sending the |  | ||||||
| empty ``<query />`` element will suffice for retrieving information. For |  | ||||||
| example, a very basic implementation of service discovery would just |  | ||||||
| need to be able to send: |  | ||||||
|  |  | ||||||
| .. code-block:: xml |  | ||||||
|  |  | ||||||
|     <iq to="user@example.com" type="get" id="1"> |  | ||||||
|       <query xmlns="http://jabber.org/protocol/disco#info" /> |  | ||||||
|     </iq> |  | ||||||
|  |  | ||||||
| Creating Iq Stanzas |  | ||||||
| ~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Slixmpp provides built-in support for creating basic :class:`~slixmpp.stanza.iq.Iq` |  | ||||||
| stanzas this way. The relevant methods are: |  | ||||||
|  |  | ||||||
| * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq` |  | ||||||
| * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_get` |  | ||||||
| * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_set` |  | ||||||
| * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_result` |  | ||||||
| * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_error` |  | ||||||
| * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_query` |  | ||||||
|  |  | ||||||
| These methods all follow the same pattern: create or modify an existing |  | ||||||
| :class:`~slixmpp.stanza.iq.Iq` stanza, set the ``'type'`` value based |  | ||||||
| on the method name, and finally add a ``<query />`` element with the given |  | ||||||
| namespace. For example, to produce the query above, you would use: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     self.make_iq_get(queryxmlns='http://jabber.org/protocol/disco#info', |  | ||||||
|                      ito='user@example.com') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Sending Iq Stanzas |  | ||||||
| ~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Once an :class:`~slixmpp.stanza.iq.Iq` stanza is created, sending it |  | ||||||
| over the wire is done using its :meth:`~slixmpp.stanza.iq.Iq.send()` |  | ||||||
| method, like any other stanza object. However, there are a few extra |  | ||||||
| options to control how to wait for the query's response. |  | ||||||
|  |  | ||||||
| These options are: |  | ||||||
|  |  | ||||||
| * ``block``: The default behaviour is that :meth:`~slixmpp.stanza.iq.Iq.send()` |  | ||||||
|   will block until a response is received and the response stanza will be the |  | ||||||
|   return value. Setting ``block`` to ``False`` will cause the call to return |  | ||||||
|   immediately. In which case, you will need to arrange some way to capture |  | ||||||
|   the response stanza if you need it. |  | ||||||
|  |  | ||||||
| * ``timeout``: When using the blocking behaviour, the call will eventually |  | ||||||
|   timeout with an error. The default timeout is 30 seconds, but this may |  | ||||||
|   be overidden two ways. To change the timeout globally, set: |  | ||||||
|  |  | ||||||
|     .. code-block:: python |  | ||||||
|  |  | ||||||
|         self.response_timeout = 10 |  | ||||||
|  |  | ||||||
|   To change the timeout for a single call, the ``timeout`` parameter works: |  | ||||||
|  |  | ||||||
|     .. code-block:: python |  | ||||||
|  |  | ||||||
|         iq.send(timeout=60) |  | ||||||
|  |  | ||||||
| * ``callback``: When not using a blocking call, using the ``callback`` |  | ||||||
|   argument is a simple way to register a handler that will execute |  | ||||||
|   whenever a response is finally received. Using this method, there |  | ||||||
|   is no timeout limit. In case you need to remove the callback, the |  | ||||||
|   name of the newly created callback is returned. |  | ||||||
|  |  | ||||||
|     .. code-block:: python |  | ||||||
|  |  | ||||||
|        cb_name = iq.send(callback=self.a_callback) |  | ||||||
|  |  | ||||||
|        # ... later if we need to cancel |  | ||||||
|        self.remove_handler(cb_name) |  | ||||||
|  |  | ||||||
| Properly working with :class:`~slixmpp.stanza.iq.Iq` stanzas requires |  | ||||||
| handling the intended, normal flow, error responses, and timed out |  | ||||||
| requests. To make this easier, two exceptions may be thrown by |  | ||||||
| :meth:`~slixmpp.stanza.iq.Iq.send()`: :exc:`~slixmpp.exceptions.IqError` |  | ||||||
| and :exc:`~slixmpp.exceptions.IqTimeout`. These exceptions only |  | ||||||
| apply to the default, blocking calls. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         resp = iq.send() |  | ||||||
|         # ... do stuff with expected Iq result |  | ||||||
|     except IqError as e: |  | ||||||
|         err_resp = e.iq |  | ||||||
|         # ... handle error case |  | ||||||
|     except IqTimeout: |  | ||||||
|         # ... no response received in time |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
| If you do not care to distinguish between errors and timeouts, then you |  | ||||||
| can combine both cases with a generic :exc:`~slixmpp.exceptions.XMPPError` |  | ||||||
| exception: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     try: |  | ||||||
|         resp = iq.send() |  | ||||||
|     except XMPPError: |  | ||||||
|         # ... Don't care about the response |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
| Advanced Use |  | ||||||
| ------------ |  | ||||||
|  |  | ||||||
| Going beyond the basics provided by Slixmpp requires building at least a |  | ||||||
| rudimentary Slixmpp plugin to create a :term:`stanza object` for |  | ||||||
| interfacting with the :class:`~slixmpp.stanza.iq.Iq` payload. |  | ||||||
|  |  | ||||||
| .. seealso:: |  | ||||||
|  |  | ||||||
|     * :ref:`create-plugin` |  | ||||||
|     * :ref:`work-with-stanzas` |  | ||||||
|     * :ref:`using-handlers-matchers` |  | ||||||
|  |  | ||||||
|  |  | ||||||
| The typical way to respond to :class:`~slixmpp.stanza.iq.Iq` requests is |  | ||||||
| to register stream handlers. As an example, suppose we create a stanza class |  | ||||||
| named ``CustomXEP`` which uses the XML element ``<query xmlns="custom-xep" />``, |  | ||||||
| and has a :attr:`~slixmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value |  | ||||||
| of ``custom_xep``. |  | ||||||
|  |  | ||||||
| There are two types of incoming :class:`~slixmpp.stanza.iq.Iq` requests: |  | ||||||
| ``get`` and ``set``. You can register a handler that will accept both and then |  | ||||||
| filter by type as needed, as so: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     self.register_handler(Callback( |  | ||||||
|         'CustomXEP Handler', |  | ||||||
|         StanzaPath('iq/custom_xep'), |  | ||||||
|         self._handle_custom_iq)) |  | ||||||
|  |  | ||||||
|     # ... |  | ||||||
|  |  | ||||||
|     def _handle_custom_iq(self, iq): |  | ||||||
|         if iq['type'] == 'get': |  | ||||||
|             # ... |  | ||||||
|             pass |  | ||||||
|         elif iq['type'] == 'set': |  | ||||||
|             # ... |  | ||||||
|             pass |  | ||||||
|         else: |  | ||||||
|             # ... This will capture error responses too |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
| If you want to filter out query types beforehand, you can adjust the matching |  | ||||||
| filter by using ``@type=get`` or ``@type=set`` if you are using the recommended |  | ||||||
| :class:`~slixmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     self.register_handler(Callback( |  | ||||||
|         'CustomXEP Handler', |  | ||||||
|         StanzaPath('iq@type=get/custom_xep'), |  | ||||||
|         self._handle_custom_iq_get)) |  | ||||||
|  |  | ||||||
|     # ... |  | ||||||
|  |  | ||||||
|     def _handle_custom_iq_get(self, iq): |  | ||||||
|         assert(iq['type'] == 'get') |  | ||||||
| @@ -1,200 +0,0 @@ | |||||||
| .. _mucbot: |  | ||||||
|  |  | ||||||
| ========================= |  | ||||||
| Mulit-User Chat (MUC) Bot |  | ||||||
| ========================= |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     If you have any issues working through this quickstart guide |  | ||||||
|     join the chat room at `slixmpp@muc.poez.io |  | ||||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. |  | ||||||
|  |  | ||||||
| If you have not yet installed Slixmpp, do so now by either checking out a version |  | ||||||
| from `Git <http://git.poez.io/slixmpp>`_. |  | ||||||
|  |  | ||||||
| Now that you've got the basic gist of using Slixmpp by following the |  | ||||||
| echobot example (:ref:`echobot`), we can use one of the bundled plugins |  | ||||||
| to create a very popular XMPP starter project: a `Multi-User Chat`_ |  | ||||||
| (MUC) bot. Our bot will login to an XMPP server, join an MUC chat room |  | ||||||
| and "lurk" indefinitely, responding with a generic message to anyone |  | ||||||
| that mentions its nickname. It will also greet members as they join the |  | ||||||
| chat room. |  | ||||||
|  |  | ||||||
| .. _`multi-user chat`: http://xmpp.org/extensions/xep-0045.html |  | ||||||
|  |  | ||||||
| Joining The Room |  | ||||||
| ---------------- |  | ||||||
|  |  | ||||||
| As usual, our code will be based on the pattern explained in :ref:`echobot`. |  | ||||||
| To start, we create an ``MUCBot`` class based on |  | ||||||
| :class:`ClientXMPP <slixmpp.clientxmpp.ClientXMPP>` and which accepts |  | ||||||
| parameters for the JID of the MUC room to join, and the nick that the |  | ||||||
| bot will use inside the chat room.  We also register an |  | ||||||
| :term:`event handler` for the :term:`session_start` event. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     import slixmpp |  | ||||||
|  |  | ||||||
|     class MUCBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|         def __init__(self, jid, password, room, nick): |  | ||||||
|             slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|             self.room = room |  | ||||||
|             self.nick = nick |  | ||||||
|  |  | ||||||
|             self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
| After initialization, we also need to register the MUC (XEP-0045) plugin |  | ||||||
| so that we can make use of the group chat plugin's methods and events. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     xmpp.register_plugin('xep_0045') |  | ||||||
|  |  | ||||||
| Finally, we can make our bot join the chat room once an XMPP session |  | ||||||
| has been established: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         self.get_roster() |  | ||||||
|         self.send_presence() |  | ||||||
|         self.plugin['xep_0045'].join_muc(self.room, |  | ||||||
|                                          self.nick, |  | ||||||
|                                          wait=True) |  | ||||||
|  |  | ||||||
| Note that as in :ref:`echobot`, we need to include send an initial presence and request |  | ||||||
| the roster. Next, we want to join the group chat, so we call the |  | ||||||
| ``join_muc`` method of the MUC plugin. |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     The :attr:`plugin <slixmpp.basexmpp.BaseXMPP.plugin>` attribute is |  | ||||||
|     dictionary that maps to instances of plugins that we have previously |  | ||||||
|     registered, by their names. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Adding Functionality |  | ||||||
| -------------------- |  | ||||||
|  |  | ||||||
| Currently, our bot just sits dormantly inside the chat room, but we |  | ||||||
| would like it to respond to two distinct events by issuing a generic |  | ||||||
| message in each case to the chat room. In particular, when a member |  | ||||||
| mentions the bot's nickname inside the chat room, and when a member |  | ||||||
| joins the chat room. |  | ||||||
|  |  | ||||||
| Responding to Mentions |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Whenever a user mentions our bot's nickname in chat, our bot will |  | ||||||
| respond with a generic message resembling *"I heard that, user."* We do |  | ||||||
| this by examining all of the messages sent inside the chat and looking |  | ||||||
| for the ones which contain the nickname string. |  | ||||||
|  |  | ||||||
| First, we register an event handler for the :term:`groupchat_message` |  | ||||||
| event inside the bot's ``__init__`` function. |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     We do not register a handler for the :term:`message` event in this |  | ||||||
|     bot, but if we did, the group chat message would have been sent to |  | ||||||
|     both handlers. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, room, nick): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.room = room |  | ||||||
|         self.nick = nick |  | ||||||
|  |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|         self.add_event_handler("groupchat_message", self.muc_message) |  | ||||||
|  |  | ||||||
| Then, we can send our generic message whenever the bot's nickname gets |  | ||||||
| mentioned. |  | ||||||
|  |  | ||||||
| .. warning:: |  | ||||||
|  |  | ||||||
|     Always check that a message is not from yourself, |  | ||||||
|     otherwise you will create an infinite loop responding |  | ||||||
|     to your own messages. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     def muc_message(self, msg): |  | ||||||
|         if msg['mucnick'] != self.nick and self.nick in msg['body']: |  | ||||||
|             self.send_message(mto=msg['from'].bare, |  | ||||||
|                               mbody="I heard that, %s." % msg['mucnick'], |  | ||||||
|                               mtype='groupchat') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Greeting Members |  | ||||||
| ~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| Now we want to greet member whenever they join the group chat. To |  | ||||||
| do this we will use the dynamic ``muc::room@server::got_online`` [1]_ |  | ||||||
| event so it's a good idea to register an event handler for it. |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     The groupchat_presence event is triggered whenever a |  | ||||||
|     presence stanza is received from any chat room, including |  | ||||||
|     any presences you send yourself. To limit event handling |  | ||||||
|     to a single room, use the events ``muc::room@server::presence``, |  | ||||||
|     ``muc::room@server::got_online``, or ``muc::room@server::got_offline``. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, room, nick): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.room = room |  | ||||||
|         self.nick = nick |  | ||||||
|  |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|         self.add_event_handler("groupchat_message", self.muc_message) |  | ||||||
|         self.add_event_handler("muc::%s::got_online" % self.room, |  | ||||||
|                                self.muc_online) |  | ||||||
|  |  | ||||||
| Now all that's left to do is to greet them: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     def muc_online(self, presence): |  | ||||||
|         if presence['muc']['nick'] != self.nick: |  | ||||||
|             self.send_message(mto=presence['from'].bare, |  | ||||||
|                               mbody="Hello, %s %s" % (presence['muc']['role'], |  | ||||||
|                                                       presence['muc']['nick']), |  | ||||||
|                               mtype='groupchat') |  | ||||||
|  |  | ||||||
| .. [1] this is similar to the :term:`got_online` event and is sent by |  | ||||||
|        the xep_0045 plugin whenever a member joins the referenced |  | ||||||
|        MUC chat room. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Final Product |  | ||||||
| ------------- |  | ||||||
|  |  | ||||||
| .. compound:: |  | ||||||
|  |  | ||||||
|     The final step is to create a small runner script for initialising our ``MUCBot`` 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 muc.py -d -j jid@example.com -r room@muc.example.net -n lurkbot |  | ||||||
|  |  | ||||||
|     which will prompt for the password, log in, and join the group chat. To test, open |  | ||||||
|     your regular IM client and join the same group chat that you sent the bot to. You |  | ||||||
|     will see ``lurkbot`` as one of the members in the group chat, and that it greeted |  | ||||||
|     you upon entry. Send a message with the string "lurkbot" inside the body text, and you |  | ||||||
|     will also see that it responds with our pre-programmed customized message. |  | ||||||
|  |  | ||||||
| .. include:: ../../examples/muc.py |  | ||||||
|     :literal: |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| Manage Presence Subscriptions |  | ||||||
| ============================= |  | ||||||
| @@ -1,40 +0,0 @@ | |||||||
| .. _proxy: |  | ||||||
|  |  | ||||||
| ========================= |  | ||||||
| Enable HTTP Proxy Support |  | ||||||
| ========================= |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     If you have any issues working through this quickstart guide |  | ||||||
|     join the chat room at `slixmpp@muc.poez.io |  | ||||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. |  | ||||||
|  |  | ||||||
| In some instances, you may wish to route XMPP traffic through |  | ||||||
| an HTTP proxy, probably to get around restrictive firewalls. |  | ||||||
| Slixmpp provides support for basic HTTP proxying with DIGEST |  | ||||||
| authentication. |  | ||||||
|  |  | ||||||
| Enabling proxy support is done in two steps. The first is to instruct Slixmpp |  | ||||||
| to use a proxy, and the second is to configure the proxy details: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     xmpp = ClientXMPP(...) |  | ||||||
|     xmpp.use_proxy = True |  | ||||||
|     xmpp.proxy_config = { |  | ||||||
|         'host': 'proxy.example.com', |  | ||||||
|         'port': 5555, |  | ||||||
|         'username': 'example_user', |  | ||||||
|         'password': '******' |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| The ``'username'`` and ``'password'`` fields are optional if the proxy does not |  | ||||||
| require authentication. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| The Final Product |  | ||||||
| ----------------- |  | ||||||
|  |  | ||||||
| .. include:: ../../examples/proxy_echo_client.py |  | ||||||
|     :literal: |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| Send a Message Every 5 Minutes |  | ||||||
| ============================== |  | ||||||
| @@ -1,92 +0,0 @@ | |||||||
| Sign in, Send a Message, and Disconnect |  | ||||||
| ======================================= |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|  |  | ||||||
|     If you have any issues working through this quickstart guide |  | ||||||
|     join the chat room at `slixmpp@muc.poez.io |  | ||||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. |  | ||||||
|  |  | ||||||
| A common use case for Slixmpp 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 <slixmpp.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 slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     class SendMsgBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|         def __init__(self, jid, password, recipient, msg): |  | ||||||
|             super().__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 <slixmpp.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 <slixmpp.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 <slixmpp.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) <slixmpp.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) <slixmpp.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: |  | ||||||
| @@ -1,34 +0,0 @@ | |||||||
| .. _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 :class:`Matcher <.MatcherBase>` object, and |  | ||||||
|         which provides additional semantics. For example, the |  | ||||||
|         :class:`.Waiter` handler wrapper blocks thread execution until a |  | ||||||
|         matching stanza is received. |  | ||||||
|  |  | ||||||
|     event handler |  | ||||||
|         A callback function that responds to events raised by |  | ||||||
|         :meth:`.XMLStream.event`. |  | ||||||
|  |  | ||||||
|     stanza object |  | ||||||
|         Informally may refer both to classes which extend :class:`.ElementBase` |  | ||||||
|         or :class:`.StanzaBase`, and to objects of such classes. |  | ||||||
|  |  | ||||||
|         A stanza object is a wrapper for an XML object which exposes :class:`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` |  | ||||||
| @@ -1,200 +0,0 @@ | |||||||
| 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 Slixmpp.) Slixmpp 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 ``restore_defaults()``. |  | ||||||
| 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='Slixmpp', |  | ||||||
|                                   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``. |  | ||||||
|  |  | ||||||
| Performing 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 = yield from self['xep_0030'].get_info(jid='foo@example.com', |  | ||||||
|                                                 node='bar', |  | ||||||
|                                                 ifrom='baz@mycomponent.example.com', |  | ||||||
|                                                 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) |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| .. _using-handlers-matchers: |  | ||||||
|  |  | ||||||
| Using Stream Handlers and Matchers |  | ||||||
| ================================== |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| .. _work-with-stanzas: |  | ||||||
|  |  | ||||||
| How to Work with Stanza Objects |  | ||||||
| =============================== |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _create-stanza-interfaces: |  | ||||||
|  |  | ||||||
| Defining Stanza Interfaces |  | ||||||
| -------------------------- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _create-stanza-plugins: |  | ||||||
|  |  | ||||||
| Creating Stanza Plugins |  | ||||||
| ----------------------- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _create-extension-plugins: |  | ||||||
|  |  | ||||||
| Creating a Stanza Extension |  | ||||||
| --------------------------- |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _override-parent-interfaces: |  | ||||||
|  |  | ||||||
| Overriding a Parent Stanza |  | ||||||
| -------------------------- |  | ||||||
							
								
								
									
										236
									
								
								docs/index.rst
									
									
									
									
									
								
							
							
						
						
									
										236
									
								
								docs/index.rst
									
									
									
									
									
								
							| @@ -1,236 +0,0 @@ | |||||||
| Slixmpp |  | ||||||
| ######### |  | ||||||
|  |  | ||||||
| .. sidebar:: Get the Code |  | ||||||
|  |  | ||||||
|     The latest source code for Slixmpp may be found on the `Git repo |  | ||||||
|     <http://git.poez.io/slixmpp>`_. :: |  | ||||||
|  |  | ||||||
|         git clone git://git.poez.io/slixmpp |  | ||||||
|  |  | ||||||
|     An XMPP chat room is available for discussing and getting help with slixmpp. |  | ||||||
|  |  | ||||||
|     **Chat** |  | ||||||
|         `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_ |  | ||||||
|  |  | ||||||
|     **Reporting bugs** |  | ||||||
|         You can report bugs at http://dev.louiz.org/projects/slixmpp/issues. |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|     slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_ |  | ||||||
|     which goal is to use asyncio instead of threads to handle networking. See |  | ||||||
|     :ref:`differences`. |  | ||||||
|  |  | ||||||
| Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.4+, |  | ||||||
|  |  | ||||||
| Slixmpp's design goals and philosphy are: |  | ||||||
|  |  | ||||||
| **Low number of dependencies** |  | ||||||
|     Installing and using Slixmpp 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 Slixmpp 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, Slixmpp 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. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Here's your first Slixmpp Bot: |  | ||||||
| -------------------------------- |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     import asyncio |  | ||||||
|     import logging |  | ||||||
|  |  | ||||||
|     from slixmpp import ClientXMPP |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     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) |  | ||||||
|  |  | ||||||
|             # If you wanted more functionality, here's how to register plugins: |  | ||||||
|             # self.register_plugin('xep_0030') # Service Discovery |  | ||||||
|             # self.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|             # Here's how to access plugins once you've registered them: |  | ||||||
|             # self['xep_0030'].add_feature('echo_demo') |  | ||||||
|  |  | ||||||
|         def session_start(self, event): |  | ||||||
|             self.send_presence() |  | ||||||
|             self.get_roster() |  | ||||||
|  |  | ||||||
|             # Most get_*/set_* methods from plugins use Iq stanzas, which |  | ||||||
|             # are sent asynchronously. You can almost always provide a |  | ||||||
|             # callback that will be executed when the reply is received. |  | ||||||
|  |  | ||||||
|         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() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| To read if you come from SleekXMPP |  | ||||||
| ---------------------------------- |  | ||||||
|  |  | ||||||
| .. toctree:: |  | ||||||
|     :maxdepth: 1 |  | ||||||
|  |  | ||||||
|     differences |  | ||||||
|     using_asyncio |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
|     howto/stanzas |  | ||||||
|     create_plugin |  | ||||||
|     features |  | ||||||
|     sasl |  | ||||||
|     handlersmatchers |  | ||||||
|  |  | ||||||
| Plugin Guides |  | ||||||
| ~~~~~~~~~~~~~ |  | ||||||
| .. toctree:: |  | ||||||
|     :maxdepth: 1 |  | ||||||
|  |  | ||||||
|     guide_xep_0030 |  | ||||||
|  |  | ||||||
| Slixmpp Architecture and Design |  | ||||||
| --------------------------------- |  | ||||||
| .. toctree:: |  | ||||||
|     :maxdepth: 3 |  | ||||||
|  |  | ||||||
|     architecture |  | ||||||
|     plugin_arch |  | ||||||
|  |  | ||||||
| API Reference |  | ||||||
| ------------- |  | ||||||
| .. toctree:: |  | ||||||
|     :maxdepth: 2 |  | ||||||
|  |  | ||||||
|     event_index |  | ||||||
|     api/clientxmpp |  | ||||||
|     api/componentxmpp |  | ||||||
|     api/basexmpp |  | ||||||
|     api/exceptions |  | ||||||
|     api/xmlstream/jid |  | ||||||
|     api/xmlstream/stanzabase |  | ||||||
|     api/xmlstream/handler |  | ||||||
|     api/xmlstream/matcher |  | ||||||
|     api/xmlstream/xmlstream |  | ||||||
|     api/xmlstream/tostring |  | ||||||
|  |  | ||||||
| Core Stanzas |  | ||||||
| ~~~~~~~~~~~~ |  | ||||||
| .. toctree:: |  | ||||||
|     :maxdepth: 2 |  | ||||||
|  |  | ||||||
|     api/stanza/rootstanza |  | ||||||
|     api/stanza/message |  | ||||||
|     api/stanza/presence |  | ||||||
|     api/stanza/iq |  | ||||||
|  |  | ||||||
| Plugins |  | ||||||
| ~~~~~~~ |  | ||||||
| .. toctree:: |  | ||||||
|     :maxdepth: 2 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Additional Info |  | ||||||
| --------------- |  | ||||||
| .. toctree:: |  | ||||||
|     :hidden: |  | ||||||
|  |  | ||||||
|     glossary |  | ||||||
|     license |  | ||||||
|  |  | ||||||
| * :ref:`license` |  | ||||||
| * :ref:`glossary` |  | ||||||
| * :ref:`genindex` |  | ||||||
| * :ref:`modindex` |  | ||||||
| * :ref:`search` |  | ||||||
|  |  | ||||||
| SleekXMPP Credits |  | ||||||
| ----------------- |  | ||||||
|  |  | ||||||
| .. note:: |  | ||||||
|     Those people made SleekXMPP, so you should not bother them if |  | ||||||
|     you have an issue with slixmpp. But it’s still fair to credit |  | ||||||
|     them for their work. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| **Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_ |  | ||||||
|      `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, |  | ||||||
|      `@fritzy <http://twitter.com/fritzy>`_ |  | ||||||
|  |  | ||||||
|      Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP |  | ||||||
|      <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of the XMPP |  | ||||||
|      Council. |  | ||||||
|  |  | ||||||
| **Co-Author:** `Lance Stout <http://andyet.net/team/lance>`_ |  | ||||||
|      `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, |  | ||||||
|      `@lancestout <http://twitter.com/lancestout>`_ |  | ||||||
|  |  | ||||||
| Both Fritzy and Lance work for `&yet <http://andyet.net>`_, which specializes in |  | ||||||
| realtime web and XMPP applications. |  | ||||||
|  |  | ||||||
|     - `contact@andyet.net <mailto:contact@andyet.net>`_ |  | ||||||
|     - `XMPP Consulting <http://xmppconsulting.com>`_ |  | ||||||
|  |  | ||||||
| **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>`_) |  | ||||||
|  |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| .. _license: |  | ||||||
|  |  | ||||||
| License (MIT) |  | ||||||
| ============= |  | ||||||
| .. include:: ../LICENSE |  | ||||||
							
								
								
									
										170
									
								
								docs/make.bat
									
									
									
									
									
								
							
							
						
						
									
										170
									
								
								docs/make.bat
									
									
									
									
									
								
							| @@ -1,170 +0,0 @@ | |||||||
| @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\Slixmpp.qhcp |  | ||||||
| 	echo.To view the help file: |  | ||||||
| 	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Slixmpp.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 |  | ||||||
| @@ -1,2 +0,0 @@ | |||||||
| Plugin Architecture |  | ||||||
| =================== |  | ||||||
										
											Binary file not shown.
										
									
								
							| @@ -1,2 +0,0 @@ | |||||||
| How SASL Authentication Works |  | ||||||
| ============================= |  | ||||||
| @@ -1,148 +0,0 @@ | |||||||
| .. _using_asyncio: |  | ||||||
|  |  | ||||||
| ============= |  | ||||||
| Using asyncio |  | ||||||
| ============= |  | ||||||
|  |  | ||||||
| Block on IQ sending |  | ||||||
| ~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| :meth:`.Iq.send` now returns a :class:`~.Future` so you can easily block with: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     result = yield from iq.send() |  | ||||||
|  |  | ||||||
| .. warning:: |  | ||||||
|  |  | ||||||
|     If the reply is an IQ with an ``error`` type, this will raise an |  | ||||||
|     :class:`.IqError`, and if it timeouts, it will raise an |  | ||||||
|     :class:`.IqTimeout`. Don't forget to catch it. |  | ||||||
|  |  | ||||||
| You can still use callbacks instead. |  | ||||||
|  |  | ||||||
| XEP plugin integration |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| The same changes from the SleekXMPP API apply, so you can do: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     iq_info = yield from self.xmpp['xep_0030'].get_info(jid) |  | ||||||
|  |  | ||||||
| But the following will only return a Future: |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     iq_info = self.xmpp['xep_0030'].get_info(jid) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Callbacks, Event Handlers, and Stream Handlers |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| IQ callbacks and :term:`Event Handlers <event handler>` can be coroutine |  | ||||||
| functions; in this case, they will be scheduled in the event loop using |  | ||||||
| :meth:`.asyncio.async` and not ran immediately. |  | ||||||
|  |  | ||||||
| A :class:`.CoroutineCallback` class has been added as well for |  | ||||||
| :term:`Stream Handlers <stream handler>`, which will use |  | ||||||
| :meth:`.asyncio.async` to schedule the callback. |  | ||||||
|  |  | ||||||
| Running the event loop |  | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| :meth:`.XMLStream.process` is only a thin wrapper on top of |  | ||||||
| ``loop.run_forever()`` (if ``timeout`` is provided then it will |  | ||||||
| only run for this amount of time, and if ``forever`` is False it will |  | ||||||
| run until disconnection). |  | ||||||
|  |  | ||||||
| Therefore you can handle the event loop in any way you like |  | ||||||
| instead of using ``process()``. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Examples |  | ||||||
| ~~~~~~~~ |  | ||||||
|  |  | ||||||
| Blocking until the session is established |  | ||||||
| ----------------------------------------- |  | ||||||
|  |  | ||||||
| This code blocks until the XMPP session is fully established, which |  | ||||||
| can be useful to make sure external events aren’t triggering XMPP |  | ||||||
| callbacks while everything is not ready. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     import asyncio, slixmpp |  | ||||||
|  |  | ||||||
|     client = slixmpp.ClientXMPP('jid@example', 'password') |  | ||||||
|     client.connected_event = asyncio.Event() |  | ||||||
|     callback = lambda _: client.connected_event.set() |  | ||||||
|     client.add_event_handler('session_start', callback) |  | ||||||
|     client.connect() |  | ||||||
|     loop.run_until_complete(event.wait()) |  | ||||||
|     # do some other stuff before running the event loop, e.g. |  | ||||||
|     # loop.run_until_complete(httpserver.init()) |  | ||||||
|     client.process() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Use with other asyncio-based libraries |  | ||||||
| -------------------------------------- |  | ||||||
|  |  | ||||||
| This code interfaces with aiohttp to retrieve two pages asynchronously |  | ||||||
| when the session is established, and then send the HTML content inside |  | ||||||
| a simple <message>. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     import asyncio, aiohttp, slixmpp |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def get_pythonorg(event): |  | ||||||
|         req = yield from aiohttp.request('get', 'http://www.python.org') |  | ||||||
|         text = yield from req.text |  | ||||||
|         client.send_message(mto='jid2@example', mbody=text) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def get_asyncioorg(event): |  | ||||||
|         req = yield from aiohttp.request('get', 'http://www.asyncio.org') |  | ||||||
|         text = yield from req.text |  | ||||||
|         client.send_message(mto='jid3@example', mbody=text) |  | ||||||
|  |  | ||||||
|     client = slixmpp.ClientXMPP('jid@example', 'password') |  | ||||||
|     client.add_event_handler('session_start', get_pythonorg) |  | ||||||
|     client.add_event_handler('session_start', get_asyncioorg) |  | ||||||
|     client.connect() |  | ||||||
|     client.process() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Blocking Iq |  | ||||||
| ----------- |  | ||||||
|  |  | ||||||
| This client checks (via XEP-0092) the software used by every entity it |  | ||||||
| receives a message from. After this, it sends a message to a specific |  | ||||||
| JID indicating its findings. |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|     import asyncio, slixmpp |  | ||||||
|  |  | ||||||
|     class ExampleClient(slixmpp.ClientXMPP): |  | ||||||
|         def __init__(self, *args, **kwargs): |  | ||||||
|             slixmpp.ClientXMPP.__init__(self, *args, **kwargs) |  | ||||||
|             self.register_plugin('xep_0092') |  | ||||||
|             self.add_event_handler('message', self.on_message) |  | ||||||
|  |  | ||||||
|         @asyncio.coroutine |  | ||||||
|         def on_message(self, event): |  | ||||||
|             # You should probably handle IqError and IqTimeout exceptions here |  | ||||||
|             # but this is an example. |  | ||||||
|             version = yield from self['xep_0092'].get_version(message['from']) |  | ||||||
|             text = "%s sent me a message, he runs %s" % (message['from'], |  | ||||||
|                                                          version['software_version']['name']) |  | ||||||
|             self.send_message(mto='master@example.tld', mbody=text) |  | ||||||
|  |  | ||||||
|     client = ExampleClient('jid@example', 'password') |  | ||||||
|     client.connect() |  | ||||||
|     client.process() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1,50 +0,0 @@ | |||||||
| Supported XEPS |  | ||||||
| ============== |  | ||||||
|  |  | ||||||
| ======= ============================= ================ |  | ||||||
| XEP     Description                   Notes |  | ||||||
| ======= ============================= ================ |  | ||||||
| `0004`_ Data forms |  | ||||||
| `0009`_ Jabber RPC |  | ||||||
| `0012`_ Last Activity |  | ||||||
| `0030`_ Service Discovery |  | ||||||
| `0033`_ Extended Stanza Addressing |  | ||||||
| `0045`_ Multi-User Chat (MUC)         Client-side only |  | ||||||
| `0050`_ Ad-hoc Commands |  | ||||||
| `0059`_ Result Set Management |  | ||||||
| `0060`_ Publish/Subscribe (PubSub)    Client-side only |  | ||||||
| `0066`_ Out-of-band Data |  | ||||||
| `0078`_ Non-SASL Authentication |  | ||||||
| `0082`_ XMPP Date and Time Profiles |  | ||||||
| `0085`_ Chat-State Notifications |  | ||||||
| `0086`_ Error Condition Mappings |  | ||||||
| `0092`_ Software Version |  | ||||||
| `0128`_ Service Discovery Extensions |  | ||||||
| `0202`_ Entity Time |  | ||||||
| `0203`_ Delayed Delivery |  | ||||||
| `0224`_ Attention |  | ||||||
| `0249`_ Direct MUC Invitations |  | ||||||
| ======= ============================= ================ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| .. _0004: http://xmpp.org/extensions/xep-0004.html |  | ||||||
| .. _0009: http://xmpp.org/extensions/xep-0009.html |  | ||||||
| .. _0012: http://xmpp.org/extensions/xep-0012.html |  | ||||||
| .. _0030: http://xmpp.org/extensions/xep-0030.html |  | ||||||
| .. _0033: http://xmpp.org/extensions/xep-0033.html |  | ||||||
| .. _0045: http://xmpp.org/extensions/xep-0045.html |  | ||||||
| .. _0050: http://xmpp.org/extensions/xep-0050.html |  | ||||||
| .. _0059: http://xmpp.org/extensions/xep-0059.html |  | ||||||
| .. _0060: http://xmpp.org/extensions/xep-0060.html |  | ||||||
| .. _0066: http://xmpp.org/extensions/xep-0066.html |  | ||||||
| .. _0078: http://xmpp.org/extensions/xep-0078.html |  | ||||||
| .. _0082: http://xmpp.org/extensions/xep-0082.html |  | ||||||
| .. _0085: http://xmpp.org/extensions/xep-0085.html |  | ||||||
| .. _0086: http://xmpp.org/extensions/xep-0086.html |  | ||||||
| .. _0092: http://xmpp.org/extensions/xep-0092.html |  | ||||||
| .. _0128: http://xmpp.org/extensions/xep-0128.html |  | ||||||
| .. _0199: http://xmpp.org/extensions/xep-0199.html |  | ||||||
| .. _0202: http://xmpp.org/extensions/xep-0202.html |  | ||||||
| .. _0203: http://xmpp.org/extensions/xep-0203.html |  | ||||||
| .. _0224: http://xmpp.org/extensions/xep-0224.html |  | ||||||
| .. _0249: http://xmpp.org/extensions/xep-0249.html |  | ||||||
| @@ -1,249 +0,0 @@ | |||||||
| Following *XMPP: The Definitive Guide* |  | ||||||
| ====================================== |  | ||||||
|  |  | ||||||
| Slixmpp 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 |  | ||||||
| Slixmpp release, is available at http://github.com/legastero/xmpp-tdg. |  | ||||||
|  |  | ||||||
| However, since publication, Slixmpp 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 Slixmpp. |  | ||||||
| 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.send_message(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 Slixmpp 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 = {slixmpp.stanza.presence.Presence: "status", |  | ||||||
|                     slixmpp.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.send_message(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; Slixmpp 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 ``make_presence`` 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.send_message``. |  | ||||||
|  |  | ||||||
| Updated Code |  | ||||||
| ~~~~~~~~~~~~ |  | ||||||
|  |  | ||||||
| .. code-block:: python |  | ||||||
|  |  | ||||||
|   def handleXMPPPresenceProbe(self, event) : |  | ||||||
|     self.xmpp.send_presence(pto = event["from"]) |  | ||||||
|  |  | ||||||
|   def handleXMPPPresenceSubscription(self, subscription) : |  | ||||||
|     if subscription["type"] == "subscribe" : |  | ||||||
|       userJID = subscription["from"].jid |  | ||||||
|       self.xmpp.send_presence_subscription(pto=userJID, ptype="subscribed") |  | ||||||
|       self.xmpp.send_presence(pto = userJID) |  | ||||||
|       self.xmpp.send_presence_subscription(pto=userJID, ptype="subscribe") |  | ||||||
|  |  | ||||||
|   def handleMessageAddedToBackend(self, message) : |  | ||||||
|     body = message.user + ": " + message.text |  | ||||||
|     for subscriberJID in self.backend.getSubscriberJIDs(message.user) : |  | ||||||
|       self.xmpp.send_message(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 Slixmpp 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 |  | ||||||
| Slixmpp. 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.send_presence_subscription( |  | ||||||
|           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>`_ |  | ||||||
							
								
								
									
										48
									
								
								example.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								example.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | # coding=utf8 | ||||||
|  |  | ||||||
|  | import sleekxmpp | ||||||
|  | import logging | ||||||
|  | from optparse import OptionParser | ||||||
|  | import time | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | if sys.version_info < (3,0): | ||||||
|  | 	reload(sys) | ||||||
|  | 	sys.setdefaultencoding('utf8') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Example(sleekxmpp.ClientXMPP): | ||||||
|  | 	 | ||||||
|  | 	def __init__(self, jid, password): | ||||||
|  | 		sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  | 		self.add_event_handler("session_start", self.start) | ||||||
|  | 		self.add_event_handler("message", self.message) | ||||||
|  | 	 | ||||||
|  | 	def start(self, event): | ||||||
|  | 		self.getRoster() | ||||||
|  | 		self.sendPresence() | ||||||
|  |  | ||||||
|  | 	def message(self, msg): | ||||||
|  | 		msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  | 	#parse command line arguements | ||||||
|  | 	optp = OptionParser() | ||||||
|  | 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||||
|  | 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||||
|  | 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||||
|  | 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||||
|  | 	opts,args = optp.parse_args() | ||||||
|  | 	 | ||||||
|  | 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||||
|  | 	xmpp = Example('user@gmail.com/sleekxmpp', 'password') | ||||||
|  | 	xmpp.registerPlugin('xep_0030')	 | ||||||
|  | 	xmpp.registerPlugin('xep_0004') | ||||||
|  | 	xmpp.registerPlugin('xep_0060') | ||||||
|  | 	xmpp.registerPlugin('xep_0199') | ||||||
|  | 	if xmpp.connect(('talk.google.com', 5222)): | ||||||
|  | 		xmpp.process(threaded=False) | ||||||
|  | 		print("done") | ||||||
|  | 	else: | ||||||
|  | 		print("Unable to connect.") | ||||||
| @@ -1,182 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Implementation of xeps for Internet of Things |  | ||||||
|     http://wiki.xmpp.org/web/Tech_pages/IoT_systems |  | ||||||
|     Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
|  |  | ||||||
| from os.path import basename, join as pjoin |  | ||||||
| from argparse import ArgumentParser |  | ||||||
| from urllib import urlopen |  | ||||||
| from getpass import getpass |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.plugins.xep_0323.device import Device |  | ||||||
|  |  | ||||||
| #from slixmpp.exceptions import IqError, IqTimeout |  | ||||||
|  |  | ||||||
| class IoT_TestDevice(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple IoT device that can act as server or client |  | ||||||
|     """ |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|         self.add_event_handler("session_start", self.session_start) |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|         self.device=None |  | ||||||
|         self.releaseMe=False |  | ||||||
|         self.beServer=True |  | ||||||
|         self.clientJID=None |  | ||||||
|  |  | ||||||
|     def datacallback(self,from_jid,result,nodeId=None,timestamp=None,fields=None,error_msg=None): |  | ||||||
|         """ |  | ||||||
|         This method will be called when you ask another IoT device for data with the xep_0323 |  | ||||||
|         se script below for the registration of the callback |  | ||||||
|         """ |  | ||||||
|         logging.debug("we got data %s from %s",str(result),from_jid) |  | ||||||
|  |  | ||||||
|     def beClientOrServer(self,server=True,clientJID=None ): |  | ||||||
|         if server: |  | ||||||
|             self.beServer=True |  | ||||||
|             self.clientJID=None |  | ||||||
|         else: |  | ||||||
|             self.beServer=False |  | ||||||
|             self.clientJID=clientJID |  | ||||||
|  |  | ||||||
|     def testForRelease(self): |  | ||||||
|         # todo thread safe |  | ||||||
|         return self.releaseMe |  | ||||||
|  |  | ||||||
|     def doReleaseMe(self): |  | ||||||
|         # todo thread safe |  | ||||||
|         self.releaseMe=True |  | ||||||
|  |  | ||||||
|     def addDevice(self, device): |  | ||||||
|         self.device=device |  | ||||||
|  |  | ||||||
|     def session_start(self, event): |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|         # tell your preffered friend that you are alive |  | ||||||
|         self.send_message(mto='jocke@jabber.sust.se', mbody=self.boundjid.bare +' is now online use xep_323 stanza to talk to me') |  | ||||||
|  |  | ||||||
|         if not(self.beServer): |  | ||||||
|             session=self['xep_0323'].request_data(self.boundjid.full,self.clientJID,self.datacallback) |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         if msg['type'] in ('chat', 'normal'): |  | ||||||
|             logging.debug("got normal chat message" + str(msg)) |  | ||||||
|             ip=urlopen('http://icanhazip.com').read() |  | ||||||
|             msg.reply("Hi I am " + self.boundjid.full + " and I am on IP " + ip).send() |  | ||||||
|         else: |  | ||||||
|             logging.debug("got unknown message type %s", str(msg['type'])) |  | ||||||
|  |  | ||||||
| class TheDevice(Device): |  | ||||||
|     """ |  | ||||||
|     This is the actual device object that you will use to get information from your real hardware |  | ||||||
|     You will be called in the refresh method when someone is requesting information from you |  | ||||||
|     """ |  | ||||||
|     def __init__(self,nodeId): |  | ||||||
|         Device.__init__(self,nodeId) |  | ||||||
|         self.counter=0 |  | ||||||
|  |  | ||||||
|     def refresh(self,fields): |  | ||||||
|         """ |  | ||||||
|         the implementation of the refresh method |  | ||||||
|         """ |  | ||||||
|         self._set_momentary_timestamp(self._get_timestamp()) |  | ||||||
|         self.counter+=self.counter |  | ||||||
|         self._add_field_momentary_data(self, "Temperature", self.counter) |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|  |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     # |  | ||||||
|     # This script can act both as |  | ||||||
|     #   "server" an IoT device that can provide sensorinformation |  | ||||||
|     #   python IoT_TestDevice.py -j "serverjid@yourdomain.com" -p "password" -n "TestIoT" --debug |  | ||||||
|     # |  | ||||||
|     #   "client" an IoT device or other party that would like to get data from another device |  | ||||||
|  |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     parser.add_argument("-t", "--pingto", help="set jid to ping", |  | ||||||
|                         action="store", type="string", dest="pingjid", |  | ||||||
|                         default=None) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     # IoT test |  | ||||||
|     parser.add_argument("-c", "--sensorjid", dest="sensorjid", |  | ||||||
|                         help="Another device to call for data on", default=None) |  | ||||||
|     parser.add_argument("-n", "--nodeid", dest="nodeid", |  | ||||||
|                         help="I am a device get ready to be called", default=None) |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|      # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     xmpp = IoT_TestDevice(args.jid,args.password) |  | ||||||
|     xmpp.register_plugin('xep_0030') |  | ||||||
|     #xmpp['xep_0030'].add_feature(feature='urn:xmpp:iot:sensordata', |  | ||||||
|     #                             node=None, |  | ||||||
|     #    jid=None) |  | ||||||
|     xmpp.register_plugin('xep_0323') |  | ||||||
|     xmpp.register_plugin('xep_0325') |  | ||||||
|  |  | ||||||
|     if args.nodeid: |  | ||||||
|  |  | ||||||
|         # xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn', |  | ||||||
|         # node=args.nodeid, |  | ||||||
|         # jid=xmpp.boundjid.full) |  | ||||||
|  |  | ||||||
|         myDevice = TheDevice(args.nodeid); |  | ||||||
|         # myDevice._add_field(name="Relay", typename="numeric", unit="Bool"); |  | ||||||
|         myDevice._add_field(name="Temperature", typename="numeric", unit="C") |  | ||||||
|         myDevice._set_momentary_timestamp("2013-03-07T16:24:30") |  | ||||||
|         myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) |  | ||||||
|  |  | ||||||
|         xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10); |  | ||||||
|         xmpp.beClientOrServer(server=True) |  | ||||||
|         while not(xmpp.testForRelease()): |  | ||||||
|             xmpp.connect() |  | ||||||
|             xmpp.process(block=True) |  | ||||||
|             logging.debug("lost connection") |  | ||||||
|     if args.sensorjid: |  | ||||||
|         logging.debug("will try to call another device for data") |  | ||||||
|         xmpp.beClientOrServer(server=False,clientJID=args.sensorjid) |  | ||||||
|         xmpp.connect() |  | ||||||
|         xmpp.process(block=True) |  | ||||||
|         logging.debug("ready ending") |  | ||||||
|  |  | ||||||
|     else: |  | ||||||
|        print("noopp didn't happen") |  | ||||||
|  |  | ||||||
| @@ -1,173 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CommandBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that provides a basic |  | ||||||
|     adhoc command. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         # We add the command after session_start has fired |  | ||||||
|         # to ensure that the correct full JID is used. |  | ||||||
|  |  | ||||||
|         # If using a component, may also pass jid keyword parameter. |  | ||||||
|  |  | ||||||
|         self['xep_0050'].add_command(node='greeting', |  | ||||||
|                                      name='Greeting', |  | ||||||
|                                      handler=self._handle_command) |  | ||||||
|  |  | ||||||
|     def _handle_command(self, iq, session): |  | ||||||
|         """ |  | ||||||
|         Respond to the initial request for a command. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The iq stanza containing the command request. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|         form = self['xep_0004'].make_form('form', 'Greeting') |  | ||||||
|         form['instructions'] = 'Send a custom greeting to a JID' |  | ||||||
|         form.addField(var='greeting', |  | ||||||
|                       ftype='text-single', |  | ||||||
|                       label='Your greeting') |  | ||||||
|  |  | ||||||
|         session['payload'] = form |  | ||||||
|         session['next'] = self._handle_command_complete |  | ||||||
|         session['has_next'] = False |  | ||||||
|  |  | ||||||
|         # Other useful session values: |  | ||||||
|         # session['to']                    -- The JID that received the |  | ||||||
|         #                                     command request. |  | ||||||
|         # session['from']                  -- The JID that sent the |  | ||||||
|         #                                     command request. |  | ||||||
|         # session['has_next'] = True       -- There are more steps to complete |  | ||||||
|         # session['allow_complete'] = True -- Allow user to finish immediately |  | ||||||
|         #                                     and possibly skip steps |  | ||||||
|         # session['cancel'] = handler      -- Assign a handler for if the user |  | ||||||
|         #                                     cancels the command. |  | ||||||
|         # session['notes'] = [             -- Add informative notes about the |  | ||||||
|         #   ('info', 'Info message'),         command's results. |  | ||||||
|         #   ('warning', 'Warning message'), |  | ||||||
|         #   ('error', 'Error message')] |  | ||||||
|  |  | ||||||
|         return session |  | ||||||
|  |  | ||||||
|     def _handle_command_complete(self, payload, session): |  | ||||||
|         """ |  | ||||||
|         Process a command result from the user. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             payload -- Either a single item, such as a form, or a list |  | ||||||
|                        of items or forms if more than one form was |  | ||||||
|                        provided to the user. The payload may be any |  | ||||||
|                        stanza, such as jabber:x:oob for out of band |  | ||||||
|                        data, or jabber:x:data for typical data forms. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         # In this case (as is typical), the payload is a form |  | ||||||
|         form = payload |  | ||||||
|  |  | ||||||
|         greeting = form['values']['greeting'] |  | ||||||
|  |  | ||||||
|         self.send_message(mto=session['from'], |  | ||||||
|                           mbody="%s, World!" % greeting, |  | ||||||
|                           mtype='chat') |  | ||||||
|  |  | ||||||
|         # Having no return statement is the same as unsetting the 'payload' |  | ||||||
|         # and 'next' session values and returning the session. |  | ||||||
|  |  | ||||||
|         # Unless it is the final step, always return the session dictionary. |  | ||||||
|  |  | ||||||
|         session['payload'] = None |  | ||||||
|         session['next'] = None |  | ||||||
|  |  | ||||||
|         return session |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the CommandBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = CommandBot(args.jid, args.password) |  | ||||||
|     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}) |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,179 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CommandUserBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that uses the adhoc command |  | ||||||
|     provided by the adhoc_provider.py example. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, other, greeting): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.command_provider = other |  | ||||||
|         self.greeting = greeting |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         # We first create a session dictionary containing: |  | ||||||
|         #   'next'  -- the handler to execute on a successful response |  | ||||||
|         #   'error' -- the handler to execute if an error occurs |  | ||||||
|  |  | ||||||
|         # The session may also contain custom data. |  | ||||||
|  |  | ||||||
|         session = {'greeting': self.greeting, |  | ||||||
|                    'next': self._command_start, |  | ||||||
|                    'error': self._command_error} |  | ||||||
|  |  | ||||||
|         self['xep_0050'].start_command(jid=self.command_provider, |  | ||||||
|                                        node='greeting', |  | ||||||
|                                        session=session) |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. |  | ||||||
|         """ |  | ||||||
|         logging.info(msg['body']) |  | ||||||
|  |  | ||||||
|     def _command_start(self, iq, session): |  | ||||||
|         """ |  | ||||||
|         Process the initial command result. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The iq stanza containing the command result. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         # The greeting command provides a form with a single field: |  | ||||||
|         # <x xmlns="jabber:x:data" type="form"> |  | ||||||
|         #   <field var="greeting" |  | ||||||
|         #          type="text-single" |  | ||||||
|         #          label="Your greeting" /> |  | ||||||
|         # </x> |  | ||||||
|  |  | ||||||
|         form = self['xep_0004'].make_form(ftype='submit') |  | ||||||
|         form.addField(var='greeting', |  | ||||||
|                       value=session['greeting']) |  | ||||||
|  |  | ||||||
|         session['payload'] = form |  | ||||||
|  |  | ||||||
|         # We don't need to process the next result. |  | ||||||
|         session['next'] = None |  | ||||||
|  |  | ||||||
|         # Other options include using: |  | ||||||
|         # continue_command() -- Continue to the next step in the workflow |  | ||||||
|         # cancel_command()   -- Stop command execution. |  | ||||||
|  |  | ||||||
|         self['xep_0050'].complete_command(session) |  | ||||||
|  |  | ||||||
|     def _command_error(self, iq, session): |  | ||||||
|         """ |  | ||||||
|         Process an error that occurs during command execution. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             iq      -- The iq stanza containing the error. |  | ||||||
|             session -- A dictionary of data relevant to the command |  | ||||||
|                        session. Additional, custom data may be saved |  | ||||||
|                        here to persist across handler callbacks. |  | ||||||
|         """ |  | ||||||
|         logging.error("COMMAND: %s %s" % (iq['error']['condition'], |  | ||||||
|                                           iq['error']['text'])) |  | ||||||
|  |  | ||||||
|         # Terminate the command's execution and clear its session. |  | ||||||
|         # The session will automatically be cleared if no error |  | ||||||
|         # handler is provided. |  | ||||||
|         self['xep_0050'].terminate_command(session) |  | ||||||
|         self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("-o", "--other", dest="other", |  | ||||||
|                         help="JID providing commands") |  | ||||||
|     parser.add_argument("-g", "--greeting", dest="greeting", |  | ||||||
|                         help="Greeting") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.other is None: |  | ||||||
|         args.other = input("JID Providing Commands: ") |  | ||||||
|     if args.greeting is None: |  | ||||||
|         args.greeting = input("Greeting: ") |  | ||||||
|  |  | ||||||
|     # Setup the CommandBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = CommandUserBot(args.jid, args.password, args.other, args.greeting) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,147 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdminCommands(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that uses admin commands to |  | ||||||
|     add a new user to a server. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, command): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.command = command |  | ||||||
|  |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         def command_success(iq, session): |  | ||||||
|             print('Command completed') |  | ||||||
|             if iq['command']['form']: |  | ||||||
|                 for var, field in iq['command']['form']['fields'].items(): |  | ||||||
|                     print('%s: %s' % (var, field['value'])) |  | ||||||
|             if iq['command']['notes']: |  | ||||||
|                 print('Command Notes:') |  | ||||||
|                 for note in iq['command']['notes']: |  | ||||||
|                     print('%s: %s' % note) |  | ||||||
|             self.disconnect() |  | ||||||
|  |  | ||||||
|         def command_error(iq, session): |  | ||||||
|             print('Error completing command') |  | ||||||
|             print('%s: %s' % (iq['error']['condition'], |  | ||||||
|                               iq['error']['text'])) |  | ||||||
|             self['xep_0050'].terminate_command(session) |  | ||||||
|             self.disconnect() |  | ||||||
|  |  | ||||||
|         def process_form(iq, session): |  | ||||||
|             form = iq['command']['form'] |  | ||||||
|             answers = {} |  | ||||||
|             for var, field in form['fields'].items(): |  | ||||||
|                 if var != 'FORM_TYPE': |  | ||||||
|                     if field['type'] == 'boolean': |  | ||||||
|                         answers[var] = input('%s (y/n): ' % field['label']) |  | ||||||
|                         if answers[var].lower() in ('1', 'true', 'y', 'yes'): |  | ||||||
|                             answers[var] = '1' |  | ||||||
|                         else: |  | ||||||
|                             answers[var] = '0' |  | ||||||
|                     else: |  | ||||||
|                         answers[var] = input('%s: ' % field['label']) |  | ||||||
|                 else: |  | ||||||
|                     answers['FORM_TYPE'] = field['value'] |  | ||||||
|             form['type'] = 'submit' |  | ||||||
|             form['values'] = answers |  | ||||||
|  |  | ||||||
|             session['next'] = command_success |  | ||||||
|             session['payload'] = form |  | ||||||
|  |  | ||||||
|             self['xep_0050'].complete_command(session) |  | ||||||
|  |  | ||||||
|         session = {'next': process_form, |  | ||||||
|                    'error': command_error} |  | ||||||
|  |  | ||||||
|         command = self.command.replace('-', '_') |  | ||||||
|         handler = getattr(self['xep_0133'], command, None) |  | ||||||
|  |  | ||||||
|         if handler: |  | ||||||
|             handler(session={ |  | ||||||
|                 'next': process_form, |  | ||||||
|                 'error': command_error |  | ||||||
|             }) |  | ||||||
|         else: |  | ||||||
|             print('Invalid command name: %s' % self.command) |  | ||||||
|             self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("-c", "--command", dest="command", |  | ||||||
|                         help="admin command to use") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.command is None: |  | ||||||
|         args.command = input("Admin command: ") |  | ||||||
|  |  | ||||||
|     # Setup the CommandBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = AdminCommands(args.jid, args.password, args.command) |  | ||||||
|     xmpp.register_plugin('xep_0133') # Service Administration |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,100 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2015 Emmanuel Gil Peyrot |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.exceptions import XMPPError |  | ||||||
| from slixmpp import asyncio |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AnswerConfirm(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A basic client demonstrating how to confirm or deny an HTTP request. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, trusted): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.add_event_handler("http_confirm", self.confirm) |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     def start(self, *args): |  | ||||||
|         self.make_presence().send() |  | ||||||
|  |  | ||||||
|     def prompt(self, stanza): |  | ||||||
|         confirm = stanza['confirm'] |  | ||||||
|         print('Received confirm request %s from %s to access %s using ' |  | ||||||
|                  'method %s' % ( |  | ||||||
|                      confirm['id'], stanza['from'], confirm['url'], |  | ||||||
|                      confirm['method']) |  | ||||||
|                 ) |  | ||||||
|         result = input("Do you accept (y/N)? ") |  | ||||||
|         return 'y' == result.lower() |  | ||||||
|  |  | ||||||
|     def confirm(self, stanza): |  | ||||||
|         if self.prompt(stanza): |  | ||||||
|             reply = stanza.reply() |  | ||||||
|         else: |  | ||||||
|             reply = stanza.reply() |  | ||||||
|             reply.enable('error') |  | ||||||
|             reply['error']['type'] = 'auth' |  | ||||||
|             reply['error']['code'] = '401' |  | ||||||
|             reply['error']['condition'] = 'not-authorized' |  | ||||||
|         reply.append(stanza['confirm']) |  | ||||||
|         reply.send() |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.ERROR, |  | ||||||
|                         default=logging.INFO) |  | ||||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, |  | ||||||
|                         default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     # Other options. |  | ||||||
|     parser.add_argument("-t", "--trusted", nargs='*', |  | ||||||
|                         help="List of trusted JIDs") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     xmpp = AnswerConfirm(args.jid, args.password, args.trusted) |  | ||||||
|     xmpp.register_plugin('xep_0070') |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,125 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2015 Emmanuel Gil Peyrot |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.exceptions import XMPPError, IqError |  | ||||||
| from slixmpp import asyncio |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AskConfirm(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A basic client asking an entity if they confirm the access to an HTTP URL. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, recipient, id, url, method): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.recipient = recipient |  | ||||||
|         self.id = id |  | ||||||
|         self.url = url |  | ||||||
|         self.method = method |  | ||||||
|  |  | ||||||
|         # Will be used to set the proper exit code. |  | ||||||
|         self.confirmed = asyncio.Future() |  | ||||||
|  |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|         self.add_event_handler("message", self.start) |  | ||||||
|         self.add_event_handler("http_confirm_message", self.confirm) |  | ||||||
|  |  | ||||||
|     def confirm(self, message): |  | ||||||
|         print(message) |  | ||||||
|         if message['confirm']['id'] == self.id: |  | ||||||
|             if message['type'] == 'error': |  | ||||||
|                 self.confirmed.set_result(False) |  | ||||||
|             else: |  | ||||||
|                 self.confirmed.set_result(True) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def start(self, event): |  | ||||||
|         log.info('Sending confirm request %s to %s who wants to access %s using ' |  | ||||||
|                  'method %s...' % (self.id, self.recipient, self.url, self.method)) |  | ||||||
|         try: |  | ||||||
|             confirmed = yield from self['xep_0070'].ask_confirm(self.recipient, |  | ||||||
|                                                                 id=self.id, |  | ||||||
|                                                                 url=self.url, |  | ||||||
|                                                                 method=self.method, |  | ||||||
|                                                                 message='Plz say yes or no for {method} {url} ({id}).') |  | ||||||
|             if isinstance(confirmed, slixmpp.Message): |  | ||||||
|                 confirmed = yield from self.confirmed |  | ||||||
|             else: |  | ||||||
|                 confirmed = True |  | ||||||
|         except IqError: |  | ||||||
|             confirmed = False |  | ||||||
|         if confirmed: |  | ||||||
|             print('Confirmed') |  | ||||||
|         else: |  | ||||||
|             print('Denied') |  | ||||||
|         self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.ERROR, |  | ||||||
|                         default=logging.INFO) |  | ||||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, |  | ||||||
|                         default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     # Other options. |  | ||||||
|     parser.add_argument("-r", "--recipient", required=True, |  | ||||||
|                         help="Recipient JID") |  | ||||||
|     parser.add_argument("-i", "--id", required=True, |  | ||||||
|                         help="id TODO") |  | ||||||
|     parser.add_argument("-u", "--url", required=True, |  | ||||||
|                         help="URL the user tried to access") |  | ||||||
|     parser.add_argument("-m", "--method", required=True, |  | ||||||
|                         help="HTTP method used") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id, |  | ||||||
|                       args.url, args.method) |  | ||||||
|     xmpp.register_plugin('xep_0070') |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process(forever=False) |  | ||||||
|     sys.exit(0 if xmpp.confirmed else 1) |  | ||||||
| @@ -1,137 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
| from slixmpp import ClientXMPP, Iq |  | ||||||
| from slixmpp.exceptions import IqError, IqTimeout, XMPPError |  | ||||||
| from slixmpp.xmlstream import register_stanza_plugin |  | ||||||
| from slixmpp.xmlstream.handler import Callback |  | ||||||
| from slixmpp.xmlstream.matcher import StanzaPath |  | ||||||
| from stanza import Action |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActionBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that receives a custom stanza |  | ||||||
|     from another client. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         self.register_handler( |  | ||||||
|           Callback('Some custom iq', |  | ||||||
|             StanzaPath('iq@type=set/action'), |  | ||||||
|             self._handle_action)) |  | ||||||
|  |  | ||||||
|         self.add_event_handler('custom_action', |  | ||||||
|                 self._handle_action_event) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, Action) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|     def _handle_action(self, iq): |  | ||||||
|         """ |  | ||||||
|         Raise an event for the stanza so that it can be processed in its |  | ||||||
|         own thread without blocking the main stanza processing loop. |  | ||||||
|         """ |  | ||||||
|         self.event('custom_action', iq) |  | ||||||
|  |  | ||||||
|     def _handle_action_event(self, iq): |  | ||||||
|         """ |  | ||||||
|         Respond to the custom action event. |  | ||||||
|         """ |  | ||||||
|         method = iq['action']['method'] |  | ||||||
|         param = iq['action']['param'] |  | ||||||
|  |  | ||||||
|         if method == 'is_prime' and param == '2': |  | ||||||
|             print("got message: %s" % iq) |  | ||||||
|             iq.reply() |  | ||||||
|             iq['action']['status'] = 'done' |  | ||||||
|             iq.send() |  | ||||||
|         elif method == 'bye': |  | ||||||
|             print("got message: %s" % iq) |  | ||||||
|             self.disconnect() |  | ||||||
|         else: |  | ||||||
|             print("got message: %s" % iq) |  | ||||||
|             iq.reply() |  | ||||||
|             iq['action']['status'] = 'error' |  | ||||||
|             iq.send() |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the CommandBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = ActionBot(args.jid, args.password) |  | ||||||
|     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}) |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,142 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp import Iq |  | ||||||
| from slixmpp.exceptions import XMPPError |  | ||||||
| from slixmpp.xmlstream import register_stanza_plugin |  | ||||||
|  |  | ||||||
| from stanza import Action |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ActionUserBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that sends a custom action stanza |  | ||||||
|     to another client. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, other): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.action_provider = other |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, Action) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         self.send_custom_iq() |  | ||||||
|  |  | ||||||
|     def send_custom_iq(self): |  | ||||||
|         """Create and send two custom actions. |  | ||||||
|  |  | ||||||
|         If the first action was successful, then send |  | ||||||
|         a shutdown command and then disconnect. |  | ||||||
|         """ |  | ||||||
|         iq = self.Iq() |  | ||||||
|         iq['to'] = self.action_provider |  | ||||||
|         iq['type'] = 'set' |  | ||||||
|         iq['action']['method'] = 'is_prime' |  | ||||||
|         iq['action']['param'] = '2' |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             resp = iq.send() |  | ||||||
|             if resp['action']['status'] == 'done': |  | ||||||
|                 #sending bye |  | ||||||
|                 iq2 = self.Iq() |  | ||||||
|                 iq2['to'] = self.action_provider |  | ||||||
|                 iq2['type'] = 'set' |  | ||||||
|                 iq2['action']['method'] = 'bye' |  | ||||||
|                 iq2.send(block=False) |  | ||||||
|  |  | ||||||
|                 self.disconnect() |  | ||||||
|         except XMPPError: |  | ||||||
|             print('There was an error sending the custom action.') |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. |  | ||||||
|         """ |  | ||||||
|         logging.info(msg['body']) |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("-o", "--other", dest="other", |  | ||||||
|                         help="JID providing custom stanza") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.other is None: |  | ||||||
|         args.other = input("JID Providing custom stanza: ") |  | ||||||
|  |  | ||||||
|     # Setup the CommandBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = ActionUserBot(args.jid, args.password, args.other) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,56 +0,0 @@ | |||||||
| from slixmpp.xmlstream import ElementBase |  | ||||||
|  |  | ||||||
| class Action(ElementBase): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A stanza class for XML content of the form: |  | ||||||
|  |  | ||||||
|     <action xmlns="slixmpp:custom:actions"> |  | ||||||
|       <method>X</method> |  | ||||||
|       <param>X</param> |  | ||||||
|       <status>X</status> |  | ||||||
|     </action> |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     #: The `name` field refers to the basic XML tag name of the |  | ||||||
|     #: stanza. Here, the tag name will be 'action'. |  | ||||||
|     name = 'action' |  | ||||||
|  |  | ||||||
|     #: The namespace of the main XML tag. |  | ||||||
|     namespace = 'slixmpp:custom:actions' |  | ||||||
|  |  | ||||||
|     #: The `plugin_attrib` value is the name that can be used |  | ||||||
|     #: with a parent stanza to access this stanza. For example |  | ||||||
|     #: from an Iq stanza object, accessing: |  | ||||||
|     #: |  | ||||||
|     #:     iq['action'] |  | ||||||
|     #: |  | ||||||
|     #: would reference an Action object, and will even create |  | ||||||
|     #: an Action object and append it to the Iq stanza if |  | ||||||
|     #: one doesn't already exist. |  | ||||||
|     plugin_attrib = 'action' |  | ||||||
|  |  | ||||||
|     #: Stanza objects expose dictionary-like interfaces for |  | ||||||
|     #: accessing and manipulating substanzas and other values. |  | ||||||
|     #: The set of interfaces defined here are the names of |  | ||||||
|     #: these dictionary-like interfaces provided by this stanza |  | ||||||
|     #: type. For example, an Action stanza object can use: |  | ||||||
|     #: |  | ||||||
|     #:     action['method'] = 'foo' |  | ||||||
|     #:     print(action['param']) |  | ||||||
|     #:     del action['status'] |  | ||||||
|     #: |  | ||||||
|     #: to set, get, or remove its values. |  | ||||||
|     interfaces = {'method', 'param', 'status'} |  | ||||||
|  |  | ||||||
|     #: By default, values in the `interfaces` set are mapped to |  | ||||||
|     #: attribute values. This can be changed such that an interface |  | ||||||
|     #: maps to a subelement's text value by adding interfaces to |  | ||||||
|     #: the sub_interfaces set. For example, here all interfaces |  | ||||||
|     #: are marked as sub_interfaces, and so the XML produced will |  | ||||||
|     #: look like: |  | ||||||
|     #: |  | ||||||
|     #:     <action xmlns="slixmpp:custom:actions"> |  | ||||||
|     #:       <method>foo</method> |  | ||||||
|     #:     </action> |  | ||||||
|     sub_interfaces = interfaces |  | ||||||
| @@ -1,161 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.exceptions import IqError, IqTimeout |  | ||||||
| from slixmpp.xmlstream.asyncio import asyncio |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Disco(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A demonstration for using basic service discovery. |  | ||||||
|  |  | ||||||
|     Send a disco#info and disco#items request to a JID/node combination, |  | ||||||
|     and print out the results. |  | ||||||
|  |  | ||||||
|     May also request only particular info categories such as just features, |  | ||||||
|     or just items. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, target_jid, target_node='', get=''): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # Using service discovery requires the XEP-0030 plugin. |  | ||||||
|         self.register_plugin('xep_0030') |  | ||||||
|  |  | ||||||
|         self.get = get |  | ||||||
|         self.target_jid = target_jid |  | ||||||
|         self.target_node = target_node |  | ||||||
|  |  | ||||||
|         # Values to control which disco entities are reported |  | ||||||
|         self.info_types = ['', 'all', 'info', 'identities', 'features'] |  | ||||||
|         self.identity_types = ['', 'all', 'info', 'identities'] |  | ||||||
|         self.feature_types = ['', 'all', 'info', 'features'] |  | ||||||
|         self.items_types = ['', 'all', 'items'] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         In this case, we send disco#info and disco#items |  | ||||||
|         stanzas to the requested JID and print the results. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.get_roster() |  | ||||||
|         self.send_presence() |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             if self.get in self.info_types: |  | ||||||
|                 # function using the callback parameter. |  | ||||||
|                 info = yield from self['xep_0030'].get_info(jid=self.target_jid, |  | ||||||
|                                                             node=self.target_node) |  | ||||||
|             if self.get in self.items_types: |  | ||||||
|                 # The same applies from above. Listen for the |  | ||||||
|                 # disco_items event or pass a callback function |  | ||||||
|                 # if you need to process a non-blocking request. |  | ||||||
|                 items = yield from self['xep_0030'].get_items(jid=self.target_jid, |  | ||||||
|                                                               node=self.target_node) |  | ||||||
|             if self.get not in self.info_types and self.get not in self.items_types: |  | ||||||
|                 logging.error("Invalid disco request type.") |  | ||||||
|                 return |  | ||||||
|         except IqError as e: |  | ||||||
|             logging.error("Entity returned an error: %s" % e.iq['error']['condition']) |  | ||||||
|         except IqTimeout: |  | ||||||
|             logging.error("No response received.") |  | ||||||
|         else: |  | ||||||
|             header = 'XMPP Service Discovery: %s' % self.target_jid |  | ||||||
|             print(header) |  | ||||||
|             print('-' * len(header)) |  | ||||||
|             if self.target_node != '': |  | ||||||
|                 print('Node: %s' % self.target_node) |  | ||||||
|                 print('-' * len(header)) |  | ||||||
|  |  | ||||||
|             if self.get in self.identity_types: |  | ||||||
|                 print('Identities:') |  | ||||||
|                 for identity in info['disco_info']['identities']: |  | ||||||
|                     print('  - %s' % str(identity)) |  | ||||||
|  |  | ||||||
|             if self.get in self.feature_types: |  | ||||||
|                 print('Features:') |  | ||||||
|                 for feature in info['disco_info']['features']: |  | ||||||
|                     print('  - %s' % feature) |  | ||||||
|  |  | ||||||
|             if self.get in self.items_types: |  | ||||||
|                 print('Items:') |  | ||||||
|                 for item in items['disco_items']['items']: |  | ||||||
|                     print('  - %s' % str(item)) |  | ||||||
|         finally: |  | ||||||
|             self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser(description=Disco.__doc__) |  | ||||||
|  |  | ||||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.ERROR, |  | ||||||
|                         default=logging.ERROR) |  | ||||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, |  | ||||||
|                         default=logging.ERROR) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("query", choices=["all", "info", "items", "identities", "features"]) |  | ||||||
|     parser.add_argument("target_jid") |  | ||||||
|     parser.add_argument("node", nargs='?') |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the Disco browser. |  | ||||||
|     xmpp = Disco(args.jid, args.password, args.target_jid, args.node, args.query) |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process(forever=False) |  | ||||||
| @@ -1,165 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2012  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.exceptions import XMPPError |  | ||||||
| from slixmpp import asyncio |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FILE_TYPES = { |  | ||||||
|     'image/png': 'png', |  | ||||||
|     'image/gif': 'gif', |  | ||||||
|     'image/jpeg': 'jpg' |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AvatarDownloader(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A basic script for downloading the avatars for a user's contacts. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|         self.add_event_handler("changed_status", self.wait_for_presences) |  | ||||||
|  |  | ||||||
|         self.add_event_handler('vcard_avatar_update', self.on_vcard_avatar) |  | ||||||
|         self.add_event_handler('avatar_metadata_publish', self.on_avatar) |  | ||||||
|  |  | ||||||
|         self.received = set() |  | ||||||
|         self.presences_received = asyncio.Event() |  | ||||||
|         self.roster_received = asyncio.Event() |  | ||||||
|  |  | ||||||
|     def roster_received_cb(self, event): |  | ||||||
|         self.roster_received.set() |  | ||||||
|         self.presences_received.clear() |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster(callback=self.roster_received_cb) |  | ||||||
|  |  | ||||||
|         print('Waiting for presence updates...\n') |  | ||||||
|         yield from self.roster_received.wait() |  | ||||||
|         print('Roster received') |  | ||||||
|         yield from self.presences_received.wait() |  | ||||||
|         self.disconnect() |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def on_vcard_avatar(self, pres): |  | ||||||
|         print("Received vCard avatar update from %s" % pres['from'].bare) |  | ||||||
|         try: |  | ||||||
|             result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True, |  | ||||||
|                                                            timeout=5) |  | ||||||
|         except XMPPError: |  | ||||||
|             print("Error retrieving avatar for %s" % pres['from']) |  | ||||||
|             return |  | ||||||
|         avatar = result['vcard_temp']['PHOTO'] |  | ||||||
|  |  | ||||||
|         filetype = FILE_TYPES.get(avatar['TYPE'], 'png') |  | ||||||
|         filename = 'vcard_avatar_%s_%s.%s' % ( |  | ||||||
|                 pres['from'].bare, |  | ||||||
|                 pres['vcard_temp_update']['photo'], |  | ||||||
|                 filetype) |  | ||||||
|         with open(filename, 'wb+') as img: |  | ||||||
|             img.write(avatar['BINVAL']) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def on_avatar(self, msg): |  | ||||||
|         print("Received avatar update from %s" % msg['from']) |  | ||||||
|         metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] |  | ||||||
|         for info in metadata['items']: |  | ||||||
|             if not info['url']: |  | ||||||
|                 try: |  | ||||||
|                     result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'], |  | ||||||
|                                                                          timeout=5) |  | ||||||
|                 except XMPPError: |  | ||||||
|                     print("Error retrieving avatar for %s" % msg['from']) |  | ||||||
|                     return |  | ||||||
|  |  | ||||||
|                 avatar = result['pubsub']['items']['item']['avatar_data'] |  | ||||||
|  |  | ||||||
|                 filetype = FILE_TYPES.get(metadata['type'], 'png') |  | ||||||
|                 filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) |  | ||||||
|                 with open(filename, 'wb+') as img: |  | ||||||
|                     img.write(avatar['value']) |  | ||||||
|             else: |  | ||||||
|                 # We could retrieve the avatar via HTTP, etc here instead. |  | ||||||
|                 pass |  | ||||||
|  |  | ||||||
|     def wait_for_presences(self, pres): |  | ||||||
|         """ |  | ||||||
|         Wait to receive updates from all roster contacts. |  | ||||||
|         """ |  | ||||||
|         self.received.add(pres['from'].bare) |  | ||||||
|         print((len(self.received), len(self.client_roster.keys()))) |  | ||||||
|         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. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.ERROR, |  | ||||||
|                         default=logging.ERROR) |  | ||||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, |  | ||||||
|                         default=logging.ERROR) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     xmpp = AvatarDownloader(args.jid, args.password) |  | ||||||
|     xmpp.register_plugin('xep_0054') |  | ||||||
|     xmpp.register_plugin('xep_0153') |  | ||||||
|     xmpp.register_plugin('xep_0084') |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,113 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EchoBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that will echo messages it |  | ||||||
|     receives, along with a short thank you message. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The message event is triggered whenever a message |  | ||||||
|         # stanza is received. Be aware that that includes |  | ||||||
|         # MUC messages and error messages. |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. Be aware that this also |  | ||||||
|         includes MUC messages and error messages. It is usually |  | ||||||
|         a good idea to check the messages's type before processing |  | ||||||
|         or sending replies. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         if msg['type'] in ('chat', 'normal'): |  | ||||||
|             msg.reply("Thanks for sending\n%(body)s" % msg).send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser(description=EchoBot.__doc__) |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the EchoBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = EchoBot(args.jid, args.password) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060') # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,105 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.componentxmpp import ComponentXMPP |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EchoComponent(ComponentXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp 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. |  | ||||||
|     parser = ArgumentParser(description=EchoComponent.__doc__) |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("-s", "--server", dest="server", |  | ||||||
|                         help="server to connect to") |  | ||||||
|     parser.add_argument("-P", "--port", dest="port", |  | ||||||
|                         help="port to connect to") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Component JID: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.server is None: |  | ||||||
|         args.server = input("Server: ") |  | ||||||
|     if args.port is None: |  | ||||||
|         args.port = int(input("Port: ")) |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.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(args.jid, args.password, args.server, args.port) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060') # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,133 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
| import ssl |  | ||||||
| from slixmpp.xmlstream import cert |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GTalkBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A demonstration of using Slixmpp with accounts from a Google Apps |  | ||||||
|     account with a custom domain, because it requires custom certificate |  | ||||||
|     validation. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         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) |  | ||||||
|  |  | ||||||
|         # Using a Google Apps custom domain, the certificate |  | ||||||
|         # does not contain the custom domain, just the GTalk |  | ||||||
|         # server name. So we will need to process invalid |  | ||||||
|         # certifcates ourselves and check that it really |  | ||||||
|         # is from Google. |  | ||||||
|         self.add_event_handler("ssl_invalid_cert", self.invalid_cert) |  | ||||||
|  |  | ||||||
|     def invalid_cert(self, pem_cert): |  | ||||||
|         der_cert = ssl.PEM_cert_to_DER_cert(pem_cert) |  | ||||||
|         try: |  | ||||||
|             cert.verify('talk.google.com', der_cert) |  | ||||||
|             logging.debug("CERT: Found GTalk certificate") |  | ||||||
|         except cert.CertificateError as err: |  | ||||||
|             log.error(err.message) |  | ||||||
|             self.disconnect(send_close=False) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. Be aware that this also |  | ||||||
|         includes MUC messages and error messages. It is usually |  | ||||||
|         a good idea to check the messages's type before processing |  | ||||||
|         or sending replies. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         if msg['type'] in ('chat', 'normal'): |  | ||||||
|             msg.reply("Thanks for sending\n%(body)s" % msg).send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the GTalkBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = GTalkBot(args.jid, args.password) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060') # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,97 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Implementation of HTTP over XMPP transport |  | ||||||
|     http://xmpp.org/extensions/xep-0332.html |  | ||||||
|     Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com |  | ||||||
|     This file is part of slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| from slixmpp import ClientXMPP |  | ||||||
|  |  | ||||||
| from optparse import OptionParser |  | ||||||
| import logging |  | ||||||
| import getpass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class HTTPOverXMPPClient(ClientXMPP): |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         ClientXMPP.__init__(self, jid, password) |  | ||||||
|         self.register_plugin('xep_0332')    # HTTP over XMPP Transport |  | ||||||
|         self.add_event_handler( |  | ||||||
|             'session_start', self.session_start, threaded=True |  | ||||||
|         ) |  | ||||||
|         self.add_event_handler('http_request', self.http_request_received) |  | ||||||
|         self.add_event_handler('http_response', self.http_response_received) |  | ||||||
|  |  | ||||||
|     def http_request_received(self, iq): |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|     def http_response_received(self, iq): |  | ||||||
|         print('HTTP Response Received : %s' % iq) |  | ||||||
|         print('From    : %s' %  iq['from']) |  | ||||||
|         print('To      : %s' % iq['to']) |  | ||||||
|         print('Type    : %s' % iq['type']) |  | ||||||
|         print('Headers : %s' % iq['resp']['headers']) |  | ||||||
|         print('Code    : %s' % iq['resp']['code']) |  | ||||||
|         print('Message : %s' % iq['resp']['message']) |  | ||||||
|         print('Data    : %s' % iq['resp']['data']) |  | ||||||
|  |  | ||||||
|     def session_start(self, event): |  | ||||||
|         # TODO: Fill in the blanks |  | ||||||
|         self['xep_0332'].send_request( |  | ||||||
|             to='?', method='?', resource='?', headers={} |  | ||||||
|         ) |  | ||||||
|         self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|  |  | ||||||
|     # |  | ||||||
|     # NOTE: To run this example, fill up the blanks in session_start() and |  | ||||||
|     #       use the following command. |  | ||||||
|     # |  | ||||||
|     # ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v] |  | ||||||
|     # |  | ||||||
|  |  | ||||||
|     parser = OptionParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_option( |  | ||||||
|         '-v', '--verbose', help='set logging to DEBUG', action='store_const', |  | ||||||
|         dest='loglevel', const=logging.DEBUG, default=logging.ERROR |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_option('-J', '--jid', dest='jid', help='JID') |  | ||||||
|     parser.add_option('-P', '--password', dest='password', help='Password') |  | ||||||
|  |  | ||||||
|     # XMPP server ip and port options. |  | ||||||
|     parser.add_option( |  | ||||||
|         '-i', '--ipaddr', dest='ipaddr', |  | ||||||
|         help='IP Address of the XMPP server', default=None |  | ||||||
|     ) |  | ||||||
|     parser.add_option( |  | ||||||
|         '-p', '--port', dest='port', |  | ||||||
|         help='Port of the XMPP server', default=None |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     opts, args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=opts.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if opts.jid is None: |  | ||||||
|         opts.jid = input('Username: ') |  | ||||||
|     if opts.password is None: |  | ||||||
|         opts.password = getpass.getpass('Password: ') |  | ||||||
|  |  | ||||||
|     xmpp = HTTPOverXMPPClient(opts.jid, opts.password) |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
|  |  | ||||||
| @@ -1,112 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IBBReceiver(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A basic example of creating and using an in-band bytestream. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, filename): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.file = open(filename, 'wb') |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         self.add_event_handler("ibb_stream_start", self.stream_opened) |  | ||||||
|         self.add_event_handler("ibb_stream_data", self.stream_data) |  | ||||||
|         self.add_event_handler("ibb_stream_end", self.stream_closed) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|     def stream_opened(self, stream): |  | ||||||
|         print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) |  | ||||||
|  |  | ||||||
|     def stream_data(self, stream): |  | ||||||
|         self.file.write(stream.read()) |  | ||||||
|  |  | ||||||
|     def stream_closed(self, stream): |  | ||||||
|         print('Stream closed: %s from %s' % (stream.sid, stream.peer_jid)) |  | ||||||
|         self.file.close() |  | ||||||
|         self.disconnect() |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("-o", "--out", dest="filename", |  | ||||||
|                         help="file to save to") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.filename is None: |  | ||||||
|         args.filename = input("File path: ") |  | ||||||
|  |  | ||||||
|     # Setup the IBBReceiver and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = IBBReceiver(args.jid, args.password, args.filename) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0047', { |  | ||||||
|         'auto_accept': True |  | ||||||
|     }) # In-band Bytestreams |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process(forever=False) |  | ||||||
| @@ -1,125 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import asyncio |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.exceptions import IqError, IqTimeout |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IBBSender(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A basic example of creating and using an in-band bytestream. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, receiver, filename, use_messages=False): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.receiver = receiver |  | ||||||
|  |  | ||||||
|         self.file = open(filename, 'rb') |  | ||||||
|         self.use_messages = use_messages |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             # Open the IBB stream in which to write to. |  | ||||||
|             stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages) |  | ||||||
|  |  | ||||||
|             # If you want to send in-memory bytes, use stream.sendall() instead. |  | ||||||
|             yield from stream.sendfile(self.file, timeout=10) |  | ||||||
|  |  | ||||||
|             # And finally close the stream. |  | ||||||
|             yield from stream.close(timeout=10) |  | ||||||
|         except (IqError, IqTimeout): |  | ||||||
|             print('File transfer errored') |  | ||||||
|         else: |  | ||||||
|             print('File transfer finished') |  | ||||||
|         finally: |  | ||||||
|             self.file.close() |  | ||||||
|             self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("-r", "--receiver", dest="receiver", |  | ||||||
|                         help="JID of the receiver") |  | ||||||
|     parser.add_argument("-f", "--file", dest="filename", |  | ||||||
|                         help="file to send") |  | ||||||
|     parser.add_argument("-m", "--use-messages", action="store_true", |  | ||||||
|                         help="use messages instead of iqs for file transfer") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.receiver is None: |  | ||||||
|         args.receiver = input("Receiver: ") |  | ||||||
|     if args.filename is None: |  | ||||||
|         args.filename = input("File path: ") |  | ||||||
|  |  | ||||||
|     # Setup the IBBSender and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename, args.use_messages) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0047') # In-band Bytestreams |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process(forever=False) |  | ||||||
| @@ -1,107 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # Setup the command line arguments. |  | ||||||
| parser = ArgumentParser() |  | ||||||
|  |  | ||||||
| # Output verbosity options. |  | ||||||
| parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                     action="store_const", dest="loglevel", |  | ||||||
|                     const=logging.ERROR, default=logging.INFO) |  | ||||||
| parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                     action="store_const", dest="loglevel", |  | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
| # JID and password options. |  | ||||||
| parser.add_argument("--oldjid", dest="old_jid", |  | ||||||
|                     help="JID of the old account") |  | ||||||
| parser.add_argument("--oldpassword", dest="old_password", |  | ||||||
|                     help="password of the old account") |  | ||||||
|  |  | ||||||
| parser.add_argument("--newjid", dest="new_jid", |  | ||||||
|                     help="JID of the old account") |  | ||||||
| parser.add_argument("--newpassword", dest="new_password", |  | ||||||
|                     help="password of the old account") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| args = parser.parse_args() |  | ||||||
|  |  | ||||||
| # Setup logging. |  | ||||||
| logging.basicConfig(level=args.loglevel, |  | ||||||
|                     format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
| if args.old_jid is None: |  | ||||||
|     args.old_jid = input("Old JID: ") |  | ||||||
| if args.old_password is None: |  | ||||||
|     args.old_password = getpass("Old Password: ") |  | ||||||
|  |  | ||||||
| if args.new_jid is None: |  | ||||||
|     args.new_jid = input("New JID: ") |  | ||||||
| if args.new_password is None: |  | ||||||
|     args.new_password = getpass("New Password: ") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| old_xmpp = slixmpp.ClientXMPP(args.old_jid, args.old_password) |  | ||||||
|  |  | ||||||
| # If you are connecting to Facebook and wish to use the |  | ||||||
| # X-FACEBOOK-PLATFORM authentication mechanism, you will need |  | ||||||
| # your API key and an access token. Then you'll set: |  | ||||||
| # xmpp.credentials['api_key'] = 'THE_API_KEY' |  | ||||||
| # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' |  | ||||||
|  |  | ||||||
| # If you are connecting to MSN, then you will need an |  | ||||||
| # access token, and it does not matter what JID you |  | ||||||
| # specify other than that the domain is 'messenger.live.com', |  | ||||||
| # so '_@messenger.live.com' will work. You can specify |  | ||||||
| # the access token as so: |  | ||||||
| # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' |  | ||||||
|  |  | ||||||
| # 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" |  | ||||||
|  |  | ||||||
| roster = [] |  | ||||||
|  |  | ||||||
| def on_session(event): |  | ||||||
|     roster.append(old_xmpp.get_roster()) |  | ||||||
|     old_xmpp.disconnect() |  | ||||||
| old_xmpp.add_event_handler('session_start', on_session) |  | ||||||
|  |  | ||||||
| if old_xmpp.connect(): |  | ||||||
|     old_xmpp.process(block=True) |  | ||||||
|  |  | ||||||
| if not roster: |  | ||||||
|     print('No roster to migrate') |  | ||||||
|     sys.exit() |  | ||||||
|  |  | ||||||
| new_xmpp = slixmpp.ClientXMPP(args.new_jid, args.new_password) |  | ||||||
| def on_session2(event): |  | ||||||
|     new_xmpp.get_roster() |  | ||||||
|     new_xmpp.send_presence() |  | ||||||
|  |  | ||||||
|     logging.info(roster[0]) |  | ||||||
|     data = roster[0]['roster']['items'] |  | ||||||
|     logging.info(data) |  | ||||||
|  |  | ||||||
|     for jid, item in data.items(): |  | ||||||
|         if item['subscription'] != 'none': |  | ||||||
|             new_xmpp.send_presence(ptype='subscribe', pto=jid) |  | ||||||
|         new_xmpp.update_roster(jid, |  | ||||||
|                 name = item['name'], |  | ||||||
|                 groups = item['groups']) |  | ||||||
|     new_xmpp.disconnect() |  | ||||||
| new_xmpp.add_event_handler('session_start', on_session2) |  | ||||||
|  |  | ||||||
| new_xmpp.connect() |  | ||||||
| new_xmpp.process() |  | ||||||
							
								
								
									
										169
									
								
								examples/muc.py
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								examples/muc.py
									
									
									
									
									
								
							| @@ -1,169 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MUCBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that will greets those |  | ||||||
|     who enter the room, and acknowledge any messages |  | ||||||
|     that mentions the bot's nickname. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, room, nick): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         self.room = room |  | ||||||
|         self.nick = nick |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The groupchat_message event is triggered whenever a message |  | ||||||
|         # stanza is received from any chat room. If you also also |  | ||||||
|         # register a handler for the 'message' event, MUC messages |  | ||||||
|         # will be processed by both handlers. |  | ||||||
|         self.add_event_handler("groupchat_message", self.muc_message) |  | ||||||
|  |  | ||||||
|         # The groupchat_presence event is triggered whenever a |  | ||||||
|         # presence stanza is received from any chat room, including |  | ||||||
|         # any presences you send yourself. To limit event handling |  | ||||||
|         # to a single room, use the events muc::room@server::presence, |  | ||||||
|         # muc::room@server::got_online, or muc::room@server::got_offline. |  | ||||||
|         self.add_event_handler("muc::%s::got_online" % self.room, |  | ||||||
|                                self.muc_online) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.get_roster() |  | ||||||
|         self.send_presence() |  | ||||||
|         self.plugin['xep_0045'].join_muc(self.room, |  | ||||||
|                                          self.nick, |  | ||||||
|                                          # If a room password is needed, use: |  | ||||||
|                                          # password=the_room_password, |  | ||||||
|                                          wait=True) |  | ||||||
|  |  | ||||||
|     def muc_message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas from any chat room. Be aware |  | ||||||
|         that if you also have any handlers for the 'message' event, |  | ||||||
|         message stanzas may be processed by both handlers, so check |  | ||||||
|         the 'type' attribute when using a 'message' event handler. |  | ||||||
|  |  | ||||||
|         Whenever the bot's nickname is mentioned, respond to |  | ||||||
|         the message. |  | ||||||
|  |  | ||||||
|         IMPORTANT: Always check that a message is not from yourself, |  | ||||||
|                    otherwise you will create an infinite loop responding |  | ||||||
|                    to your own messages. |  | ||||||
|  |  | ||||||
|         This handler will reply to messages that mention |  | ||||||
|         the bot's nickname. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         if msg['mucnick'] != self.nick and self.nick in msg['body']: |  | ||||||
|             self.send_message(mto=msg['from'].bare, |  | ||||||
|                               mbody="I heard that, %s." % msg['mucnick'], |  | ||||||
|                               mtype='groupchat') |  | ||||||
|  |  | ||||||
|     def muc_online(self, presence): |  | ||||||
|         """ |  | ||||||
|         Process a presence stanza from a chat room. In this case, |  | ||||||
|         presences from users that have just come online are |  | ||||||
|         handled by sending a welcome message that includes |  | ||||||
|         the user's nickname and role in the room. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             presence -- The received presence stanza. See the |  | ||||||
|                         documentation for the Presence stanza |  | ||||||
|                         to see how else it may be used. |  | ||||||
|         """ |  | ||||||
|         if presence['muc']['nick'] != self.nick: |  | ||||||
|             self.send_message(mto=presence['from'].bare, |  | ||||||
|                               mbody="Hello, %s %s" % (presence['muc']['role'], |  | ||||||
|                                                       presence['muc']['nick']), |  | ||||||
|                               mtype='groupchat') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("-r", "--room", dest="room", |  | ||||||
|                         help="MUC room to join") |  | ||||||
|     parser.add_argument("-n", "--nick", dest="nick", |  | ||||||
|                         help="MUC nickname") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.room is None: |  | ||||||
|         args.room = input("MUC room: ") |  | ||||||
|     if args.nick is None: |  | ||||||
|         args.nick = input("MUC nickname: ") |  | ||||||
|  |  | ||||||
|     # Setup the MUCBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = MUCBot(args.jid, args.password, args.room, args.nick) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0045') # Multi-User Chat |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
							
								
								
									
										114
									
								
								examples/ping.py
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								examples/ping.py
									
									
									
									
									
								
							| @@ -1,114 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
| from slixmpp.exceptions import IqError, IqTimeout |  | ||||||
| from slixmpp import asyncio |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PingTest(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that will send a ping request |  | ||||||
|     to a given JID. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, pingjid): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|         if pingjid is None: |  | ||||||
|             pingjid = self.boundjid.bare |  | ||||||
|         self.pingjid = pingjid |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             rtt = yield from self['xep_0199'].ping(self.pingjid, |  | ||||||
|                                                    timeout=10) |  | ||||||
|             logging.info("Success! RTT: %s", rtt) |  | ||||||
|         except IqError as e: |  | ||||||
|             logging.info("Error pinging %s: %s", |  | ||||||
|                     self.pingjid, |  | ||||||
|                     e.iq['error']['condition']) |  | ||||||
|         except IqTimeout: |  | ||||||
|             logging.info("No response from %s", self.pingjid) |  | ||||||
|         finally: |  | ||||||
|             self.disconnect() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|     parser.add_argument("-t", "--pingto", help="set jid to ping", |  | ||||||
|                         dest="pingjid", default=None) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the PingTest and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = PingTest(args.jid, args.password, args.pingjid) |  | ||||||
|     xmpp.register_plugin('xep_0030')  # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004')  # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060')  # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199')  # XMPP Ping |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,135 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| """ |  | ||||||
|     Slixmpp: The Slick XMPP Library |  | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |  | ||||||
|     This file is part of Slixmpp. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import slixmpp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class EchoBot(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     """ |  | ||||||
|     A simple Slixmpp bot that will echo messages it |  | ||||||
|     receives, along with a short thank you message. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |  | ||||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) |  | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |  | ||||||
|         # the bot establishes its connection with the server |  | ||||||
|         # and the XML streams are ready for use. We want to |  | ||||||
|         # listen for this event so that we we can initialize |  | ||||||
|         # our roster. |  | ||||||
|         self.add_event_handler("session_start", self.start) |  | ||||||
|  |  | ||||||
|         # The message event is triggered whenever a message |  | ||||||
|         # stanza is received. Be aware that that includes |  | ||||||
|         # MUC messages and error messages. |  | ||||||
|         self.add_event_handler("message", self.message) |  | ||||||
|  |  | ||||||
|     def start(self, event): |  | ||||||
|         """ |  | ||||||
|         Process the session_start event. |  | ||||||
|  |  | ||||||
|         Typical actions for the session_start event are |  | ||||||
|         requesting the roster and broadcasting an initial |  | ||||||
|         presence stanza. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             event -- An empty dictionary. The session_start |  | ||||||
|                      event does not provide any additional |  | ||||||
|                      data. |  | ||||||
|         """ |  | ||||||
|         self.send_presence() |  | ||||||
|         self.get_roster() |  | ||||||
|  |  | ||||||
|     def message(self, msg): |  | ||||||
|         """ |  | ||||||
|         Process incoming message stanzas. Be aware that this also |  | ||||||
|         includes MUC messages and error messages. It is usually |  | ||||||
|         a good idea to check the messages's type before processing |  | ||||||
|         or sending replies. |  | ||||||
|  |  | ||||||
|         Arguments: |  | ||||||
|             msg -- The received message stanza. See the documentation |  | ||||||
|                    for stanza objects and the Message stanza to see |  | ||||||
|                    how it may be used. |  | ||||||
|         """ |  | ||||||
|         msg.reply("Thanks for sending\n%(body)s" % msg).send() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|  |  | ||||||
|     # Output verbosity options. |  | ||||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.ERROR, default=logging.INFO) |  | ||||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|     parser.add_argument("--phost", dest="proxy_host", |  | ||||||
|                         help="Proxy hostname") |  | ||||||
|     parser.add_argument("--pport", dest="proxy_port", |  | ||||||
|                         help="Proxy port") |  | ||||||
|     parser.add_argument("--puser", dest="proxy_user", |  | ||||||
|                         help="Proxy username") |  | ||||||
|     parser.add_argument("--ppass", dest="proxy_pass", |  | ||||||
|                         help="Proxy password") |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|     if args.proxy_host is None: |  | ||||||
|         args.proxy_host = input("Proxy host: ") |  | ||||||
|     if args.proxy_port is None: |  | ||||||
|         args.proxy_port = input("Proxy port: ") |  | ||||||
|     if args.proxy_user is None: |  | ||||||
|         args.proxy_user = input("Proxy username: ") |  | ||||||
|     if args.proxy_pass is None and args.proxy_user: |  | ||||||
|         args.proxy_pass = getpass("Proxy password: ") |  | ||||||
|  |  | ||||||
|     # Setup the EchoBot and register plugins. Note that while plugins may |  | ||||||
|     # have interdependencies, the order in which you register them does |  | ||||||
|     # not matter. |  | ||||||
|     xmpp = EchoBot(args.jid, args.password) |  | ||||||
|     xmpp.register_plugin('xep_0030') # Service Discovery |  | ||||||
|     xmpp.register_plugin('xep_0004') # Data Forms |  | ||||||
|     xmpp.register_plugin('xep_0060') # PubSub |  | ||||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping |  | ||||||
|  |  | ||||||
|     xmpp.use_proxy = True |  | ||||||
|     xmpp.proxy_config = { |  | ||||||
|         'host': args.proxy_host, |  | ||||||
|         'port': int(args.proxy_port), |  | ||||||
|         'username': args.proxy_user, |  | ||||||
|         'password': args.proxy_pass} |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process() |  | ||||||
| @@ -1,166 +0,0 @@ | |||||||
| #!/usr/bin/env python3 |  | ||||||
| # -*- coding: utf-8 -*- |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| from getpass import getpass |  | ||||||
| from argparse import ArgumentParser |  | ||||||
|  |  | ||||||
| import asyncio |  | ||||||
| import slixmpp |  | ||||||
| from slixmpp.exceptions import XMPPError |  | ||||||
| from slixmpp.xmlstream import ET, tostring |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PubsubClient(slixmpp.ClientXMPP): |  | ||||||
|  |  | ||||||
|     def __init__(self, jid, password, server, |  | ||||||
|                        node=None, action='nodes', data=''): |  | ||||||
|         super().__init__(jid, password) |  | ||||||
|  |  | ||||||
|         self.register_plugin('xep_0030') |  | ||||||
|         self.register_plugin('xep_0059') |  | ||||||
|         self.register_plugin('xep_0060') |  | ||||||
|  |  | ||||||
|         self.actions = ['nodes', 'create', 'delete', |  | ||||||
|                         'publish', 'get', 'retract', |  | ||||||
|                         'purge', 'subscribe', 'unsubscribe'] |  | ||||||
|  |  | ||||||
|         self.action = action |  | ||||||
|         self.node = node |  | ||||||
|         self.data = data |  | ||||||
|         self.pubsub_server = server |  | ||||||
|  |  | ||||||
|         self.add_event_handler('session_start', self.start) |  | ||||||
|  |  | ||||||
|     @asyncio.coroutine |  | ||||||
|     def start(self, event): |  | ||||||
|         self.get_roster() |  | ||||||
|         self.send_presence() |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             yield from getattr(self, self.action)() |  | ||||||
|         except: |  | ||||||
|             logging.error('Could not execute: %s', self.action) |  | ||||||
|         self.disconnect() |  | ||||||
|  |  | ||||||
|     def nodes(self): |  | ||||||
|         try: |  | ||||||
|             result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node) |  | ||||||
|             for item in result['disco_items']['items']: |  | ||||||
|                 logging.info('  - %s', str(item)) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not retrieve node list: %s', error.format()) |  | ||||||
|  |  | ||||||
|     def create(self): |  | ||||||
|         try: |  | ||||||
|             yield from self['xep_0060'].create_node(self.pubsub_server, self.node) |  | ||||||
|             logging.info('Created node %s', self.node) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not create node %s: %s', self.node, error.format()) |  | ||||||
|  |  | ||||||
|     def delete(self): |  | ||||||
|         try: |  | ||||||
|             yield from self['xep_0060'].delete_node(self.pubsub_server, self.node) |  | ||||||
|             logging.info('Deleted node %s', self.node) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not delete node %s: %s', self.node, error.format()) |  | ||||||
|  |  | ||||||
|     def publish(self): |  | ||||||
|         payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data) |  | ||||||
|         try: |  | ||||||
|             result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) |  | ||||||
|             logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id']) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not publish to %s: %s', self.node, error.format()) |  | ||||||
|  |  | ||||||
|     def get(self): |  | ||||||
|         try: |  | ||||||
|             result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) |  | ||||||
|             for item in result['pubsub']['items']['substanzas']: |  | ||||||
|                 logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload'])) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format()) |  | ||||||
|  |  | ||||||
|     def retract(self): |  | ||||||
|         try: |  | ||||||
|             yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data) |  | ||||||
|             logging.info('Retracted item %s from node %s', self.data, self.node) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format()) |  | ||||||
|  |  | ||||||
|     def purge(self): |  | ||||||
|         try: |  | ||||||
|             yield from self['xep_0060'].purge(self.pubsub_server, self.node) |  | ||||||
|             logging.info('Purged all items from node %s', self.node) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not purge items from node %s: %s', self.node, error.format()) |  | ||||||
|  |  | ||||||
|     def subscribe(self): |  | ||||||
|         try: |  | ||||||
|             iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node) |  | ||||||
|             subscription = iq['pubsub']['subscription'] |  | ||||||
|             logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node']) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format()) |  | ||||||
|  |  | ||||||
|     def unsubscribe(self): |  | ||||||
|         try: |  | ||||||
|             yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node) |  | ||||||
|             logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node) |  | ||||||
|         except XMPPError as error: |  | ||||||
|             logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if __name__ == '__main__': |  | ||||||
|     # Setup the command line arguments. |  | ||||||
|     parser = ArgumentParser() |  | ||||||
|     parser.version = '%%prog 0.1' |  | ||||||
|     parser.usage = "Usage: %%prog [options] <jid> " + \ |  | ||||||
|                              'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \ |  | ||||||
|                              ' [<node> <data>]' |  | ||||||
|  |  | ||||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.ERROR, |  | ||||||
|                         default=logging.INFO) |  | ||||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", |  | ||||||
|                         action="store_const", |  | ||||||
|                         dest="loglevel", |  | ||||||
|                         const=logging.DEBUG, |  | ||||||
|                         default=logging.INFO) |  | ||||||
|  |  | ||||||
|     # JID and password options. |  | ||||||
|     parser.add_argument("-j", "--jid", dest="jid", |  | ||||||
|                         help="JID to use") |  | ||||||
|     parser.add_argument("-p", "--password", dest="password", |  | ||||||
|                         help="password to use") |  | ||||||
|  |  | ||||||
|     parser.add_argument("server") |  | ||||||
|     parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"]) |  | ||||||
|     parser.add_argument("node", nargs='?') |  | ||||||
|     parser.add_argument("data", nargs='?') |  | ||||||
|  |  | ||||||
|     args = parser.parse_args() |  | ||||||
|  |  | ||||||
|     # Setup logging. |  | ||||||
|     logging.basicConfig(level=args.loglevel, |  | ||||||
|                         format='%(levelname)-8s %(message)s') |  | ||||||
|  |  | ||||||
|     if args.jid is None: |  | ||||||
|         args.jid = input("Username: ") |  | ||||||
|     if args.password is None: |  | ||||||
|         args.password = getpass("Password: ") |  | ||||||
|  |  | ||||||
|     # Setup the Pubsub client |  | ||||||
|     xmpp = PubsubClient(args.jid, args.password, |  | ||||||
|                         server=args.server, |  | ||||||
|                         node=args.node, |  | ||||||
|                         action=args.action, |  | ||||||
|                         data=args.data) |  | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |  | ||||||
|     xmpp.connect() |  | ||||||
|     xmpp.process(forever=False) |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user