Compare commits
	
		
			294 Commits
		
	
	
		
			exp_idle_c
			...
			slix-1.2.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 36824379c3 | ||
|   | a0a37c19ff | ||
|   | 1b5fe57a5e | ||
|   | 5da31db0c7 | ||
|   | f8cea760b6 | ||
|   | 5ef01ecdd1 | ||
|   | 62aafe0ee7 | ||
|   | cf3f36ac52 | ||
|   | b88d2ecd77 | ||
|   | e691850a2b | ||
|   | d4bff8dee6 | ||
|   | 187c350805 | ||
|   | 96d1c26f90 | ||
|   | 46a90749f8 | ||
|   | 0c63a4bbda | ||
|   | e4696e0471 | ||
|   | 8217dc5239 | ||
|   | 2586abc0d3 | ||
|   | 28f84ab3d9 | ||
|   | 813b45aded | ||
|   | 3a9b45e4f2 | ||
|   | b8e091233e | ||
|   | 0edeefd977 | ||
|   | 6ba53cf1ff | ||
|   | d7758eb7f4 | ||
|   | 125336aeee | ||
|   | 7cd1cf32ae | ||
|   | d099e353a4 | ||
|   | 1e4a301c6e | ||
|   | f53b12d227 | ||
|   | e2562dcccf | ||
|   | 7b69ae3738 | ||
|   | ab6df235d7 | ||
|   | 52cd8f4b22 | ||
|   | e28318c271 | ||
|   | 39ee833c29 | ||
|   | 9019e2bc71 | ||
|   | 9208bf5bf1 | ||
|   | f0f1698e46 | ||
|   | eccd7f1c98 | ||
|   | 2587d82af8 | ||
|   | 7ea121b115 | ||
|   | bb81fbbdfc | ||
|   | 1a00a08b7d | ||
|   | 90ea2a3411 | ||
|   | 8fc6814b6d | ||
|   | ffced0ed9a | ||
|   | e7248d9af9 | ||
|   | 6b1a04f59d | ||
|   | 4905407092 | ||
|   | bd6ec10939 | ||
|   | e15e6735f1 | ||
|   | 67afd6a462 | ||
|   | 2e2b97c53b | ||
|   | a35df7fe1f | ||
|   | fbc8562779 | ||
|   | b549db959a | ||
|   | d5188ac68a | ||
|   | ada9444bf8 | ||
|   | acc52fd935 | ||
|   | 1100ff1feb | ||
|   | c17fc3a869 | ||
|   | 4dba697075 | ||
|   | e42d651d7e | ||
|   | 4305eddb4f | ||
|   | c2dc44cfd1 | ||
|   | 5fc14de32e | ||
|   | d245558fd5 | ||
|   | 9d45370e8a | ||
|   | cc1cc61d36 | ||
|   | c6740a4908 | ||
|   | 55114bcffe | ||
|   | 4fa5dedc47 | ||
|   | 5525ef2285 | ||
|   | a7ac969215 | ||
|   | 329cb5a9f8 | ||
|   | d9b47b33f5 | ||
|   | 3582ac9941 | ||
|   | 2a127a57a7 | ||
|   | 7059400020 | ||
|   | 0b14ef82d4 | ||
|   | 83953af53d | ||
|   | 110cf25c6d | ||
|   | f2bf6072ec | ||
|   | 5f9abe2e0e | ||
|   | ea65b672e7 | ||
|   | 93c705fb31 | ||
|   | 0724f623bb | ||
|   | 82e549c0e9 | ||
|   | 1aa15792b4 | ||
|   | ffb2b6bc04 | ||
|   | 27f98bf22c | ||
|   | 3978078710 | ||
|   | 00a0698720 | ||
|   | 4a24f58be2 | ||
|   | da14ce16ec | ||
|   | 18e5abb9dd | ||
|   | 1a75b76916 | ||
|   | 53b56899a0 | ||
|   | 804b23d390 | ||
|   | 04eaf52b1d | ||
|   | dc7fef1064 | ||
|   | 488c433555 | ||
|   | 9c5dd024b1 | ||
|   | 6e61adf3db | ||
|   | 041bd63864 | ||
|   | a366482551 | ||
|   | a721084f6e | ||
|   | 1b4187fa56 | ||
|   | cf7a60705e | ||
|   | 349b05b9b7 | ||
|   | 9fbacf377a | ||
|   | 2da9e35cbc | ||
|   | 8adc8fa2ba | ||
|   | 9efa909dfc | ||
|   | 7f21fdbe26 | ||
|   | f9c7fa92ea | ||
|   | e75a160d52 | ||
|   | 170bd51387 | ||
|   | abcec1e2d3 | ||
|   | eeab646bfa | ||
|   | 2c69144189 | ||
|   | f54ebec654 | ||
|   | 2ce931cb7a | ||
|   | 84eddd2ed2 | ||
|   | 2042e1a4d5 | ||
|   | be14f0cc52 | ||
|   | edd9199be8 | ||
|   | bb094cc649 | ||
|   | dbaa6ed952 | ||
|   | 8c94d894ab | ||
|   | ffc7eac4dc | ||
|   | 555fd6d926 | ||
|   | c024ac8f0b | ||
|   | f00177c0cf | ||
|   | d0ad25745a | ||
|   | 55be23a6da | ||
|   | 75ba283572 | ||
|   | f7164d35d2 | ||
|   | 4afbb0322b | ||
|   | 7bce1ecc8a | ||
|   | bbce16d526 | ||
|   | c29fc39ef1 | ||
|   | 8335c08782 | ||
|   | 224d7ae133 | ||
|   | 04bff00171 | ||
|   | f3e31baf04 | ||
|   | 9b25a7cf77 | ||
|   | 7a908ac07b | ||
|   | 92901637ec | ||
|   | 3590b663ed | ||
|   | a33bde9cc3 | ||
|   | ac50fdccfc | ||
|   | a0c6bf15e9 | ||
|   | a2852eb249 | ||
|   | f1e6d6b0a9 | ||
|   | 116a33ba51 | ||
|   | a8ac115310 | ||
|   | 1345b7c1d0 | ||
|   | d60a652259 | ||
|   | 61a7cecb31 | ||
|   | 192b7e0349 | ||
|   | 80b60fc048 | ||
|   | b8d7b9520c | ||
|   | 0305ce66b7 | ||
|   | 474405ab90 | ||
|   | 4415d3be1a | ||
|   | 058c530787 | ||
|   | 766d0dfd40 | ||
|   | ac31913a65 | ||
|   | d34ddf33db | ||
|   | eb4e09b0ca | ||
|   | ce085bf4f4 | ||
|   | 990113f8e7 | ||
|   | aa022204ee | ||
|   | c1f23b566b | ||
|   | 45f7cb8bda | ||
|   | bdb1f66ac9 | ||
|   | 842157a6cc | ||
|   | a63cc01482 | ||
|   | 1bbb6f3ff9 | ||
|   | 93894247a4 | ||
|   | 16bb5e2537 | ||
|   | d19a6e05b2 | ||
|   | 86e85f9835 | ||
|   | cc145d20b0 | ||
|   | 881d9040c4 | ||
|   | 1e77ea0944 | ||
|   | 140f0885b2 | ||
|   | 83f71a6610 | ||
|   | 271343a32d | ||
|   | 48857b0030 | ||
|   | 1fe7f5f4e6 | ||
|   | d5b1904ebb | ||
|   | b6b0e82dec | ||
|   | 632b7b4afe | ||
|   | 81b7b2c190 | ||
|   | 460de7d301 | ||
|   | 69022c6db7 | ||
|   | 0ef3fa2703 | ||
|   | 8da269de88 | ||
|   | 93ce318259 | ||
|   | 997928de91 | ||
|   | 83d00a5913 | ||
|   | bf5d7c83af | ||
|   | c66a4d4097 | ||
|   | e112e86475 | ||
|   | e034b31d6b | ||
|   | 18a4978456 | ||
|   | 17464b10a4 | ||
|   | 6fb3ecd414 | ||
|   | c214e4f037 | ||
|   | 2ee05d9616 | ||
|   | f795ac02e3 | ||
|   | 6e8235544c | ||
|   | 6e35948276 | ||
|   | 4da870fd19 | ||
|   | cd7ff685fb | ||
|   | 1e4944d47e | ||
|   | e68135f59f | ||
|   | 6408c5a747 | ||
|   | 115fe954ac | ||
|   | 3d243f7da5 | ||
|   | ea5615f236 | ||
|   | 69da1c1d7c | ||
|   | e85fa4203e | ||
|   | 506ca69917 | ||
|   | 8ac0ecdf40 | ||
|   | dbd8115557 | ||
|   | 74b4ea20bf | ||
|   | 11fbaa4241 | ||
|   | 8fd0d7c993 | ||
|   | 1450d36377 | ||
|   | 06358d0665 | ||
|   | 2b3b86e281 | ||
|   | 92e4bc752a | ||
|   | ffb2e05f21 | ||
|   | 1e2665df19 | ||
|   | 4d063e287e | ||
|   | 44f02fb3ab | ||
|   | f6b3a0c6cf | ||
|   | 8b36e918e8 | ||
|   | 9044807121 | ||
|   | 24264d3a07 | ||
|   | 8bc70264ef | ||
|   | 957c635fb7 | ||
|   | 4027927c6e | ||
|   | c16b862200 | ||
|   | a96f608469 | ||
|   | e1f25604ec | ||
|   | 0fe057b5c3 | ||
|   | be76dda21d | ||
|   | ecd124dd06 | ||
|   | 4a8951c4ee | ||
|   | 8afba7de85 | ||
|   | 1ce42d3a2f | ||
|   | 2f4d811db4 | ||
|   | 61127f521d | ||
|   | 62eefdbd6a | ||
|   | 225e07eb64 | ||
|   | 063e73c0d2 | ||
|   | d261318e1a | ||
|   | d33cc00fe9 | ||
|   | 27582f6fd2 | ||
|   | e328ff4833 | ||
|   | 403462fdb8 | ||
|   | f22d8e67b4 | ||
|   | 35f33f1614 | ||
|   | c9f8ddff65 | ||
|   | f5ae98aaf1 | ||
|   | 073e85381a | ||
|   | afc939708f | ||
|   | aabec8b993 | ||
|   | e5e2fbb16b | ||
|   | 3dd379cdf1 | ||
|   | a20582aba4 | ||
|   | 09cdbf1b76 | ||
|   | ca306e7cec | ||
|   | 1bf34f7fe6 | ||
|   | 4144d60017 | ||
|   | 7265682a4d | ||
|   | 08c62a6bf1 | ||
|   | d61f1cd035 | ||
|   | 1063feb33b | ||
|   | 79f3c1ac8f | ||
|   | a5c03b763a | ||
|   | 3670d82f1c | ||
|   | e94a73553d | ||
|   | 577fd71472 | ||
|   | ef1c4368d0 | ||
|   | 48def71d0c | ||
|   | c8c20fff71 | ||
|   | 75a18b5ffe | ||
|   | ea3d39b50e | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -12,3 +12,4 @@ slixmpp.egg-info/ | ||||
| *~ | ||||
| .baboon/ | ||||
| .DS_STORE | ||||
| .idea/ | ||||
|   | ||||
							
								
								
									
										10
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| language: python | ||||
| python: | ||||
|   - "2.6" | ||||
|   - "2.7" | ||||
|   - "3.2" | ||||
|   - "3.3" | ||||
|   - "3.4" | ||||
| install: | ||||
|   - "pip install ." | ||||
| script: testall.py | ||||
							
								
								
									
										14
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| 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. | ||||
							
								
								
									
										5
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,5 +1,6 @@ | ||||
| Pre-requisites: | ||||
| - Python 3.1 or 2.6 | ||||
| - Python 3.4 | ||||
| - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) | ||||
|  | ||||
| Install: | ||||
| > python3 setup.py install | ||||
| @@ -9,4 +10,4 @@ Root install: | ||||
|  | ||||
| To test: | ||||
| > cd examples | ||||
| > python echo_client.py -v -j [USER@example.com] -p [PASSWORD] | ||||
| > python3 echo_client.py -d -j [USER@example.com] -p [PASSWORD] | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| include README.rst | ||||
| include LICENSE | ||||
| include testall.py | ||||
| 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 | ||||
|   | ||||
							
								
								
									
										29
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								README.rst
									
									
									
									
									
								
							| @@ -8,6 +8,14 @@ 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 | ||||
| ------------------------- | ||||
| @@ -21,7 +29,7 @@ be in ``docs/_build/html``:: | ||||
|  | ||||
| To run the test suite for Slixmpp:: | ||||
|  | ||||
|     python testall.py | ||||
|     python run_tests.py | ||||
|  | ||||
|  | ||||
| The Slixmpp Boilerplate | ||||
| @@ -88,17 +96,26 @@ Slixmpp projects:: | ||||
|  | ||||
|         xmpp = EchoBot('somejid@example.com', 'use_getpass') | ||||
|         xmpp.connect() | ||||
|         xmpp.process(block=True) | ||||
|         xmpp.process(forever=True) | ||||
|  | ||||
|  | ||||
| Slixmpp Credits | ||||
| --------------- | ||||
|  | ||||
| **Maintainer of the slixmpp fork:** Florent Le Coz | ||||
|     `louiz@louiz.org <xmpp:louiz@louiz.org?message>`_, | ||||
| **Maintainers:** | ||||
|     - Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_), | ||||
|     - Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_), | ||||
|  | ||||
| Credits | ||||
| ------- | ||||
| **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>`_, | ||||
|   | ||||
							
								
								
									
										8
									
								
								docs/api/stanza/iq.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/iq.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| IQ Stanza | ||||
| ========= | ||||
|  | ||||
| .. module:: slixmpp.stanza | ||||
|  | ||||
| .. autoclass:: Iq | ||||
|     :members: | ||||
|  | ||||
							
								
								
									
										7
									
								
								docs/api/stanza/message.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/api/stanza/message.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| Message Stanza | ||||
| ============== | ||||
|  | ||||
| .. module:: slixmpp.stanza | ||||
|  | ||||
| .. autoclass:: Message | ||||
|     :members: | ||||
							
								
								
									
										8
									
								
								docs/api/stanza/presence.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/presence.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| Presence Stanza | ||||
| =============== | ||||
|  | ||||
| .. module:: slixmpp.stanza | ||||
|  | ||||
| .. autoclass:: Presence | ||||
|     :members: | ||||
|  | ||||
							
								
								
									
										8
									
								
								docs/api/stanza/rootstanza.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/rootstanza.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| Root Stanza | ||||
| =========== | ||||
|  | ||||
| .. module:: slixmpp.stanza.rootstanza | ||||
|  | ||||
| .. autoclass:: RootStanza | ||||
|     :members: | ||||
|  | ||||
| @@ -1,12 +0,0 @@ | ||||
| .. module:: slixmpp.xmlstream.filesocket | ||||
|  | ||||
| .. _filesocket: | ||||
|  | ||||
| Python 2.6 File Socket Shims | ||||
| ============================ | ||||
|  | ||||
| .. autoclass:: FileSocket | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: Socket26 | ||||
|     :members: | ||||
| @@ -10,15 +10,19 @@ The Basic Handler | ||||
|  | ||||
| Callback | ||||
| -------- | ||||
| .. module:: slixmpp.xmlstream.handler.callback | ||||
| .. module:: slixmpp.xmlstream.handler | ||||
|  | ||||
| .. autoclass:: Callback | ||||
|     :members: | ||||
|  | ||||
| CoroutineCallback | ||||
| ----------------- | ||||
|  | ||||
| .. autoclass:: CoroutineCallback | ||||
|     :members: | ||||
|  | ||||
| Waiter | ||||
| ------ | ||||
| .. module:: slixmpp.xmlstream.handler.waiter | ||||
|  | ||||
| .. autoclass:: Waiter | ||||
|     :members: | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| Jabber IDs (JID) | ||||
| ================= | ||||
|  | ||||
| .. module:: slixmpp.xmlstream.jid | ||||
| .. module:: slixmpp.jid | ||||
|  | ||||
| .. autoclass:: JID | ||||
|     :members: | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| ========= | ||||
| Scheduler | ||||
| ========= | ||||
|  | ||||
| .. module:: slixmpp.xmlstream.scheduler | ||||
|  | ||||
| .. autoclass:: Task | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: Scheduler | ||||
|     :members: | ||||
| @@ -61,8 +61,8 @@ interacting with a given :term:`stanza` a :term:`stanza object`. | ||||
| To make dealing with more complicated and nested :term:`stanzas <stanza>` | ||||
| or XML chunks easier, :term:`stanza objects <stanza object>` can be | ||||
| composed in two ways: as iterable child objects or as plugins. Iterable | ||||
| child stanzas, or :term:`substanzas`, are accessible through a special | ||||
| ``'substanzas'`` interface. This option is useful for stanzas which | ||||
| 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 | ||||
|   | ||||
| @@ -28,7 +28,7 @@ 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:: tostring | ||||
| .. autofunction:: slixmpp.xmlstream.tostring | ||||
|  | ||||
| Escaping Special Characters | ||||
| --------------------------- | ||||
| @@ -43,4 +43,5 @@ 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 | ||||
| .. | ||||
|     autofunction:: xml_escape | ||||
|   | ||||
| @@ -24,21 +24,20 @@ patterns is received; these callbacks are also referred to as :term:`stream | ||||
| handlers <stream handler>`. The class also provides a basic eventing system | ||||
| which can be triggered either manually or on a timed schedule. | ||||
|  | ||||
| The Main Threads | ||||
| ~~~~~~~~~~~~~~~~ | ||||
| :class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances run using at | ||||
| least three background threads: the send thread, the read thread, and the | ||||
| scheduler thread. The send thread is in charge of monitoring the send queue | ||||
| and writing text to the outgoing XML stream. The read thread pulls text off | ||||
| of the incoming XML stream and stores the results in an event queue. The | ||||
| scheduler thread is used to emit events after a given period of time. | ||||
| 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. | ||||
|  | ||||
| Additionally, the main event processing loop may be executed in its | ||||
| own thread if Slixmpp is being used in the background for another | ||||
| application. | ||||
| 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`. | ||||
|  | ||||
| Short-lived threads may also be spawned as requested for threaded | ||||
| :term:`event handlers <event handler>`. | ||||
| :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 | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| @@ -53,7 +52,7 @@ when this bit of XML is received (with an assumed namespace of | ||||
|     </message> | ||||
|  | ||||
|  | ||||
| 1. **Convert XML strings into objects.** | ||||
| #. **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 | ||||
| @@ -66,65 +65,43 @@ when this bit of XML is received (with an assumed namespace of | ||||
|    ``{jabber:client}message`` is associated with the class | ||||
|    :class:`~slixmpp.stanza.Message`. | ||||
|  | ||||
| 2. **Match stanza objects to callbacks.** | ||||
| #. **Match stanza objects to callbacks.** | ||||
|  | ||||
|    These objects are then compared against the stored patterns associated | ||||
|    with the registered callback handlers. For each match, a copy of the | ||||
|    :term:`stanza object` is paired with a reference to the handler and | ||||
|    placed into the event queue. | ||||
|    with the registered callback handlers. | ||||
|  | ||||
|    Our :class:`~slixmpp.stanza.Message` object is thus paired with the message stanza handler | ||||
|    :meth:`BaseXMPP._handle_message` to create the tuple:: | ||||
|    Each handler matching our :term:`stanza object` is then added to a list. | ||||
|  | ||||
|        ('stanza', stanza_obj, handler) | ||||
| #. **Processing callbacks** | ||||
|  | ||||
| 3. **Process the event queue.** | ||||
|    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. | ||||
|  | ||||
|    The event queue is the heart of Slixmpp. Nearly every action that | ||||
|    takes place is first inserted into this queue, whether that be received | ||||
|    stanzas, custom events, or scheduled events. | ||||
|  | ||||
|    When the stanza is pulled out of the event queue with an associated | ||||
|    callback, the callback function is executed with the stanza as its only | ||||
|    parameter. | ||||
|  | ||||
|    .. warning:: | ||||
|        The callback, aka :term:`stream handler`, is executed in the main event | ||||
|        processing thread. If the handler blocks, event processing will also | ||||
|        block. | ||||
|  | ||||
| 4. **Raise Custom Events** | ||||
| #. **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. Importantly, these events may be handled in their | ||||
|    own thread. | ||||
|    be raised at any time. | ||||
|  | ||||
|    When the event is raised, a copy of the stanza is created for each | ||||
|    handler registered for the event. In contrast to :term:`stream handlers | ||||
|    <stream handler>`, these functions are referred to as :term:`event | ||||
|    handlers <event handler>`. Each stanza/handler pair is then put into the | ||||
|    event queue. | ||||
|    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:: | ||||
|    raises a ``'message'`` event | ||||
|  | ||||
|        self.event('message', msg) | ||||
|    .. code-block:: python | ||||
|  | ||||
|    The event call then places the message object back into the event queue | ||||
|    paired with an :term:`event handler`:: | ||||
|         self.event('message', msg) | ||||
|  | ||||
|        ('event', 'message', msg_copy1, custom_event_handler_1) | ||||
|        ('event', 'message', msg_copy2, custom_evetn_handler_2) | ||||
| #. **Process Custom Events** | ||||
|  | ||||
| 5. **Process Custom Events** | ||||
|  | ||||
|    The stanza and :term:`event handler` are then pulled from the event | ||||
|    queue, and the handler is executed, passing the stanza as its only | ||||
|    argument. If the handler was registered as threaded, then a new thread | ||||
|    will be spawned for it. | ||||
|    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>`. | ||||
| @@ -135,9 +112,9 @@ when this bit of XML is received (with an assumed namespace of | ||||
|    Finally, after a long trek, our message is handed off to the user's | ||||
|    custom handler in order to do awesome stuff:: | ||||
|  | ||||
|        msg.reply() | ||||
|        msg['body'] = "Hey! This is awesome!" | ||||
|        msg.send() | ||||
|        reply = msg.reply() | ||||
|        reply['body'] = "Hey! This is awesome!" | ||||
|        reply.send() | ||||
|  | ||||
|  | ||||
| .. index:: BaseXMPP, XMLStream | ||||
|   | ||||
| @@ -48,9 +48,9 @@ copyright = u'2011, Nathan Fritz, Lance Stout' | ||||
| # built documents. | ||||
| # | ||||
| # The short X.Y version. | ||||
| version = '1.0' | ||||
| version = '1.1' | ||||
| # The full version, including alpha/beta/rc tags. | ||||
| release = '1.0' | ||||
| release = '1.1' | ||||
|  | ||||
| # The language for content autogenerated by Sphinx. Refer to documentation | ||||
| # for a list of supported languages. | ||||
| @@ -105,7 +105,7 @@ html_theme = 'haiku' | ||||
|  | ||||
| # The name for this set of Sphinx documents.  If None, it defaults to | ||||
| # "<project> v<release> documentation". | ||||
| html_title = 'Slixmpp' | ||||
| html_title = 'slixmpp' | ||||
|  | ||||
| # A shorter title for the navigation bar.  Default is the same as html_title. | ||||
| html_short_title = '%s Documentation' % release | ||||
| @@ -219,4 +219,4 @@ man_pages = [ | ||||
|      [u'Nathan Fritz, Lance Stout'], 1) | ||||
| ] | ||||
|  | ||||
| intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')} | ||||
| intersphinx_mapping = {'python': ('http://docs.python.org/3.4', 'python-objects.inv')} | ||||
|   | ||||
| @@ -634,8 +634,9 @@ with some additional registration fields implemented. | ||||
|                 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() | ||||
|                     reply = iq.reply() | ||||
|                     reply.set_payload(iq['register'].xml) | ||||
|                     reply.send() | ||||
|                 else: | ||||
|                     # Conflicting registration | ||||
|                     self._sendError(iq, '409', 'cancel', 'conflict', | ||||
| @@ -666,14 +667,16 @@ with some additional registration fields implemented. | ||||
|                     # Add a blank field | ||||
|                     reg.addField(field) | ||||
|  | ||||
|             iq.reply().set_payload(reg.xml) | ||||
|             iq.send() | ||||
|             reply = iq.reply() | ||||
|             reply.set_payload(reg.xml) | ||||
|             reply.send() | ||||
|  | ||||
|         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() | ||||
|             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() | ||||
|   | ||||
							
								
								
									
										47
									
								
								docs/differences.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								docs/differences.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| .. _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. | ||||
| @@ -152,6 +152,13 @@ Event Index | ||||
|         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` | ||||
| @@ -252,8 +259,8 @@ Event Index | ||||
|  | ||||
|         Signal that a connection to the XMPP server has been lost and the current | ||||
|         stream session has ended. Currently equivalent to :term:`disconnected`, but | ||||
|         future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ | ||||
|         will distinguish the two events. | ||||
|         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. | ||||
|   | ||||
| @@ -7,19 +7,11 @@ Create and Run a Server Component | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     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 `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install slixmpp  # Or: easy_install slixmpp | ||||
|  | ||||
| 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 | ||||
|   | ||||
| @@ -7,19 +7,11 @@ Slixmpp Quickstart - Echo Bot | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     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 `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install slixmpp  # Or: easy_install slixmpp | ||||
|  | ||||
| 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 | ||||
| @@ -44,6 +36,7 @@ To get started, here is a brief outline of the structure that the final project | ||||
|     # -*- coding: utf-8 -*- | ||||
|  | ||||
|     import sys | ||||
|     import asyncio | ||||
|     import logging | ||||
|     import getpass | ||||
|     from optparse import OptionParser | ||||
| @@ -59,24 +52,6 @@ To get started, here is a brief outline of the structure that the final project | ||||
|  | ||||
|         '''Finally, we connect the bot and start listening for messages''' | ||||
|  | ||||
| Default Encoding | ||||
| ---------------- | ||||
| XMPP requires support for UTF-8 and so Slixmpp must use UTF-8 as well. In | ||||
| Python3 this is simple because Unicode is the default string type. For Python2.6+ | ||||
| the situation is not as easy because standard strings are simply byte arrays and | ||||
| use ASCII. We can get Python to use UTF-8 as the default encoding by including: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     if sys.version_info < (3, 0): | ||||
|         from slixmpp.util.misc_ops import setdefaultencoding | ||||
|         setdefaultencoding('utf8') | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     Until we are able to ensure that Slixmpp will always use Unicode in Python2.6+, this | ||||
|     may cause issues embedding Slixmpp into other applications which assume ASCII encoding. | ||||
|  | ||||
| Creating the EchoBot Class | ||||
| -------------------------- | ||||
|  | ||||
| @@ -95,7 +70,7 @@ as well. | ||||
|     class EchoBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password): | ||||
|             super(EchoBot, self).__init__(jid, password) | ||||
|             super().__init__(jid, password) | ||||
|  | ||||
| Handling Session Start | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
| @@ -108,7 +83,7 @@ started. To do that, we will register an event handler for the :term:`session_st | ||||
| .. code-block:: python | ||||
|  | ||||
|      def __init__(self, jid, password): | ||||
|         super(EchoBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|  | ||||
| @@ -178,7 +153,7 @@ whenever a messsage is received. | ||||
| .. code-block:: python | ||||
|  | ||||
|      def __init__(self, jid, password): | ||||
|         super(EchoBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|         self.add_event_handler('message', self.message) | ||||
| @@ -313,9 +288,9 @@ the ``EchoBot.__init__`` method instead. | ||||
|         xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
| Now we're ready to connect and begin echoing messages. If you have the package | ||||
| ``dnspython`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method | ||||
| ``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 ``dnspython``, then Slixmpp will attempt to | ||||
| 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`. | ||||
|  | ||||
| @@ -330,22 +305,6 @@ to :meth:`slixmpp.clientxmpp.ClientXMPP`. | ||||
|         else: | ||||
|             print('Unable to connect') | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     For Google Talk users withouth ``dnspython`` installed, the above code | ||||
|     should look like: | ||||
|  | ||||
|     .. code-block:: python | ||||
|  | ||||
|         if __name__ == '__main__': | ||||
|  | ||||
|             # .. option parsing & echo bot configuration | ||||
|  | ||||
|             if xmpp.connect(('talk.google.com', 5222)): | ||||
|                 xmpp.process(block=True) | ||||
|             else: | ||||
|                 print('Unable to connect') | ||||
|  | ||||
| To begin responding to messages, you'll see we called :meth:`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 | ||||
| @@ -370,7 +329,7 @@ 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://github.com/fritzy/Slixmpp/tree/master/examples>`_. | ||||
| can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_. | ||||
|  | ||||
| .. compound:: | ||||
|  | ||||
|   | ||||
| @@ -7,19 +7,11 @@ Mulit-User Chat (MUC) Bot | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     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 `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install slixmpp  # Or: easy_install slixmpp | ||||
|  | ||||
| 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 | ||||
| @@ -71,13 +63,13 @@ has been established: | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|         self.plugin['xep_0045'].joinMUC(self.room, | ||||
|                                         self.nick, | ||||
|                                         wait=True) | ||||
|         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 | ||||
| ``joinMUC`` method of the MUC plugin. | ||||
| ``join_muc`` method of the MUC plugin. | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|   | ||||
| @@ -7,10 +7,8 @@ Enable HTTP Proxy Support | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     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. | ||||
|   | ||||
| @@ -4,10 +4,8 @@ Sign in, Send a Message, and Disconnect | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     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 | ||||
| @@ -26,7 +24,7 @@ for the JID that will receive our message, and the string content of the message | ||||
|     class SendMsgBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password, recipient, msg): | ||||
|             super(SendMsgBot, self).__init__(jid, password) | ||||
|             super().__init__(jid, password) | ||||
|  | ||||
|             self.recipient = recipient | ||||
|             self.msg = msg | ||||
|   | ||||
| @@ -9,21 +9,20 @@ Glossary | ||||
|     stream handler | ||||
|         A callback function that accepts stanza objects pulled directly | ||||
|         from the XML stream. A stream handler is encapsulated in a | ||||
|         object that includes a :term:`Matcher` object, and which provides | ||||
|         additional semantics. For example, the ``Waiter`` handler wrapper | ||||
|         blocks thread execution until a matching stanza is received. | ||||
|         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 | ||||
|         ``XMLStream.event``. An event handler may be marked as | ||||
|         threaded, allowing it to execute outside of the main processing | ||||
|         loop. | ||||
|         :meth:`.XMLStream.event`. | ||||
|  | ||||
|     stanza object | ||||
|         Informally may refer both to classes which extend ``ElementBase`` | ||||
|         or ``StanzaBase``, and to objects of such classes. | ||||
|         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 ``dict`` | ||||
|         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 | ||||
|   | ||||
| @@ -61,7 +61,7 @@ operation using these stanzas without doing any complex operations such as | ||||
| checking an ACL, etc. | ||||
|  | ||||
| You may find it necessary at some point to revert a particular node or JID to | ||||
| using the default, static handlers. To do so, use the method ``make_static()``. | ||||
| 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 | ||||
| @@ -161,8 +161,8 @@ item itself, and the JID and node that will own the item. | ||||
|     In this case, the owning JID and node are provided with the | ||||
|     parameters ``ijid`` and ``node``. | ||||
|  | ||||
| Peforming Disco Queries | ||||
| ----------------------- | ||||
| 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()`` | ||||
| @@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     info = self['xep_0030'].get_info(jid='foo@example.com', | ||||
|                                      node='bar', | ||||
|                                      ifrom='baz@mycomponent.example.com', | ||||
|                                      block=True, | ||||
|                                      timeout=30) | ||||
|     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', | ||||
|   | ||||
| @@ -3,38 +3,25 @@ Slixmpp | ||||
|  | ||||
| .. sidebar:: Get the Code | ||||
|  | ||||
|     .. code-block:: sh | ||||
|     The latest source code for Slixmpp may be found on the `Git repo | ||||
|     <http://git.poez.io/slixmpp>`_. :: | ||||
|  | ||||
|         pip install slixmpp | ||||
|         git clone git://git.poez.io/slixmpp | ||||
|  | ||||
|     The latest source code for Slixmpp may be found on `Github | ||||
|     <http://github.com/fritzy/Slixmpp>`_. Releases can be found in the | ||||
|     ``master`` branch, while the latest development version is in the | ||||
|     ``develop`` branch. | ||||
|  | ||||
|     **Latest Stable Release** | ||||
|         - `1.0 <http://github.com/fritzy/Slixmpp/zipball/1.0>`_ | ||||
|  | ||||
|     **Develop Releases** | ||||
|         - `Latest Develop Version <http://github.com/fritzy/Slixmpp/zipball/develop>`_ | ||||
|  | ||||
|  | ||||
|     A mailing list and XMPP chat room are available for discussing and getting | ||||
|     help with Slixmpp. | ||||
|  | ||||
|     **Mailing List** | ||||
|         `Slixmpp Discussion on Google Groups <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     An XMPP chat room is available for discussing and getting help with slixmpp. | ||||
|  | ||||
|     **Chat** | ||||
|         `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ | ||||
|         `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_ | ||||
|  | ||||
|     **Reporting bugs** | ||||
|         You can report bugs at http://dev.louiz.org/projects/slixmpp/issues. | ||||
|  | ||||
| Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+, | ||||
| and is featured in examples in | ||||
| `XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ | ||||
| by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived | ||||
| here from reading the Definitive Guide, please see the notes on updating | ||||
| the examples to the latest version of Slixmpp. | ||||
| .. 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: | ||||
|  | ||||
| @@ -59,15 +46,16 @@ Slixmpp's design goals and philosphy are: | ||||
|     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 | ||||
|     from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
|     class EchoBot(ClientXMPP): | ||||
| @@ -85,27 +73,13 @@ Here's your first Slixmpp Bot: | ||||
|             # 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() | ||||
|             # 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'): | ||||
| @@ -121,9 +95,18 @@ Here's your first Slixmpp Bot: | ||||
|  | ||||
|         xmpp = EchoBot('somejid@example.com', 'use_getpass') | ||||
|         xmpp.connect() | ||||
|         xmpp.process(block=True) | ||||
|         xmpp.process() | ||||
|  | ||||
|  | ||||
| To read if you come from SleekXMPP | ||||
| ---------------------------------- | ||||
|  | ||||
| .. toctree:: | ||||
|     :maxdepth: 1 | ||||
|  | ||||
|     differences | ||||
|     using_asyncio | ||||
|  | ||||
|  | ||||
| Getting Started (with Examples) | ||||
| ------------------------------- | ||||
| @@ -145,7 +128,6 @@ Tutorials, FAQs, and How To Guides | ||||
| .. toctree:: | ||||
|     :maxdepth: 1 | ||||
|  | ||||
|     faq | ||||
|     xeps | ||||
|     xmpp_tdg | ||||
|     howto/stanzas | ||||
| @@ -184,9 +166,7 @@ API Reference | ||||
|     api/xmlstream/handler | ||||
|     api/xmlstream/matcher | ||||
|     api/xmlstream/xmlstream | ||||
|     api/xmlstream/scheduler | ||||
|     api/xmlstream/tostring | ||||
|     api/xmlstream/filesocket | ||||
|  | ||||
| Core Stanzas | ||||
| ~~~~~~~~~~~~ | ||||
| @@ -197,8 +177,6 @@ Core Stanzas | ||||
|     api/stanza/message | ||||
|     api/stanza/presence | ||||
|     api/stanza/iq | ||||
|     api/stanza/error | ||||
|     api/stanza/stream_error | ||||
|  | ||||
| Plugins | ||||
| ~~~~~~~ | ||||
| @@ -220,8 +198,14 @@ Additional Info | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
|  | ||||
| Credits | ||||
| ------- | ||||
| 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>`_, | ||||
|   | ||||
							
								
								
									
										148
									
								
								docs/using_asyncio.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								docs/using_asyncio.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| .. _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() | ||||
|  | ||||
|  | ||||
| @@ -11,20 +11,12 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| # This can be used when you are in a test environment and need to make paths right | ||||
| sys.path=['/Users/jocke/Dropbox/06_dev/Slixmpp']+sys.path | ||||
|  | ||||
| import logging | ||||
| import unittest | ||||
| import distutils.core | ||||
| import datetime | ||||
|  | ||||
| from glob import glob | ||||
| from os.path import splitext, basename, join as pjoin | ||||
| 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 | ||||
| @@ -168,9 +160,9 @@ if __name__ == '__main__': | ||||
|  | ||||
|         myDevice = TheDevice(args.nodeid); | ||||
|         # myDevice._add_field(name="Relay", typename="numeric", unit="Bool"); | ||||
|         myDevice._add_field(name="Temperature", typename="numeric", unit="C"); | ||||
|         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"}); | ||||
|         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) | ||||
| @@ -186,5 +178,5 @@ if __name__ == '__main__': | ||||
|         logging.debug("ready ending") | ||||
|  | ||||
|     else: | ||||
|        print "noopp didn't happen" | ||||
|        print("noopp didn't happen") | ||||
|  | ||||
|   | ||||
| @@ -68,7 +68,7 @@ class CommandBot(slixmpp.ClientXMPP): | ||||
|                        session. Additional, custom data may be saved | ||||
|                        here to persist across handler callbacks. | ||||
|         """ | ||||
|         form = self['xep_0004'].makeForm('form', 'Greeting') | ||||
|         form = self['xep_0004'].make_form('form', 'Greeting') | ||||
|         form['instructions'] = 'Send a custom greeting to a JID' | ||||
|         form.addField(var='greeting', | ||||
|                       ftype='text-single', | ||||
|   | ||||
| @@ -94,7 +94,7 @@ class CommandUserBot(slixmpp.ClientXMPP): | ||||
|         #          label="Your greeting" /> | ||||
|         # </x> | ||||
|  | ||||
|         form = self['xep_0004'].makeForm(ftype='submit') | ||||
|         form = self['xep_0004'].make_form(ftype='submit') | ||||
|         form.addField(var='greeting', | ||||
|                       value=session['greeting']) | ||||
|  | ||||
|   | ||||
							
								
								
									
										100
									
								
								examples/confirm_answer.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										100
									
								
								examples/confirm_answer.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| #!/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() | ||||
							
								
								
									
										125
									
								
								examples/confirm_ask.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										125
									
								
								examples/confirm_ask.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| #!/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) | ||||
| @@ -15,6 +15,7 @@ from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
| from slixmpp.xmlstream.asyncio import asyncio | ||||
|  | ||||
|  | ||||
| class Disco(slixmpp.ClientXMPP): | ||||
| @@ -53,6 +54,7 @@ class Disco(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
| @@ -74,22 +76,16 @@ class Disco(slixmpp.ClientXMPP): | ||||
|  | ||||
|         try: | ||||
|             if self.get in self.info_types: | ||||
|                 # By using block=True, the result stanza will be | ||||
|                 # returned. Execution will block until the reply is | ||||
|                 # received. Non-blocking options would be to listen | ||||
|                 # for the disco_info event, or passing a handler | ||||
|                 # function using the callback parameter. | ||||
|                 info = self['xep_0030'].get_info(jid=self.target_jid, | ||||
|                                                  node=self.target_node, | ||||
|                                                  block=True) | ||||
|             elif self.get in self.items_types: | ||||
|                 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 = self['xep_0030'].get_items(jid=self.target_jid, | ||||
|                                                    node=self.target_node, | ||||
|                                                    block=True) | ||||
|             else: | ||||
|                 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: | ||||
| @@ -143,7 +139,7 @@ if __name__ == '__main__': | ||||
|     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("target_jid") | ||||
|     parser.add_argument("node", nargs='?') | ||||
|  | ||||
|     args = parser.parse_args() | ||||
| @@ -162,4 +158,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -11,11 +11,11 @@ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| import threading | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError | ||||
| from slixmpp import asyncio | ||||
|  | ||||
|  | ||||
| FILE_TYPES = { | ||||
| @@ -40,8 +40,14 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|         self.add_event_handler('avatar_metadata_publish', self.on_avatar) | ||||
|  | ||||
|         self.received = set() | ||||
|         self.presences_received = threading.Event() | ||||
|         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. | ||||
| @@ -56,16 +62,20 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         self.get_roster(callback=self.roster_received_cb) | ||||
|  | ||||
|         print('Waiting for presence updates...\n') | ||||
|         self.presences_received.wait(15) | ||||
|         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 = self['xep_0054'].get_vcard(pres['from'], cached=True) | ||||
|             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 | ||||
| @@ -76,16 +86,18 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|                 pres['from'].bare, | ||||
|                 pres['vcard_temp_update']['photo'], | ||||
|                 filetype) | ||||
|         with open(filename, 'w+') as img: | ||||
|         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 = self['xep_0084'].retrieve_avatar(msg['from'], info['id']) | ||||
|                     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 | ||||
| @@ -94,7 +106,7 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|  | ||||
|                 filetype = FILE_TYPES.get(metadata['type'], 'png') | ||||
|                 filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) | ||||
|                 with open(filename, 'w+') as img: | ||||
|                 with open(filename, 'wb+') as img: | ||||
|                     img.write(avatar['value']) | ||||
|             else: | ||||
|                 # We could retrieve the avatar via HTTP, etc here instead. | ||||
| @@ -105,6 +117,7 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|         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: | ||||
|   | ||||
							
								
								
									
										97
									
								
								examples/http_over_xmpp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								examples/http_over_xmpp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| #!/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() | ||||
|  | ||||
| @@ -22,13 +22,10 @@ class IBBReceiver(slixmpp.ClientXMPP): | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|     def __init__(self, jid, password, filename): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') # Service Discovery | ||||
|         self.register_plugin('xep_0047', { | ||||
|             'auto_accept': True | ||||
|         }) # In-band Bytestreams | ||||
|         self.file = open(filename, 'wb') | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
| @@ -39,6 +36,7 @@ class IBBReceiver(slixmpp.ClientXMPP): | ||||
|  | ||||
|         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): | ||||
|         """ | ||||
| @@ -56,29 +54,16 @@ class IBBReceiver(slixmpp.ClientXMPP): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def accept_stream(self, iq): | ||||
|         """ | ||||
|         Check that it is ok to accept a stream request. | ||||
|  | ||||
|         Controlling stream acceptance can be done via either: | ||||
|             - setting 'auto_accept' to False in the plugin | ||||
|               configuration. The default is True. | ||||
|             - setting 'accept_stream' to a function which accepts | ||||
|               an Iq stanza as its argument, like this one. | ||||
|  | ||||
|         The accept_stream function will be used if it exists, and the | ||||
|         auto_accept value will be used otherwise. | ||||
|         """ | ||||
|         return True | ||||
|  | ||||
|     def stream_opened(self, stream): | ||||
|         print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) | ||||
|  | ||||
|         # You could run a loop reading from the stream using stream.recv(), | ||||
|         # or use the ibb_stream_data event. | ||||
|     def stream_data(self, stream): | ||||
|         self.file.write(stream.read()) | ||||
|  | ||||
|     def stream_data(self, event): | ||||
|         print(event['data']) | ||||
|     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. | ||||
| @@ -97,6 +82,8 @@ if __name__ == '__main__': | ||||
|                         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() | ||||
|  | ||||
| @@ -108,9 +95,18 @@ if __name__ == '__main__': | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|     if args.filename is None: | ||||
|         args.filename = input("File path: ") | ||||
|  | ||||
|     xmpp = IBBReceiver(args.jid, args.password) | ||||
|     # 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() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -9,11 +9,13 @@ | ||||
|     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): | ||||
| @@ -22,11 +24,13 @@ class IBBSender(slixmpp.ClientXMPP): | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, receiver, filename): | ||||
|     def __init__(self, jid, password, receiver, filename, use_messages=False): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.receiver = receiver | ||||
|         self.filename = filename | ||||
|  | ||||
|         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 | ||||
| @@ -35,6 +39,7 @@ class IBBSender(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
| @@ -51,15 +56,22 @@ class IBBSender(slixmpp.ClientXMPP): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         # For the purpose of demonstration, we'll set a very small block | ||||
|         # size. The default block size is 4096. We'll also use a window | ||||
|         # allowing sending multiple blocks at a time; in this case, three | ||||
|         # block transfers may be in progress at any time. | ||||
|         stream = self['xep_0047'].open_stream(self.receiver) | ||||
|         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) | ||||
|  | ||||
|         with open(self.filename) as f: | ||||
|             data = f.read() | ||||
|             stream.sendall(data) | ||||
|             # 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__': | ||||
| @@ -80,9 +92,11 @@ if __name__ == '__main__': | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|     parser.add_argument("-r", "--receiver", dest="receiver", | ||||
|                         help="JID to use") | ||||
|                         help="JID of the receiver") | ||||
|     parser.add_argument("-f", "--file", dest="filename", | ||||
|                         help="JID to use") | ||||
|                         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() | ||||
|  | ||||
| @@ -99,16 +113,13 @@ if __name__ == '__main__': | ||||
|     if args.filename is None: | ||||
|         args.filename = input("File path: ") | ||||
|  | ||||
|     # Setup the EchoBot and register plugins. Note that while plugins may | ||||
|     # 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) | ||||
|     xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename, args.use_messages) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0047') # In-band Bytestreams | ||||
|     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() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -100,8 +100,8 @@ def on_session2(event): | ||||
|         new_xmpp.update_roster(jid, | ||||
|                 name = item['name'], | ||||
|                 groups = item['groups']) | ||||
|         new_xmpp.disconnect() | ||||
|     new_xmpp.disconnect() | ||||
| new_xmpp.add_event_handler('session_start', on_session2) | ||||
|  | ||||
| if new_xmpp.connect(): | ||||
|     new_xmpp.process(block=True) | ||||
| new_xmpp.connect() | ||||
| new_xmpp.process() | ||||
|   | ||||
| @@ -67,11 +67,11 @@ class MUCBot(slixmpp.ClientXMPP): | ||||
|         """ | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|         self.plugin['xep_0045'].joinMUC(self.room, | ||||
|                                         self.nick, | ||||
|                                         # If a room password is needed, use: | ||||
|                                         # password=the_room_password, | ||||
|                                         wait=True) | ||||
|         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): | ||||
|         """ | ||||
|   | ||||
| @@ -12,6 +12,8 @@ | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
| from slixmpp import asyncio | ||||
|  | ||||
| import slixmpp | ||||
|  | ||||
| @@ -36,6 +38,7 @@ class PingTest(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
| @@ -53,8 +56,8 @@ class PingTest(slixmpp.ClientXMPP): | ||||
|         self.get_roster() | ||||
|  | ||||
|         try: | ||||
|             rtt = self['xep_0199'].ping(self.pingjid, | ||||
|                                         timeout=10) | ||||
|             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", | ||||
| @@ -78,8 +81,7 @@ if __name__ == '__main__': | ||||
|                         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) | ||||
|                         dest="pingjid", default=None) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|   | ||||
| @@ -5,15 +5,17 @@ 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='list', data=''): | ||||
|         super(PubsubClient, self).__init__(jid, password) | ||||
|                        node=None, action='nodes', data=''): | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0059') | ||||
| @@ -30,81 +32,83 @@ class PubsubClient(slixmpp.ClientXMPP): | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         try: | ||||
|             getattr(self, self.action)() | ||||
|             yield from getattr(self, self.action)() | ||||
|         except: | ||||
|             logging.error('Could not execute: %s' % self.action) | ||||
|             logging.error('Could not execute: %s', self.action) | ||||
|         self.disconnect() | ||||
|  | ||||
|     def nodes(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].get_nodes(self.pubsub_server, self.node) | ||||
|             result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node) | ||||
|             for item in result['disco_items']['items']: | ||||
|                 print('  - %s' % str(item)) | ||||
|         except: | ||||
|             logging.error('Could not retrieve node list.') | ||||
|                 logging.info('  - %s', str(item)) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not retrieve node list: %s', error.format()) | ||||
|  | ||||
|     def create(self): | ||||
|         try: | ||||
|             self['xep_0060'].create_node(self.pubsub_server, self.node) | ||||
|         except: | ||||
|             logging.error('Could not create node: %s' % self.node) | ||||
|             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: | ||||
|             self['xep_0060'].delete_node(self.pubsub_server, self.node) | ||||
|             print('Deleted node: %s' % self.node) | ||||
|         except: | ||||
|             logging.error('Could not delete node: %s' % self.node) | ||||
|             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 = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) | ||||
|             id = result['pubsub']['publish']['item']['id'] | ||||
|             print('Published at item id: %s' % id) | ||||
|         except: | ||||
|             logging.error('Could not publish to: %s' % self.node) | ||||
|             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 = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) | ||||
|             result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) | ||||
|             for item in result['pubsub']['items']['substanzas']: | ||||
|                 print('Retrieved item %s: %s' % (item['id'], tostring(item['payload']))) | ||||
|         except: | ||||
|             logging.error('Could not retrieve item %s from node %s' % (self.data, self.node)) | ||||
|                 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: | ||||
|             result = self['xep_0060'].retract(self.pubsub_server, self.node, self.data) | ||||
|             print('Retracted item %s from node %s' % (self.data, self.node)) | ||||
|         except: | ||||
|             logging.error('Could not retract item %s from node %s' % (self.data, self.node)) | ||||
|             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: | ||||
|             result = self['xep_0060'].purge(self.pubsub_server, self.node) | ||||
|             print('Purged all items from node %s' % self.node) | ||||
|         except: | ||||
|             logging.error('Could not purge items from node %s' % self.node) | ||||
|             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: | ||||
|             result = self['xep_0060'].subscribe(self.pubsub_server, self.node) | ||||
|             print('Subscribed %s to node %s' % (self.boundjid.bare, self.node)) | ||||
|         except: | ||||
|             logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node)) | ||||
|             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: | ||||
|             result = self['xep_0060'].unsubscribe(self.pubsub_server, self.node) | ||||
|             print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node)) | ||||
|         except: | ||||
|             logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node)) | ||||
|             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()) | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -121,12 +125,12 @@ if __name__ == '__main__': | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.ERROR, | ||||
|                         default=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.ERROR) | ||||
|                         default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
| @@ -135,7 +139,7 @@ if __name__ == '__main__': | ||||
|                         help="password to use") | ||||
|  | ||||
|     parser.add_argument("server") | ||||
|     parser.add_argument("action", choice=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"]) | ||||
|     parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"]) | ||||
|     parser.add_argument("node", nargs='?') | ||||
|     parser.add_argument("data", nargs='?') | ||||
|  | ||||
| @@ -159,4 +163,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ from slixmpp.xmlstream.handler import Callback | ||||
| class PubsubEvents(slixmpp.ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(PubsubEvents, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0059') | ||||
|   | ||||
| @@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP): | ||||
|         resp['register']['password'] = self.password | ||||
|  | ||||
|         try: | ||||
|             resp.send() | ||||
|             yield from resp.send() | ||||
|             logging.info("Account created for %s!" % self.boundjid) | ||||
|         except IqError as e: | ||||
|             logging.error("Could not register account: %s" % | ||||
|   | ||||
| @@ -11,11 +11,11 @@ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| import threading | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
| from slixmpp.xmlstream.asyncio import asyncio | ||||
|  | ||||
|  | ||||
| class RosterBrowser(slixmpp.ClientXMPP): | ||||
| @@ -36,8 +36,9 @@ class RosterBrowser(slixmpp.ClientXMPP): | ||||
|         self.add_event_handler("changed_status", self.wait_for_presences) | ||||
|  | ||||
|         self.received = set() | ||||
|         self.presences_received = threading.Event() | ||||
|         self.presences_received = asyncio.Event() | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
| @@ -51,17 +52,21 @@ class RosterBrowser(slixmpp.ClientXMPP): | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         future = asyncio.Future() | ||||
|         def callback(result): | ||||
|             future.set_result(None) | ||||
|         try: | ||||
|             self.get_roster() | ||||
|             self.get_roster(callback=callback) | ||||
|             yield from future | ||||
|         except IqError as err: | ||||
|             print('Error: %' % err.iq['error']['condition']) | ||||
|             print('Error: %s' % err.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
|             print('Error: Request timed out') | ||||
|         self.send_presence() | ||||
|  | ||||
|  | ||||
|         print('Waiting for presence updates...\n') | ||||
|         self.presences_received.wait(5) | ||||
|         yield from asyncio.sleep(10) | ||||
|  | ||||
|         print('Roster for %s' % self.boundjid.bare) | ||||
|         groups = self.client_roster.groups() | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class Boomerang(Endpoint): | ||||
|  | ||||
|     @remote | ||||
|     def throw(self): | ||||
|         print "Duck!" | ||||
|         print("Duck!") | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										90
									
								
								examples/s5b_transfer/s5b_receiver.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										90
									
								
								examples/s5b_transfer/s5b_receiver.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| #!/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 asyncio | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
|  | ||||
|  | ||||
| class S5BReceiver(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using a SOCKS5 bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, filename): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.file = open(filename, 'wb') | ||||
|  | ||||
|         self.add_event_handler("socks5_connected", self.stream_opened) | ||||
|         self.add_event_handler("socks5_data", self.stream_data) | ||||
|         self.add_event_handler("socks5_closed", self.stream_closed) | ||||
|  | ||||
|     def stream_opened(self, sid): | ||||
|         logging.info('Stream opened. %s', sid) | ||||
|  | ||||
|     def stream_data(self, data): | ||||
|         self.file.write(data) | ||||
|  | ||||
|     def stream_closed(self, exception): | ||||
|         logging.info('Stream closed. %s', exception) | ||||
|         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 S5BReceiver and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = S5BReceiver(args.jid, args.password, args.filename) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0065', { | ||||
|         'auto_accept': True | ||||
|     }) # SOCKS5 Bytestreams | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
							
								
								
									
										124
									
								
								examples/s5b_transfer/s5b_sender.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										124
									
								
								examples/s5b_transfer/s5b_sender.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| #!/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 asyncio | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
| class S5BSender(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using a SOCKS5 bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, receiver, filename): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.receiver = receiver | ||||
|  | ||||
|         self.file = open(filename, 'rb') | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
|         # and the XML streams are ready for use. | ||||
|         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. | ||||
|         """ | ||||
|  | ||||
|         try: | ||||
|             # Open the S5B stream in which to write to. | ||||
|             proxy = yield from self['xep_0065'].handshake(self.receiver) | ||||
|  | ||||
|             # Send the entire file. | ||||
|             while True: | ||||
|                 data = self.file.read(1048576) | ||||
|                 if not data: | ||||
|                     break | ||||
|                 yield from proxy.write(data) | ||||
|  | ||||
|             # And finally close the stream. | ||||
|             proxy.transport.write_eof() | ||||
|         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 S5BSender and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = S5BSender(args.jid, args.password, args.receiver, args.filename) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
| @@ -18,7 +18,7 @@ from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError | ||||
|  | ||||
| from slixmpp import asyncio | ||||
|  | ||||
| class AvatarSetter(slixmpp.ClientXMPP): | ||||
|  | ||||
| @@ -33,6 +33,7 @@ class AvatarSetter(slixmpp.ClientXMPP): | ||||
|  | ||||
|         self.filepath = filepath | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
| @@ -51,7 +52,7 @@ class AvatarSetter(slixmpp.ClientXMPP): | ||||
|  | ||||
|         avatar_file = None | ||||
|         try: | ||||
|             avatar_file = open(os.path.expanduser(self.filepath)) | ||||
|             avatar_file = open(os.path.expanduser(self.filepath), 'rb') | ||||
|         except IOError: | ||||
|             print('Could not find file: %s' % self.filepath) | ||||
|             return self.disconnect() | ||||
| @@ -65,32 +66,31 @@ class AvatarSetter(slixmpp.ClientXMPP): | ||||
|         avatar_file.close() | ||||
|  | ||||
|         used_xep84 = False | ||||
|         try: | ||||
|             print('Publish XEP-0084 avatar data') | ||||
|             self['xep_0084'].publish_avatar(avatar) | ||||
|             used_xep84 = True | ||||
|         except XMPPError: | ||||
|             print('Could not publish XEP-0084 avatar') | ||||
|  | ||||
|         try: | ||||
|             print('Update vCard with avatar') | ||||
|             self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) | ||||
|         except XMPPError: | ||||
|         print('Publish XEP-0084 avatar data') | ||||
|         result = yield from self['xep_0084'].publish_avatar(avatar) | ||||
|         if isinstance(result, XMPPError): | ||||
|             print('Could not publish XEP-0084 avatar') | ||||
|         else: | ||||
|             used_xep84 = True | ||||
|  | ||||
|         print('Update vCard with avatar') | ||||
|         result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) | ||||
|         if isinstance(result, XMPPError): | ||||
|             print('Could not set vCard avatar') | ||||
|  | ||||
|         if used_xep84: | ||||
|             try: | ||||
|                 print('Advertise XEP-0084 avatar metadata') | ||||
|                 self['xep_0084'].publish_avatar_metadata([ | ||||
|                     {'id': avatar_id, | ||||
|                      'type': avatar_type, | ||||
|                      'bytes': avatar_bytes} | ||||
|                     # We could advertise multiple avatars to provide | ||||
|                     # options in image type, source (HTTP vs pubsub), | ||||
|                     # size, etc. | ||||
|                     # {'id': ....} | ||||
|                 ]) | ||||
|             except XMPPError: | ||||
|             print('Advertise XEP-0084 avatar metadata') | ||||
|             result = yield from self['xep_0084'].publish_avatar_metadata([ | ||||
|                 {'id': avatar_id, | ||||
|                  'type': avatar_type, | ||||
|                  'bytes': avatar_bytes} | ||||
|                 # We could advertise multiple avatars to provide | ||||
|                 # options in image type, source (HTTP vs pubsub), | ||||
|                 # size, etc. | ||||
|                 # {'id': ....} | ||||
|             ]) | ||||
|             if isinstance(result, XMPPError): | ||||
|                 print('Could not publish XEP-0084 metadata') | ||||
|  | ||||
|         print('Wait for presence updates to propagate...') | ||||
|   | ||||
| @@ -22,7 +22,7 @@ from slixmpp import ClientXMPP | ||||
| class LocationBot(ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(LocationBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|         self.add_event_handler('user_location_publish', | ||||
|   | ||||
| @@ -17,7 +17,7 @@ from slixmpp import ClientXMPP | ||||
| class TuneBot(ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(TuneBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         # Check for the current song every 5 seconds. | ||||
|         self.schedule('Check Current Tune', 5, self._update_tune, repeat=True) | ||||
|   | ||||
							
								
								
									
										11
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								setup.py
									
									
									
									
									
								
							| @@ -13,6 +13,14 @@ try: | ||||
| except ImportError: | ||||
|     from distutils.core import setup | ||||
|  | ||||
| try: | ||||
|     from Cython.Build import cythonize | ||||
| except ImportError: | ||||
|     print('Cython not found, falling back to the slow stringprep module.') | ||||
|     ext_modules = None | ||||
| else: | ||||
|     ext_modules = cythonize('slixmpp/stringprep.pyx') | ||||
|  | ||||
| from run_tests import TestCommand | ||||
| from slixmpp.version import __version__ | ||||
|  | ||||
| @@ -43,7 +51,8 @@ setup( | ||||
|     license='MIT', | ||||
|     platforms=['any'], | ||||
|     packages=packages, | ||||
|     requires=['aiodns', 'pyasn1', 'pyasn1_modules'], | ||||
|     ext_modules=ext_modules, | ||||
|     install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'], | ||||
|     classifiers=CLASSIFIERS, | ||||
|     cmdclass={'test': TestCommand} | ||||
| ) | ||||
|   | ||||
| @@ -6,6 +6,9 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| if hasattr(asyncio, 'sslproto'): # no ssl proto: very old asyncio = no need for this | ||||
|     asyncio.sslproto._is_sslproto_available=lambda: False | ||||
| import logging | ||||
| logging.getLogger(__name__).addHandler(logging.NullHandler()) | ||||
|  | ||||
| @@ -16,6 +19,7 @@ from slixmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin | ||||
| from slixmpp.xmlstream.handler import * | ||||
| from slixmpp.xmlstream import XMLStream | ||||
| from slixmpp.xmlstream.matcher import * | ||||
| from slixmpp.xmlstream.asyncio import asyncio, future_wrapper | ||||
| from slixmpp.basexmpp import BaseXMPP | ||||
| from slixmpp.clientxmpp import ClientXMPP | ||||
| from slixmpp.componentxmpp import ComponentXMPP | ||||
|   | ||||
| @@ -12,8 +12,8 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| from slixmpp import plugins, roster, stanza | ||||
| from slixmpp.api import APIRegistry | ||||
| @@ -21,8 +21,6 @@ from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
| from slixmpp.stanza import Message, Presence, Iq, StreamError | ||||
| from slixmpp.stanza.roster import Roster | ||||
| from slixmpp.stanza.nick import Nick | ||||
| from slixmpp.stanza.htmlim import HTMLIM | ||||
|  | ||||
| from slixmpp.xmlstream import XMLStream, JID | ||||
| from slixmpp.xmlstream import ET, register_stanza_plugin | ||||
| @@ -46,8 +44,8 @@ class BaseXMPP(XMLStream): | ||||
|                        is used during initialization. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid='', default_ns='jabber:client'): | ||||
|         XMLStream.__init__(self) | ||||
|     def __init__(self, jid='', default_ns='jabber:client', **kwargs): | ||||
|         XMLStream.__init__(self, **kwargs) | ||||
|  | ||||
|         self.default_ns = default_ns | ||||
|         self.stream_ns = 'http://etherx.jabber.org/streams' | ||||
| @@ -57,12 +55,12 @@ class BaseXMPP(XMLStream): | ||||
|         self.stream_id = None | ||||
|  | ||||
|         #: The JabberID (JID) requested for this connection. | ||||
|         self.requested_jid = JID(jid, cache_lock=True) | ||||
|         self.requested_jid = JID(jid) | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection, | ||||
|         #: as set after session binding. This may even be a | ||||
|         #: different bare JID than what was requested. | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|         self.boundjid = JID(jid) | ||||
|  | ||||
|         self._expected_server_name = self.boundjid.host | ||||
|         self._redirect_attempts = 0 | ||||
| @@ -71,7 +69,7 @@ class BaseXMPP(XMLStream): | ||||
|         #: redirections that will be followed before quitting. | ||||
|         self.max_redirects = 5 | ||||
|  | ||||
|         self.session_bind_event = threading.Event() | ||||
|         self.session_bind_event = asyncio.Event() | ||||
|  | ||||
|         #: A dictionary mapping plugin names to plugins. | ||||
|         self.plugin = PluginManager(self) | ||||
| @@ -143,6 +141,13 @@ class BaseXMPP(XMLStream): | ||||
|                      MatchXPath('{%s}message/{%s}body' % (self.default_ns, | ||||
|                                                           self.default_ns)), | ||||
|                      self._handle_message)) | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('IMError', | ||||
|                      MatchXPath('{%s}message/{%s}error' % (self.default_ns, | ||||
|                                                            self.default_ns)), | ||||
|                      self._handle_message_error)) | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('Presence', | ||||
|                      MatchXPath("{%s}presence" % self.default_ns), | ||||
| @@ -188,7 +193,6 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         # Initialize a few default stanza plugins. | ||||
|         register_stanza_plugin(Iq, Roster) | ||||
|         register_stanza_plugin(Message, Nick) | ||||
|  | ||||
|     def start_stream_handler(self, xml): | ||||
|         """Save the stream ID once the streams have been established. | ||||
| @@ -203,9 +207,9 @@ class BaseXMPP(XMLStream): | ||||
|             log.warning('Legacy XMPP 0.9 protocol detected.') | ||||
|             self.event('legacy_protocol') | ||||
|  | ||||
|     def process(self, timeout=None): | ||||
|     def process(self, *, forever=True, timeout=None): | ||||
|         self.init_plugins() | ||||
|         XMLStream.process(self, timeout) | ||||
|         XMLStream.process(self, forever=forever, timeout=timeout) | ||||
|  | ||||
|     def init_plugins(self): | ||||
|         for name in self.plugin: | ||||
| @@ -214,7 +218,7 @@ class BaseXMPP(XMLStream): | ||||
|                     self.plugin[name].post_init() | ||||
|                 self.plugin[name].post_inited = True | ||||
|  | ||||
|     def register_plugin(self, plugin, pconfig={}, module=None): | ||||
|     def register_plugin(self, plugin, pconfig=None, module=None): | ||||
|         """Register and configure  a plugin for use in this stream. | ||||
|  | ||||
|         :param plugin: The name of the plugin class. Plugin names must | ||||
| @@ -631,7 +635,7 @@ class BaseXMPP(XMLStream): | ||||
|     def set_jid(self, jid): | ||||
|         """Rip a JID apart and claim it as our own.""" | ||||
|         log.debug("setting jid to %s", jid) | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|         self.boundjid = JID(jid) | ||||
|  | ||||
|     def getjidresource(self, fulljid): | ||||
|         if '/' in fulljid: | ||||
| @@ -681,7 +685,6 @@ class BaseXMPP(XMLStream): | ||||
|             self.address = (host, port) | ||||
|             self.default_domain = host | ||||
|             self.dns_records = None | ||||
|             self.reconnect_delay = None | ||||
|             self.reconnect() | ||||
|  | ||||
|     def _handle_message(self, msg): | ||||
| @@ -690,6 +693,12 @@ class BaseXMPP(XMLStream): | ||||
|             msg['to'] = self.boundjid | ||||
|         self.event('message', msg) | ||||
|  | ||||
|     def _handle_message_error(self, msg): | ||||
|         """Process incoming message error stanzas.""" | ||||
|         if not self.is_component and not msg['to'].bare: | ||||
|             msg['to'] = self.boundjid | ||||
|         self.event('message_error', msg) | ||||
|  | ||||
|     def _handle_available(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_available(pres) | ||||
|  | ||||
| @@ -741,6 +750,9 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         Update the roster with presence information. | ||||
|         """ | ||||
|         if self.roster[presence['from']].ignore_updates: | ||||
|             return | ||||
|  | ||||
|         if not self.is_component and not presence['to'].bare: | ||||
|             presence['to'] = self.boundjid | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
|  | ||||
| from slixmpp.stanza import StreamFeatures | ||||
| @@ -19,7 +20,7 @@ from slixmpp.basexmpp import BaseXMPP | ||||
| from slixmpp.exceptions import XMPPError | ||||
| from slixmpp.xmlstream import XMLStream | ||||
| from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| from slixmpp.xmlstream.handler import Callback, CoroutineCallback | ||||
|  | ||||
| # Flag indicating if DNS SRV records are available for use. | ||||
| try: | ||||
| @@ -50,7 +51,6 @@ class ClientXMPP(BaseXMPP): | ||||
|  | ||||
|     :param jid: The JID of the XMPP user account. | ||||
|     :param password: The password for the XMPP user account. | ||||
|     :param ssl: **Deprecated.** | ||||
|     :param plugin_config: A dictionary of plugin configurations. | ||||
|     :param plugin_whitelist: A list of approved plugins that | ||||
|                     will be loaded when calling | ||||
| @@ -58,9 +58,15 @@ class ClientXMPP(BaseXMPP): | ||||
|     :param escape_quotes: **Deprecated.** | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], | ||||
|                  escape_quotes=True, sasl_mech=None, lang='en'): | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||
|     def __init__(self, jid, password, plugin_config=None, | ||||
|                  plugin_whitelist=None, escape_quotes=True, sasl_mech=None, | ||||
|                  lang='en', **kwargs): | ||||
|         if not plugin_whitelist: | ||||
|             plugin_whitelist = [] | ||||
|         if not plugin_config: | ||||
|             plugin_config = {} | ||||
|  | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs) | ||||
|  | ||||
|         self.escape_quotes = escape_quotes | ||||
|         self.plugin_config = plugin_config | ||||
| @@ -99,9 +105,9 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.register_stanza(StreamFeatures) | ||||
|  | ||||
|         self.register_handler( | ||||
|                 Callback('Stream Features', | ||||
|                          MatchXPath('{%s}features' % self.stream_ns), | ||||
|                          self._handle_stream_features)) | ||||
|                 CoroutineCallback('Stream Features', | ||||
|                      MatchXPath('{%s}features' % self.stream_ns), | ||||
|                      self._handle_stream_features)) | ||||
|         self.register_handler( | ||||
|                 Callback('Roster Update', | ||||
|                          StanzaPath('iq@type=set/roster'), | ||||
| @@ -135,10 +141,11 @@ class ClientXMPP(BaseXMPP): | ||||
|         will be used. | ||||
|  | ||||
|         :param address: A tuple containing the server's host and port. | ||||
|         :param reattempt: If ``True``, repeat attempting to connect if an | ||||
|                          error occurs. Defaults to ``True``. | ||||
|         :param use_tls: Indicates if TLS should be used for the | ||||
|                         connection. Defaults to ``True``. | ||||
|         :param force_starttls: Indicates that negotiation should be aborted | ||||
|                                if the server does not advertise support for | ||||
|                                STARTTLS. Defaults to ``True``. | ||||
|         :param disable_starttls: Disables TLS for the connection. | ||||
|                                  Defaults to ``False``. | ||||
|         :param use_ssl: Indicates if the older SSL connection method | ||||
|                         should be used. Defaults to ``False``. | ||||
|         """ | ||||
| @@ -246,6 +253,7 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.bindfail = False | ||||
|         self.features = set() | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def _handle_stream_features(self, features): | ||||
|         """Process the received stream features. | ||||
|  | ||||
| @@ -254,7 +262,11 @@ class ClientXMPP(BaseXMPP): | ||||
|         for order, name in self._stream_feature_order: | ||||
|             if name in features['features']: | ||||
|                 handler, restart = self._stream_feature_handlers[name] | ||||
|                 if handler(features) and restart: | ||||
|                 if asyncio.iscoroutinefunction(handler): | ||||
|                     result = yield from handler(features) | ||||
|                 else: | ||||
|                     result = handler(features) | ||||
|                 if result and restart: | ||||
|                     # Don't continue if the feature requires | ||||
|                     # restarting the XML stream. | ||||
|                     return True | ||||
|   | ||||
| @@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP): | ||||
|                       Defaults to ``False``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, secret, host=None, port=None, | ||||
|                  plugin_config={}, plugin_whitelist=[], use_jc_ns=False): | ||||
|     def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False): | ||||
|  | ||||
|         if not plugin_whitelist: | ||||
|             plugin_whitelist = [] | ||||
|         if not plugin_config: | ||||
|             plugin_config = {} | ||||
|  | ||||
|         if use_jc_ns: | ||||
|             default_ns = 'jabber:client' | ||||
|         else: | ||||
| @@ -55,7 +60,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|         BaseXMPP.__init__(self, jid, default_ns) | ||||
|  | ||||
|         self.auto_authorize = None | ||||
|         self.stream_header = "<stream:stream %s %s to='%s'>" % ( | ||||
|         self.stream_header = '<stream:stream %s %s to="%s">' % ( | ||||
|                 'xmlns="jabber:component:accept"', | ||||
|                 'xmlns:stream="%s"' % self.stream_ns, | ||||
|                 jid) | ||||
| @@ -68,6 +73,8 @@ class ComponentXMPP(BaseXMPP): | ||||
|         self.plugin_whitelist = plugin_whitelist | ||||
|         self.is_component = True | ||||
|  | ||||
|         self.sessionstarted = False | ||||
|  | ||||
|         self.register_handler( | ||||
|                 Callback('Handshake', | ||||
|                          MatchXPath('{jabber:component:accept}handshake'), | ||||
| @@ -75,12 +82,9 @@ class ComponentXMPP(BaseXMPP): | ||||
|         self.add_event_handler('presence_probe', | ||||
|                                self._handle_probe) | ||||
|  | ||||
|     def connect(self, host=None, port=None, use_ssl=False, | ||||
|                       use_tls=False, reattempt=True): | ||||
|     def connect(self, host=None, port=None, use_ssl=False): | ||||
|         """Connect to the server. | ||||
|  | ||||
|         Setting ``reattempt`` to ``True`` will cause connection attempts to | ||||
|         be made every second until a successful connection is established. | ||||
|  | ||||
|         :param host: The name of the desired server for the connection. | ||||
|                      Defaults to :attr:`server_host`. | ||||
| @@ -88,11 +92,6 @@ class ComponentXMPP(BaseXMPP): | ||||
|                      Defauts to :attr:`server_port`. | ||||
|         :param use_ssl: Flag indicating if SSL should be used by connecting | ||||
|                         directly to a port using SSL. | ||||
|         :param use_tls: Flag indicating if TLS should be used, allowing for | ||||
|                         connecting to a port without using SSL immediately and | ||||
|                         later upgrading the connection. | ||||
|         :param reattempt: Flag indicating if the socket should reconnect | ||||
|                           after disconnections. | ||||
|         """ | ||||
|         if host is None: | ||||
|             host = self.server_host | ||||
| @@ -101,14 +100,9 @@ class ComponentXMPP(BaseXMPP): | ||||
|  | ||||
|         self.server_name = self.boundjid.host | ||||
|  | ||||
|         if use_tls: | ||||
|             log.info("XEP-0114 components can not use TLS") | ||||
|  | ||||
|         log.debug("Connecting to %s:%s", host, port) | ||||
|         return XMLStream.connect(self, host=host, port=port, | ||||
|                                        use_ssl=use_ssl, | ||||
|                                        use_tls=False, | ||||
|                                        reattempt=reattempt) | ||||
|                                        use_ssl=use_ssl) | ||||
|  | ||||
|     def incoming_filter(self, xml): | ||||
|         """ | ||||
| @@ -145,7 +139,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|         :param xml: The reply handshake stanza. | ||||
|         """ | ||||
|         self.session_bind_event.set() | ||||
|         self.session_started_event.set() | ||||
|         self.sessionstarted = True | ||||
|         self.event('session_bind', self.boundjid) | ||||
|         self.event('session_start') | ||||
|  | ||||
|   | ||||
| @@ -56,6 +56,18 @@ class XMPPError(Exception): | ||||
|         self.extension_ns = extension_ns | ||||
|         self.extension_args = extension_args | ||||
|  | ||||
|     def format(self): | ||||
|         """ | ||||
|         Format the error in a simple user-readable string. | ||||
|         """ | ||||
|         text = [self.etype, self.condition] | ||||
|         if self.text: | ||||
|             text.append(self.text) | ||||
|         if self.extension: | ||||
|             text.append(self.extension) | ||||
|         # TODO: handle self.extension_args | ||||
|         return ': '.join(text) | ||||
|  | ||||
|  | ||||
| class IqTimeout(XMPPError): | ||||
|  | ||||
| @@ -65,7 +77,7 @@ class IqTimeout(XMPPError): | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, iq): | ||||
|         super(IqTimeout, self).__init__( | ||||
|         super().__init__( | ||||
|                 condition='remote-server-timeout', | ||||
|                 etype='cancel') | ||||
|  | ||||
| @@ -82,7 +94,7 @@ class IqError(XMPPError): | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, iq): | ||||
|         super(IqError, self).__init__( | ||||
|         super().__init__( | ||||
|                 condition=iq['error']['condition'], | ||||
|                 text=iq['error']['text'], | ||||
|                 etype=iq['error']['type']) | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_bind.stanza import Bind | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureBind) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_bind = FeatureBind | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
|  | ||||
| from slixmpp.jid import JID | ||||
| @@ -34,6 +35,7 @@ class FeatureBind(BasePlugin): | ||||
|         register_stanza_plugin(Iq, stanza.Bind) | ||||
|         register_stanza_plugin(StreamFeatures, stanza.Bind) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def _handle_bind_resource(self, features): | ||||
|         """ | ||||
|         Handle requesting a specific resource. | ||||
| @@ -49,10 +51,10 @@ class FeatureBind(BasePlugin): | ||||
|         if self.xmpp.requested_jid.resource: | ||||
|             iq['bind']['resource'] = self.xmpp.requested_jid.resource | ||||
|  | ||||
|         iq.send(callback=self._on_bind_response) | ||||
|         yield from iq.send(callback=self._on_bind_response) | ||||
|  | ||||
|     def _on_bind_response(self, response): | ||||
|         self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) | ||||
|         self.xmpp.boundjid = JID(response['bind']['jid']) | ||||
|         self.xmpp.bound = True | ||||
|         self.xmpp.event('session_bind', self.xmpp.boundjid) | ||||
|         self.xmpp.session_bind_event.set() | ||||
|   | ||||
| @@ -16,7 +16,3 @@ from slixmpp.features.feature_mechanisms.stanza import Failure | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureMechanisms) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_mechanisms = FeatureMechanisms | ||||
|   | ||||
| @@ -190,14 +190,14 @@ class FeatureMechanisms(BasePlugin): | ||||
|         except sasl.SASLCancelled: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         except sasl.SASLFailed: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         except sasl.SASLFailed: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         else: | ||||
|             resp.send() | ||||
|  | ||||
| @@ -210,13 +210,13 @@ class FeatureMechanisms(BasePlugin): | ||||
|             resp['value'] = self.mech.process(stanza['value']) | ||||
|         except sasl.SASLCancelled: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLFailed: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         except sasl.SASLFailed: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         else: | ||||
|             if resp.get_value() == '': | ||||
|                 resp.del_value() | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class Mechanisms(ElementBase): | ||||
|         """ | ||||
|         """ | ||||
|         results = [] | ||||
|         mechs = self.findall('{%s}mechanism' % self.namespace) | ||||
|         mechs = self.xml.findall('{%s}mechanism' % self.namespace) | ||||
|         if mechs: | ||||
|             for mech in mechs: | ||||
|                 results.append(mech.text) | ||||
| @@ -47,7 +47,7 @@ class Mechanisms(ElementBase): | ||||
|     def del_mechanisms(self): | ||||
|         """ | ||||
|         """ | ||||
|         mechs = self.findall('{%s}mechanism' % self.namespace) | ||||
|         mechs = self.xml.findall('{%s}mechanism' % self.namespace) | ||||
|         if mechs: | ||||
|             for mech in mechs: | ||||
|                 self.xml.remove(mech) | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_rosterver.stanza import RosterVer | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureRosterVer) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_rosterver = FeatureRosterVer | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_session.stanza import Session | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureSession) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_session = FeatureSession | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
|  | ||||
| from slixmpp.stanza import Iq, StreamFeatures | ||||
| @@ -34,6 +35,7 @@ class FeatureSession(BasePlugin): | ||||
|         register_stanza_plugin(Iq, stanza.Session) | ||||
|         register_stanza_plugin(StreamFeatures, stanza.Session) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def _handle_start_session(self, features): | ||||
|         """ | ||||
|         Handle the start of the session. | ||||
| @@ -44,7 +46,7 @@ class FeatureSession(BasePlugin): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq.enable('session') | ||||
|         iq.send(callback=self._on_start_session_response) | ||||
|         yield from iq.send(callback=self._on_start_session_response) | ||||
|  | ||||
|     def _on_start_session_response(self, response): | ||||
|         self.xmpp.features.add('session') | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_starttls.stanza import * | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureSTARTTLS) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_starttls = FeatureSTARTTLS | ||||
|   | ||||
							
								
								
									
										529
									
								
								slixmpp/jid.py
									
									
									
									
									
								
							
							
						
						
									
										529
									
								
								slixmpp/jid.py
									
									
									
									
									
								
							| @@ -11,24 +11,15 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import re | ||||
| import socket | ||||
| import stringprep | ||||
| import threading | ||||
| import encodings.idna | ||||
|  | ||||
| from copy import deepcopy | ||||
| from functools import lru_cache | ||||
|  | ||||
| from slixmpp.util import stringprep_profiles | ||||
| from collections import OrderedDict | ||||
| from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError | ||||
|  | ||||
| #: These characters are not allowed to appear in a JID. | ||||
| ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \ | ||||
|                 '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \ | ||||
|                 '\x1a\x1b\x1c\x1d\x1e\x1f' + \ | ||||
|                 ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f' | ||||
| HAVE_INET_PTON = hasattr(socket, 'inet_pton') | ||||
|  | ||||
| #: The basic regex pattern that a JID must match in order to determine | ||||
| #: the local, domain, and resource parts. This regex does NOT do any | ||||
| @@ -38,22 +29,8 @@ JID_PATTERN = re.compile( | ||||
| ) | ||||
|  | ||||
| #: The set of escape sequences for the characters not allowed by nodeprep. | ||||
| JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f', | ||||
|                             '\\3a', '\\3c', '\\3e', '\\40', '\\5c']) | ||||
|  | ||||
| #: A mapping of unallowed characters to their escape sequences. An escape | ||||
| #: sequence for '\' is also included since it must also be escaped in | ||||
| #: certain situations. | ||||
| JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20', | ||||
|                               '"': '\\22', | ||||
|                               '&': '\\26', | ||||
|                               "'": '\\27', | ||||
|                               '/': '\\2f', | ||||
|                               ':': '\\3a', | ||||
|                               '<': '\\3c', | ||||
|                               '>': '\\3e', | ||||
|                               '@': '\\40', | ||||
|                               '\\': '\\5c'} | ||||
| JID_ESCAPE_SEQUENCES = {'\\20', '\\22', '\\26', '\\27', '\\2f', | ||||
|                         '\\3a', '\\3c', '\\3e', '\\40', '\\5c'} | ||||
|  | ||||
| #: The reverse mapping of escape sequences to their original forms. | ||||
| JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', | ||||
| @@ -67,70 +44,9 @@ JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', | ||||
|                                 '\\40': '@', | ||||
|                                 '\\5c': '\\'} | ||||
|  | ||||
| JID_CACHE = OrderedDict() | ||||
| JID_CACHE_LOCK = threading.Lock() | ||||
| JID_CACHE_MAX_SIZE = 1024 | ||||
|  | ||||
| def _cache(key, parts, locked): | ||||
|     JID_CACHE[key] = (parts, locked) | ||||
|     if len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|         with JID_CACHE_LOCK: | ||||
|             while len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|                 found = None | ||||
|                 for key, item in JID_CACHE.items(): | ||||
|                     if not item[1]: # if not locked | ||||
|                         found = key | ||||
|                         break | ||||
|                 if not found: # more than MAX_SIZE locked | ||||
|                     # warn? | ||||
|                     break | ||||
|                 del JID_CACHE[found] | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The nodeprep profile of stringprep used to validate the local, | ||||
| #: or username, portion of a JID. | ||||
| nodeprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[ | ||||
|         stringprep_profiles.b1_mapping, | ||||
|         stringprep.map_table_b2], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c11, | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9, | ||||
|         lambda c: c in ' \'"&/:<>@'], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The resourceprep profile of stringprep, which is used to validate | ||||
| #: the resource portion of a JID. | ||||
| resourceprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[stringprep_profiles.b1_mapping], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
|  | ||||
| # TODO: Find the best cache size for a standard usage. | ||||
| @lru_cache(maxsize=1024) | ||||
| def _parse_jid(data): | ||||
|     """ | ||||
|     Parse string data into the node, domain, and resource | ||||
| @@ -162,17 +78,19 @@ def _validate_node(node): | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by nodeprep. | ||||
|     """ | ||||
|     try: | ||||
|         if node is not None: | ||||
|             node = nodeprep(node) | ||||
|     if node is None: | ||||
|         return '' | ||||
|  | ||||
|             if not node: | ||||
|                 raise InvalidJID('Localpart must not be 0 bytes') | ||||
|             if len(node) > 1023: | ||||
|                 raise InvalidJID('Localpart must be less than 1024 bytes') | ||||
|             return node | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid local part') | ||||
|     try: | ||||
|         node = nodeprep(node) | ||||
|     except StringprepError: | ||||
|         raise InvalidJID('Nodeprep failed') | ||||
|  | ||||
|     if not node: | ||||
|         raise InvalidJID('Localpart must not be 0 bytes') | ||||
|     if len(node) > 1023: | ||||
|         raise InvalidJID('Localpart must be less than 1024 bytes') | ||||
|     return node | ||||
|  | ||||
|  | ||||
| def _validate_domain(domain): | ||||
| @@ -199,10 +117,10 @@ def _validate_domain(domain): | ||||
|         pass | ||||
|  | ||||
|     # Check if this is an IPv6 address | ||||
|     if not ip_addr and hasattr(socket, 'inet_pton'): | ||||
|     if not ip_addr and HAVE_INET_PTON and domain[0] == '[' and domain[-1] == ']': | ||||
|         try: | ||||
|             socket.inet_pton(socket.AF_INET6, domain.strip('[]')) | ||||
|             domain = '[%s]' % domain.strip('[]') | ||||
|             ip = domain[1:-1] | ||||
|             socket.inet_pton(socket.AF_INET6, ip) | ||||
|             ip_addr = True | ||||
|         except (socket.error, ValueError): | ||||
|             pass | ||||
| @@ -213,31 +131,19 @@ def _validate_domain(domain): | ||||
|         if domain and domain[-1] == '.': | ||||
|             domain = domain[:-1] | ||||
|  | ||||
|         domain_parts = [] | ||||
|         try: | ||||
|             domain = idna(domain) | ||||
|         except StringprepError: | ||||
|             raise InvalidJID('idna validation failed') | ||||
|  | ||||
|         if ':' in domain: | ||||
|             raise InvalidJID('Domain containing a port') | ||||
|         for label in domain.split('.'): | ||||
|             try: | ||||
|                 label = encodings.idna.nameprep(label) | ||||
|                 encodings.idna.ToASCII(label) | ||||
|                 pass_nameprep = True | ||||
|             except UnicodeError: | ||||
|                 pass_nameprep = False | ||||
|  | ||||
|             if not pass_nameprep: | ||||
|                 raise InvalidJID('Could not encode domain as ASCII') | ||||
|  | ||||
|             if label.startswith('xn--'): | ||||
|                 label = encodings.idna.ToUnicode(label) | ||||
|  | ||||
|             for char in label: | ||||
|                 if char in ILLEGAL_CHARS: | ||||
|                     raise InvalidJID('Domain contains illegal characters') | ||||
|  | ||||
|             if not label: | ||||
|                 raise InvalidJID('Domain containing too many dots') | ||||
|             if '-' in (label[0], label[-1]): | ||||
|                 raise InvalidJID('Domain started or ended with -') | ||||
|  | ||||
|             domain_parts.append(label) | ||||
|         domain = '.'.join(domain_parts) | ||||
|  | ||||
|     if not domain: | ||||
|         raise InvalidJID('Domain must not be 0 bytes') | ||||
|     if len(domain) > 1023: | ||||
| @@ -253,42 +159,19 @@ def _validate_resource(resource): | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by resourceprep. | ||||
|     """ | ||||
|     if resource is None: | ||||
|         return '' | ||||
|  | ||||
|     try: | ||||
|         if resource is not None: | ||||
|             resource = resourceprep(resource) | ||||
|         resource = resourceprep(resource) | ||||
|     except StringprepError: | ||||
|         raise InvalidJID('Resourceprep failed') | ||||
|  | ||||
|             if not resource: | ||||
|                 raise InvalidJID('Resource must not be 0 bytes') | ||||
|             if len(resource) > 1023: | ||||
|                 raise InvalidJID('Resource must be less than 1024 bytes') | ||||
|             return resource | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid resource') | ||||
|  | ||||
|  | ||||
| def _escape_node(node): | ||||
|     """Escape the local portion of a JID.""" | ||||
|     result = [] | ||||
|  | ||||
|     for i, char in enumerate(node): | ||||
|         if char == '\\': | ||||
|             if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES: | ||||
|                 result.append('\\5c') | ||||
|                 continue | ||||
|         result.append(char) | ||||
|  | ||||
|     for i, char in enumerate(result): | ||||
|         if char != '\\': | ||||
|             result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char) | ||||
|  | ||||
|     escaped = ''.join(result) | ||||
|  | ||||
|     if escaped.startswith('\\20') or escaped.endswith('\\20'): | ||||
|         raise InvalidJID('Escaped local part starts or ends with "\\20"') | ||||
|  | ||||
|     _validate_node(escaped) | ||||
|  | ||||
|     return escaped | ||||
|     if not resource: | ||||
|         raise InvalidJID('Resource must not be 0 bytes') | ||||
|     if len(resource) > 1023: | ||||
|         raise InvalidJID('Resource must be less than 1024 bytes') | ||||
|     return resource | ||||
|  | ||||
|  | ||||
| def _unescape_node(node): | ||||
| @@ -313,9 +196,7 @@ def _unescape_node(node): | ||||
|             seq = seq[1:] | ||||
|         else: | ||||
|             unescaped.append(char) | ||||
|     unescaped = ''.join(unescaped) | ||||
|  | ||||
|     return unescaped | ||||
|     return ''.join(unescaped) | ||||
|  | ||||
|  | ||||
| def _format_jid(local=None, domain=None, resource=None): | ||||
| @@ -327,16 +208,15 @@ def _format_jid(local=None, domain=None, resource=None): | ||||
|  | ||||
|     :return: A full or bare JID string. | ||||
|     """ | ||||
|     result = [] | ||||
|     if local: | ||||
|         result.append(local) | ||||
|         result.append('@') | ||||
|     if domain: | ||||
|         result.append(domain) | ||||
|     if resource: | ||||
|         result.append('/') | ||||
|         result.append(resource) | ||||
|     return ''.join(result) | ||||
|     if domain is None: | ||||
|         return '' | ||||
|     if local is not None: | ||||
|         result = local + '@' + domain | ||||
|     else: | ||||
|         result = domain | ||||
|     if resource is not None: | ||||
|         result += '/' + resource | ||||
|     return result | ||||
|  | ||||
|  | ||||
| class InvalidJID(ValueError): | ||||
| @@ -349,47 +229,47 @@ class InvalidJID(ValueError): | ||||
|     """ | ||||
|  | ||||
| # pylint: disable=R0903 | ||||
| class UnescapedJID(object): | ||||
| class UnescapedJID: | ||||
|  | ||||
|     """ | ||||
|     .. versionadded:: 1.1.10 | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, local, domain, resource): | ||||
|         self._jid = (local, domain, resource) | ||||
|     __slots__ = ('_node', '_domain', '_resource') | ||||
|  | ||||
|     # pylint: disable=R0911 | ||||
|     def __getattr__(self, name): | ||||
|     def __init__(self, node, domain, resource): | ||||
|         self._node = node | ||||
|         self._domain = domain | ||||
|         self._resource = resource | ||||
|  | ||||
|     def __getattribute__(self, name): | ||||
|         """Retrieve the given JID component. | ||||
|  | ||||
|         :param name: one of: user, server, domain, resource, | ||||
|                      full, or bare. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             return self._jid[2] or '' | ||||
|         elif name in ('user', 'username', 'local', 'node'): | ||||
|             return self._jid[0] or '' | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             return self._jid[1] or '' | ||||
|         elif name in ('full', 'jid'): | ||||
|             return _format_jid(*self._jid) | ||||
|         elif name == 'bare': | ||||
|             return _format_jid(self._jid[0], self._jid[1]) | ||||
|         elif name == '_jid': | ||||
|             return getattr(super(JID, self), '_jid') | ||||
|         else: | ||||
|             return None | ||||
|             return self._resource or '' | ||||
|         if name in ('user', 'username', 'local', 'node'): | ||||
|             return self._node or '' | ||||
|         if name in ('server', 'domain', 'host'): | ||||
|             return self._domain or '' | ||||
|         if name in ('full', 'jid'): | ||||
|             return _format_jid(self._node, self._domain, self._resource) | ||||
|         if name == 'bare': | ||||
|             return _format_jid(self._node, self._domain) | ||||
|         return object.__getattribute__(self, name) | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|         return _format_jid(self._node, self._domain, self._resource) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|         return _format_jid(self._node, self._domain, self._resource) | ||||
|  | ||||
|  | ||||
| class JID(object): | ||||
| class JID: | ||||
|  | ||||
|     """ | ||||
|     A representation of a Jabber ID, or JID. | ||||
| @@ -401,13 +281,13 @@ class JID(object): | ||||
|     The JID is a full JID otherwise. | ||||
|  | ||||
|     **JID Properties:** | ||||
|         :jid: Alias for ``full``. | ||||
|         :full: The string value of the full JID. | ||||
|         :jid: Alias for ``full``. | ||||
|         :bare: The string value of the bare JID. | ||||
|         :user: The username portion of the JID. | ||||
|         :username: Alias for ``user``. | ||||
|         :local: Alias for ``user``. | ||||
|         :node: Alias for ``user``. | ||||
|         :node: The node portion of the JID. | ||||
|         :user: Alias for ``node``. | ||||
|         :local: Alias for ``node``. | ||||
|         :username: Alias for ``node``. | ||||
|         :domain: The domain name portion of the JID. | ||||
|         :server: Alias for ``domain``. | ||||
|         :host: Alias for ``domain``. | ||||
| @@ -415,67 +295,27 @@ class JID(object): | ||||
|  | ||||
|     :param string jid: | ||||
|         A string of the form ``'[user@]domain[/resource]'``. | ||||
|     :param string local: | ||||
|         Optional. Specify the local, or username, portion | ||||
|         of the JID. If provided, it will override the local | ||||
|         value provided by the `jid` parameter. The given | ||||
|         local value will also be escaped if necessary. | ||||
|     :param string domain: | ||||
|         Optional. Specify the domain of the JID. If | ||||
|         provided, it will override the domain given by | ||||
|         the `jid` parameter. | ||||
|     :param string resource: | ||||
|         Optional. Specify the resource value of the JID. | ||||
|         If provided, it will override the domain given | ||||
|         by the `jid` parameter. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|     """ | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __init__(self, jid=None, **kwargs): | ||||
|         locked = kwargs.get('cache_lock', False) | ||||
|         in_local = kwargs.get('local', None) | ||||
|         in_domain = kwargs.get('domain', None) | ||||
|         in_resource = kwargs.get('resource', None) | ||||
|         parts = None | ||||
|         if in_local or in_domain or in_resource: | ||||
|             parts = (in_local, in_domain, in_resource) | ||||
|     __slots__ = ('_node', '_domain', '_resource', '_bare', '_full') | ||||
|  | ||||
|         # only check cache if there is a jid string, or parts, not if there | ||||
|         # are both | ||||
|         self._jid = None | ||||
|         key = None | ||||
|         if (jid is not None) and (parts is None): | ||||
|             if isinstance(jid, JID): | ||||
|                 # it's already good to go, and there are no additions | ||||
|                 self._jid = jid._jid | ||||
|                 return | ||||
|             key = jid | ||||
|             self._jid, locked = JID_CACHE.get(jid, (None, locked)) | ||||
|         elif jid is None and parts is not None: | ||||
|             key = parts | ||||
|             self._jid, locked = JID_CACHE.get(parts, (None, locked)) | ||||
|         if not self._jid: | ||||
|             if not jid: | ||||
|                 parsed_jid = (None, None, None) | ||||
|             elif not isinstance(jid, JID): | ||||
|                 parsed_jid = _parse_jid(jid) | ||||
|             else: | ||||
|                 parsed_jid = jid._jid | ||||
|  | ||||
|             local, domain, resource = parsed_jid | ||||
|  | ||||
|             if 'local' in kwargs: | ||||
|                 local = _escape_node(in_local) | ||||
|             if 'domain' in kwargs: | ||||
|                 domain = _validate_domain(in_domain) | ||||
|             if 'resource' in kwargs: | ||||
|                 resource = _validate_resource(in_resource) | ||||
|  | ||||
|             self._jid = (local, domain, resource) | ||||
|             if key: | ||||
|                 _cache(key, self._jid, locked) | ||||
|     def __init__(self, jid=None): | ||||
|         if not jid: | ||||
|             self._node = '' | ||||
|             self._domain = '' | ||||
|             self._resource = '' | ||||
|             self._bare = '' | ||||
|             self._full = '' | ||||
|             return | ||||
|         elif not isinstance(jid, JID): | ||||
|             self._node, self._domain, self._resource = _parse_jid(jid) | ||||
|         else: | ||||
|             self._node = jid._node | ||||
|             self._domain = jid._domain | ||||
|             self._resource = jid._resource | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     def unescape(self): | ||||
|         """Return an unescaped JID object. | ||||
| @@ -488,151 +328,146 @@ class JID(object): | ||||
|  | ||||
|         .. versionadded:: 1.1.10 | ||||
|         """ | ||||
|         return UnescapedJID(_unescape_node(self._jid[0]), | ||||
|                             self._jid[1], | ||||
|                             self._jid[2]) | ||||
|         return UnescapedJID(_unescape_node(self._node), | ||||
|                             self._domain, | ||||
|                             self._resource) | ||||
|  | ||||
|     def regenerate(self): | ||||
|         """No-op | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|     def _update_bare_full(self): | ||||
|         """Format the given JID into a bare and a full JID. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def reset(self, data): | ||||
|         """Start fresh from a new JID string. | ||||
|  | ||||
|         :param string data: A string of the form ``'[user@]domain[/resource]'``. | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|         """ | ||||
|         self._jid = JID(data)._jid | ||||
|  | ||||
|     @property | ||||
|     def resource(self): | ||||
|         return self._jid[2] or '' | ||||
|  | ||||
|     @property | ||||
|     def user(self): | ||||
|         return self._jid[0] or '' | ||||
|  | ||||
|     @property | ||||
|     def local(self): | ||||
|         return self._jid[0] or '' | ||||
|         self._bare = (self._node + '@' + self._domain | ||||
|                       if self._node | ||||
|                       else self._domain) | ||||
|         self._full = (self._bare + '/' + self._resource | ||||
|                       if self._resource | ||||
|                       else self._bare) | ||||
|  | ||||
|     @property | ||||
|     def node(self): | ||||
|         return self._jid[0] or '' | ||||
|         return self._node | ||||
|  | ||||
|     @property | ||||
|     def user(self): | ||||
|         return self._node | ||||
|  | ||||
|     @property | ||||
|     def local(self): | ||||
|         return self._node | ||||
|  | ||||
|     @property | ||||
|     def username(self): | ||||
|         return self._jid[0] or '' | ||||
|  | ||||
|     @property | ||||
|     def bare(self): | ||||
|         return _format_jid(self._jid[0], self._jid[1]) | ||||
|  | ||||
|     @property | ||||
|     def server(self): | ||||
|         return self._jid[1] or '' | ||||
|         return self._node | ||||
|  | ||||
|     @property | ||||
|     def domain(self): | ||||
|         return self._jid[1] or '' | ||||
|         return self._domain | ||||
|  | ||||
|     @property | ||||
|     def server(self): | ||||
|         return self._domain | ||||
|  | ||||
|     @property | ||||
|     def host(self): | ||||
|         return self._jid[1] or '' | ||||
|         return self._domain | ||||
|  | ||||
|     @property | ||||
|     def full(self): | ||||
|         return _format_jid(*self._jid) | ||||
|  | ||||
|     @property | ||||
|     def jid(self): | ||||
|         return _format_jid(*self._jid) | ||||
|     def resource(self): | ||||
|         return self._resource | ||||
|  | ||||
|     @property | ||||
|     def bare(self): | ||||
|         return _format_jid(self._jid[0], self._jid[1]) | ||||
|         return self._bare | ||||
|  | ||||
|     @property | ||||
|     def full(self): | ||||
|         return self._full | ||||
|  | ||||
|     @resource.setter | ||||
|     def resource(self, value): | ||||
|         self._jid = JID(self, resource=value)._jid | ||||
|  | ||||
|     @user.setter | ||||
|     def user(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @username.setter | ||||
|     def username(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @local.setter | ||||
|     def local(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|     @property | ||||
|     def jid(self): | ||||
|         return self._full | ||||
|  | ||||
|     @node.setter | ||||
|     def node(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|         self._node = _validate_node(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @server.setter | ||||
|     def server(self, value): | ||||
|         self._jid = JID(self, domain=value)._jid | ||||
|     @user.setter | ||||
|     def user(self, value): | ||||
|         self._node = _validate_node(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @local.setter | ||||
|     def local(self, value): | ||||
|         self._node = _validate_node(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @username.setter | ||||
|     def username(self, value): | ||||
|         self._node = _validate_node(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @domain.setter | ||||
|     def domain(self, value): | ||||
|         self._jid = JID(self, domain=value)._jid | ||||
|         self._domain = _validate_domain(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @server.setter | ||||
|     def server(self, value): | ||||
|         self._domain = _validate_domain(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @host.setter | ||||
|     def host(self, value): | ||||
|         self._jid = JID(self, domain=value)._jid | ||||
|  | ||||
|     @full.setter | ||||
|     def full(self, value): | ||||
|         self._jid = JID(value)._jid | ||||
|  | ||||
|     @jid.setter | ||||
|     def jid(self, value): | ||||
|         self._jid = JID(value)._jid | ||||
|         self._domain = _validate_domain(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @bare.setter | ||||
|     def bare(self, value): | ||||
|         parsed = JID(value)._jid | ||||
|         self._jid = (parsed[0], parsed[1], self._jid[2]) | ||||
|         node, domain, resource = _parse_jid(value) | ||||
|         assert not resource | ||||
|         self._node = node | ||||
|         self._domain = domain | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @resource.setter | ||||
|     def resource(self, value): | ||||
|         self._resource = _validate_resource(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @full.setter | ||||
|     def full(self, value): | ||||
|         self._node, self._domain, self._resource = _parse_jid(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @jid.setter | ||||
|     def jid(self, value): | ||||
|         self._node, self._domain, self._resource = _parse_jid(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|         return self._full | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|         return self._full | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __eq__(self, other): | ||||
|         """Two JIDs are equal if they have the same full JID value.""" | ||||
|         if isinstance(other, UnescapedJID): | ||||
|             return False | ||||
|         if not isinstance(other, JID): | ||||
|             other = JID(other) | ||||
|  | ||||
|         other = JID(other) | ||||
|         return self._jid == other._jid | ||||
|         return (self._node == other._node and | ||||
|                 self._domain == other._domain and | ||||
|                 self._resource == other._resource) | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __ne__(self, other): | ||||
|         """Two JIDs are considered unequal if they are not equal.""" | ||||
|         return not self == other | ||||
|  | ||||
|     def __hash__(self): | ||||
|         """Hash a JID based on the string version of its full JID.""" | ||||
|         return hash(self.__str__()) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         """Generate a duplicate JID.""" | ||||
|         return JID(self) | ||||
|  | ||||
|     def __deepcopy__(self, memo): | ||||
|         """Generate a duplicate JID.""" | ||||
|         return JID(deepcopy(str(self), memo)) | ||||
|         return hash(self._full) | ||||
|   | ||||
| @@ -47,6 +47,7 @@ __all__ = [ | ||||
|     'xep_0108',  # User Activity | ||||
|     'xep_0115',  # Entity Capabilities | ||||
|     'xep_0118',  # User Tune | ||||
|     'xep_0122',  # Data Forms Validation | ||||
|     'xep_0128',  # Extended Service Discovery | ||||
|     'xep_0131',  # Standard Headers and Internet Metadata | ||||
|     'xep_0133',  # Service Administration | ||||
| @@ -83,4 +84,5 @@ __all__ = [ | ||||
|     'xep_0319',  # Last User Interaction in Presence | ||||
|     'xep_0323',  # IoT Systems Sensor Data | ||||
|     'xep_0325',  # IoT Systems Control | ||||
|     'xep_0332',  # HTTP Over XMPP Transport | ||||
| ] | ||||
|   | ||||
| @@ -142,7 +142,6 @@ class PluginManager(object): | ||||
|         :param dict config: Optional settings dictionary for | ||||
|                             configuring plugin behaviour. | ||||
|         """ | ||||
|         top_level = False | ||||
|         if enabled is None: | ||||
|             enabled = set() | ||||
|  | ||||
| @@ -166,14 +165,14 @@ class PluginManager(object): | ||||
|                     self.enable(dep, enabled=enabled) | ||||
|                 plugin._init() | ||||
|  | ||||
|         if top_level: | ||||
|             for name in enabled: | ||||
|                 if hasattr(self.plugins[name], 'old_style'): | ||||
|                     # Older style plugins require post_init() | ||||
|                     # to run just before stream processing begins, | ||||
|                     # so we don't call it here. | ||||
|                     pass | ||||
|                 self.plugins[name].post_init() | ||||
|         for name in enabled: | ||||
|             if hasattr(self._plugins[name], 'old_style'): | ||||
|                 # Older style plugins require post_init() | ||||
|                 # to run just before stream processing begins, | ||||
|                 # so we don't call it here. | ||||
|                 pass | ||||
|             else: | ||||
|                 self._plugins[name].post_init() | ||||
|  | ||||
|     def enable_all(self, names=None, config=None): | ||||
|         """Enable all registered plugins. | ||||
| @@ -309,7 +308,7 @@ class BasePlugin(object): | ||||
|         if key in self.default_config: | ||||
|             self.config[key] = value | ||||
|         else: | ||||
|             super(BasePlugin, self).__setattr__(key, value) | ||||
|             super().__setattr__(key, value) | ||||
|  | ||||
|     def _init(self): | ||||
|         """Initialize plugin state, such as registering event handlers. | ||||
|   | ||||
| @@ -23,13 +23,13 @@ class GmailQuery(ElementBase): | ||||
|     plugin_attrib = 'gmail' | ||||
|     interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) | ||||
|  | ||||
|     def getSearch(self): | ||||
|     def get_search(self): | ||||
|         return self['q'] | ||||
|  | ||||
|     def setSearch(self, search): | ||||
|     def set_search(self, search): | ||||
|         self['q'] = search | ||||
|  | ||||
|     def delSearch(self): | ||||
|     def del_search(self): | ||||
|         del self['q'] | ||||
|  | ||||
|  | ||||
| @@ -40,17 +40,17 @@ class MailBox(ElementBase): | ||||
|     interfaces = set(('result-time', 'total-matched', 'total-estimate', | ||||
|                       'url', 'threads', 'matched', 'estimate')) | ||||
|  | ||||
|     def getThreads(self): | ||||
|     def get_threads(self): | ||||
|         threads = [] | ||||
|         for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, | ||||
|                                                       MailThread.name)): | ||||
|             threads.append(MailThread(xml=threadXML, parent=None)) | ||||
|         return threads | ||||
|  | ||||
|     def getMatched(self): | ||||
|     def get_matched(self): | ||||
|         return self['total-matched'] | ||||
|  | ||||
|     def getEstimate(self): | ||||
|     def get_estimate(self): | ||||
|         return self['total-estimate'] == '1' | ||||
|  | ||||
|  | ||||
| @@ -62,7 +62,7 @@ class MailThread(ElementBase): | ||||
|                       'senders', 'url', 'labels', 'subject', 'snippet')) | ||||
|     sub_interfaces = set(('labels', 'subject', 'snippet')) | ||||
|  | ||||
|     def getSenders(self): | ||||
|     def get_senders(self): | ||||
|         senders = [] | ||||
|         sendersXML = self.xml.find('{%s}senders' % self.namespace) | ||||
|         if sendersXML is not None: | ||||
| @@ -77,10 +77,10 @@ class MailSender(ElementBase): | ||||
|     plugin_attrib = 'sender' | ||||
|     interfaces = set(('address', 'name', 'originator', 'unread')) | ||||
|  | ||||
|     def getOriginator(self): | ||||
|     def get_originator(self): | ||||
|         return self.xml.attrib.get('originator', '0') == '1' | ||||
|  | ||||
|     def getUnread(self): | ||||
|     def get_unread(self): | ||||
|         return self.xml.attrib.get('unread', '0') == '1' | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										47
									
								
								slixmpp/plugins/google/auth/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								slixmpp/plugins/google/auth/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.xmlstream import ElementBase, ET | ||||
|  | ||||
|  | ||||
| class GoogleAuth(ElementBase): | ||||
|     name = 'auth' | ||||
|     namespace = 'http://www.google.com/talk/protocol/auth' | ||||
|     plugin_attrib = 'google' | ||||
|     interfaces = set(['client_uses_full_bind_result', 'service']) | ||||
|  | ||||
|     discovery_attr= '{%s}client-uses-full-bind-result' % namespace | ||||
|     service_attr= '{%s}service' % namespace | ||||
|  | ||||
|     def setup(self, xml): | ||||
|         """Don't create XML for the plugin.""" | ||||
|         self.xml = ET.Element('') | ||||
|  | ||||
|     def get_client_uses_full_bind_result(self): | ||||
|         return self.parent()._get_attr(self.discovery_attr) == 'true' | ||||
|  | ||||
|     def set_client_uses_full_bind_result(self, value): | ||||
|         if value in (True, 'true'): | ||||
|             self.parent()._set_attr(self.discovery_attr, 'true') | ||||
|         else: | ||||
|             self.parent()._del_attr(self.discovery_attr) | ||||
|  | ||||
|     def del_client_uses_full_bind_result(self): | ||||
|         self.parent()._del_attr(self.discovery_attr) | ||||
|  | ||||
|     def get_service(self): | ||||
|         return self.parent()._get_attr(self.service_attr, '') | ||||
|  | ||||
|     def set_service(self, value): | ||||
|         if value: | ||||
|             self.parent()._set_attr(self.service_attr, value) | ||||
|         else: | ||||
|             self.parent()._del_attr(self.service_attr) | ||||
|  | ||||
|     def del_service(self): | ||||
|         self.parent()._del_attr(self.service_attr) | ||||
							
								
								
									
										90
									
								
								slixmpp/plugins/google/gmail/notifications.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								slixmpp/plugins/google/gmail/notifications.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from slixmpp.stanza import Iq | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| from slixmpp.xmlstream.matcher import MatchXPath | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
| from slixmpp.plugins import BasePlugin | ||||
| from slixmpp.plugins.google.gmail import stanza | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class Gmail(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     Google: Gmail Notifications | ||||
|  | ||||
|     Also see <https://developers.google.com/talk/jep_extensions/gmail>. | ||||
|     """ | ||||
|  | ||||
|     name = 'gmail' | ||||
|     description = 'Google: Gmail Notifications' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, stanza.GmailQuery) | ||||
|         register_stanza_plugin(Iq, stanza.MailBox) | ||||
|         register_stanza_plugin(Iq, stanza.NewMail) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Gmail New Mail', | ||||
|                     MatchXPath('{%s}iq/{%s}%s' % ( | ||||
|                         self.xmpp.default_ns, | ||||
|                         stanza.NewMail.namespace, | ||||
|                         stanza.NewMail.name)), | ||||
|                     self._handle_new_mail)) | ||||
|  | ||||
|         self._last_result_time = None | ||||
|         self._last_result_tid = None | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Gmail New Mail') | ||||
|  | ||||
|     def _handle_new_mail(self, iq): | ||||
|         log.info('Gmail: New email!') | ||||
|         iq.reply().send() | ||||
|         self.xmpp.event('gmail_notification') | ||||
|  | ||||
|     def check(self, timeout=None, callback=None): | ||||
|         last_time = self._last_result_time | ||||
|         last_tid = self._last_result_tid | ||||
|  | ||||
|         callback = lambda iq: self._update_last_results(iq, callback) | ||||
|  | ||||
|         return self.search(newer_time=last_time, | ||||
|                            newer_tid=last_tid, | ||||
|                            timeout=timeout, | ||||
|                            callback=callback) | ||||
|  | ||||
|     def _update_last_results(self, iq, callback=None): | ||||
|         self._last_result_time = iq['gmail_messages']['result_time'] | ||||
|         threads = iq['gmail_messages']['threads'] | ||||
|         if threads: | ||||
|             self._last_result_tid = threads[0]['tid'] | ||||
|         if callback: | ||||
|             callback(iq) | ||||
|  | ||||
|     def search(self, query=None, newer_time=None, newer_tid=None, | ||||
|                      timeout=None, callback=None): | ||||
|         if not query: | ||||
|             log.info('Gmail: Checking for new email') | ||||
|         else: | ||||
|             log.info('Gmail: Searching for emails matching: "%s"', query) | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['to'] = self.xmpp.boundjid.bare | ||||
|         iq['gmail']['search'] = query | ||||
|         iq['gmail']['newer_than_time'] = newer_time | ||||
|         iq['gmail']['newer_than_tid'] = newer_tid | ||||
|         return iq.send(timeout=timeout, callback=callback) | ||||
							
								
								
									
										59
									
								
								slixmpp/plugins/google/nosave/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								slixmpp/plugins/google/nosave/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.jid import JID | ||||
| from slixmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class NoSave(ElementBase): | ||||
|     name = 'x' | ||||
|     namespace = 'google:nosave' | ||||
|     plugin_attrib = 'google_nosave' | ||||
|     interfaces = set(['value']) | ||||
|  | ||||
|     def get_value(self): | ||||
|         return self._get_attr('value', '') == 'enabled' | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         self._set_attr('value', 'enabled' if value else 'disabled') | ||||
|  | ||||
|  | ||||
| class NoSaveQuery(ElementBase): | ||||
|     name = 'query' | ||||
|     namespace = 'google:nosave' | ||||
|     plugin_attrib = 'google_nosave' | ||||
|     interfaces = set() | ||||
|  | ||||
|  | ||||
| class Item(ElementBase): | ||||
|     name = 'item' | ||||
|     namespace = 'google:nosave' | ||||
|     plugin_attrib = 'item' | ||||
|     plugin_multi_attrib = 'items' | ||||
|     interfaces = set(['jid', 'source', 'value']) | ||||
|  | ||||
|     def get_value(self): | ||||
|         return self._get_attr('value', '') == 'enabled' | ||||
|  | ||||
|     def set_value(self, value): | ||||
|         self._set_attr('value', 'enabled' if value else 'disabled') | ||||
|  | ||||
|     def get_jid(self): | ||||
|         return JID(self._get_attr('jid', '')) | ||||
|  | ||||
|     def set_jid(self, value): | ||||
|         self._set_attr('jid', str(value)) | ||||
|  | ||||
|     def get_source(self): | ||||
|         return JID(self._get_attr('source', '')) | ||||
|  | ||||
|     def set_source(self, value): | ||||
|         self._set_attr('source', str(value)) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(NoSaveQuery, Item) | ||||
							
								
								
									
										63
									
								
								slixmpp/plugins/google/settings/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								slixmpp/plugins/google/settings/settings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.stanza import Iq | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| from slixmpp.xmlstream.matcher import StanzaPath | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
| from slixmpp.plugins import BasePlugin | ||||
| from slixmpp.plugins.google.settings import stanza | ||||
|  | ||||
|  | ||||
| class GoogleSettings(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     Google: Gmail Notifications | ||||
|  | ||||
|     Also see <https://developers.google.com/talk/jep_extensions/usersettings>. | ||||
|     """ | ||||
|  | ||||
|     name = 'google_settings' | ||||
|     description = 'Google: User Settings' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, stanza.UserSettings) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Google Settings', | ||||
|                     StanzaPath('iq@type=set/google_settings'), | ||||
|                     self._handle_settings_change)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Google Settings') | ||||
|  | ||||
|     def get(self, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq.enable('google_settings') | ||||
|         return iq.send(timeout=timeout, callback=callback) | ||||
|  | ||||
|     def update(self, settings, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq.enable('google_settings') | ||||
|  | ||||
|         for setting, value in settings.items(): | ||||
|             iq['google_settings'][setting] = value | ||||
|  | ||||
|         return iq.send(timeout=timeout, callback=callback) | ||||
|  | ||||
|     def _handle_settings_change(self, iq): | ||||
|         reply = self.xmpp.Iq() | ||||
|         reply['type'] = 'result' | ||||
|         reply['id'] = iq['id'] | ||||
|         reply['to'] = iq['from'] | ||||
|         reply.send() | ||||
|         self.xmpp.event('google_settings_change', iq) | ||||
| @@ -14,9 +14,3 @@ from slixmpp.plugins.xep_0004.dataforms import XEP_0004 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0004) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0004 = XEP_0004 | ||||
| xep_0004.makeForm = xep_0004.make_form | ||||
| xep_0004.buildForm = xep_0004.build_form | ||||
|   | ||||
| @@ -13,8 +13,9 @@ class FormField(ElementBase): | ||||
|     namespace = 'jabber:x:data' | ||||
|     name = 'field' | ||||
|     plugin_attrib = 'field' | ||||
|     plugin_multi_attrib = 'fields' | ||||
|     interfaces = set(('answer', 'desc', 'required', 'value', | ||||
|                       'options', 'label', 'type', 'var')) | ||||
|                       'label', 'type', 'var')) | ||||
|     sub_interfaces = set(('desc',)) | ||||
|     plugin_tag_map = {} | ||||
|     plugin_attrib_map = {} | ||||
| @@ -165,6 +166,7 @@ class FieldOption(ElementBase): | ||||
|     plugin_attrib = 'option' | ||||
|     interfaces = set(('label', 'value')) | ||||
|     sub_interfaces = set(('value',)) | ||||
|     plugin_multi_attrib = 'options' | ||||
|  | ||||
|  | ||||
| FormField.addOption = FormField.add_option | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import copy | ||||
| import logging | ||||
|  | ||||
| from collections import OrderedDict | ||||
| from slixmpp.thirdparty import OrderedSet | ||||
|  | ||||
| from slixmpp.xmlstream import ElementBase, ET | ||||
| from slixmpp.plugins.xep_0004.stanza import FormField | ||||
| @@ -22,8 +23,7 @@ class Form(ElementBase): | ||||
|     namespace = 'jabber:x:data' | ||||
|     name = 'x' | ||||
|     plugin_attrib = 'form' | ||||
|     interfaces = set(('fields', 'instructions', 'items', | ||||
|                       'reported', 'title', 'type', 'values')) | ||||
|     interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', )) | ||||
|     sub_interfaces = set(('title',)) | ||||
|     form_types = set(('cancel', 'form', 'result', 'submit')) | ||||
|  | ||||
| @@ -43,12 +43,12 @@ class Form(ElementBase): | ||||
|  | ||||
|     @property | ||||
|     def field(self): | ||||
|         return self['fields'] | ||||
|         return self.get_fields() | ||||
|  | ||||
|     def set_type(self, ftype): | ||||
|         self._set_attr('type', ftype) | ||||
|         if ftype == 'submit': | ||||
|             fields = self['fields'] | ||||
|             fields = self.get_fields() | ||||
|             for var in fields: | ||||
|                 field = fields[var] | ||||
|                 del field['type'] | ||||
| @@ -74,24 +74,13 @@ class Form(ElementBase): | ||||
|             field['desc'] = desc | ||||
|             field['required'] = required | ||||
|             if options is not None: | ||||
|                 field['options'] = options | ||||
|                 for option in options: | ||||
|                     field.add_option(**option) | ||||
|         else: | ||||
|             del field['type'] | ||||
|         self.append(field) | ||||
|         return field | ||||
|  | ||||
|     def getXML(self, type='submit'): | ||||
|         self['type'] = type | ||||
|         log.warning("Form.getXML() is deprecated API compatibility " + \ | ||||
|                     "with plugins/old_0004.py") | ||||
|         return self.xml | ||||
|  | ||||
|     def fromXML(self, xml): | ||||
|         log.warning("Form.fromXML() is deprecated API compatibility " + \ | ||||
|                     "with plugins/old_0004.py") | ||||
|         n = Form(xml=xml) | ||||
|         return n | ||||
|  | ||||
|     def add_item(self, values): | ||||
|         itemXML = ET.Element('{%s}item' % self.namespace) | ||||
|         self.xml.append(itemXML) | ||||
| @@ -151,7 +140,6 @@ class Form(ElementBase): | ||||
|         return fields | ||||
|  | ||||
|     def get_instructions(self): | ||||
|         instructions = '' | ||||
|         instsXML = self.xml.findall('{%s}instructions' % self.namespace) | ||||
|         return "\n".join([instXML.text for instXML in instsXML]) | ||||
|  | ||||
| @@ -170,7 +158,7 @@ class Form(ElementBase): | ||||
|     def get_reported(self): | ||||
|         fields = OrderedDict() | ||||
|         xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, | ||||
|                                      FormField.namespace)) | ||||
|                                                            FormField.namespace)) | ||||
|         for field in xml: | ||||
|             field = FormField(xml=field) | ||||
|             fields[field['var']] = field | ||||
| @@ -178,7 +166,7 @@ class Form(ElementBase): | ||||
|  | ||||
|     def get_values(self): | ||||
|         values = OrderedDict() | ||||
|         fields = self['fields'] | ||||
|         fields = self.get_fields() | ||||
|         for var in fields: | ||||
|             values[var] = fields[var]['value'] | ||||
|         return values | ||||
| @@ -195,7 +183,14 @@ class Form(ElementBase): | ||||
|             fields = fields.items() | ||||
|         for var, field in fields: | ||||
|             field['var'] = var | ||||
|             self.add_field(**field) | ||||
|             self.add_field( | ||||
|                 var=field.get('var'), | ||||
|                 label=field.get('label'), | ||||
|                 desc=field.get('desc'), | ||||
|                 required=field.get('required'), | ||||
|                 value=field.get('value'), | ||||
|                 options=field.get('options'), | ||||
|                 type=field.get('type')) | ||||
|  | ||||
|     def set_instructions(self, instructions): | ||||
|         del self['instructions'] | ||||
| @@ -213,17 +208,33 @@ class Form(ElementBase): | ||||
|             self.add_item(item) | ||||
|  | ||||
|     def set_reported(self, reported): | ||||
|         """ | ||||
|         This either needs a dictionary of dictionaries or a dictionary of form fields. | ||||
|         :param reported: | ||||
|         :return: | ||||
|         """ | ||||
|         for var in reported: | ||||
|             field = reported[var] | ||||
|             field['var'] = var | ||||
|             self.add_reported(var, **field) | ||||
|  | ||||
|             if isinstance(field, dict): | ||||
|                 self.add_reported(**field) | ||||
|             else: | ||||
|                 reported = self.xml.find('{%s}reported' % self.namespace) | ||||
|                 if reported is None: | ||||
|                     reported = ET.Element('{%s}reported' % self.namespace) | ||||
|                     self.xml.append(reported) | ||||
|  | ||||
|                 fieldXML = ET.Element('{%s}field' % FormField.namespace) | ||||
|                 reported.append(fieldXML) | ||||
|                 new_field = FormField(xml=fieldXML) | ||||
|                 new_field.values = field.values | ||||
|  | ||||
|     def set_values(self, values): | ||||
|         fields = self['fields'] | ||||
|         fields = self.get_fields() | ||||
|         for field in values: | ||||
|             if field not in fields: | ||||
|             if field not in self.get_fields(): | ||||
|                 fields[field] = self.add_field(var=field) | ||||
|             fields[field]['value'] = values[field] | ||||
|             self.get_fields()[field]['value'] = values[field] | ||||
|  | ||||
|     def merge(self, other): | ||||
|         new = copy.copy(self) | ||||
|   | ||||
| @@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0009) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0009 = XEP_0009 | ||||
|   | ||||
| @@ -25,7 +25,7 @@ def fault2xml(fault): | ||||
|  | ||||
| def xml2fault(params): | ||||
|     vals = [] | ||||
|     for value in params.findall('{%s}value' % _namespace): | ||||
|     for value in params.xml.findall('{%s}value' % _namespace): | ||||
|         vals.append(_xml2py(value)) | ||||
|     fault = dict() | ||||
|     fault['code'] = vals[0]['faultCode'] | ||||
| @@ -98,33 +98,34 @@ def xml2py(params): | ||||
|  | ||||
| def _xml2py(value): | ||||
|     namespace = 'jabber:iq:rpc' | ||||
|     if value.find('{%s}nil' % namespace) is not None: | ||||
|     find_value = value.find | ||||
|     if find_value('{%s}nil' % namespace) is not None: | ||||
|         return None | ||||
|     if value.find('{%s}i4' % namespace) is not None: | ||||
|         return int(value.find('{%s}i4' % namespace).text) | ||||
|     if value.find('{%s}int' % namespace) is not None: | ||||
|         return int(value.find('{%s}int' % namespace).text) | ||||
|     if value.find('{%s}boolean' % namespace) is not None: | ||||
|         return bool(int(value.find('{%s}boolean' % namespace).text)) | ||||
|     if value.find('{%s}string' % namespace) is not None: | ||||
|         return value.find('{%s}string' % namespace).text | ||||
|     if value.find('{%s}double' % namespace) is not None: | ||||
|         return float(value.find('{%s}double' % namespace).text) | ||||
|     if value.find('{%s}base64' % namespace) is not None: | ||||
|         return rpcbase64(value.find('{%s}base64' % namespace).text.encode()) | ||||
|     if value.find('{%s}Base64' % namespace) is not None: | ||||
|     if find_value('{%s}i4' % namespace) is not None: | ||||
|         return int(find_value('{%s}i4' % namespace).text) | ||||
|     if find_value('{%s}int' % namespace) is not None: | ||||
|         return int(find_value('{%s}int' % namespace).text) | ||||
|     if find_value('{%s}boolean' % namespace) is not None: | ||||
|         return bool(int(find_value('{%s}boolean' % namespace).text)) | ||||
|     if find_value('{%s}string' % namespace) is not None: | ||||
|         return find_value('{%s}string' % namespace).text | ||||
|     if find_value('{%s}double' % namespace) is not None: | ||||
|         return float(find_value('{%s}double' % namespace).text) | ||||
|     if find_value('{%s}base64' % namespace) is not None: | ||||
|         return rpcbase64(find_value('{%s}base64' % namespace).text.encode()) | ||||
|     if find_value('{%s}Base64' % namespace) is not None: | ||||
|         # Older versions of XEP-0009 used Base64 | ||||
|         return rpcbase64(value.find('{%s}Base64' % namespace).text.encode()) | ||||
|     if value.find('{%s}dateTime.iso8601' % namespace) is not None: | ||||
|         return rpctime(value.find('{%s}dateTime.iso8601' % namespace).text) | ||||
|     if value.find('{%s}struct' % namespace) is not None: | ||||
|         return rpcbase64(find_value('{%s}Base64' % namespace).text.encode()) | ||||
|     if find_value('{%s}dateTime.iso8601' % namespace) is not None: | ||||
|         return rpctime(find_value('{%s}dateTime.iso8601' % namespace).text) | ||||
|     if find_value('{%s}struct' % namespace) is not None: | ||||
|         struct = {} | ||||
|         for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace): | ||||
|         for member in find_value('{%s}struct' % namespace).findall('{%s}member' % namespace): | ||||
|             struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace)) | ||||
|         return struct | ||||
|     if value.find('{%s}array' % namespace) is not None: | ||||
|     if find_value('{%s}array' % namespace) is not None: | ||||
|         array = [] | ||||
|         for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace): | ||||
|         for val in find_value('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace): | ||||
|             array.append(_xml2py(val)) | ||||
|         return array | ||||
|     raise ValueError() | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from binding import py2xml, xml2py, xml2fault, fault2xml | ||||
| from slixmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml | ||||
| from threading import RLock | ||||
| import abc | ||||
| import inspect | ||||
| @@ -18,6 +18,38 @@ import traceback | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
| def _isstr(obj): | ||||
|     return isinstance(obj, str) | ||||
|  | ||||
|  | ||||
| # Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3. | ||||
| # This decorator is copied from 'six' (https://bitbucket.org/gutworth/six): | ||||
| # | ||||
| # Copyright (c) 2010-2015 Benjamin Peterson | ||||
| # | ||||
| # 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. | ||||
| def _add_metaclass(metaclass): | ||||
|     def wrapper(cls): | ||||
|         orig_vars = cls.__dict__.copy() | ||||
|         slots = orig_vars.get('__slots__') | ||||
|         if slots is not None: | ||||
|             if isinstance(slots, str): | ||||
|                 slots = [slots] | ||||
|             for slots_var in slots: | ||||
|                 orig_vars.pop(slots_var) | ||||
|         orig_vars.pop('__dict__', None) | ||||
|         orig_vars.pop('__weakref__', None) | ||||
|         return metaclass(cls.__name__, cls.__bases__, orig_vars) | ||||
|     return wrapper | ||||
|  | ||||
| def _intercept(method, name, public): | ||||
|     def _resolver(instance, *args, **kwargs): | ||||
|         log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args) | ||||
| @@ -68,7 +100,7 @@ def remote(function_argument, public = True): | ||||
|     if hasattr(function_argument, '__call__'): | ||||
|         return _intercept(function_argument, None, public) | ||||
|     else: | ||||
|         if not isinstance(function_argument, basestring): | ||||
|         if not _isstr(function_argument): | ||||
|             if not isinstance(function_argument, bool): | ||||
|                 raise Exception('Expected an RPC method name or visibility modifier!') | ||||
|             else: | ||||
| @@ -131,7 +163,7 @@ class ACL: | ||||
|  | ||||
|     @classmethod | ||||
|     def _next_token(cls, expression, index): | ||||
|         new_index = expression.find('*', index) | ||||
|         new_index = expression.xml.find('*', index) | ||||
|         if new_index == 0: | ||||
|             return '' | ||||
|         else: | ||||
| @@ -150,7 +182,7 @@ class ACL: | ||||
|             #! print "[TOKEN] '%s'" % token | ||||
|             size = len(token) | ||||
|             if size > 0: | ||||
|                 token_index = value.find(token, position) | ||||
|                 token_index = value.xml.find(token, position) | ||||
|                 if token_index == -1: | ||||
|                     return False | ||||
|                 else: | ||||
| @@ -222,12 +254,11 @@ class TimeoutException(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @_add_metaclass(abc.ABCMeta) | ||||
| class Callback(object): | ||||
|     ''' | ||||
|     A base class for callback handlers. | ||||
|     ''' | ||||
|     __metaclass__ = abc.ABCMeta | ||||
|  | ||||
|  | ||||
|     @abc.abstractproperty | ||||
|     def set_value(self, value): | ||||
| @@ -291,7 +322,7 @@ class Future(Callback): | ||||
|         self._event.set() | ||||
|  | ||||
|  | ||||
|  | ||||
| @_add_metaclass(abc.ABCMeta) | ||||
| class Endpoint(object): | ||||
|     ''' | ||||
|     The Endpoint class is an abstract base class for all objects | ||||
| @@ -303,8 +334,6 @@ class Endpoint(object): | ||||
|     which specifies which object an RPC call refers to. It is the | ||||
|     first part in a RPC method name '<fqn>.<method>'. | ||||
|     ''' | ||||
|     __metaclass__ = abc.ABCMeta | ||||
|  | ||||
|  | ||||
|     def __init__(self, session, target_jid): | ||||
|         ''' | ||||
| @@ -491,7 +520,7 @@ class RemoteSession(object): | ||||
|  | ||||
|     def _find_key(self, dict, value): | ||||
|         """return the key of dictionary dic given the value""" | ||||
|         search = [k for k, v in dict.iteritems() if v == value] | ||||
|         search = [k for k, v in dict.items() if v == value] | ||||
|         if len(search) == 0: | ||||
|             return None | ||||
|         else: | ||||
| @@ -547,7 +576,7 @@ class RemoteSession(object): | ||||
|             result = handler_cls(*args, **kwargs) | ||||
|             Endpoint.__init__(result, self, self._client.boundjid.full) | ||||
|         method_dict = result.get_methods() | ||||
|         for method_name, method in method_dict.iteritems(): | ||||
|         for method_name, method in method_dict.items(): | ||||
|             #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name) | ||||
|             self._register_call(result.FQN(), method, method_name) | ||||
|         self._register_acl(result.FQN(), acl) | ||||
| @@ -569,11 +598,11 @@ class RemoteSession(object): | ||||
|             self._register_callback(pid, callback) | ||||
|             iq.send() | ||||
|  | ||||
|     def close(self): | ||||
|     def close(self, wait=False): | ||||
|         ''' | ||||
|         Closes this session. | ||||
|         ''' | ||||
|         self._client.disconnect(False) | ||||
|         self._client.disconnect(wait=wait) | ||||
|         self._session_close_callback() | ||||
|  | ||||
|     def _on_jabber_rpc_method_call(self, iq): | ||||
| @@ -697,7 +726,8 @@ class Remote(object): | ||||
|             if(client.boundjid.bare in cls._sessions): | ||||
|                 raise RemoteException("There already is a session associated with these credentials!") | ||||
|             else: | ||||
|                 cls._sessions[client.boundjid.bare] = client; | ||||
|                 cls._sessions[client.boundjid.bare] = client | ||||
|  | ||||
|         def _session_close_callback(): | ||||
|             with Remote._lock: | ||||
|                 del cls._sessions[client.boundjid.bare] | ||||
|   | ||||
| @@ -93,7 +93,8 @@ class XEP_0009(BasePlugin): | ||||
|  | ||||
|     def _item_not_found(self, iq): | ||||
|         payload = iq.get_payload() | ||||
|         iq.reply().error().set_payload(payload) | ||||
|         iq = iq.reply() | ||||
|         iq.error().set_payload(payload) | ||||
|         iq['error']['code'] = '404' | ||||
|         iq['error']['type'] = 'cancel' | ||||
|         iq['error']['condition'] = 'item-not-found' | ||||
| @@ -101,7 +102,8 @@ class XEP_0009(BasePlugin): | ||||
|  | ||||
|     def _undefined_condition(self, iq): | ||||
|         payload = iq.get_payload() | ||||
|         iq.reply().error().set_payload(payload) | ||||
|         iq = iq.reply() | ||||
|         iq.error().set_payload(payload) | ||||
|         iq['error']['code'] = '500' | ||||
|         iq['error']['type'] = 'cancel' | ||||
|         iq['error']['condition'] = 'undefined-condition' | ||||
| @@ -109,7 +111,8 @@ class XEP_0009(BasePlugin): | ||||
|  | ||||
|     def _forbidden(self, iq): | ||||
|         payload = iq.get_payload() | ||||
|         iq.reply().error().set_payload(payload) | ||||
|         iq = iq.reply() | ||||
|         iq.error().set_payload(payload) | ||||
|         iq['error']['code'] = '403' | ||||
|         iq['error']['type'] = 'auth' | ||||
|         iq['error']['condition'] = 'forbidden' | ||||
| @@ -117,7 +120,8 @@ class XEP_0009(BasePlugin): | ||||
|  | ||||
|     def _recipient_unvailable(self, iq): | ||||
|         payload = iq.get_payload() | ||||
|         iq.reply().error().set_payload(payload) | ||||
|         iq = iq.reply() | ||||
|         error().set_payload(payload) | ||||
|         iq['error']['code'] = '404' | ||||
|         iq['error']['type'] = 'wait' | ||||
|         iq['error']['condition'] = 'recipient-unavailable' | ||||
| @@ -216,3 +220,4 @@ class XEP_0009(BasePlugin): | ||||
|     def _extract_method(self, stanza): | ||||
|         xml = ET.fromstring("%s" % stanza) | ||||
|         return xml.find("./methodCall/methodName").text | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0012.last_activity import XEP_0012 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0012) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0004 = XEP_0012 | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import logging | ||||
| from datetime import datetime, timedelta | ||||
|  | ||||
| from slixmpp.plugins import BasePlugin, register_plugin | ||||
| from slixmpp import Iq | ||||
| from slixmpp import future_wrapper, Iq | ||||
| from slixmpp.exceptions import XMPPError | ||||
| from slixmpp.xmlstream import JID, register_stanza_plugin | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| @@ -66,7 +66,7 @@ class XEP_0012(BasePlugin): | ||||
|         self.del_last_activity(jid) | ||||
|  | ||||
|     def start_uptime(self, status=None): | ||||
|         self.set_last_activity(jid, 0, status) | ||||
|         self.set_last_activity(None, 0, status) | ||||
|  | ||||
|     def set_last_activity(self, jid=None, seconds=None, status=None): | ||||
|         self.api['set_last_activity'](jid, args={ | ||||
| @@ -76,6 +76,7 @@ class XEP_0012(BasePlugin): | ||||
|     def del_last_activity(self, jid): | ||||
|         self.api['del_last_activity'](jid) | ||||
|  | ||||
|     @future_wrapper | ||||
|     def get_last_activity(self, jid, local=False, ifrom=None, timeout=None, | ||||
|                           callback=None, timeout_callback=None): | ||||
|         if jid is not None and not isinstance(jid, JID): | ||||
| @@ -132,8 +133,7 @@ class XEP_0012(BasePlugin): | ||||
|         if not isinstance(iq, Iq): | ||||
|             reply = self.xmpp.Iq() | ||||
|         else: | ||||
|             iq.reply() | ||||
|             reply = iq | ||||
|             reply = iq.reply() | ||||
|  | ||||
|         if jid not in self._last_activities: | ||||
|             raise XMPPError('service-unavailable') | ||||
|   | ||||
| @@ -67,8 +67,7 @@ class XEP_0027(BasePlugin): | ||||
|         register_stanza_plugin(Message, Encrypted) | ||||
|  | ||||
|         self.xmpp.add_event_handler('unverified_signed_presence', | ||||
|                 self._handle_unverified_signed_presence, | ||||
|                 threaded=True) | ||||
|                 self._handle_unverified_signed_presence) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Signed Presence', | ||||
|   | ||||
| @@ -15,8 +15,3 @@ from slixmpp.plugins.xep_0030.disco import XEP_0030 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0030) | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0030 = XEP_0030 | ||||
| XEP_0030.getInfo = XEP_0030.get_info | ||||
| XEP_0030.make_static = XEP_0030.restore_defaults | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| import logging | ||||
|  | ||||
| from slixmpp import Iq | ||||
| from slixmpp import future_wrapper | ||||
| from slixmpp.plugins import BasePlugin | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| from slixmpp.xmlstream.matcher import StanzaPath | ||||
| @@ -122,6 +123,12 @@ class XEP_0030(BasePlugin): | ||||
|         for op in self._disco_ops: | ||||
|             self.api.register(getattr(self.static, op), op, default=True) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.add_feature('http://jabber.org/protocol/disco#info') | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.del_feature('http://jabber.org/protocol/disco#info') | ||||
|  | ||||
|     def _add_disco_op(self, op, default_handler): | ||||
|         self.api.register(default_handler, op) | ||||
|         self.api.register_default(default_handler, op) | ||||
| @@ -288,6 +295,7 @@ class XEP_0030(BasePlugin): | ||||
|                 'cached': cached} | ||||
|         return self.api['has_identity'](jid, node, ifrom, data) | ||||
|  | ||||
|     @future_wrapper | ||||
|     def get_info(self, jid=None, node=None, local=None, | ||||
|                        cached=None, **kwargs): | ||||
|         """ | ||||
| @@ -362,9 +370,9 @@ class XEP_0030(BasePlugin): | ||||
|         iq['to'] = jid | ||||
|         iq['type'] = 'get' | ||||
|         iq['disco_info']['node'] = node if node else '' | ||||
|         iq.send(timeout=kwargs.get('timeout', None), | ||||
|                 callback=kwargs.get('callback', None), | ||||
|                 timeout_callback=kwargs.get('timeout_callback', None)) | ||||
|         return iq.send(timeout=kwargs.get('timeout', None), | ||||
|                        callback=kwargs.get('callback', None), | ||||
|                        timeout_callback=kwargs.get('timeout_callback', None)) | ||||
|  | ||||
|     def set_info(self, jid=None, node=None, info=None): | ||||
|         """ | ||||
| @@ -375,6 +383,7 @@ class XEP_0030(BasePlugin): | ||||
|             info = info['disco_info'] | ||||
|         self.api['set_info'](jid, node, None, info) | ||||
|  | ||||
|     @future_wrapper | ||||
|     def get_items(self, jid=None, node=None, local=False, **kwargs): | ||||
|         """ | ||||
|         Retrieve the disco#items results from a given JID/node combination. | ||||
| @@ -423,9 +432,9 @@ class XEP_0030(BasePlugin): | ||||
|             raise NotImplementedError("XEP 0059 has not yet been fixed") | ||||
|             return self.xmpp['xep_0059'].iterate(iq, 'disco_items') | ||||
|         else: | ||||
|             iq.send(timeout=kwargs.get('timeout', None), | ||||
|                     callback=kwargs.get('callback', None), | ||||
|                     timeout_callback=kwargs.get('timeout_callback', None)) | ||||
|             return iq.send(timeout=kwargs.get('timeout', None), | ||||
|                            callback=kwargs.get('callback', None), | ||||
|                            timeout_callback=kwargs.get('timeout_callback', None)) | ||||
|  | ||||
|     def set_items(self, jid=None, node=None, **kwargs): | ||||
|         """ | ||||
| @@ -600,7 +609,7 @@ class XEP_0030(BasePlugin): | ||||
|         """ | ||||
|         self.api['del_features'](jid, node, None, kwargs) | ||||
|  | ||||
|     def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}): | ||||
|     def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None): | ||||
|         """ | ||||
|         Execute the most specific node handler for the given | ||||
|         JID/node combination. | ||||
| @@ -611,6 +620,9 @@ class XEP_0030(BasePlugin): | ||||
|             node  -- The node requested. | ||||
|             data  -- Optional, custom data to pass to the handler. | ||||
|         """ | ||||
|         if not data: | ||||
|             data = {} | ||||
|  | ||||
|         return self.api[htype](jid, node, ifrom, data) | ||||
|  | ||||
|     def _handle_disco_info(self, iq): | ||||
| @@ -634,7 +646,7 @@ class XEP_0030(BasePlugin): | ||||
|                 info['id'] = iq['id'] | ||||
|                 info.send() | ||||
|             else: | ||||
|                 iq.reply() | ||||
|                 iq = iq.reply() | ||||
|                 if info: | ||||
|                     info = self._fix_default_info(info) | ||||
|                     iq.set_payload(info.xml) | ||||
| @@ -674,7 +686,7 @@ class XEP_0030(BasePlugin): | ||||
|             if isinstance(items, Iq): | ||||
|                 items.send() | ||||
|             else: | ||||
|                 iq.reply() | ||||
|                 iq = iq.reply() | ||||
|                 if items: | ||||
|                     iq.set_payload(items.xml) | ||||
|                 iq.send() | ||||
|   | ||||
| @@ -120,7 +120,7 @@ class DiscoInfo(ElementBase): | ||||
|                 id_xml.attrib['{%s}lang' % self.xml_ns] = lang | ||||
|             if name: | ||||
|                 id_xml.attrib['name'] = name | ||||
|             self.xml.append(id_xml) | ||||
|             self.xml.insert(0, id_xml) | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
| @@ -137,7 +137,7 @@ class DiscoInfo(ElementBase): | ||||
|         identity = (category, itype, lang) | ||||
|         if identity in self._identities: | ||||
|             self._identities.remove(identity) | ||||
|             for id_xml in self.findall('{%s}identity' % self.namespace): | ||||
|             for id_xml in self.xml.findall('{%s}identity' % self.namespace): | ||||
|                 id = (id_xml.attrib['category'], | ||||
|                       id_xml.attrib['type'], | ||||
|                       id_xml.attrib.get('{%s}lang' % self.xml_ns, None)) | ||||
| @@ -163,7 +163,7 @@ class DiscoInfo(ElementBase): | ||||
|             identities = set() | ||||
|         else: | ||||
|             identities = [] | ||||
|         for id_xml in self.findall('{%s}identity' % self.namespace): | ||||
|         for id_xml in self.xml.findall('{%s}identity' % self.namespace): | ||||
|             xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None) | ||||
|             if lang is None or xml_lang == lang: | ||||
|                 id = (id_xml.attrib['category'], | ||||
| @@ -205,7 +205,7 @@ class DiscoInfo(ElementBase): | ||||
|         Arguments: | ||||
|             lang -- Optional, standard xml:lang value. | ||||
|         """ | ||||
|         for id_xml in self.findall('{%s}identity' % self.namespace): | ||||
|         for id_xml in self.xml.findall('{%s}identity' % self.namespace): | ||||
|             if lang is None: | ||||
|                 self.xml.remove(id_xml) | ||||
|             elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang: | ||||
| @@ -239,7 +239,7 @@ class DiscoInfo(ElementBase): | ||||
|         """ | ||||
|         if feature in self._features: | ||||
|             self._features.remove(feature) | ||||
|             for feature_xml in self.findall('{%s}feature' % self.namespace): | ||||
|             for feature_xml in self.xml.findall('{%s}feature' % self.namespace): | ||||
|                 if feature_xml.attrib['var'] == feature: | ||||
|                     self.xml.remove(feature_xml) | ||||
|                     return True | ||||
| @@ -251,7 +251,7 @@ class DiscoInfo(ElementBase): | ||||
|             features = set() | ||||
|         else: | ||||
|             features = [] | ||||
|         for feature_xml in self.findall('{%s}feature' % self.namespace): | ||||
|         for feature_xml in self.xml.findall('{%s}feature' % self.namespace): | ||||
|             if dedupe: | ||||
|                 features.add(feature_xml.attrib['var']) | ||||
|             else: | ||||
| @@ -272,5 +272,5 @@ class DiscoInfo(ElementBase): | ||||
|     def del_features(self): | ||||
|         """Remove all features.""" | ||||
|         self._features = set() | ||||
|         for feature_xml in self.findall('{%s}feature' % self.namespace): | ||||
|         for feature_xml in self.xml.findall('{%s}feature' % self.namespace): | ||||
|             self.xml.remove(feature_xml) | ||||
|   | ||||
| @@ -95,7 +95,7 @@ class DiscoItems(ElementBase): | ||||
|             node -- Optional extra identifying information. | ||||
|         """ | ||||
|         if (jid, node) in self._items: | ||||
|             for item_xml in self.findall('{%s}item' % self.namespace): | ||||
|             for item_xml in self.xml.findall('{%s}item' % self.namespace): | ||||
|                 item = (item_xml.attrib['jid'], | ||||
|                         item_xml.attrib.get('node', None)) | ||||
|                 if item == (jid, node): | ||||
|   | ||||
| @@ -7,7 +7,6 @@ | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| from slixmpp import Iq | ||||
| from slixmpp.exceptions import XMPPError, IqError, IqTimeout | ||||
| @@ -48,7 +47,6 @@ class StaticDisco(object): | ||||
|         self.nodes = {} | ||||
|         self.xmpp = xmpp | ||||
|         self.disco = disco | ||||
|         self.lock = threading.RLock() | ||||
|  | ||||
|     def add_node(self, jid=None, node=None, ifrom=None): | ||||
|         """ | ||||
| @@ -59,48 +57,45 @@ class StaticDisco(object): | ||||
|             jid  -- The JID that will own the new stanzas. | ||||
|             node -- The node that will own the new stanzas. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if jid is None: | ||||
|                 jid = self.xmpp.boundjid.full | ||||
|             if node is None: | ||||
|                 node = '' | ||||
|             if ifrom is None: | ||||
|                 ifrom = '' | ||||
|             if isinstance(ifrom, JID): | ||||
|                 ifrom = ifrom.full | ||||
|             if (jid, node, ifrom) not in self.nodes: | ||||
|                 self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(), | ||||
|                                            'items': DiscoItems()} | ||||
|                 self.nodes[(jid, node, ifrom)]['info']['node'] = node | ||||
|                 self.nodes[(jid, node, ifrom)]['items']['node'] = node | ||||
|         if jid is None: | ||||
|             jid = self.xmpp.boundjid.full | ||||
|         if node is None: | ||||
|             node = '' | ||||
|         if ifrom is None: | ||||
|             ifrom = '' | ||||
|         if isinstance(ifrom, JID): | ||||
|             ifrom = ifrom.full | ||||
|         if (jid, node, ifrom) not in self.nodes: | ||||
|             self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(), | ||||
|                                        'items': DiscoItems()} | ||||
|             self.nodes[(jid, node, ifrom)]['info']['node'] = node | ||||
|             self.nodes[(jid, node, ifrom)]['items']['node'] = node | ||||
|  | ||||
|     def get_node(self, jid=None, node=None, ifrom=None): | ||||
|         with self.lock: | ||||
|             if jid is None: | ||||
|                 jid = self.xmpp.boundjid.full | ||||
|             if node is None: | ||||
|                 node = '' | ||||
|             if ifrom is None: | ||||
|                 ifrom = '' | ||||
|             if isinstance(ifrom, JID): | ||||
|                 ifrom = ifrom.full | ||||
|             if (jid, node, ifrom) not in self.nodes: | ||||
|                 self.add_node(jid, node, ifrom) | ||||
|             return self.nodes[(jid, node, ifrom)] | ||||
|         if jid is None: | ||||
|             jid = self.xmpp.boundjid.full | ||||
|         if node is None: | ||||
|             node = '' | ||||
|         if ifrom is None: | ||||
|             ifrom = '' | ||||
|         if isinstance(ifrom, JID): | ||||
|             ifrom = ifrom.full | ||||
|         if (jid, node, ifrom) not in self.nodes: | ||||
|             self.add_node(jid, node, ifrom) | ||||
|         return self.nodes[(jid, node, ifrom)] | ||||
|  | ||||
|     def node_exists(self, jid=None, node=None, ifrom=None): | ||||
|         with self.lock: | ||||
|             if jid is None: | ||||
|                 jid = self.xmpp.boundjid.full | ||||
|             if node is None: | ||||
|                 node = '' | ||||
|             if ifrom is None: | ||||
|                 ifrom = '' | ||||
|             if isinstance(ifrom, JID): | ||||
|                 ifrom = ifrom.full | ||||
|             if (jid, node, ifrom) not in self.nodes: | ||||
|                 return False | ||||
|             return True | ||||
|         if jid is None: | ||||
|             jid = self.xmpp.boundjid.full | ||||
|         if node is None: | ||||
|             node = '' | ||||
|         if ifrom is None: | ||||
|             ifrom = '' | ||||
|         if isinstance(ifrom, JID): | ||||
|             ifrom = ifrom.full | ||||
|         if (jid, node, ifrom) not in self.nodes: | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|     # ================================================================= | ||||
|     # Node Handlers | ||||
| @@ -199,14 +194,13 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if not self.node_exists(jid, node): | ||||
|                 if not node: | ||||
|                     return DiscoInfo() | ||||
|                 else: | ||||
|                     raise XMPPError(condition='item-not-found') | ||||
|         if not self.node_exists(jid, node): | ||||
|             if not node: | ||||
|                 return DiscoInfo() | ||||
|             else: | ||||
|                 return self.get_node(jid, node)['info'] | ||||
|                 raise XMPPError(condition='item-not-found') | ||||
|         else: | ||||
|             return self.get_node(jid, node)['info'] | ||||
|  | ||||
|     def set_info(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -214,9 +208,8 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is a disco#info substanza. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             self.add_node(jid, node) | ||||
|             self.get_node(jid, node)['info'] = data | ||||
|         self.add_node(jid, node) | ||||
|         self.get_node(jid, node)['info'] = data | ||||
|  | ||||
|     def del_info(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -224,9 +217,8 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if self.node_exists(jid, node): | ||||
|                 self.get_node(jid, node)['info'] = DiscoInfo() | ||||
|         if self.node_exists(jid, node): | ||||
|             self.get_node(jid, node)['info'] = DiscoInfo() | ||||
|  | ||||
|     def get_items(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -234,14 +226,13 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if not self.node_exists(jid, node): | ||||
|                 if not node: | ||||
|                     return DiscoItems() | ||||
|                 else: | ||||
|                     raise XMPPError(condition='item-not-found') | ||||
|         if not self.node_exists(jid, node): | ||||
|             if not node: | ||||
|                 return DiscoItems() | ||||
|             else: | ||||
|                 return self.get_node(jid, node)['items'] | ||||
|                 raise XMPPError(condition='item-not-found') | ||||
|         else: | ||||
|             return self.get_node(jid, node)['items'] | ||||
|  | ||||
|     def set_items(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -250,10 +241,9 @@ class StaticDisco(object): | ||||
|         The data parameter may provide: | ||||
|             items -- A set of items in tuple format. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             items = data.get('items', set()) | ||||
|             self.add_node(jid, node) | ||||
|             self.get_node(jid, node)['items']['items'] = items | ||||
|         items = data.get('items', set()) | ||||
|         self.add_node(jid, node) | ||||
|         self.get_node(jid, node)['items']['items'] = items | ||||
|  | ||||
|     def del_items(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -261,9 +251,8 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if self.node_exists(jid, node): | ||||
|                 self.get_node(jid, node)['items'] = DiscoItems() | ||||
|         if self.node_exists(jid, node): | ||||
|             self.get_node(jid, node)['items'] = DiscoItems() | ||||
|  | ||||
|     def add_identity(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -275,13 +264,12 @@ class StaticDisco(object): | ||||
|             name     -- Optional human readable name for this identity. | ||||
|             lang     -- Optional standard xml:lang value. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             self.add_node(jid, node) | ||||
|             self.get_node(jid, node)['info'].add_identity( | ||||
|                     data.get('category', ''), | ||||
|                     data.get('itype', ''), | ||||
|                     data.get('name', None), | ||||
|                     data.get('lang', None)) | ||||
|         self.add_node(jid, node) | ||||
|         self.get_node(jid, node)['info'].add_identity( | ||||
|                 data.get('category', ''), | ||||
|                 data.get('itype', ''), | ||||
|                 data.get('name', None), | ||||
|                 data.get('lang', None)) | ||||
|  | ||||
|     def set_identities(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -291,10 +279,9 @@ class StaticDisco(object): | ||||
|             identities -- A list of identities in tuple form: | ||||
|                             (category, type, name, lang) | ||||
|         """ | ||||
|         with self.lock: | ||||
|             identities = data.get('identities', set()) | ||||
|             self.add_node(jid, node) | ||||
|             self.get_node(jid, node)['info']['identities'] = identities | ||||
|         identities = data.get('identities', set()) | ||||
|         self.add_node(jid, node) | ||||
|         self.get_node(jid, node)['info']['identities'] = identities | ||||
|  | ||||
|     def del_identity(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -306,13 +293,12 @@ class StaticDisco(object): | ||||
|             name     -- Optional human readable name for this identity. | ||||
|             lang     -- Optional, standard xml:lang value. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if self.node_exists(jid, node): | ||||
|                 self.get_node(jid, node)['info'].del_identity( | ||||
|                         data.get('category', ''), | ||||
|                         data.get('itype', ''), | ||||
|                         data.get('name', None), | ||||
|                         data.get('lang', None)) | ||||
|         if self.node_exists(jid, node): | ||||
|             self.get_node(jid, node)['info'].del_identity( | ||||
|                     data.get('category', ''), | ||||
|                     data.get('itype', ''), | ||||
|                     data.get('name', None), | ||||
|                     data.get('lang', None)) | ||||
|  | ||||
|     def del_identities(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -320,9 +306,8 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if self.node_exists(jid, node): | ||||
|                 del self.get_node(jid, node)['info']['identities'] | ||||
|         if self.node_exists(jid, node): | ||||
|             del self.get_node(jid, node)['info']['identities'] | ||||
|  | ||||
|     def add_feature(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -331,10 +316,9 @@ class StaticDisco(object): | ||||
|         The data parameter should include: | ||||
|             feature -- The namespace of the supported feature. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             self.add_node(jid, node) | ||||
|             self.get_node(jid, node)['info'].add_feature( | ||||
|                     data.get('feature', '')) | ||||
|         self.add_node(jid, node) | ||||
|         self.get_node(jid, node)['info'].add_feature( | ||||
|                 data.get('feature', '')) | ||||
|  | ||||
|     def set_features(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -343,10 +327,9 @@ class StaticDisco(object): | ||||
|         The data parameter should include: | ||||
|             features -- The new set of supported features. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             features = data.get('features', set()) | ||||
|             self.add_node(jid, node) | ||||
|             self.get_node(jid, node)['info']['features'] = features | ||||
|         features = data.get('features', set()) | ||||
|         self.add_node(jid, node) | ||||
|         self.get_node(jid, node)['info']['features'] = features | ||||
|  | ||||
|     def del_feature(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -355,10 +338,9 @@ class StaticDisco(object): | ||||
|         The data parameter should include: | ||||
|             feature -- The namespace of the removed feature. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if self.node_exists(jid, node): | ||||
|                 self.get_node(jid, node)['info'].del_feature( | ||||
|                         data.get('feature', '')) | ||||
|         if self.node_exists(jid, node): | ||||
|             self.get_node(jid, node)['info'].del_feature( | ||||
|                     data.get('feature', '')) | ||||
|  | ||||
|     def del_features(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -366,10 +348,9 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if not self.node_exists(jid, node): | ||||
|                 return | ||||
|             del self.get_node(jid, node)['info']['features'] | ||||
|         if not self.node_exists(jid, node): | ||||
|             return | ||||
|         del self.get_node(jid, node)['info']['features'] | ||||
|  | ||||
|     def add_item(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -381,12 +362,11 @@ class StaticDisco(object): | ||||
|                      non-addressable items. | ||||
|             name  -- Optional human readable name for the item. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             self.add_node(jid, node) | ||||
|             self.get_node(jid, node)['items'].add_item( | ||||
|                     data.get('ijid', ''), | ||||
|                     node=data.get('inode', ''), | ||||
|                     name=data.get('name', '')) | ||||
|         self.add_node(jid, node) | ||||
|         self.get_node(jid, node)['items'].add_item( | ||||
|                 data.get('ijid', ''), | ||||
|                 node=data.get('inode', ''), | ||||
|                 name=data.get('name', '')) | ||||
|  | ||||
|     def del_item(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -396,11 +376,10 @@ class StaticDisco(object): | ||||
|             ijid  -- JID of the item to remove. | ||||
|             inode -- Optional extra identifying information. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if self.node_exists(jid, node): | ||||
|                 self.get_node(jid, node)['items'].del_item( | ||||
|                         data.get('ijid', ''), | ||||
|                         node=data.get('inode', None)) | ||||
|         if self.node_exists(jid, node): | ||||
|             self.get_node(jid, node)['items'].del_item( | ||||
|                     data.get('ijid', ''), | ||||
|                     node=data.get('inode', None)) | ||||
|  | ||||
|     def cache_info(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -410,12 +389,11 @@ class StaticDisco(object): | ||||
|         containing the disco info to cache, or | ||||
|         the disco#info substanza itself. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if isinstance(data, Iq): | ||||
|                 data = data['disco_info'] | ||||
|         if isinstance(data, Iq): | ||||
|             data = data['disco_info'] | ||||
|  | ||||
|             self.add_node(jid, node, ifrom) | ||||
|             self.get_node(jid, node, ifrom)['info'] = data | ||||
|         self.add_node(jid, node, ifrom) | ||||
|         self.get_node(jid, node, ifrom)['info'] = data | ||||
|  | ||||
|     def get_cached_info(self, jid, node, ifrom, data): | ||||
|         """ | ||||
| @@ -423,8 +401,7 @@ class StaticDisco(object): | ||||
|  | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if not self.node_exists(jid, node, ifrom): | ||||
|                 return None | ||||
|             else: | ||||
|                 return self.get_node(jid, node, ifrom)['info'] | ||||
|         if not self.node_exists(jid, node, ifrom): | ||||
|             return None | ||||
|         else: | ||||
|             return self.get_node(jid, node, ifrom)['info'] | ||||
|   | ||||
| @@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0033.addresses import XEP_0033 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0033) | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0033 = XEP_0033 | ||||
| Addresses.addAddress = Addresses.add_address | ||||
|   | ||||
| @@ -117,15 +117,12 @@ for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'): | ||||
|     setattr(Addresses, "set_%s" % atype, set_multi) | ||||
|     setattr(Addresses, "del_%s" % atype, del_multi) | ||||
|  | ||||
|     # To retain backwards compatibility: | ||||
|     setattr(Addresses, "get%s" % atype.title(), get_multi) | ||||
|     setattr(Addresses, "set%s" % atype.title(), set_multi) | ||||
|     setattr(Addresses, "del%s" % atype.title(), del_multi) | ||||
|     if atype == 'all': | ||||
|         Addresses.interfaces.add('addresses') | ||||
|         setattr(Addresses, "getAddresses", get_multi) | ||||
|         setattr(Addresses, "setAddresses", set_multi) | ||||
|         setattr(Addresses, "delAddresses", del_multi) | ||||
|         setattr(Addresses, "get_addresses", get_multi) | ||||
|         setattr(Addresses, "set_addresses", set_multi) | ||||
|         setattr(Addresses, "del_addresses", del_multi) | ||||
|  | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Addresses, Address, iterable=True) | ||||
|   | ||||
| @@ -29,82 +29,82 @@ class MUCPresence(ElementBase): | ||||
|     affiliations = set(('', )) | ||||
|     roles = set(('', )) | ||||
|  | ||||
|     def getXMLItem(self): | ||||
|     def get_xml_item(self): | ||||
|         item = self.xml.find('{http://jabber.org/protocol/muc#user}item') | ||||
|         if item is None: | ||||
|             item = ET.Element('{http://jabber.org/protocol/muc#user}item') | ||||
|             self.xml.append(item) | ||||
|         return item | ||||
|  | ||||
|     def getAffiliation(self): | ||||
|     def get_affiliation(self): | ||||
|         #TODO if no affilation, set it to the default and return default | ||||
|         item = self.getXMLItem() | ||||
|         item = self.get_xml_item() | ||||
|         return item.get('affiliation', '') | ||||
|  | ||||
|     def setAffiliation(self, value): | ||||
|         item = self.getXMLItem() | ||||
|     def set_affiliation(self, value): | ||||
|         item = self.get_xml_item() | ||||
|         #TODO check for valid affiliation | ||||
|         item.attrib['affiliation'] = value | ||||
|         return self | ||||
|  | ||||
|     def delAffiliation(self): | ||||
|         item = self.getXMLItem() | ||||
|     def del_affiliation(self): | ||||
|         item = self.get_xml_item() | ||||
|         #TODO set default affiliation | ||||
|         if 'affiliation' in item.attrib: del item.attrib['affiliation'] | ||||
|         return self | ||||
|  | ||||
|     def getJid(self): | ||||
|         item = self.getXMLItem() | ||||
|     def get_jid(self): | ||||
|         item = self.get_xml_item() | ||||
|         return JID(item.get('jid', '')) | ||||
|  | ||||
|     def setJid(self, value): | ||||
|         item = self.getXMLItem() | ||||
|     def set_jid(self, value): | ||||
|         item = self.get_xml_item() | ||||
|         if not isinstance(value, str): | ||||
|             value = str(value) | ||||
|         item.attrib['jid'] = value | ||||
|         return self | ||||
|  | ||||
|     def delJid(self): | ||||
|         item = self.getXMLItem() | ||||
|     def del_jid(self): | ||||
|         item = self.get_xml_item() | ||||
|         if 'jid' in item.attrib: del item.attrib['jid'] | ||||
|         return self | ||||
|  | ||||
|     def getRole(self): | ||||
|         item = self.getXMLItem() | ||||
|     def get_role(self): | ||||
|         item = self.get_xml_item() | ||||
|         #TODO get default role, set default role if none | ||||
|         return item.get('role', '') | ||||
|  | ||||
|     def setRole(self, value): | ||||
|         item = self.getXMLItem() | ||||
|     def set_role(self, value): | ||||
|         item = self.get_xml_item() | ||||
|         #TODO check for valid role | ||||
|         item.attrib['role'] = value | ||||
|         return self | ||||
|  | ||||
|     def delRole(self): | ||||
|         item = self.getXMLItem() | ||||
|     def del_role(self): | ||||
|         item = self.get_xml_item() | ||||
|         #TODO set default role | ||||
|         if 'role' in item.attrib: del item.attrib['role'] | ||||
|         return self | ||||
|  | ||||
|     def getNick(self): | ||||
|     def get_nick(self): | ||||
|         return self.parent()['from'].resource | ||||
|  | ||||
|     def getRoom(self): | ||||
|     def get_room(self): | ||||
|         return self.parent()['from'].bare | ||||
|  | ||||
|     def setNick(self, value): | ||||
|     def set_nick(self, value): | ||||
|         log.warning("Cannot set nick through mucpresence plugin.") | ||||
|         return self | ||||
|  | ||||
|     def setRoom(self, value): | ||||
|     def set_room(self, value): | ||||
|         log.warning("Cannot set room through mucpresence plugin.") | ||||
|         return self | ||||
|  | ||||
|     def delNick(self): | ||||
|     def del_nick(self): | ||||
|         log.warning("Cannot delete nick through mucpresence plugin.") | ||||
|         return self | ||||
|  | ||||
|     def delRoom(self): | ||||
|     def del_room(self): | ||||
|         log.warning("Cannot delete room through mucpresence plugin.") | ||||
|         return self | ||||
|  | ||||
| @@ -121,7 +121,7 @@ class XEP_0045(BasePlugin): | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.rooms = {} | ||||
|         self.ourNicks = {} | ||||
|         self.our_nicks = {} | ||||
|         self.xep = '0045' | ||||
|         # load MUC support in presence stanzas | ||||
|         register_stanza_plugin(Presence, MUCPresence) | ||||
| @@ -135,6 +135,12 @@ class XEP_0045(BasePlugin): | ||||
|             'http://jabber.org/protocol/muc#user', | ||||
|             'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.plugin['xep_0030'].del_feature(feature='http://jabber.org/protocol/muc') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/muc') | ||||
|  | ||||
|     def handle_groupchat_invite(self, inv): | ||||
|         """ Handle an invite into a muc. | ||||
|         """ | ||||
| @@ -154,6 +160,7 @@ class XEP_0045(BasePlugin): | ||||
|         got_online = False | ||||
|         if pr['muc']['room'] not in self.rooms.keys(): | ||||
|             return | ||||
|         self.xmpp.roster[pr['from']].ignore_updates = True | ||||
|         entry = pr['muc'].get_stanza_values() | ||||
|         entry['show'] = pr['show'] | ||||
|         entry['status'] = pr['status'] | ||||
| @@ -194,28 +201,28 @@ class XEP_0045(BasePlugin): | ||||
|         """ | ||||
|         self.xmpp.event('groupchat_subject', msg) | ||||
|  | ||||
|     def jidInRoom(self, room, jid): | ||||
|     def jid_in_room(self, room, jid): | ||||
|         for nick in self.rooms[room]: | ||||
|             entry = self.rooms[room][nick] | ||||
|             if entry is not None and entry['jid'].full == jid: | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
|     def getNick(self, room, jid): | ||||
|     def get_nick(self, room, jid): | ||||
|         for nick in self.rooms[room]: | ||||
|             entry = self.rooms[room][nick] | ||||
|             if entry is not None and entry['jid'].full == jid: | ||||
|                 return nick | ||||
|  | ||||
|     def configureRoom(self, room, form=None, ifrom=None): | ||||
|     def configure_room(self, room, form=None, ifrom=None): | ||||
|         if form is None: | ||||
|             form = self.getRoomConfig(room, ifrom=ifrom) | ||||
|             form = self.get_room_config(room, ifrom=ifrom) | ||||
|         iq = self.xmpp.make_iq_set() | ||||
|         iq['to'] = room | ||||
|         if ifrom is not None: | ||||
|             iq['from'] = ifrom | ||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||
|         form = form.getXML('submit') | ||||
|         form['type'] = 'submit' | ||||
|         query.append(form) | ||||
|         iq.append(query) | ||||
|         # For now, swallow errors to preserve existing API | ||||
| @@ -227,7 +234,7 @@ class XEP_0045(BasePlugin): | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|     def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None): | ||||
|     def join_muc(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None): | ||||
|         """ Join the specified room, requesting 'maxhistory' lines of history. | ||||
|         """ | ||||
|         stanza = self.xmpp.make_presence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom) | ||||
| @@ -251,7 +258,7 @@ class XEP_0045(BasePlugin): | ||||
|             expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)}) | ||||
|             self.xmpp.send(stanza, expect) | ||||
|         self.rooms[room] = {} | ||||
|         self.ourNicks[room] = nick | ||||
|         self.our_nicks[room] = nick | ||||
|  | ||||
|     def destroy(self, room, reason='', altroom = '', ifrom=None): | ||||
|         iq = self.xmpp.make_iq_set() | ||||
| @@ -276,7 +283,7 @@ class XEP_0045(BasePlugin): | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|     def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None): | ||||
|     def set_affiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None): | ||||
|         """ Change room affiliation.""" | ||||
|         if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): | ||||
|             raise TypeError | ||||
| @@ -298,7 +305,7 @@ class XEP_0045(BasePlugin): | ||||
|             return False | ||||
|         return True | ||||
|  | ||||
|     def setRole(self, room, nick, role): | ||||
|     def set_role(self, room, nick, role): | ||||
|         """ Change role property of a nick in a room. | ||||
|             Typically, roles are temporary (they last only as long as you are in the | ||||
|             room), whereas affiliations are permanent (they last across groupchat | ||||
| @@ -330,7 +337,7 @@ class XEP_0045(BasePlugin): | ||||
|         msg.append(x) | ||||
|         self.xmpp.send(msg) | ||||
|  | ||||
|     def leaveMUC(self, room, nick, msg='', pfrom=None): | ||||
|     def leave_muc(self, room, nick, msg='', pfrom=None): | ||||
|         """ Leave the specified room. | ||||
|         """ | ||||
|         if msg: | ||||
| @@ -339,7 +346,7 @@ class XEP_0045(BasePlugin): | ||||
|             self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom) | ||||
|         del self.rooms[room] | ||||
|  | ||||
|     def getRoomConfig(self, room, ifrom=''): | ||||
|     def get_room_config(self, room, ifrom=''): | ||||
|         iq = self.xmpp.make_iq_get('http://jabber.org/protocol/muc#owner') | ||||
|         iq['to'] = room | ||||
|         iq['from'] = ifrom | ||||
| @@ -353,9 +360,9 @@ class XEP_0045(BasePlugin): | ||||
|         form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') | ||||
|         if form is None: | ||||
|             raise ValueError | ||||
|         return self.xmpp.plugin['xep_0004'].buildForm(form) | ||||
|         return self.xmpp.plugin['xep_0004'].build_form(form) | ||||
|  | ||||
|     def cancelConfig(self, room, ifrom=None): | ||||
|     def cancel_config(self, room, ifrom=None): | ||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||
|         x = ET.Element('{jabber:x:data}x', type='cancel') | ||||
|         query.append(x) | ||||
| @@ -364,39 +371,48 @@ class XEP_0045(BasePlugin): | ||||
|         iq['from'] = ifrom | ||||
|         iq.send() | ||||
|  | ||||
|     def setRoomConfig(self, room, config, ifrom=''): | ||||
|     def set_room_config(self, room, config, ifrom=''): | ||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') | ||||
|         x = config.getXML('submit') | ||||
|         query.append(x) | ||||
|         config['type'] = 'submit' | ||||
|         query.append(config) | ||||
|         iq = self.xmpp.make_iq_set(query) | ||||
|         iq['to'] = room | ||||
|         iq['from'] = ifrom | ||||
|         iq.send() | ||||
|  | ||||
|     def getJoinedRooms(self): | ||||
|     def get_joined_rooms(self): | ||||
|         return self.rooms.keys() | ||||
|  | ||||
|     def getOurJidInRoom(self, roomJid): | ||||
|     def get_our_jid_in_room(self, room_jid): | ||||
|         """ Return the jid we're using in a room. | ||||
|         """ | ||||
|         return "%s/%s" % (roomJid, self.ourNicks[roomJid]) | ||||
|         return "%s/%s" % (room_jid, self.our_nicks[room_jid]) | ||||
|  | ||||
|     def getJidProperty(self, room, nick, jidProperty): | ||||
|     def get_jid_property(self, room, nick, jid_property): | ||||
|         """ Get the property of a nick in a room, such as its 'jid' or 'affiliation' | ||||
|             If not found, return None. | ||||
|         """ | ||||
|         if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]: | ||||
|             return self.rooms[room][nick][jidProperty] | ||||
|         if room in self.rooms and nick in self.rooms[room] and jid_property in self.rooms[room][nick]: | ||||
|             return self.rooms[room][nick][jid_property] | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def getRoster(self, room): | ||||
|     def get_roster(self, room): | ||||
|         """ Get the list of nicks in a room. | ||||
|         """ | ||||
|         if room not in self.rooms.keys(): | ||||
|             return None | ||||
|         return self.rooms[room].keys() | ||||
|  | ||||
|     def get_users_by_affiliation(cls, room, affiliation='member', ifrom=None): | ||||
|         if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'): | ||||
|             raise TypeError | ||||
|         query = ET.Element('{http://jabber.org/protocol/muc#admin}query') | ||||
|         item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation}) | ||||
|         query.append(item) | ||||
|         iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get') | ||||
|         iq.append(query) | ||||
|         return iq.send() | ||||
|  | ||||
|  | ||||
| xep_0045 = XEP_0045 | ||||
| register_plugin(XEP_0045) | ||||
|   | ||||
| @@ -15,7 +15,3 @@ from slixmpp.plugins.xep_0047.ibb import XEP_0047 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0047) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0047 = XEP_0047 | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import asyncio | ||||
| import uuid | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| from slixmpp import Message, Iq | ||||
| from slixmpp.exceptions import XMPPError | ||||
| @@ -23,17 +23,11 @@ class XEP_0047(BasePlugin): | ||||
|     default_config = { | ||||
|         'block_size': 4096, | ||||
|         'max_block_size': 8192, | ||||
|         'window_size': 1, | ||||
|         'auto_accept': False, | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self._streams = {} | ||||
|         self._pending_streams = {} | ||||
|         self._pending_lock = threading.Lock() | ||||
|         self._stream_lock = threading.Lock() | ||||
|  | ||||
|         self._preauthed_sids_lock = threading.Lock() | ||||
|         self._preauthed_sids = {} | ||||
|  | ||||
|         register_stanza_plugin(Iq, Open) | ||||
| @@ -85,9 +79,8 @@ class XEP_0047(BasePlugin): | ||||
|         self._streams[(jid, sid, peer_jid)] = stream | ||||
|  | ||||
|     def _del_stream(self, jid, sid, peer_jid, data): | ||||
|         with self._stream_lock: | ||||
|             if (jid, sid, peer_jid) in self._streams: | ||||
|                 del self._streams[(jid, sid, peer_jid)] | ||||
|         if (jid, sid, peer_jid) in self._streams: | ||||
|             del self._streams[(jid, sid, peer_jid)] | ||||
|  | ||||
|     def _accept_stream(self, iq): | ||||
|         receiver = iq['to'] | ||||
| @@ -100,23 +93,20 @@ class XEP_0047(BasePlugin): | ||||
|  | ||||
|     def _authorized(self, jid, sid, ifrom, iq): | ||||
|         if self.auto_accept: | ||||
|             if iq['ibb_open']['block_size'] <= self.max_block_size: | ||||
|                 return True | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     def _authorized_sid(self, jid, sid, ifrom, iq): | ||||
|         with self._preauthed_sids_lock: | ||||
|             if (jid, sid, ifrom) in self._preauthed_sids: | ||||
|                 del self._preauthed_sids[(jid, sid, ifrom)] | ||||
|                 return True | ||||
|             return False | ||||
|         if (jid, sid, ifrom) in self._preauthed_sids: | ||||
|             del self._preauthed_sids[(jid, sid, ifrom)] | ||||
|             return True | ||||
|         return False | ||||
|  | ||||
|     def _preauthorize_sid(self, jid, sid, ifrom, data): | ||||
|         with self._preauthed_sids_lock: | ||||
|             self._preauthed_sids[(jid, sid, ifrom)] = True | ||||
|         self._preauthed_sids[(jid, sid, ifrom)] = True | ||||
|  | ||||
|     def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False, | ||||
|                     ifrom=None, block=True, timeout=None, callback=None): | ||||
|     def open_stream(self, jid, block_size=None, sid=None, use_messages=False, | ||||
|                     ifrom=None, timeout=None, callback=None): | ||||
|         if sid is None: | ||||
|             sid = str(uuid.uuid4()) | ||||
|         if block_size is None: | ||||
| @@ -128,48 +118,28 @@ class XEP_0047(BasePlugin): | ||||
|         iq['from'] = ifrom | ||||
|         iq['ibb_open']['block_size'] = block_size | ||||
|         iq['ibb_open']['sid'] = sid | ||||
|         iq['ibb_open']['stanza'] = 'iq' | ||||
|         iq['ibb_open']['stanza'] = 'message' if use_messages else 'iq' | ||||
|  | ||||
|         stream = IBBytestream(self.xmpp, sid, block_size, | ||||
|                               iq['from'], iq['to'], window, | ||||
|                               use_messages) | ||||
|                               iq['from'], iq['to'], use_messages) | ||||
|  | ||||
|         with self._stream_lock: | ||||
|             self._pending_streams[iq['id']] = stream | ||||
|         stream_future = asyncio.Future() | ||||
|  | ||||
|         self._pending_streams[iq['id']] = stream | ||||
|  | ||||
|         if block: | ||||
|             resp = iq.send(timeout=timeout) | ||||
|             self._handle_opened_stream(resp) | ||||
|             return stream | ||||
|         else: | ||||
|             cb = None | ||||
|         def _handle_opened_stream(iq): | ||||
|             log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from']) | ||||
|             stream.self_jid = iq['to'] | ||||
|             stream.peer_jid = iq['from'] | ||||
|             stream.stream_started = True | ||||
|             self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) | ||||
|             stream_future.set_result(stream) | ||||
|             if callback is not None: | ||||
|                 def chained(resp): | ||||
|                     self._handle_opened_stream(resp) | ||||
|                     callback(resp) | ||||
|                 cb = chained | ||||
|             else: | ||||
|                 cb = self._handle_opened_stream | ||||
|             return iq.send(block=block, timeout=timeout, callback=cb) | ||||
|                 callback(stream) | ||||
|             self.xmpp.event('ibb_stream_start', stream) | ||||
|             self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream) | ||||
|  | ||||
|     def _handle_opened_stream(self, iq): | ||||
|         if iq['type'] == 'result': | ||||
|             with self._stream_lock: | ||||
|                 stream = self._pending_streams.get(iq['id'], None) | ||||
|             if stream is not None: | ||||
|                 log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from']) | ||||
|                 stream.self_jid = iq['to'] | ||||
|                 stream.peer_jid = iq['from'] | ||||
|                 stream.stream_started.set() | ||||
|                 self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) | ||||
|                 self.xmpp.event('ibb_stream_start', stream) | ||||
|                 self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream) | ||||
|         iq.send(timeout=timeout, callback=_handle_opened_stream) | ||||
|  | ||||
|         with self._stream_lock: | ||||
|             if iq['id'] in self._pending_streams: | ||||
|                 del self._pending_streams[iq['id']] | ||||
|         return stream_future | ||||
|  | ||||
|     def _handle_open_request(self, iq): | ||||
|         sid = iq['ibb_open']['sid'] | ||||
| @@ -181,18 +151,16 @@ class XEP_0047(BasePlugin): | ||||
|             raise XMPPError(etype='modify', condition='bad-request') | ||||
|  | ||||
|         if not self._accept_stream(iq): | ||||
|             raise XMPPError(etype='modify', condition='not-acceptable') | ||||
|             raise XMPPError(etype='cancel', condition='not-acceptable') | ||||
|  | ||||
|         if size > self.max_block_size: | ||||
|             raise XMPPError('resource-constraint') | ||||
|  | ||||
|         stream = IBBytestream(self.xmpp, sid, size, | ||||
|                               iq['to'], iq['from'], | ||||
|                               self.window_size) | ||||
|         stream.stream_started.set() | ||||
|                               iq['to'], iq['from']) | ||||
|         stream.stream_started = True | ||||
|         self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) | ||||
|         iq.reply() | ||||
|         iq.send() | ||||
|         iq.reply().send() | ||||
|  | ||||
|         self.xmpp.event('ibb_stream_start', stream) | ||||
|         self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream) | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class Open(ElementBase): | ||||
|     interfaces = set(('block_size', 'sid', 'stanza')) | ||||
|  | ||||
|     def get_block_size(self): | ||||
|         return int(self._get_attr('block-size')) | ||||
|         return int(self._get_attr('block-size', '0')) | ||||
|  | ||||
|     def set_block_size(self, value): | ||||
|         self._set_attr('block-size', str(value)) | ||||
| @@ -47,7 +47,10 @@ class Data(ElementBase): | ||||
|         self._set_attr('seq', str(value)) | ||||
|  | ||||
|     def get_data(self): | ||||
|         b64_data = self.xml.text.strip() | ||||
|         text = self.xml.text | ||||
|         if not text: | ||||
|             raise XMPPError('not-acceptable', 'IBB data element is empty.') | ||||
|         b64_data = text.strip() | ||||
|         if VALID_B64.match(b64_data).group() == b64_data: | ||||
|             return from_b64(b64_data) | ||||
|         else: | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import asyncio | ||||
| import socket | ||||
| import threading | ||||
| import logging | ||||
| from queue import Queue | ||||
|  | ||||
| from slixmpp.stanza import Iq | ||||
| from slixmpp.exceptions import XMPPError | ||||
| @@ -12,11 +11,10 @@ log = logging.getLogger(__name__) | ||||
|  | ||||
| class IBBytestream(object): | ||||
|  | ||||
|     def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False): | ||||
|     def __init__(self, xmpp, sid, block_size, jid, peer, use_messages=False): | ||||
|         self.xmpp = xmpp | ||||
|         self.sid = sid | ||||
|         self.block_size = block_size | ||||
|         self.window_size = window_size | ||||
|         self.use_messages = use_messages | ||||
|  | ||||
|         if jid is None: | ||||
| @@ -27,29 +25,20 @@ class IBBytestream(object): | ||||
|         self.send_seq = -1 | ||||
|         self.recv_seq = -1 | ||||
|  | ||||
|         self._send_seq_lock = threading.Lock() | ||||
|         self._recv_seq_lock = threading.Lock() | ||||
|         self.stream_started = False | ||||
|         self.stream_in_closed = False | ||||
|         self.stream_out_closed = False | ||||
|  | ||||
|         self.stream_started = threading.Event() | ||||
|         self.stream_in_closed = threading.Event() | ||||
|         self.stream_out_closed = threading.Event() | ||||
|         self.recv_queue = asyncio.Queue() | ||||
|  | ||||
|         self.recv_queue = Queue() | ||||
|  | ||||
|         self.send_window = threading.BoundedSemaphore(value=self.window_size) | ||||
|         self.window_ids = set() | ||||
|         self.window_empty = threading.Event() | ||||
|         self.window_empty.set() | ||||
|  | ||||
|     def send(self, data): | ||||
|         if not self.stream_started.is_set() or \ | ||||
|                self.stream_out_closed.is_set(): | ||||
|     @asyncio.coroutine | ||||
|     def send(self, data, timeout=None): | ||||
|         if not self.stream_started or self.stream_out_closed: | ||||
|             raise socket.error | ||||
|         data = data[0:self.block_size] | ||||
|         self.send_window.acquire() | ||||
|         with self._send_seq_lock: | ||||
|             self.send_seq = (self.send_seq + 1) % 65535 | ||||
|             seq = self.send_seq | ||||
|         if len(data) > self.block_size: | ||||
|             data = data[:self.block_size] | ||||
|         self.send_seq = (self.send_seq + 1) % 65535 | ||||
|         seq = self.send_seq | ||||
|         if self.use_messages: | ||||
|             msg = self.xmpp.Message() | ||||
|             msg['to'] = self.peer_jid | ||||
| @@ -59,7 +48,6 @@ class IBBytestream(object): | ||||
|             msg['ibb_data']['seq'] = seq | ||||
|             msg['ibb_data']['data'] = data | ||||
|             msg.send() | ||||
|             self.send_window.release() | ||||
|         else: | ||||
|             iq = self.xmpp.Iq() | ||||
|             iq['type'] = 'set' | ||||
| @@ -68,74 +56,66 @@ class IBBytestream(object): | ||||
|             iq['ibb_data']['sid'] = self.sid | ||||
|             iq['ibb_data']['seq'] = seq | ||||
|             iq['ibb_data']['data'] = data | ||||
|             self.window_empty.clear() | ||||
|             self.window_ids.add(iq['id']) | ||||
|             iq.send(block=False, callback=self._recv_ack) | ||||
|             yield from iq.send(timeout=timeout) | ||||
|         return len(data) | ||||
|  | ||||
|     def sendall(self, data): | ||||
|     @asyncio.coroutine | ||||
|     def sendall(self, data, timeout=None): | ||||
|         sent_len = 0 | ||||
|         while sent_len < len(data): | ||||
|             sent_len += self.send(data[sent_len:]) | ||||
|             sent_len += yield from self.send(data[sent_len:self.block_size], timeout=timeout) | ||||
|  | ||||
|     def _recv_ack(self, iq): | ||||
|         self.window_ids.remove(iq['id']) | ||||
|         if not self.window_ids: | ||||
|             self.window_empty.set() | ||||
|         self.send_window.release() | ||||
|         if iq['type'] == 'error': | ||||
|             self.close() | ||||
|     @asyncio.coroutine | ||||
|     def sendfile(self, file, timeout=None): | ||||
|         while True: | ||||
|             data = file.read(self.block_size) | ||||
|             if not data: | ||||
|                 break | ||||
|             yield from self.send(data, timeout=timeout) | ||||
|  | ||||
|     def _recv_data(self, stanza): | ||||
|         with self._recv_seq_lock: | ||||
|             new_seq = stanza['ibb_data']['seq'] | ||||
|             if new_seq != (self.recv_seq + 1) % 65535: | ||||
|                 self.close() | ||||
|                 raise XMPPError('unexpected-request') | ||||
|             self.recv_seq = new_seq | ||||
|         new_seq = stanza['ibb_data']['seq'] | ||||
|         if new_seq != (self.recv_seq + 1) % 65535: | ||||
|             self.close() | ||||
|             raise XMPPError('unexpected-request') | ||||
|         self.recv_seq = new_seq | ||||
|  | ||||
|         data = stanza['ibb_data']['data'] | ||||
|         if len(data) > self.block_size: | ||||
|             self.close() | ||||
|             raise XMPPError('not-acceptable') | ||||
|  | ||||
|         self.recv_queue.put(data) | ||||
|         self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data}) | ||||
|         self.recv_queue.put_nowait(data) | ||||
|         self.xmpp.event('ibb_stream_data', self) | ||||
|  | ||||
|         if isinstance(stanza, Iq): | ||||
|             stanza.reply() | ||||
|             stanza.send() | ||||
|             stanza.reply().send() | ||||
|  | ||||
|     def recv(self, *args, **kwargs): | ||||
|         return self.read(block=True) | ||||
|         return self.read() | ||||
|  | ||||
|     def read(self, block=True, timeout=None, **kwargs): | ||||
|         if not self.stream_started.is_set() or \ | ||||
|                self.stream_in_closed.is_set(): | ||||
|     def read(self): | ||||
|         if not self.stream_started or self.stream_in_closed: | ||||
|             raise socket.error | ||||
|         if timeout is not None: | ||||
|             block = True | ||||
|         try: | ||||
|             return self.recv_queue.get(block, timeout) | ||||
|         except: | ||||
|             return None | ||||
|         return self.recv_queue.get_nowait() | ||||
|  | ||||
|     def close(self): | ||||
|     def close(self, timeout=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['to'] = self.peer_jid | ||||
|         iq['from'] = self.self_jid | ||||
|         iq['ibb_close']['sid'] = self.sid | ||||
|         self.stream_out_closed.set() | ||||
|         iq.send(block=False, | ||||
|                 callback=lambda x: self.stream_in_closed.set()) | ||||
|         self.stream_out_closed = True | ||||
|         def _close_stream(_): | ||||
|             self.stream_in_closed = True | ||||
|         future = iq.send(timeout=timeout, callback=_close_stream) | ||||
|         self.xmpp.event('ibb_stream_end', self) | ||||
|         return future | ||||
|  | ||||
|     def _closed(self, iq): | ||||
|         self.stream_in_closed.set() | ||||
|         self.stream_out_closed.set() | ||||
|         iq.reply() | ||||
|         iq.send() | ||||
|         self.stream_in_closed = True | ||||
|         self.stream_out_closed = True | ||||
|         iq.reply().send() | ||||
|         self.xmpp.event('ibb_stream_end', self) | ||||
|  | ||||
|     def makefile(self, *args, **kwargs): | ||||
|   | ||||
| @@ -59,7 +59,7 @@ class XEP_0048(BasePlugin): | ||||
|         for conf in bookmarks['conferences']: | ||||
|             if conf['autojoin']: | ||||
|                 log.debug('Auto joining %s as %s', conf['jid'], conf['nick']) | ||||
|                 self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'], | ||||
|                 self.xmpp['xep_0045'].join_muc(conf['jid'], conf['nick'], | ||||
|                         password=conf['password']) | ||||
|  | ||||
|     def set_bookmarks(self, bookmarks, method=None, **iqargs): | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0050.adhoc import XEP_0050 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0050) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0050 = XEP_0050 | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user