Compare commits
	
		
			332 Commits
		
	
	
		
			hildjj-dev
			...
			1.2.5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | ef2f5d2978 | ||
|   | 62671e0f56 | ||
|   | 93869f77a0 | ||
|   | 8282d135cc | ||
|   | 9acc78c81d | ||
|   | 3642469630 | ||
|   | 34cd20339c | ||
|   | 7548f44047 | ||
|   | 7cf55ef695 | ||
|   | 543250da13 | ||
|   | 69e55d7316 | ||
|   | 158411e918 | ||
|   | 3f873002c4 | ||
|   | 818f4e5973 | ||
|   | c8d6e512d2 | ||
|   | a2423b8499 | ||
|   | 49acdac776 | ||
|   | 7e1587faa2 | ||
|   | 84a6ed8e80 | ||
|   | 654420e351 | ||
|   | 651915f31c | ||
|   | d9db1b84fe | ||
|   | bd03f071c6 | ||
|   | eb6ac68d5c | ||
|   | 848e6ebd83 | ||
|   | f76524fc9f | ||
|   | b95532b68b | ||
|   | d002d4c06f | ||
|   | 7c03cc622c | ||
|   | cebfd84416 | ||
|   | 12995e280e | ||
|   | 4ae6d44efc | ||
|   | 01e1878900 | ||
|   | df9ad82336 | ||
|   | c183fd5e35 | ||
|   | 820d07f309 | ||
|   | f4e3c04bbf | ||
|   | 540d6e9dbb | ||
|   | 79a3a2befd | ||
|   | 08a0fd5420 | ||
|   | 92d6bc6875 | ||
|   | fb5d20c4f8 | ||
|   | 65e3122f52 | ||
|   | be874e3c70 | ||
|   | beae845281 | ||
|   | 6f64dac262 | ||
|   | cd2d25cf87 | ||
|   | b8b2f37e7b | ||
|   | 00152358de | ||
|   | a2784be4d6 | ||
|   | ad7a57103d | ||
|   | 19b24b276d | ||
|   | 23750357e2 | ||
|   | 07284f380f | ||
|   | e60401278f | ||
|   | 24c474a9ec | ||
|   | 8fd3781ef5 | ||
|   | c85f2494a8 | ||
|   | 6c2fa7a382 | ||
|   | 45689fd879 | ||
|   | 45a2cfb01b | ||
|   | c4bb6c900c | ||
|   | f7c042fc77 | ||
|   | b20dc9fe2b | ||
|   | a030e05993 | ||
|   | 648b03f811 | ||
|   | e57e321d33 | ||
|   | b6e53c7b1b | ||
|   | 1c3bfd949b | ||
|   | 6401c9aaaa | ||
|   | c02adbb8e1 | ||
|   | 88e64dbfae | ||
|   | afd48b9e08 | ||
|   | db0ab9a0b3 | ||
|   | 556e4bd74d | ||
|   | d439c4f215 | ||
|   | a9f2e1482c | ||
|   | 2c26fb0d76 | ||
|   | 18dde97c8c | ||
|   | 85bc6f5301 | ||
|   | 8f364b9a95 | ||
|   | ee6c5632ac | ||
|   | cc81a0e8da | ||
|   | 262652992d | ||
|   | eb63825dfd | ||
|   | c49017c6f1 | ||
|   | 7d08bd3142 | ||
|   | f12c241dca | ||
|   | cedc9dd175 | ||
|   | 669e708b70 | ||
|   | e76a483931 | ||
|   | c0437d2de8 | ||
|   | 37a8043202 | ||
|   | f4c69d4045 | ||
|   | a3606d9e4d | ||
|   | 805f1c0e39 | ||
|   | 7430a8ca40 | ||
|   | 1776e2edcc | ||
|   | baf9aaf26c | ||
|   | 4864b07e13 | ||
|   | 13c919773e | ||
|   | ed3a4fb8d4 | ||
|   | df3e826d0a | ||
|   | a9e7d489b8 | ||
|   | da6b549f8b | ||
|   | 76e07a9089 | ||
|   | 4a590d1497 | ||
|   | 82e1508d6f | ||
|   | 400f08db9d | ||
|   | e48b650caa | ||
|   | d9f595283a | ||
|   | 85fd14f47f | ||
|   | b7adaafb3e | ||
|   | d0bba87cdd | ||
|   | 2cc75d4bbd | ||
|   | 24bd591faa | ||
|   | 2e9ccd0623 | ||
|   | 7b49c82210 | ||
|   | d3284f1604 | ||
|   | 3279697128 | ||
|   | 60cfab995f | ||
|   | 8ec18bdb2c | ||
|   | 3c3cd65235 | ||
|   | 7ac75de19d | ||
|   | fae39e1ab4 | ||
|   | 3732139fc3 | ||
|   | 0a2737dc77 | ||
|   | 481971928c | ||
|   | 020197718f | ||
|   | a0c77c04a5 | ||
|   | 620ee9719f | ||
|   | c0d02d9935 | ||
|   | 01356d23e5 | ||
|   | 8b73c2bcff | ||
|   | 5a771dbe2f | ||
|   | 9ba5b644cf | ||
|   | f76f0c3787 | ||
|   | 01abd6a705 | ||
|   | 44e2b5d945 | ||
|   | 82bbe5d1a6 | ||
|   | a1d71d31e8 | ||
|   | 766e0b685d | ||
|   | 58f5e4702b | ||
|   | d9906756cf | ||
|   | 9a45ebd98b | ||
|   | 7f9ff9d0e7 | ||
|   | 8c763fcf43 | ||
|   | 6dd4456b11 | ||
|   | c30c47d291 | ||
|   | d8c9662302 | ||
|   | ec5e819b16 | ||
|   | 55e50ad979 | ||
|   | 99ecb166d3 | ||
|   | cdeae7e72f | ||
|   | fbf79755d7 | ||
|   | 78bd21b7cf | ||
|   | 88c7c29954 | ||
|   | d4dde89ea6 | ||
|   | 774bf35fab | ||
|   | 1a2db7fb11 | ||
|   | da3223ac92 | ||
|   | b0fed5a48d | ||
|   | 43132dab85 | ||
|   | badd327360 | ||
|   | 9a6bfc6614 | ||
|   | 79914fb56b | ||
|   | 75a792eb6f | ||
|   | 23f112602c | ||
|   | 639a3aa832 | ||
|   | 79a8c5ceae | ||
|   | 97a2f4449d | ||
|   | 7f42d15175 | ||
|   | ef9c8e910c | ||
|   | a1b33da9ca | ||
|   | 1741059cf6 | ||
|   | 1f137735e1 | ||
|   | a186972f09 | ||
|   | 751628401e | ||
|   | 403b1802ec | ||
|   | 9165cbf7f6 | ||
|   | bad405bea9 | ||
|   | 4f9a95b011 | ||
|   | 903e641457 | ||
|   | f34b9399cc | ||
|   | 7d0d96f940 | ||
|   | 27196a21ae | ||
|   | ea0381fa09 | ||
|   | 3423589ba1 | ||
|   | 1f9286d39e | ||
|   | 93b8e66b5d | ||
|   | a1716de683 | ||
|   | ccf7916257 | ||
|   | d86adfa1b1 | ||
|   | 648f3f978a | ||
|   | 5e4b8bd67c | ||
|   | 64ef690432 | ||
|   | 41991b5982 | ||
|   | 01da222d67 | ||
|   | 518eee05c2 | ||
|   | 1dbfa29a1e | ||
|   | 6bac4741f6 | ||
|   | a0266dac6f | ||
|   | ce977a7809 | ||
|   | 8644a83ed9 | ||
|   | 7b45245b1d | ||
|   | f04f4e4a1a | ||
|   | b07f1b3bd3 | ||
|   | 0e7486d7b4 | ||
|   | 6c0afb87b9 | ||
|   | e5750b368e | ||
|   | ef76f923ad | ||
|   | 2c04ae084c | ||
|   | 91dc58d967 | ||
|   | 0e2abe74d5 | ||
|   | fea444925e | ||
|   | 0998429b07 | ||
|   | 597eb1779c | ||
|   | 9ae3a7dbff | ||
|   | 3519e845a3 | ||
|   | 29c049612a | ||
|   | ed48185732 | ||
|   | f431bbfca2 | ||
|   | 8b29900be4 | ||
|   | 6f8a4f8354 | ||
|   | def34f0e42 | ||
|   | e25a49f804 | ||
|   | b820351f64 | ||
|   | 0eb009496e | ||
|   | 2c2498b658 | ||
|   | a1d988fed5 | ||
|   | b0c50b7a59 | ||
|   | 1a2b404076 | ||
|   | 2d066c34fd | ||
|   | 7a1ed64985 | ||
|   | 1b449585f7 | ||
|   | 032d41dbb8 | ||
|   | 3a7569e3ea | ||
|   | d444930494 | ||
|   | 6045a6bfb3 | ||
|   | f3f543b31e | ||
|   | 0fea4262ea | ||
|   | 4b7ec4a32a | ||
|   | 2229ad8d8e | ||
|   | 61aff9f49a | ||
|   | 67235c4214 | ||
|   | a00eee1bbe | ||
|   | 06a690a259 | ||
|   | 14c9e9a9cc | ||
|   | 931d49560a | ||
|   | 3655827ef2 | ||
|   | 12e0e1a16b | ||
|   | 0d448b8221 | ||
|   | 77f2a339e1 | ||
|   | 7c485c6a8b | ||
|   | fc07e23ff8 | ||
|   | 84a2fc382b | ||
|   | 44e7585bf8 | ||
|   | a2c60a4911 | ||
|   | 73ce9a5ecc | ||
|   | d385b9e708 | ||
|   | 67147570e9 | ||
|   | df9ac58d05 | ||
|   | 19a78f63f4 | ||
|   | e20610ab80 | ||
|   | f52a10b061 | ||
|   | 7d382a2bfd | ||
|   | 09bec1c4fe | ||
|   | ff28b0a005 | ||
|   | a249f8736a | ||
|   | f09adf0014 | ||
|   | 04dc68f5f6 | ||
|   | 5c25208fb5 | ||
|   | 962dfad216 | ||
|   | 14aa831169 | ||
|   | 75d904ed01 | ||
|   | f81d5e4bd6 | ||
|   | 2f65fdbc76 | ||
|   | 2f4149c7d0 | ||
|   | fb4275648c | ||
|   | 06a9d9fc30 | ||
|   | 44ce01a70b | ||
|   | c2189b4ecd | ||
|   | c9b2cf6043 | ||
|   | 16ec0f151a | ||
|   | c42f1ad4c7 | ||
|   | a3ec1af205 | ||
|   | 2e580304f9 | ||
|   | 5492e9028d | ||
|   | 060c9ab679 | ||
|   | 78f0325398 | ||
|   | 1efe049959 | ||
|   | 2393148908 | ||
|   | c7594b3ef0 | ||
|   | b210870f48 | ||
|   | 5d6019a962 | ||
|   | eb5df1aa37 | ||
|   | 546066d677 | ||
|   | 3234596974 | ||
|   | 5820d49cd4 | ||
|   | 1ab66e5767 | ||
|   | aab2682f9a | ||
|   | 55d332bcc8 | ||
|   | f89df6e70c | ||
|   | 250d28e870 | ||
|   | 19f65c8510 | ||
|   | f70b49882f | ||
|   | a7b092a305 | ||
|   | daa73a3f3c | ||
|   | 0b51afe87a | ||
|   | 2b298766c9 | ||
|   | 10664d723b | ||
|   | c012208a8f | ||
|   | 0953896d2d | ||
|   | cf9e89d0ae | ||
|   | 48dd01b0bb | ||
|   | 7247efe055 | ||
|   | 8def3758e4 | ||
|   | 1851ab6f5f | ||
|   | 289b052338 | ||
|   | 26147f5ae0 | ||
|   | ae01f1071a | ||
|   | dcdf5dcd09 | ||
|   | c59a6d0f51 | ||
|   | 2cd936318d | ||
|   | 2f38857681 | ||
|   | 39505ae1ff | ||
|   | 44ee0633f2 | ||
|   | b52d2768b0 | ||
|   | cf24b870b1 | ||
|   | 69cffce7dc | ||
|   | a14979375b | ||
|   | 40ef4a16b1 | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| *.pyc | *.py[co] | ||||||
| build/ | build/ | ||||||
| dist/ | dist/ | ||||||
| MANIFEST | MANIFEST | ||||||
| @@ -7,3 +7,8 @@ docs/_build/ | |||||||
| .tox/ | .tox/ | ||||||
| .coverage | .coverage | ||||||
| sleekxmpp.egg-info/ | sleekxmpp.egg-info/ | ||||||
|  | .ropeproject/ | ||||||
|  | 4913 | ||||||
|  | *~ | ||||||
|  | .baboon/ | ||||||
|  | .DS_STORE | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -119,7 +119,7 @@ SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY | |||||||
|  |  | ||||||
| This software is subject to "The MIT License" | This software is subject to "The MIT License" | ||||||
|  |  | ||||||
| Copyright 2007-2010 David Alan Cridland | Copyright 2004-2013 David Alan Cridland | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||||
| @@ -167,3 +167,28 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||||||
| OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | ||||||
| ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  | ||||||
|  | socksipy: A Python SOCKS client module. | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  | Copyright 2006 Dan-Haim. All rights reserved. | ||||||
|  |  | ||||||
|  | Redistribution and use in source and binary forms, with or without modification, | ||||||
|  | are permitted provided that the following conditions are met: | ||||||
|  | 1. Redistributions of source code must retain the above copyright notice, this | ||||||
|  |    list of conditions and the following disclaimer. | ||||||
|  | 2. Redistributions in binary form must reproduce the above copyright notice, | ||||||
|  |    this list of conditions and the following disclaimer in the documentation | ||||||
|  |    and/or other materials provided with the distribution. | ||||||
|  | 3. Neither the name of Dan Haim nor the names of his contributors may be used | ||||||
|  |    to endorse or promote products derived from this software without specific | ||||||
|  |    prior written permission. | ||||||
|  |  | ||||||
|  | THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED | ||||||
|  | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||||||
|  | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | ||||||
|  | EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||||
|  | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||||
|  | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA | ||||||
|  | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | ||||||
|  | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github | |||||||
| ``develop`` branch. | ``develop`` branch. | ||||||
|  |  | ||||||
| **Latest Release** | **Latest Release** | ||||||
|     - `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_ |     - `1.2.5 <http://github.com/fritzy/SleekXMPP/zipball/1.2.5>`_ | ||||||
|  |  | ||||||
| **Develop Releases** | **Develop Releases** | ||||||
|     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ |     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||||
|   | |||||||
| @@ -222,7 +222,7 @@ handler function to process registration requests. | |||||||
|         self.description = "In-Band Registration" |         self.description = "In-Band Registration" | ||||||
|         self.xep = "0077" |         self.xep = "0077" | ||||||
|  |  | ||||||
|         self.xmpp.registerHandler( |         self.xmpp.register_handler( | ||||||
|           Callback('In-Band Registration', |           Callback('In-Band Registration', | ||||||
|             MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), |             MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), | ||||||
|             self.__handleRegistration)) |             self.__handleRegistration)) | ||||||
| @@ -601,7 +601,7 @@ with some additional registration fields implemented. | |||||||
|             self.form_instructions = "" |             self.form_instructions = "" | ||||||
|             self.backend = UserStore() |             self.backend = UserStore() | ||||||
|  |  | ||||||
|             self.xmpp.registerHandler( |             self.xmpp.register_handler( | ||||||
|                 Callback('In-Band Registration', |                 Callback('In-Band Registration', | ||||||
|                          MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), |                          MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), | ||||||
|                          self.__handleRegistration)) |                          self.__handleRegistration)) | ||||||
|   | |||||||
| @@ -6,14 +6,20 @@ Event Index | |||||||
|  |  | ||||||
|     connected |     connected | ||||||
|         - **Data:** ``{}`` |         - **Data:** ``{}`` | ||||||
|         - **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP` |         - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` | ||||||
|  |  | ||||||
|         Signal that a connection has been made with the XMPP server, but a session |         Signal that a connection has been made with the XMPP server, but a session | ||||||
|         has not yet been established. |         has not yet been established. | ||||||
|  |  | ||||||
|  |     connection_failed | ||||||
|  |         - **Data:** ``{}`` or ``Failure Stanza`` if available | ||||||
|  |         - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` | ||||||
|  |  | ||||||
|  |         Signal that a connection can not be established after number of attempts. | ||||||
|  |  | ||||||
|     changed_status |     changed_status | ||||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` |         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` |         - **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem` | ||||||
|  |  | ||||||
|         Triggered when a presence stanza is received from a JID with a show type |         Triggered when a presence stanza is received from a JID with a show type | ||||||
|         different than the last presence stanza from the same JID. |         different than the last presence stanza from the same JID. | ||||||
| @@ -65,7 +71,7 @@ Event Index | |||||||
|  |  | ||||||
|     disconnected |     disconnected | ||||||
|         - **Data:** ``{}`` |         - **Data:** ``{}`` | ||||||
|         - **Source:** :py:class:`~sleekxmpp.ClientXMPP` |         - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` | ||||||
|  |  | ||||||
|         Signal that the connection with the XMPP server has been lost. |         Signal that the connection with the XMPP server has been lost. | ||||||
|  |  | ||||||
| @@ -93,7 +99,7 @@ Event Index | |||||||
|  |  | ||||||
|     got_online |     got_online | ||||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` |         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` |         - **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem` | ||||||
|  |  | ||||||
|         If a presence stanza is received from a JID which was previously marked as |         If a presence stanza is received from a JID which was previously marked as | ||||||
|         offline, and the presence has a show type of '``chat``', '``dnd``', '``away``', |         offline, and the presence has a show type of '``chat``', '``dnd``', '``away``', | ||||||
| @@ -101,7 +107,7 @@ Event Index | |||||||
|  |  | ||||||
|     got_offline |     got_offline | ||||||
|         - **Data:** :py:class:`~sleekxmpp.Presence` |         - **Data:** :py:class:`~sleekxmpp.Presence` | ||||||
|         - **Source:** :py:class:`~sleekxmpp.BaseXMPP` |         - **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem` | ||||||
|  |  | ||||||
|         Signal that an unavailable presence stanza has been received from a JID. |         Signal that an unavailable presence stanza has been received from a JID. | ||||||
|  |  | ||||||
| @@ -158,7 +164,7 @@ Event Index | |||||||
|  |  | ||||||
|         Triggered whenever a data form is received inside a message. |         Triggered whenever a data form is received inside a message. | ||||||
|  |  | ||||||
|     mucc::[room]::got_offline |     muc::[room]::got_offline | ||||||
|         - **Data:** |         - **Data:** | ||||||
|         - **Source:** |         - **Source:** | ||||||
|  |  | ||||||
| @@ -236,15 +242,13 @@ Event Index | |||||||
|  |  | ||||||
|     sent_presence |     sent_presence | ||||||
|         - **Data:** ``{}`` |         - **Data:** ``{}`` | ||||||
|         - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>` |         - **Source:** :py:class:`~sleekxmpp.roster.multi.Roster` | ||||||
|          |          | ||||||
|         Signal that an initial presence stanza has been written to the XML stream. |         Signal that an initial presence stanza has been written to the XML stream. | ||||||
|  |  | ||||||
|     session_end |     session_end | ||||||
|         - **Data:** ``{}`` |         - **Data:** ``{}`` | ||||||
|         - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,  |         - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` | ||||||
|           :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>` |  | ||||||
|           :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>` |  | ||||||
|  |  | ||||||
|         Signal that a connection to the XMPP server has been lost and the current |         Signal that a connection to the XMPP server has been lost and the current | ||||||
|         stream session has ended. Currently equivalent to :term:`disconnected`, but |         stream session has ended. Currently equivalent to :term:`disconnected`, but | ||||||
|   | |||||||
							
								
								
									
										203
									
								
								examples/IoT_TestDevice.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										203
									
								
								examples/IoT_TestDevice.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,203 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Implementation of xeps for Internet of Things | ||||||
|  |     http://wiki.xmpp.org/web/Tech_pages/IoT_systems | ||||||
|  |     Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     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/SleekXMPP']+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 optparse import OptionParser | ||||||
|  | from urllib import urlopen | ||||||
|  |  | ||||||
|  | import sleekxmpp | ||||||
|  | # Python versions before 3.0 do not use UTF-8 encoding | ||||||
|  | # by default. To ensure that Unicode is handled properly | ||||||
|  | # throughout SleekXMPP, we will set the default encoding | ||||||
|  | # ourselves to UTF-8. | ||||||
|  | if sys.version_info < (3, 0): | ||||||
|  |     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||||
|  |     setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0323.device import Device | ||||||
|  |  | ||||||
|  | #from sleekxmpp.exceptions import IqError, IqTimeout | ||||||
|  |  | ||||||
|  | class IoT_TestDevice(sleekxmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple IoT device that can act as server or client | ||||||
|  |     """ | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |         self.add_event_handler("session_start", self.session_start) | ||||||
|  |         self.add_event_handler("message", self.message) | ||||||
|  |         self.device=None | ||||||
|  |         self.releaseMe=False | ||||||
|  |         self.beServer=True | ||||||
|  |         self.clientJID=None | ||||||
|  |  | ||||||
|  |     def datacallback(self,from_jid,result,nodeId=None,timestamp=None,fields=None,error_msg=None): | ||||||
|  |         """ | ||||||
|  |         This method will be called when you ask another IoT device for data with the xep_0323 | ||||||
|  |         se script below for the registration of the callback | ||||||
|  |         """ | ||||||
|  |         logging.debug("we got data %s from %s",str(result),from_jid) | ||||||
|  |  | ||||||
|  |     def beClientOrServer(self,server=True,clientJID=None ): | ||||||
|  |         if server: | ||||||
|  |             self.beServer=True | ||||||
|  |             self.clientJID=None | ||||||
|  |         else: | ||||||
|  |             self.beServer=False | ||||||
|  |             self.clientJID=clientJID | ||||||
|  |  | ||||||
|  |     def testForRelease(self): | ||||||
|  |         # todo thread safe | ||||||
|  |         return self.releaseMe | ||||||
|  |  | ||||||
|  |     def doReleaseMe(self): | ||||||
|  |         # todo thread safe | ||||||
|  |         self.releaseMe=True | ||||||
|  |  | ||||||
|  |     def addDevice(self, device): | ||||||
|  |         self.device=device | ||||||
|  |  | ||||||
|  |     def session_start(self, event): | ||||||
|  |         self.send_presence() | ||||||
|  |         self.get_roster() | ||||||
|  |         # tell your preffered friend that you are alive | ||||||
|  |         self.send_message(mto='jocke@jabber.sust.se', mbody=self.boundjid.bare +' is now online use xep_323 stanza to talk to me') | ||||||
|  |  | ||||||
|  |         if not(self.beServer): | ||||||
|  |             session=self['xep_0323'].request_data(self.boundjid.full,self.clientJID,self.datacallback) | ||||||
|  |  | ||||||
|  |     def message(self, msg): | ||||||
|  |         if msg['type'] in ('chat', 'normal'): | ||||||
|  |             logging.debug("got normal chat message" + str(msg)) | ||||||
|  |             ip=urlopen('http://icanhazip.com').read() | ||||||
|  |             msg.reply("Hi I am " + self.boundjid.full + " and I am on IP " + ip).send() | ||||||
|  |         else: | ||||||
|  |             logging.debug("got unknown message type %s", str(msg['type'])) | ||||||
|  |  | ||||||
|  | class TheDevice(Device): | ||||||
|  |     """ | ||||||
|  |     This is the actual device object that you will use to get information from your real hardware | ||||||
|  |     You will be called in the refresh method when someone is requesting information from you | ||||||
|  |     """ | ||||||
|  |     def __init__(self,nodeId): | ||||||
|  |         Device.__init__(self,nodeId) | ||||||
|  |         self.counter=0 | ||||||
|  |  | ||||||
|  |     def refresh(self,fields): | ||||||
|  |         """ | ||||||
|  |         the implementation of the refresh method | ||||||
|  |         """ | ||||||
|  |         self._set_momentary_timestamp(self._get_timestamp()) | ||||||
|  |         self.counter+=self.counter | ||||||
|  |         self._add_field_momentary_data(self, "Temperature", self.counter) | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |  | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     # | ||||||
|  |     # This script can act both as | ||||||
|  |     #   "server" an IoT device that can provide sensorinformation | ||||||
|  |     #   python IoT_TestDevice.py -j "serverjid@yourdomain.com" -p "password" -n "TestIoT" --debug | ||||||
|  |     # | ||||||
|  |     #   "client" an IoT device or other party that would like to get data from another device | ||||||
|  |  | ||||||
|  |     optp = OptionParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=logging.ERROR, default=logging.INFO) | ||||||
|  |     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=logging.DEBUG, default=logging.INFO) | ||||||
|  |     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||||
|  |                     action='store_const', dest='loglevel', | ||||||
|  |                     const=5, default=logging.INFO) | ||||||
|  |     optp.add_option('-t', '--pingto', help='set jid to ping', | ||||||
|  |                     action='store', type='string', dest='pingjid', | ||||||
|  |                     default=None) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     optp.add_option("-j", "--jid", dest="jid", | ||||||
|  |                     help="JID to use") | ||||||
|  |     optp.add_option("-p", "--password", dest="password", | ||||||
|  |                     help="password to use") | ||||||
|  |  | ||||||
|  |     # IoT test | ||||||
|  |     optp.add_option("-c", "--sensorjid", dest="sensorjid", | ||||||
|  |                     help="Another device to call for data on", default=None) | ||||||
|  |     optp.add_option("-n", "--nodeid", dest="nodeid", | ||||||
|  |                     help="I am a device get ready to be called", default=None) | ||||||
|  |  | ||||||
|  |     opts, args = optp.parse_args() | ||||||
|  |  | ||||||
|  |      # Setup logging. | ||||||
|  |     logging.basicConfig(level=opts.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if opts.jid is None: | ||||||
|  |         opts.jid = raw_input("Username: ") | ||||||
|  |     if opts.password is None: | ||||||
|  |         opts.password = getpass.getpass("Password: ") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     xmpp = IoT_TestDevice(opts.jid,opts.password) | ||||||
|  |     xmpp.register_plugin('xep_0030') | ||||||
|  |     #xmpp['xep_0030'].add_feature(feature='urn:xmpp:iot:sensordata', | ||||||
|  |     #                             node=None, | ||||||
|  |     #    jid=None) | ||||||
|  |     xmpp.register_plugin('xep_0323') | ||||||
|  |     xmpp.register_plugin('xep_0325') | ||||||
|  |  | ||||||
|  |     if opts.nodeid: | ||||||
|  |  | ||||||
|  |         # xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn', | ||||||
|  |         # node=opts.nodeid, | ||||||
|  |         # jid=xmpp.boundjid.full) | ||||||
|  |  | ||||||
|  |         myDevice = TheDevice(opts.nodeid); | ||||||
|  |         # myDevice._add_field(name="Relay", typename="numeric", unit="Bool"); | ||||||
|  |         myDevice._add_field(name="Temperature", typename="numeric", unit="C"); | ||||||
|  |         myDevice._set_momentary_timestamp("2013-03-07T16:24:30") | ||||||
|  |         myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}); | ||||||
|  |  | ||||||
|  |         xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10); | ||||||
|  |         xmpp.beClientOrServer(server=True) | ||||||
|  |         while not(xmpp.testForRelease()): | ||||||
|  |             xmpp.connect() | ||||||
|  |             xmpp.process(block=True) | ||||||
|  |             logging.debug("lost connection") | ||||||
|  |     if opts.sensorjid: | ||||||
|  |         logging.debug("will try to call another device for data") | ||||||
|  |         xmpp.beClientOrServer(server=False,clientJID=opts.sensorjid) | ||||||
|  |         xmpp.connect() | ||||||
|  |         xmpp.process(block=True) | ||||||
|  |         logging.debug("ready ending") | ||||||
|  |  | ||||||
|  |     else: | ||||||
|  |        print "noopp didn't happen" | ||||||
|  |  | ||||||
| @@ -51,7 +51,7 @@ class ActionBot(sleekxmpp.ClientXMPP): | |||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|         self.registerHandler( |         self.register_handler( | ||||||
|           Callback('Some custom iq', |           Callback('Some custom iq', | ||||||
|             StanzaPath('iq@type=set/action'), |             StanzaPath('iq@type=set/action'), | ||||||
|             self._handle_action)) |             self._handle_action)) | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								examples/download_avatars.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								examples/download_avatars.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -38,7 +38,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP): | |||||||
|  |  | ||||||
|         self.register_plugin('xep_0030') # Service Discovery |         self.register_plugin('xep_0030') # Service Discovery | ||||||
|         self.register_plugin('xep_0047', { |         self.register_plugin('xep_0047', { | ||||||
|             'accept_stream': self.accept_stream |             'auto_accept': True | ||||||
|         }) # In-band Bytestreams |         }) # In-band Bytestreams | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
| @@ -48,7 +48,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP): | |||||||
|         # our roster. |         # our roster. | ||||||
|         self.add_event_handler("session_start", self.start) |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|         self.add_event_handler("ibb_stream_start", self.stream_opened) |         self.add_event_handler("ibb_stream_start", self.stream_opened, threaded=True) | ||||||
|         self.add_event_handler("ibb_stream_data", self.stream_data) |         self.add_event_handler("ibb_stream_data", self.stream_data) | ||||||
|  |  | ||||||
|     def start(self, event): |     def start(self, event): | ||||||
| @@ -83,9 +83,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP): | |||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def stream_opened(self, stream): |     def stream_opened(self, stream): | ||||||
|         # NOTE: IBB streams are bi-directional, so the original sender is |         print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) | ||||||
|         # now the opened stream's receiver. |  | ||||||
|         print('Stream opened: %s from %s' % (stream.sid, stream.receiver)) |  | ||||||
|  |  | ||||||
|         # You could run a loop reading from the stream using stream.recv(), |         # You could run a loop reading from the stream using stream.recv(), | ||||||
|         # or use the ibb_stream_data event. |         # or use the ibb_stream_data event. | ||||||
|   | |||||||
							
								
								
									
										120
									
								
								examples/migrate_roster.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										120
									
								
								examples/migrate_roster.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,120 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import logging | ||||||
|  | import getpass | ||||||
|  | from optparse import OptionParser | ||||||
|  |  | ||||||
|  | import sleekxmpp | ||||||
|  |  | ||||||
|  | # Python versions before 3.0 do not use UTF-8 encoding | ||||||
|  | # by default. To ensure that Unicode is handled properly | ||||||
|  | # throughout SleekXMPP, we will set the default encoding | ||||||
|  | # ourselves to UTF-8. | ||||||
|  | if sys.version_info < (3, 0): | ||||||
|  |     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||||
|  |     setdefaultencoding('utf8') | ||||||
|  | else: | ||||||
|  |     raw_input = input | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Setup the command line arguments. | ||||||
|  | optp = OptionParser() | ||||||
|  |  | ||||||
|  | # Output verbosity options. | ||||||
|  | optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||||
|  |                 action='store_const', dest='loglevel', | ||||||
|  |                 const=logging.ERROR, default=logging.INFO) | ||||||
|  | optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||||
|  |                 action='store_const', dest='loglevel', | ||||||
|  |                 const=logging.DEBUG, default=logging.INFO) | ||||||
|  | optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||||
|  |                 action='store_const', dest='loglevel', | ||||||
|  |                 const=5, default=logging.INFO) | ||||||
|  |  | ||||||
|  | # JID and password options. | ||||||
|  | optp.add_option("--oldjid", dest="old_jid", | ||||||
|  |                 help="JID of the old account") | ||||||
|  | optp.add_option("--oldpassword", dest="old_password", | ||||||
|  |                 help="password of the old account") | ||||||
|  |  | ||||||
|  | optp.add_option("--newjid", dest="new_jid", | ||||||
|  |                 help="JID of the old account") | ||||||
|  | optp.add_option("--newpassword", dest="new_password", | ||||||
|  |                 help="password of the old account") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | opts, args = optp.parse_args() | ||||||
|  |  | ||||||
|  | # Setup logging. | ||||||
|  | logging.basicConfig(level=opts.loglevel, | ||||||
|  |                     format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  | if opts.old_jid is None: | ||||||
|  |     opts.old_jid = raw_input("Old JID: ") | ||||||
|  | if opts.old_password is None: | ||||||
|  |     opts.old_password = getpass.getpass("Old Password: ") | ||||||
|  |  | ||||||
|  | if opts.new_jid is None: | ||||||
|  |     opts.new_jid = raw_input("New JID: ") | ||||||
|  | if opts.new_password is None: | ||||||
|  |     opts.new_password = getpass.getpass("New Password: ") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | old_xmpp = sleekxmpp.ClientXMPP(opts.old_jid, opts.old_password) | ||||||
|  |  | ||||||
|  | # If you are connecting to Facebook and wish to use the | ||||||
|  | # X-FACEBOOK-PLATFORM authentication mechanism, you will need | ||||||
|  | # your API key and an access token. Then you'll set: | ||||||
|  | # xmpp.credentials['api_key'] = 'THE_API_KEY' | ||||||
|  | # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' | ||||||
|  |  | ||||||
|  | # If you are connecting to MSN, then you will need an | ||||||
|  | # access token, and it does not matter what JID you | ||||||
|  | # specify other than that the domain is 'messenger.live.com', | ||||||
|  | # so '_@messenger.live.com' will work. You can specify | ||||||
|  | # the access token as so: | ||||||
|  | # xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN' | ||||||
|  |  | ||||||
|  | # If you are working with an OpenFire server, you may need | ||||||
|  | # to adjust the SSL version used: | ||||||
|  | # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||||
|  |  | ||||||
|  | # If you want to verify the SSL certificates offered by a server: | ||||||
|  | # xmpp.ca_certs = "path/to/ca/cert" | ||||||
|  |  | ||||||
|  | roster = [] | ||||||
|  |  | ||||||
|  | def on_session(event): | ||||||
|  |     roster.append(old_xmpp.get_roster()) | ||||||
|  |     old_xmpp.disconnect() | ||||||
|  | old_xmpp.add_event_handler('session_start', on_session) | ||||||
|  |  | ||||||
|  | if old_xmpp.connect(): | ||||||
|  |     old_xmpp.process(block=True) | ||||||
|  |  | ||||||
|  | if not roster: | ||||||
|  |     print('No roster to migrate') | ||||||
|  |     sys.exit() | ||||||
|  |  | ||||||
|  | new_xmpp = sleekxmpp.ClientXMPP(opts.new_jid, opts.new_password) | ||||||
|  | def on_session2(event): | ||||||
|  |     new_xmpp.get_roster() | ||||||
|  |     new_xmpp.send_presence() | ||||||
|  |  | ||||||
|  |     logging.info(roster[0]) | ||||||
|  |     data = roster[0]['roster']['items'] | ||||||
|  |     logging.info(data) | ||||||
|  |  | ||||||
|  |     for jid, item in data.items(): | ||||||
|  |         if item['subscription'] != 'none': | ||||||
|  |             new_xmpp.send_presence(ptype='subscribe', pto=jid) | ||||||
|  |         new_xmpp.update_roster(jid, | ||||||
|  |                 name = item['name'], | ||||||
|  |                 groups = item['groups']) | ||||||
|  |         new_xmpp.disconnect() | ||||||
|  | new_xmpp.add_event_handler('session_start', on_session2) | ||||||
|  |  | ||||||
|  | if new_xmpp.connect(): | ||||||
|  |     new_xmpp.process(block=True) | ||||||
| @@ -37,7 +37,7 @@ class PingTest(sleekxmpp.ClientXMPP): | |||||||
|     def __init__(self, jid, password, pingjid): |     def __init__(self, jid, password, pingjid): | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|         if pingjid is None: |         if pingjid is None: | ||||||
|             pingjid = self.jid |             pingjid = self.boundjid.bare | ||||||
|         self.pingjid = pingjid |         self.pingjid = pingjid | ||||||
|  |  | ||||||
|         # The session_start event will be triggered when |         # The session_start event will be triggered when | ||||||
| @@ -62,16 +62,18 @@ class PingTest(sleekxmpp.ClientXMPP): | |||||||
|         """ |         """ | ||||||
|         self.send_presence() |         self.send_presence() | ||||||
|         self.get_roster() |         self.get_roster() | ||||||
|         result = self['xep_0199'].send_ping(self.pingjid, |  | ||||||
|                                             timeout=10, |         try: | ||||||
|                                             errorfalse=True) |             rtt = self['xep_0199'].ping(self.pingjid, | ||||||
|         logging.info("Pinging...") |                                         timeout=10) | ||||||
|         if result is False: |             logging.info("Success! RTT: %s", rtt) | ||||||
|             logging.info("Couldn't ping.") |         except IqError as e: | ||||||
|             self.disconnect() |             logging.info("Error pinging %s: %s", | ||||||
|             sys.exit(1) |                     self.pingjid, | ||||||
|         else: |                     e.iq['error']['condition']) | ||||||
|             logging.info("Success! RTT: %s", str(result)) |         except IqTimeout: | ||||||
|  |             logging.info("No response from %s", self.pingjid) | ||||||
|  |         finally: | ||||||
|             self.disconnect() |             self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								examples/pubsub_client.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										3
									
								
								examples/pubsub_client.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,3 +1,6 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import getpass | import getpass | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								examples/pubsub_events.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										3
									
								
								examples/pubsub_events.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,3 +1,6 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| import sys | import sys | ||||||
| import logging | import logging | ||||||
| import getpass | import getpass | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								examples/register_account.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										4
									
								
								examples/register_account.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -153,6 +153,10 @@ if __name__ == '__main__': | |||||||
|     xmpp.register_plugin('xep_0066') # Out-of-band Data |     xmpp.register_plugin('xep_0066') # Out-of-band Data | ||||||
|     xmpp.register_plugin('xep_0077') # In-band Registration |     xmpp.register_plugin('xep_0077') # In-band Registration | ||||||
|  |  | ||||||
|  |     # Some servers don't advertise support for inband registration, even | ||||||
|  |     # though they allow it. If this applies to your server, use: | ||||||
|  |     xmpp['xep_0077'].force_registration = True | ||||||
|  |  | ||||||
|     # If you are working with an OpenFire server, you may need |     # If you are working with an OpenFire server, you may need | ||||||
|     # to adjust the SSL version used: |     # to adjust the SSL version used: | ||||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 |     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								examples/roster_browser.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								examples/roster_browser.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										3
									
								
								examples/rpc_async.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										3
									
								
								examples/rpc_async.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,3 +1,6 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2011  Dann Martens |     Copyright (C) 2011  Dann Martens | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								examples/rpc_client_side.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										3
									
								
								examples/rpc_client_side.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,3 +1,6 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2011  Dann Martens |     Copyright (C) 2011  Dann Martens | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								examples/rpc_server_side.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										3
									
								
								examples/rpc_server_side.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,3 +1,6 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     SleekXMPP: The Sleek XMPP Library | ||||||
|     Copyright (C) 2011  Dann Martens |     Copyright (C) 2011  Dann Martens | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								examples/set_avatar.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								examples/set_avatar.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										0
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										0
									
								
								examples/user_location.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								examples/user_location.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										0
									
								
								examples/user_tune.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								examples/user_tune.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
								
								
									
										19
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								setup.py
									
									
									
									
									
								
							| @@ -42,6 +42,7 @@ CLASSIFIERS      = [ 'Intended Audience :: Developers', | |||||||
|                      'Programming Language :: Python :: 2.7', |                      'Programming Language :: Python :: 2.7', | ||||||
|                      'Programming Language :: Python :: 3.1', |                      'Programming Language :: Python :: 3.1', | ||||||
|                      'Programming Language :: Python :: 3.2', |                      'Programming Language :: Python :: 3.2', | ||||||
|  |                      'Programming Language :: Python :: 3.3', | ||||||
|                      'Topic :: Software Development :: Libraries :: Python Modules', |                      'Topic :: Software Development :: Libraries :: Python Modules', | ||||||
|                    ] |                    ] | ||||||
|  |  | ||||||
| @@ -62,18 +63,22 @@ packages     = [ 'sleekxmpp', | |||||||
|                  'sleekxmpp/plugins/xep_0012', |                  'sleekxmpp/plugins/xep_0012', | ||||||
|                  'sleekxmpp/plugins/xep_0013', |                  'sleekxmpp/plugins/xep_0013', | ||||||
|                  'sleekxmpp/plugins/xep_0016', |                  'sleekxmpp/plugins/xep_0016', | ||||||
|  |                  'sleekxmpp/plugins/xep_0020', | ||||||
|                  'sleekxmpp/plugins/xep_0027', |                  'sleekxmpp/plugins/xep_0027', | ||||||
|                  'sleekxmpp/plugins/xep_0030', |                  'sleekxmpp/plugins/xep_0030', | ||||||
|                  'sleekxmpp/plugins/xep_0030/stanza', |                  'sleekxmpp/plugins/xep_0030/stanza', | ||||||
|                  'sleekxmpp/plugins/xep_0033', |                  'sleekxmpp/plugins/xep_0033', | ||||||
|                  'sleekxmpp/plugins/xep_0047', |                  'sleekxmpp/plugins/xep_0047', | ||||||
|  |                  'sleekxmpp/plugins/xep_0048', | ||||||
|                  'sleekxmpp/plugins/xep_0049', |                  'sleekxmpp/plugins/xep_0049', | ||||||
|                  'sleekxmpp/plugins/xep_0050', |                  'sleekxmpp/plugins/xep_0050', | ||||||
|                  'sleekxmpp/plugins/xep_0054', |                  'sleekxmpp/plugins/xep_0054', | ||||||
|                  'sleekxmpp/plugins/xep_0059', |                  'sleekxmpp/plugins/xep_0059', | ||||||
|                  'sleekxmpp/plugins/xep_0060', |                  'sleekxmpp/plugins/xep_0060', | ||||||
|                  'sleekxmpp/plugins/xep_0060/stanza', |                  'sleekxmpp/plugins/xep_0060/stanza', | ||||||
|  |                  'sleekxmpp/plugins/xep_0065', | ||||||
|                  'sleekxmpp/plugins/xep_0066', |                  'sleekxmpp/plugins/xep_0066', | ||||||
|  |                  'sleekxmpp/plugins/xep_0071', | ||||||
|                  'sleekxmpp/plugins/xep_0077', |                  'sleekxmpp/plugins/xep_0077', | ||||||
|                  'sleekxmpp/plugins/xep_0078', |                  'sleekxmpp/plugins/xep_0078', | ||||||
|                  'sleekxmpp/plugins/xep_0080', |                  'sleekxmpp/plugins/xep_0080', | ||||||
| @@ -82,17 +87,21 @@ packages     = [ 'sleekxmpp', | |||||||
|                  'sleekxmpp/plugins/xep_0086', |                  'sleekxmpp/plugins/xep_0086', | ||||||
|                  'sleekxmpp/plugins/xep_0091', |                  'sleekxmpp/plugins/xep_0091', | ||||||
|                  'sleekxmpp/plugins/xep_0092', |                  'sleekxmpp/plugins/xep_0092', | ||||||
|  |                  'sleekxmpp/plugins/xep_0095', | ||||||
|  |                  'sleekxmpp/plugins/xep_0096', | ||||||
|                  'sleekxmpp/plugins/xep_0107', |                  'sleekxmpp/plugins/xep_0107', | ||||||
|                  'sleekxmpp/plugins/xep_0108', |                  'sleekxmpp/plugins/xep_0108', | ||||||
|                  'sleekxmpp/plugins/xep_0115', |                  'sleekxmpp/plugins/xep_0115', | ||||||
|                  'sleekxmpp/plugins/xep_0118', |                  'sleekxmpp/plugins/xep_0118', | ||||||
|                  'sleekxmpp/plugins/xep_0128', |                  'sleekxmpp/plugins/xep_0128', | ||||||
|                  'sleekxmpp/plugins/xep_0131', |                  'sleekxmpp/plugins/xep_0131', | ||||||
|  |                  'sleekxmpp/plugins/xep_0152', | ||||||
|                  'sleekxmpp/plugins/xep_0153', |                  'sleekxmpp/plugins/xep_0153', | ||||||
|                  'sleekxmpp/plugins/xep_0172', |                  'sleekxmpp/plugins/xep_0172', | ||||||
|                  'sleekxmpp/plugins/xep_0184', |                  'sleekxmpp/plugins/xep_0184', | ||||||
|                  'sleekxmpp/plugins/xep_0186', |                  'sleekxmpp/plugins/xep_0186', | ||||||
|                  'sleekxmpp/plugins/xep_0191', |                  'sleekxmpp/plugins/xep_0191', | ||||||
|  |                  'sleekxmpp/plugins/xep_0196', | ||||||
|                  'sleekxmpp/plugins/xep_0198', |                  'sleekxmpp/plugins/xep_0198', | ||||||
|                  'sleekxmpp/plugins/xep_0199', |                  'sleekxmpp/plugins/xep_0199', | ||||||
|                  'sleekxmpp/plugins/xep_0202', |                  'sleekxmpp/plugins/xep_0202', | ||||||
| @@ -109,6 +118,16 @@ packages     = [ 'sleekxmpp', | |||||||
|                  'sleekxmpp/plugins/xep_0297', |                  'sleekxmpp/plugins/xep_0297', | ||||||
|                  'sleekxmpp/plugins/xep_0308', |                  'sleekxmpp/plugins/xep_0308', | ||||||
|                  'sleekxmpp/plugins/xep_0313', |                  'sleekxmpp/plugins/xep_0313', | ||||||
|  |                  'sleekxmpp/plugins/xep_0319', | ||||||
|  |                  'sleekxmpp/plugins/xep_0323', | ||||||
|  |                  'sleekxmpp/plugins/xep_0323/stanza', | ||||||
|  |                  'sleekxmpp/plugins/xep_0325', | ||||||
|  |                  'sleekxmpp/plugins/xep_0325/stanza', | ||||||
|  |                  'sleekxmpp/plugins/google', | ||||||
|  |                  'sleekxmpp/plugins/google/gmail', | ||||||
|  |                  'sleekxmpp/plugins/google/auth', | ||||||
|  |                  'sleekxmpp/plugins/google/settings', | ||||||
|  |                  'sleekxmpp/plugins/google/nosave', | ||||||
|                  'sleekxmpp/features', |                  'sleekxmpp/features', | ||||||
|                  'sleekxmpp/features/feature_mechanisms', |                  'sleekxmpp/features/feature_mechanisms', | ||||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', |                  'sleekxmpp/features/feature_mechanisms/stanza', | ||||||
|   | |||||||
| @@ -6,14 +6,25 @@ | |||||||
|     See the file LICENSE for copying permission. |     See the file LICENSE for copying permission. | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from sleekxmpp.basexmpp import BaseXMPP | import logging | ||||||
| from sleekxmpp.clientxmpp import ClientXMPP | if hasattr(logging, 'NullHandler'): | ||||||
| from sleekxmpp.componentxmpp import ComponentXMPP |     NullHandler = logging.NullHandler | ||||||
|  | else: | ||||||
|  |     class NullHandler(logging.Handler): | ||||||
|  |         def handle(self, record): | ||||||
|  |             pass | ||||||
|  | logging.getLogger(__name__).addHandler(NullHandler()) | ||||||
|  | del NullHandler | ||||||
|  |  | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Message, Presence, Iq | from sleekxmpp.stanza import Message, Presence, Iq | ||||||
| from sleekxmpp.jid import JID, InvalidJID | from sleekxmpp.jid import JID, InvalidJID | ||||||
|  | from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin | ||||||
| from sleekxmpp.xmlstream.handler import * | from sleekxmpp.xmlstream.handler import * | ||||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||||
| from sleekxmpp.xmlstream.matcher import * | from sleekxmpp.xmlstream.matcher import * | ||||||
| from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET | from sleekxmpp.basexmpp import BaseXMPP | ||||||
|  | from sleekxmpp.clientxmpp import ClientXMPP | ||||||
|  | from sleekxmpp.componentxmpp import ComponentXMPP | ||||||
|  |  | ||||||
| from sleekxmpp.version import __version__, __version_info__ | from sleekxmpp.version import __version__, __version_info__ | ||||||
|   | |||||||
| @@ -18,8 +18,7 @@ import sys | |||||||
| import logging | import logging | ||||||
| import threading | import threading | ||||||
|  |  | ||||||
| import sleekxmpp | from sleekxmpp import plugins, roster, stanza | ||||||
| from sleekxmpp import plugins, features, roster |  | ||||||
| from sleekxmpp.api import APIRegistry | from sleekxmpp.api import APIRegistry | ||||||
| from sleekxmpp.exceptions import IqError, IqTimeout | from sleekxmpp.exceptions import IqError, IqTimeout | ||||||
|  |  | ||||||
| @@ -34,8 +33,7 @@ from sleekxmpp.xmlstream.matcher import MatchXPath | |||||||
| from sleekxmpp.xmlstream.handler import Callback | from sleekxmpp.xmlstream.handler import Callback | ||||||
| from sleekxmpp.xmlstream.stanzabase import XML_NS | from sleekxmpp.xmlstream.stanzabase import XML_NS | ||||||
|  |  | ||||||
| from sleekxmpp.features import * | from sleekxmpp.plugins import PluginManager, load_plugin | ||||||
| from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
| @@ -148,7 +146,7 @@ class BaseXMPP(XMLStream): | |||||||
|  |  | ||||||
|         #: A reference to :mod:`sleekxmpp.stanza` to make accessing |         #: A reference to :mod:`sleekxmpp.stanza` to make accessing | ||||||
|         #: stanza classes easier. |         #: stanza classes easier. | ||||||
|         self.stanza = sleekxmpp.stanza |         self.stanza = stanza | ||||||
|  |  | ||||||
|         self.register_handler( |         self.register_handler( | ||||||
|             Callback('IM', |             Callback('IM', | ||||||
| @@ -201,7 +199,6 @@ class BaseXMPP(XMLStream): | |||||||
|         # Initialize a few default stanza plugins. |         # Initialize a few default stanza plugins. | ||||||
|         register_stanza_plugin(Iq, Roster) |         register_stanza_plugin(Iq, Roster) | ||||||
|         register_stanza_plugin(Message, Nick) |         register_stanza_plugin(Message, Nick) | ||||||
|         register_stanza_plugin(Message, HTMLIM) |  | ||||||
|  |  | ||||||
|     def start_stream_handler(self, xml): |     def start_stream_handler(self, xml): | ||||||
|         """Save the stream ID once the streams have been established. |         """Save the stream ID once the streams have been established. | ||||||
|   | |||||||
| @@ -96,6 +96,7 @@ class ClientXMPP(BaseXMPP): | |||||||
|  |  | ||||||
|         self.add_event_handler('connected', self._reset_connection_state) |         self.add_event_handler('connected', self._reset_connection_state) | ||||||
|         self.add_event_handler('session_bind', self._handle_session_bind) |         self.add_event_handler('session_bind', self._handle_session_bind) | ||||||
|  |         self.add_event_handler('roster_update', self._handle_roster) | ||||||
|  |  | ||||||
|         self.register_stanza(StreamFeatures) |         self.register_stanza(StreamFeatures) | ||||||
|  |  | ||||||
| @@ -106,7 +107,7 @@ class ClientXMPP(BaseXMPP): | |||||||
|         self.register_handler( |         self.register_handler( | ||||||
|                 Callback('Roster Update', |                 Callback('Roster Update', | ||||||
|                          StanzaPath('iq@type=set/roster'), |                          StanzaPath('iq@type=set/roster'), | ||||||
|                          self._handle_roster)) |                          lambda iq: self.event('roster_update', iq))) | ||||||
|  |  | ||||||
|         # Setup default stream features |         # Setup default stream features | ||||||
|         self.register_plugin('feature_starttls') |         self.register_plugin('feature_starttls') | ||||||
| @@ -114,8 +115,10 @@ class ClientXMPP(BaseXMPP): | |||||||
|         self.register_plugin('feature_session') |         self.register_plugin('feature_session') | ||||||
|         self.register_plugin('feature_rosterver') |         self.register_plugin('feature_rosterver') | ||||||
|         self.register_plugin('feature_preapproval') |         self.register_plugin('feature_preapproval') | ||||||
|         self.register_plugin('feature_mechanisms', |         self.register_plugin('feature_mechanisms') | ||||||
|                 pconfig={'use_mech': sasl_mech} if sasl_mech else None) |  | ||||||
|  |         if sasl_mech: | ||||||
|  |             self['feature_mechanisms'].use_mech = sasl_mech | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def password(self): |     def password(self): | ||||||
| @@ -133,7 +136,7 @@ class ClientXMPP(BaseXMPP): | |||||||
|         be attempted. If that fails, the server user in the JID |         be attempted. If that fails, the server user in the JID | ||||||
|         will be used. |         will be used. | ||||||
|  |  | ||||||
|         :param address   -- A tuple containing the server's host and port. |         :param address: A tuple containing the server's host and port. | ||||||
|         :param reattempt: If ``True``, repeat attempting to connect if an |         :param reattempt: If ``True``, repeat attempting to connect if an | ||||||
|                          error occurs. Defaults to ``True``. |                          error occurs. Defaults to ``True``. | ||||||
|         :param use_tls: Indicates if TLS should be used for the |         :param use_tls: Indicates if TLS should be used for the | ||||||
| @@ -152,8 +155,6 @@ class ClientXMPP(BaseXMPP): | |||||||
|             address = (self.boundjid.host, 5222) |             address = (self.boundjid.host, 5222) | ||||||
|             self.dns_service = 'xmpp-client' |             self.dns_service = 'xmpp-client' | ||||||
|  |  | ||||||
|         self._expected_server_name = self.boundjid.host |  | ||||||
|  |  | ||||||
|         return XMLStream.connect(self, address[0], address[1], |         return XMLStream.connect(self, address[0], address[1], | ||||||
|                                  use_tls=use_tls, use_ssl=use_ssl, |                                  use_tls=use_tls, use_ssl=use_ssl, | ||||||
|                                  reattempt=reattempt) |                                  reattempt=reattempt) | ||||||
| @@ -242,14 +243,22 @@ class ClientXMPP(BaseXMPP): | |||||||
|         if 'rosterver' in self.features: |         if 'rosterver' in self.features: | ||||||
|             iq['roster']['ver'] = self.client_roster.version |             iq['roster']['ver'] = self.client_roster.version | ||||||
|  |  | ||||||
|         if not block and callback is None: |  | ||||||
|             callback = lambda resp: self._handle_roster(resp) |         if not block or callback is not None: | ||||||
|  |             block = False | ||||||
|  |             if callback is None: | ||||||
|  |                 callback = lambda resp: self.event('roster_update', resp) | ||||||
|  |             else: | ||||||
|  |                 orig_cb = callback | ||||||
|  |                 def wrapped(resp): | ||||||
|  |                     self.event('roster_update', resp) | ||||||
|  |                     orig_cb(resp) | ||||||
|  |                 callback = wrapped | ||||||
|  |  | ||||||
|         response = iq.send(block, timeout, callback) |         response = iq.send(block, timeout, callback) | ||||||
|         self.event('roster_received', response) |  | ||||||
|  |  | ||||||
|         if block: |         if block: | ||||||
|             self._handle_roster(response) |             self.event('roster_update', response) | ||||||
|             return response |             return response | ||||||
|  |  | ||||||
|     def _reset_connection_state(self, event=None): |     def _reset_connection_state(self, event=None): | ||||||
| @@ -300,7 +309,6 @@ class ClientXMPP(BaseXMPP): | |||||||
|  |  | ||||||
|                 roster[jid].save(remove=(item['subscription'] == 'remove')) |                 roster[jid].save(remove=(item['subscription'] == 'remove')) | ||||||
|  |  | ||||||
|         self.event("roster_update", iq) |  | ||||||
|         if iq['type'] == 'set': |         if iq['type'] == 'set': | ||||||
|             resp = self.Iq(stype='result', |             resp = self.Iq(stype='result', | ||||||
|                            sto=iq['from'], |                            sto=iq['from'], | ||||||
|   | |||||||
| @@ -123,12 +123,6 @@ class ComponentXMPP(BaseXMPP): | |||||||
|         """ |         """ | ||||||
|         if xml.tag.startswith('{jabber:client}'): |         if xml.tag.startswith('{jabber:client}'): | ||||||
|             xml.tag = xml.tag.replace('jabber:client', self.default_ns) |             xml.tag = xml.tag.replace('jabber:client', self.default_ns) | ||||||
|  |  | ||||||
|         # The incoming_filter call is only made on top level stanza |  | ||||||
|         # elements. So we manually continue filtering on sub-elements. |  | ||||||
|         for sub in xml: |  | ||||||
|             self.incoming_filter(sub) |  | ||||||
|  |  | ||||||
|         return xml |         return xml | ||||||
|  |  | ||||||
|     def start_stream_handler(self, xml): |     def start_stream_handler(self, xml): | ||||||
| @@ -158,8 +152,8 @@ class ComponentXMPP(BaseXMPP): | |||||||
|         """ |         """ | ||||||
|         self.session_bind_event.set() |         self.session_bind_event.set() | ||||||
|         self.session_started_event.set() |         self.session_started_event.set() | ||||||
|         self.event("session_bind", self.boundjid, direct=True) |         self.event('session_bind', self.boundjid, direct=True) | ||||||
|         self.event("session_start") |         self.event('session_start') | ||||||
|  |  | ||||||
|     def _handle_probe(self, pres): |     def _handle_probe(self, pres): | ||||||
|         self.roster[pres['to']][pres['from']].handle_probe(pres) |         self.roster[pres['to']][pres['from']].handle_probe(pres) | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ class XMPPError(Exception): | |||||||
|                   Defaults to ``True``. |                   Defaults to ``True``. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, condition='undefined-condition', text=None, |     def __init__(self, condition='undefined-condition', text='', | ||||||
|                 etype='cancel', extension=None, extension_ns=None, |                 etype='cancel', extension=None, extension_ns=None, | ||||||
|                 extension_args=None, clear=True): |                 extension_args=None, clear=True): | ||||||
|         if extension_args is None: |         if extension_args is None: | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ from sleekxmpp.jid import JID | |||||||
| from sleekxmpp.stanza import Iq, StreamFeatures | from sleekxmpp.stanza import Iq, StreamFeatures | ||||||
| from sleekxmpp.features.feature_bind import stanza | from sleekxmpp.features.feature_bind import stanza | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | from sleekxmpp.plugins import BasePlugin | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) | log = logging.getLogger(__name__) | ||||||
| @@ -41,12 +41,12 @@ class FeatureBind(BasePlugin): | |||||||
|         Arguments: |         Arguments: | ||||||
|             features -- The stream features stanza. |             features -- The stream features stanza. | ||||||
|         """ |         """ | ||||||
|         log.debug("Requesting resource: %s", self.xmpp.boundjid.resource) |         log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource) | ||||||
|         iq = self.xmpp.Iq() |         iq = self.xmpp.Iq() | ||||||
|         iq['type'] = 'set' |         iq['type'] = 'set' | ||||||
|         iq.enable('bind') |         iq.enable('bind') | ||||||
|         if self.xmpp.boundjid.resource: |         if self.xmpp.requested_jid.resource: | ||||||
|             iq['bind']['resource'] = self.xmpp.boundjid.resource |             iq['bind']['resource'] = self.xmpp.requested_jid.resource | ||||||
|         response = iq.send(now=True) |         response = iq.send(now=True) | ||||||
|  |  | ||||||
|         self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) |         self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) | ||||||
| @@ -56,10 +56,10 @@ class FeatureBind(BasePlugin): | |||||||
|  |  | ||||||
|         self.xmpp.features.add('bind') |         self.xmpp.features.add('bind') | ||||||
|  |  | ||||||
|         log.info("Node set to: %s", self.xmpp.boundjid.full) |         log.info("JID set to: %s", self.xmpp.boundjid.full) | ||||||
|  |  | ||||||
|         if 'session' not in features['features']: |         if 'session' not in features['features']: | ||||||
|             log.debug("Established Session") |             log.debug("Established Session") | ||||||
|             self.xmpp.sessionstarted = True |             self.xmpp.sessionstarted = True | ||||||
|             self.xmpp.session_started_event.set() |             self.xmpp.session_started_event.set() | ||||||
|             self.xmpp.event("session_start") |             self.xmpp.event('session_start') | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ | |||||||
|     See the file LICENSE for copying permission. |     See the file LICENSE for copying permission. | ||||||
| """ | """ | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import ssl | import ssl | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| @@ -44,15 +43,16 @@ class FeatureMechanisms(BasePlugin): | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         if not self.use_mech and not self.xmpp.requested_jid.user: |  | ||||||
|             self.use_mech = 'ANONYMOUS' |  | ||||||
|  |  | ||||||
|         if self.sasl_callback is None: |         if self.sasl_callback is None: | ||||||
|             self.sasl_callback = self._default_credentials |             self.sasl_callback = self._default_credentials | ||||||
|  |  | ||||||
|         if self.security_callback is None: |         if self.security_callback is None: | ||||||
|             self.security_callback = self._default_security |             self.security_callback = self._default_security | ||||||
|  |  | ||||||
|  |         creds = self.sasl_callback(set(['username']), set()) | ||||||
|  |         if not self.use_mech and not creds['username']: | ||||||
|  |             self.use_mech = 'ANONYMOUS' | ||||||
|  |  | ||||||
|         self.mech = None |         self.mech = None | ||||||
|         self.mech_list = set() |         self.mech_list = set() | ||||||
|         self.attempted_mechs = set() |         self.attempted_mechs = set() | ||||||
| @@ -92,27 +92,26 @@ class FeatureMechanisms(BasePlugin): | |||||||
|         values = required_values.union(optional_values) |         values = required_values.union(optional_values) | ||||||
|         for value in values: |         for value in values: | ||||||
|             if value == 'username': |             if value == 'username': | ||||||
|                 result[value] = self.xmpp.requested_jid.user |                 result[value] = creds.get('username', self.xmpp.requested_jid.user) | ||||||
|             elif value == 'password': |  | ||||||
|                 result[value] = creds['password'] |  | ||||||
|             elif value == 'authzid': |  | ||||||
|                 result[value] = creds.get('authzid', '') |  | ||||||
|             elif value == 'email': |             elif value == 'email': | ||||||
|                 jid = self.xmpp.requested_jid.bare |                 jid = self.xmpp.requested_jid.bare | ||||||
|                 result[value] = creds.get('email', jid) |                 result[value] = creds.get('email', jid) | ||||||
|             elif value == 'channel_binding': |             elif value == 'channel_binding': | ||||||
|                 if sys.version_info >= (3, 3): |                 if hasattr(self.xmpp.socket, 'get_channel_binding'): | ||||||
|                     result[value] = self.xmpp.socket.get_channel_binding() |                     result[value] = self.xmpp.socket.get_channel_binding() | ||||||
|                 else: |                 else: | ||||||
|  |                     log.debug("Channel binding not supported.") | ||||||
|  |                     log.debug("Use Python 3.3+ for channel binding and " + \ | ||||||
|  |                               "SCRAM-SHA-1-PLUS support") | ||||||
|                     result[value] = None |                     result[value] = None | ||||||
|             elif value == 'host': |             elif value == 'host': | ||||||
|                 result[value] = self.xmpp.requested_jid.domain |                 result[value] = creds.get('host', self.xmpp.requested_jid.domain) | ||||||
|             elif value == 'realm': |             elif value == 'realm': | ||||||
|                 result[value] = self.xmpp.requested_jid.domain |                 result[value] = creds.get('realm', self.xmpp.requested_jid.domain) | ||||||
|             elif value == 'service-name': |             elif value == 'service-name': | ||||||
|                 result[value] = self.xmpp._service_name |                 result[value] = creds.get('service-name', self.xmpp._service_name) | ||||||
|             elif value == 'service': |             elif value == 'service': | ||||||
|                 result[value] = 'xmpp' |                 result[value] = creds.get('service', 'xmpp') | ||||||
|             elif value in creds: |             elif value in creds: | ||||||
|                 result[value] = creds[value] |                 result[value] = creds[value] | ||||||
|         return result |         return result | ||||||
| @@ -174,8 +173,12 @@ class FeatureMechanisms(BasePlugin): | |||||||
|         except sasl.SASLNoAppropriateMechanism: |         except sasl.SASLNoAppropriateMechanism: | ||||||
|             log.error("No appropriate login method.") |             log.error("No appropriate login method.") | ||||||
|             self.xmpp.event("no_auth", direct=True) |             self.xmpp.event("no_auth", direct=True) | ||||||
|  |             self.xmpp.event("failed_auth", direct=True) | ||||||
|             self.attempted_mechs = set() |             self.attempted_mechs = set() | ||||||
|             return self.xmpp.disconnect() |             return self.xmpp.disconnect() | ||||||
|  |         except StringPrepError: | ||||||
|  |             log.exception("A credential value did not pass SASLprep.") | ||||||
|  |             self.xmpp.disconnect() | ||||||
|  |  | ||||||
|         resp = stanza.Auth(self.xmpp) |         resp = stanza.Auth(self.xmpp) | ||||||
|         resp['mechanism'] = self.mech.name |         resp['mechanism'] = self.mech.name | ||||||
| @@ -192,9 +195,6 @@ class FeatureMechanisms(BasePlugin): | |||||||
|                       "A security breach is possible.") |                       "A security breach is possible.") | ||||||
|             self.attempted_mechs.add(self.mech.name) |             self.attempted_mechs.add(self.mech.name) | ||||||
|             self.xmpp.disconnect() |             self.xmpp.disconnect() | ||||||
|         except StringPrepError: |  | ||||||
|             log.exception("A credential value did not pass SASLprep.") |  | ||||||
|             self.xmpp.disconnect() |  | ||||||
|         else: |         else: | ||||||
|             resp.send(now=True) |             resp.send(now=True) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ class Auth(StanzaBase): | |||||||
|         if not self['mechanism'] in self.plain_mechs: |         if not self['mechanism'] in self.plain_mechs: | ||||||
|             if values: |             if values: | ||||||
|                 self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') |                 self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') | ||||||
|             else: |             elif values == b'': | ||||||
|                 self.xml.text = '=' |                 self.xml.text = '=' | ||||||
|         else: |         else: | ||||||
|             self.xml.text = bytes(values).decode('utf-8') |             self.xml.text = bytes(values).decode('utf-8') | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Iq, StreamFeatures | from sleekxmpp.stanza import StreamFeatures | ||||||
| from sleekxmpp.features.feature_preapproval import stanza | from sleekxmpp.features.feature_preapproval import stanza | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
| from sleekxmpp.plugins.base import BasePlugin | from sleekxmpp.plugins.base import BasePlugin | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import Iq, StreamFeatures | from sleekxmpp.stanza import StreamFeatures | ||||||
| from sleekxmpp.features.feature_rosterver import stanza | from sleekxmpp.features.feature_rosterver import stanza | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
| from sleekxmpp.plugins.base import BasePlugin | from sleekxmpp.plugins.base import BasePlugin | ||||||
|   | |||||||
| @@ -51,4 +51,4 @@ class FeatureSession(BasePlugin): | |||||||
|         log.debug("Established Session") |         log.debug("Established Session") | ||||||
|         self.xmpp.sessionstarted = True |         self.xmpp.sessionstarted = True | ||||||
|         self.xmpp.session_started_event.set() |         self.xmpp.session_started_event.set() | ||||||
|         self.xmpp.event("session_start") |         self.xmpp.event('session_start') | ||||||
|   | |||||||
							
								
								
									
										193
									
								
								sleekxmpp/jid.py
									
									
									
									
									
								
							
							
						
						
									
										193
									
								
								sleekxmpp/jid.py
									
									
									
									
									
								
							| @@ -19,6 +19,8 @@ import stringprep | |||||||
| import threading | import threading | ||||||
| import encodings.idna | import encodings.idna | ||||||
|  |  | ||||||
|  | from copy import deepcopy | ||||||
|  |  | ||||||
| from sleekxmpp.util import stringprep_profiles | from sleekxmpp.util import stringprep_profiles | ||||||
| from sleekxmpp.thirdparty import OrderedDict | from sleekxmpp.thirdparty import OrderedDict | ||||||
|  |  | ||||||
| @@ -69,6 +71,20 @@ JID_CACHE = OrderedDict() | |||||||
| JID_CACHE_LOCK = threading.Lock() | JID_CACHE_LOCK = threading.Lock() | ||||||
| JID_CACHE_MAX_SIZE = 1024 | 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 | # pylint: disable=c0103 | ||||||
| #: The nodeprep profile of stringprep used to validate the local, | #: The nodeprep profile of stringprep used to validate the local, | ||||||
| @@ -188,7 +204,7 @@ def _validate_domain(domain): | |||||||
|             socket.inet_pton(socket.AF_INET6, domain.strip('[]')) |             socket.inet_pton(socket.AF_INET6, domain.strip('[]')) | ||||||
|             domain = '[%s]' % domain.strip('[]') |             domain = '[%s]' % domain.strip('[]') | ||||||
|             ip_addr = True |             ip_addr = True | ||||||
|         except socket.error: |         except (socket.error, ValueError): | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|     if not ip_addr: |     if not ip_addr: | ||||||
| @@ -214,7 +230,7 @@ def _validate_domain(domain): | |||||||
|  |  | ||||||
|             for char in label: |             for char in label: | ||||||
|                 if char in ILLEGAL_CHARS: |                 if char in ILLEGAL_CHARS: | ||||||
|                     raise InvalidJID('Domain contains illegar characters') |                     raise InvalidJID('Domain contains illegal characters') | ||||||
|  |  | ||||||
|             if '-' in (label[0], label[-1]): |             if '-' in (label[0], label[-1]): | ||||||
|                 raise InvalidJID('Domain started or ended with -') |                 raise InvalidJID('Domain started or ended with -') | ||||||
| @@ -418,19 +434,29 @@ class JID(object): | |||||||
|  |  | ||||||
|     # pylint: disable=W0212 |     # pylint: disable=W0212 | ||||||
|     def __init__(self, jid=None, **kwargs): |     def __init__(self, jid=None, **kwargs): | ||||||
|         jid_data = (jid, kwargs.get('local', None), |  | ||||||
|                          kwargs.get('domain', None), |  | ||||||
|                          kwargs.get('resource', None)) |  | ||||||
|  |  | ||||||
|         locked = kwargs.get('cache_lock', False) |         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) | ||||||
|  |  | ||||||
|         if jid_data in JID_CACHE: |         # only check cache if there is a jid string, or parts, not if there | ||||||
|             parsed_jid, locked = JID_CACHE[jid_data] |         # are both | ||||||
|             self._jid = parsed_jid |         self._jid = None | ||||||
|         else: |         key = None | ||||||
|             if jid is None: |         if (jid is not None) and (parts is None): | ||||||
|                 jid = '' |             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: |             if not jid: | ||||||
|                 parsed_jid = (None, None, None) |                 parsed_jid = (None, None, None) | ||||||
|             elif not isinstance(jid, JID): |             elif not isinstance(jid, JID): | ||||||
| @@ -440,27 +466,16 @@ class JID(object): | |||||||
|  |  | ||||||
|             local, domain, resource = parsed_jid |             local, domain, resource = parsed_jid | ||||||
|  |  | ||||||
|             local = kwargs.get('local', local) |  | ||||||
|             domain = kwargs.get('domain', domain) |  | ||||||
|             resource = kwargs.get('resource', resource) |  | ||||||
|  |  | ||||||
|             if 'local' in kwargs: |             if 'local' in kwargs: | ||||||
|                 local = _escape_node(local) |                 local = _escape_node(in_local) | ||||||
|             if 'domain' in kwargs: |             if 'domain' in kwargs: | ||||||
|                 domain = _validate_domain(domain) |                 domain = _validate_domain(in_domain) | ||||||
|             if 'resource' in kwargs: |             if 'resource' in kwargs: | ||||||
|                 resource = _validate_resource(resource) |                 resource = _validate_resource(in_resource) | ||||||
|  |  | ||||||
|             self._jid = (local, domain, resource) |             self._jid = (local, domain, resource) | ||||||
|  |             if key: | ||||||
|         JID_CACHE[jid_data] = (self._jid, locked) |                 _cache(key, self._jid, locked) | ||||||
|         if len(JID_CACHE) > JID_CACHE_MAX_SIZE: |  | ||||||
|             with JID_CACHE_LOCK: |  | ||||||
|                 key, item = JID_CACHE.popitem(False) |  | ||||||
|                 if item[1]: |  | ||||||
|                     # Need to reinsert locked JIDs |  | ||||||
|                     JID_CACHE[key] = item |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def unescape(self): |     def unescape(self): | ||||||
|         """Return an unescaped JID object. |         """Return an unescaped JID object. | ||||||
| @@ -493,51 +508,101 @@ class JID(object): | |||||||
|         """ |         """ | ||||||
|         self._jid = JID(data)._jid |         self._jid = JID(data)._jid | ||||||
|  |  | ||||||
|     # pylint: disable=R0911 |     @property | ||||||
|     def __getattr__(self, name): |     def resource(self): | ||||||
|         """Retrieve the given JID component. |  | ||||||
|  |  | ||||||
|         :param name: one of: user, server, domain, resource, |  | ||||||
|                      full, or bare. |  | ||||||
|         """ |  | ||||||
|         if name == 'resource': |  | ||||||
|         return self._jid[2] or '' |         return self._jid[2] or '' | ||||||
|         elif name in ('user', 'username', 'local', 'node'): |  | ||||||
|  |     @property | ||||||
|  |     def user(self): | ||||||
|         return self._jid[0] or '' |         return self._jid[0] or '' | ||||||
|         elif name in ('server', 'domain', 'host'): |  | ||||||
|             return self._jid[1] or '' |     @property | ||||||
|         elif name in ('full', 'jid'): |     def local(self): | ||||||
|             return _format_jid(*self._jid) |         return self._jid[0] or '' | ||||||
|         elif name == 'bare': |  | ||||||
|  |     @property | ||||||
|  |     def node(self): | ||||||
|  |         return self._jid[0] or '' | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def username(self): | ||||||
|  |         return self._jid[0] or '' | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def bare(self): | ||||||
|         return _format_jid(self._jid[0], self._jid[1]) |         return _format_jid(self._jid[0], self._jid[1]) | ||||||
|         elif name == '_jid': |  | ||||||
|             return getattr(super(JID, self), '_jid') |  | ||||||
|         else: |  | ||||||
|             return None |  | ||||||
|  |  | ||||||
|     # pylint: disable=W0212 |     @property | ||||||
|     def __setattr__(self, name, value): |     def server(self): | ||||||
|         """Update the given JID component. |         return self._jid[1] or '' | ||||||
|  |  | ||||||
|         :param name: one of: ``user``, ``username``, ``local``, |     @property | ||||||
|                              ``node``, ``server``, ``domain``, ``host``, |     def domain(self): | ||||||
|                              ``resource``, ``full``, ``jid``, or ``bare``. |         return self._jid[1] or '' | ||||||
|         :param value: The new string value of the JID component. |  | ||||||
|         """ |     @property | ||||||
|         if name == '_jid': |     def host(self): | ||||||
|             super(JID, self).__setattr__('_jid', value) |         return self._jid[1] or '' | ||||||
|         elif name == 'resource': |  | ||||||
|  |     @property | ||||||
|  |     def full(self): | ||||||
|  |         return _format_jid(*self._jid) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def jid(self): | ||||||
|  |         return _format_jid(*self._jid) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def bare(self): | ||||||
|  |         return _format_jid(self._jid[0], self._jid[1]) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @resource.setter | ||||||
|  |     def resource(self, value): | ||||||
|         self._jid = JID(self, resource=value)._jid |         self._jid = JID(self, resource=value)._jid | ||||||
|         elif name in ('user', 'username', 'local', 'node'): |  | ||||||
|  |     @user.setter | ||||||
|  |     def user(self, value): | ||||||
|         self._jid = JID(self, local=value)._jid |         self._jid = JID(self, local=value)._jid | ||||||
|         elif name in ('server', 'domain', 'host'): |  | ||||||
|  |     @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 | ||||||
|  |  | ||||||
|  |     @node.setter | ||||||
|  |     def node(self, value): | ||||||
|  |         self._jid = JID(self, local=value)._jid | ||||||
|  |  | ||||||
|  |     @server.setter | ||||||
|  |     def server(self, value): | ||||||
|         self._jid = JID(self, domain=value)._jid |         self._jid = JID(self, domain=value)._jid | ||||||
|         elif name in ('full', 'jid'): |  | ||||||
|  |     @domain.setter | ||||||
|  |     def domain(self, value): | ||||||
|  |         self._jid = JID(self, domain=value)._jid | ||||||
|  |  | ||||||
|  |     @host.setter | ||||||
|  |     def host(self, value): | ||||||
|  |         self._jid = JID(self, domain=value)._jid | ||||||
|  |  | ||||||
|  |     @full.setter | ||||||
|  |     def full(self, value): | ||||||
|         self._jid = JID(value)._jid |         self._jid = JID(value)._jid | ||||||
|         elif name == 'bare': |  | ||||||
|  |     @jid.setter | ||||||
|  |     def jid(self, value): | ||||||
|  |         self._jid = JID(value)._jid | ||||||
|  |  | ||||||
|  |     @bare.setter | ||||||
|  |     def bare(self, value): | ||||||
|         parsed = JID(value)._jid |         parsed = JID(value)._jid | ||||||
|         self._jid = (parsed[0], parsed[1], self._jid[2]) |         self._jid = (parsed[0], parsed[1], self._jid[2]) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         """Use the full JID as the string value.""" |         """Use the full JID as the string value.""" | ||||||
|         return _format_jid(*self._jid) |         return _format_jid(*self._jid) | ||||||
| @@ -567,3 +632,7 @@ class JID(object): | |||||||
|     def __copy__(self): |     def __copy__(self): | ||||||
|         """Generate a duplicate JID.""" |         """Generate a duplicate JID.""" | ||||||
|         return JID(self) |         return JID(self) | ||||||
|  |  | ||||||
|  |     def __deepcopy__(self, memo): | ||||||
|  |         """Generate a duplicate JID.""" | ||||||
|  |         return JID(deepcopy(str(self), memo)) | ||||||
|   | |||||||
| @@ -11,28 +11,30 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin | |||||||
|  |  | ||||||
|  |  | ||||||
| __all__ = [ | __all__ = [ | ||||||
|     # Non-standard |  | ||||||
|     'gmail_notify',  # Gmail searching and notifications |  | ||||||
|  |  | ||||||
|     # XEPS |     # XEPS | ||||||
|     'xep_0004',  # Data Forms |     'xep_0004',  # Data Forms | ||||||
|     'xep_0009',  # Jabber-RPC |     'xep_0009',  # Jabber-RPC | ||||||
|     'xep_0012',  # Last Activity |     'xep_0012',  # Last Activity | ||||||
|     'xep_0013',  # Flexible Offline Message Retrieval |     'xep_0013',  # Flexible Offline Message Retrieval | ||||||
|     'xep_0016',  # Privacy Lists |     'xep_0016',  # Privacy Lists | ||||||
|  |     'xep_0020',  # Feature Negotiation | ||||||
|     'xep_0027',  # Current Jabber OpenPGP Usage |     'xep_0027',  # Current Jabber OpenPGP Usage | ||||||
|     'xep_0030',  # Service Discovery |     'xep_0030',  # Service Discovery | ||||||
|     'xep_0033',  # Extended Stanza Addresses |     'xep_0033',  # Extended Stanza Addresses | ||||||
|     'xep_0045',  # Multi-User Chat (Client) |     'xep_0045',  # Multi-User Chat (Client) | ||||||
|     'xep_0047',  # In-Band Bytestreams |     'xep_0047',  # In-Band Bytestreams | ||||||
|  |     'xep_0048',  # Bookmarks | ||||||
|     'xep_0049',  # Private XML Storage |     'xep_0049',  # Private XML Storage | ||||||
|     'xep_0050',  # Ad-hoc Commands |     'xep_0050',  # Ad-hoc Commands | ||||||
|     'xep_0054',  # vcard-temp |     'xep_0054',  # vcard-temp | ||||||
|     'xep_0059',  # Result Set Management |     'xep_0059',  # Result Set Management | ||||||
|     'xep_0060',  # Pubsub (Client) |     'xep_0060',  # Pubsub (Client) | ||||||
|  |     'xep_0065',  # SOCKS5 Bytestreams | ||||||
|     'xep_0066',  # Out of Band Data |     'xep_0066',  # Out of Band Data | ||||||
|  |     'xep_0071',  # XHTML-IM | ||||||
|     'xep_0077',  # In-Band Registration |     'xep_0077',  # In-Band Registration | ||||||
| #   'xep_0078',  # Non-SASL auth. Don't automatically load | #   'xep_0078',  # Non-SASL auth. Don't automatically load | ||||||
|  |     'xep_0079',  # Advanced Message Processing | ||||||
|     'xep_0080',  # User Location |     'xep_0080',  # User Location | ||||||
|     'xep_0082',  # XMPP Date and Time Profiles |     'xep_0082',  # XMPP Date and Time Profiles | ||||||
|     'xep_0084',  # User Avatar |     'xep_0084',  # User Avatar | ||||||
| @@ -48,12 +50,14 @@ __all__ = [ | |||||||
|     'xep_0128',  # Extended Service Discovery |     'xep_0128',  # Extended Service Discovery | ||||||
|     'xep_0131',  # Standard Headers and Internet Metadata |     'xep_0131',  # Standard Headers and Internet Metadata | ||||||
|     'xep_0133',  # Service Administration |     'xep_0133',  # Service Administration | ||||||
|  |     'xep_0152',  # Reachability Addresses | ||||||
|     'xep_0153',  # vCard-Based Avatars |     'xep_0153',  # vCard-Based Avatars | ||||||
|     'xep_0163',  # Personal Eventing Protocol |     'xep_0163',  # Personal Eventing Protocol | ||||||
|     'xep_0172',  # User Nickname |     'xep_0172',  # User Nickname | ||||||
|     'xep_0184',  # Message Receipts |     'xep_0184',  # Message Receipts | ||||||
|     'xep_0186',  # Invisible Command |     'xep_0186',  # Invisible Command | ||||||
|     'xep_0191',  # Blocking Command |     'xep_0191',  # Blocking Command | ||||||
|  |     'xep_0196',  # User Gaming | ||||||
|     'xep_0198',  # Stream Management |     'xep_0198',  # Stream Management | ||||||
|     'xep_0199',  # Ping |     'xep_0199',  # Ping | ||||||
|     'xep_0202',  # Entity Time |     'xep_0202',  # Entity Time | ||||||
| @@ -76,4 +80,7 @@ __all__ = [ | |||||||
|     'xep_0302',  # XMPP Compliance Suites 2012 |     'xep_0302',  # XMPP Compliance Suites 2012 | ||||||
|     'xep_0308',  # Last Message Correction |     'xep_0308',  # Last Message Correction | ||||||
|     'xep_0313',  # Message Archive Management |     'xep_0313',  # Message Archive Management | ||||||
|  |     'xep_0319',  # Last User Interaction in Presence | ||||||
|  |     'xep_0323',  # IoT Systems Sensor Data | ||||||
|  |     'xep_0325',  # IoT Systems Control | ||||||
| ] | ] | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								sleekxmpp/plugins/google/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								sleekxmpp/plugins/google/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin, BasePlugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.google.gmail import Gmail | ||||||
|  | from sleekxmpp.plugins.google.auth import GoogleAuth | ||||||
|  | from sleekxmpp.plugins.google.settings import GoogleSettings | ||||||
|  | from sleekxmpp.plugins.google.nosave import GoogleNoSave | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Google(BasePlugin): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     Google: Custom GTalk Features | ||||||
|  |  | ||||||
|  |     Also see: <https://developers.google.com/talk/jep_extensions/extensions> | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     name = 'google' | ||||||
|  |     description = 'Google: Custom GTalk Features' | ||||||
|  |     dependencies = set([ | ||||||
|  |         'gmail', | ||||||
|  |         'google_settings', | ||||||
|  |         'google_nosave', | ||||||
|  |         'google_auth' | ||||||
|  |     ]) | ||||||
|  |  | ||||||
|  |     def __getitem__(self, attr): | ||||||
|  |         if attr in ('settings', 'nosave', 'auth'): | ||||||
|  |             return self.xmpp['google_%s' % attr] | ||||||
|  |         elif attr == 'gmail': | ||||||
|  |             return self.xmpp['gmail'] | ||||||
|  |         else: | ||||||
|  |             raise KeyError(attr) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(Gmail) | ||||||
|  | register_plugin(GoogleAuth) | ||||||
|  | register_plugin(GoogleSettings) | ||||||
|  | register_plugin(GoogleNoSave) | ||||||
|  | register_plugin(Google) | ||||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/google/auth/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/google/auth/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.google.auth import stanza | ||||||
|  | from sleekxmpp.plugins.google.auth.auth import GoogleAuth | ||||||
							
								
								
									
										52
									
								
								sleekxmpp/plugins/google/auth/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								sleekxmpp/plugins/google/auth/auth.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.plugins.google.auth import stanza | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GoogleAuth(BasePlugin): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     Google: Auth Extensions (JID Domain Discovery, OAuth2) | ||||||
|  |  | ||||||
|  |     Also see: | ||||||
|  |         <https://developers.google.com/talk/jep_extensions/jid_domain_change> | ||||||
|  |         <https://developers.google.com/talk/jep_extensions/oauth> | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     name = 'google_auth' | ||||||
|  |     description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)' | ||||||
|  |     dependencies = set(['feature_mechanisms']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga' | ||||||
|  |  | ||||||
|  |         register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth, | ||||||
|  |                                stanza.GoogleAuth) | ||||||
|  |  | ||||||
|  |         self.xmpp.add_filter('out', self._auth) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.del_filter('out', self._auth) | ||||||
|  |  | ||||||
|  |     def _auth(self, stanza): | ||||||
|  |         if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth): | ||||||
|  |             stanza.stream = self.xmpp | ||||||
|  |             stanza['google']['client_uses_full_bind_result'] = True | ||||||
|  |             if stanza['mechanism'] == 'X-OAUTH2': | ||||||
|  |                 stanza['google']['service'] = 'oauth2' | ||||||
|  |             print(stanza) | ||||||
|  |         return stanza | ||||||
							
								
								
									
										49
									
								
								sleekxmpp/plugins/google/auth/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								sleekxmpp/plugins/google/auth/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ElementBase, ET | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class 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('') | ||||||
|  |         print('setting up google extension') | ||||||
|  |  | ||||||
|  |     def get_client_uses_full_bind_result(self): | ||||||
|  |         return self.parent()._get_attr(self.disovery_attr) == 'true' | ||||||
|  |  | ||||||
|  |     def set_client_uses_full_bind_result(self, value): | ||||||
|  |         print('>>>', 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) | ||||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/google/gmail/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/google/gmail/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.google.gmail import stanza | ||||||
|  | from sleekxmpp.plugins.google.gmail.notifications import Gmail | ||||||
							
								
								
									
										96
									
								
								sleekxmpp/plugins/google/gmail/notifications.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								sleekxmpp/plugins/google/gmail/notifications.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Iq | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import MatchXPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.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, block=True, timeout=None, callback=None): | ||||||
|  |         last_time = self._last_result_time | ||||||
|  |         last_tid = self._last_result_tid | ||||||
|  |  | ||||||
|  |         if not block: | ||||||
|  |             callback = lambda iq: self._update_last_results(iq, callback) | ||||||
|  |  | ||||||
|  |         resp = self.search(newer_time=last_time, | ||||||
|  |             newer_tid=last_tid, | ||||||
|  |             block=block, | ||||||
|  |             timeout=timeout, | ||||||
|  |             callback=callback) | ||||||
|  |  | ||||||
|  |         if block: | ||||||
|  |             self._update_last_results(resp) | ||||||
|  |             return resp | ||||||
|  |  | ||||||
|  |     def _update_last_results(self, iq, callback=None): | ||||||
|  |         self._last_result_time = data['gmail_messages']['result_time'] | ||||||
|  |         threads = data['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, block=True, | ||||||
|  |                      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(block=block, timeout=timeout, callback=callback) | ||||||
							
								
								
									
										101
									
								
								sleekxmpp/plugins/google/gmail/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								sleekxmpp/plugins/google/gmail/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GmailQuery(ElementBase): | ||||||
|  |     namespace = 'google:mail:notify' | ||||||
|  |     name = 'query' | ||||||
|  |     plugin_attrib = 'gmail' | ||||||
|  |     interfaces = set(['newer_than_time', 'newer_than_tid', 'search']) | ||||||
|  |  | ||||||
|  |     def get_search(self): | ||||||
|  |         return self._get_attr('q', '') | ||||||
|  |  | ||||||
|  |     def set_search(self, search): | ||||||
|  |         self._set_attr('q', search) | ||||||
|  |  | ||||||
|  |     def del_search(self): | ||||||
|  |         self._del_attr('q') | ||||||
|  |  | ||||||
|  |     def get_newer_than_time(self): | ||||||
|  |         return self._get_attr('newer-than-time', '') | ||||||
|  |  | ||||||
|  |     def set_newer_than_time(self, value): | ||||||
|  |         self._set_attr('newer-than-time', value) | ||||||
|  |  | ||||||
|  |     def del_newer_than_time(self): | ||||||
|  |         self._del_attr('newer-than-time') | ||||||
|  |  | ||||||
|  |     def get_newer_than_tid(self): | ||||||
|  |         return self._get_attr('newer-than-tid', '') | ||||||
|  |  | ||||||
|  |     def set_newer_than_tid(self, value): | ||||||
|  |         self._set_attr('newer-than-tid', value) | ||||||
|  |  | ||||||
|  |     def del_newer_than_tid(self): | ||||||
|  |         self._del_attr('newer-than-tid') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MailBox(ElementBase): | ||||||
|  |     namespace = 'google:mail:notify' | ||||||
|  |     name = 'mailbox' | ||||||
|  |     plugin_attrib = 'gmail_messages' | ||||||
|  |     interfaces = set(['result_time', 'url', 'matched', 'estimate']) | ||||||
|  |  | ||||||
|  |     def get_matched(self): | ||||||
|  |         return self._get_attr('total-matched', '') | ||||||
|  |  | ||||||
|  |     def get_estimate(self): | ||||||
|  |         return self._get_attr('total-estimate', '') == '1' | ||||||
|  |  | ||||||
|  |     def get_result_time(self): | ||||||
|  |         return self._get_attr('result-time', '') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MailThread(ElementBase): | ||||||
|  |     namespace = 'google:mail:notify' | ||||||
|  |     name = 'mail-thread-info' | ||||||
|  |     plugin_attrib = 'thread' | ||||||
|  |     plugin_multi_attrib = 'threads' | ||||||
|  |     interfaces = set(['tid', 'participation', 'messages', 'date', | ||||||
|  |                       'senders', 'url', 'labels', 'subject', 'snippet']) | ||||||
|  |     sub_interfaces = set(['labels', 'subject', 'snippet']) | ||||||
|  |  | ||||||
|  |     def get_senders(self): | ||||||
|  |         result = [] | ||||||
|  |         senders = self.xml.findall('{%s}senders/{%s}sender' % ( | ||||||
|  |             self.namespace, self.namespace)) | ||||||
|  |  | ||||||
|  |         for sender in senders: | ||||||
|  |             result.append(MailSender(xml=sender)) | ||||||
|  |  | ||||||
|  |         return result | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MailSender(ElementBase): | ||||||
|  |     namespace = 'google:mail:notify' | ||||||
|  |     name = 'sender' | ||||||
|  |     plugin_attrib = name | ||||||
|  |     interfaces = set(['address', 'name', 'originator', 'unread']) | ||||||
|  |  | ||||||
|  |     def get_originator(self): | ||||||
|  |         return self.xml.attrib.get('originator', '0') == '1' | ||||||
|  |  | ||||||
|  |     def get_unread(self): | ||||||
|  |         return self.xml.attrib.get('unread', '0') == '1' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class NewMail(ElementBase): | ||||||
|  |     namespace = 'google:mail:notify' | ||||||
|  |     name = 'new-mail' | ||||||
|  |     plugin_attrib = 'gmail_notification' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_stanza_plugin(MailBox, MailThread, iterable=True) | ||||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/google/nosave/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/google/nosave/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.google.nosave import stanza | ||||||
|  | from sleekxmpp.plugins.google.nosave.nosave import GoogleNoSave | ||||||
							
								
								
									
										83
									
								
								sleekxmpp/plugins/google/nosave/nosave.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								sleekxmpp/plugins/google/nosave/nosave.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Iq, Message | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.plugins.google.nosave import stanza | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GoogleNoSave(BasePlugin): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     Google: Off the Record Chats | ||||||
|  |  | ||||||
|  |     NOTE: This is NOT an encryption method. | ||||||
|  |  | ||||||
|  |     Also see <https://developers.google.com/talk/jep_extensions/otr>. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     name = 'google_nosave' | ||||||
|  |     description = 'Google: Off the Record Chats' | ||||||
|  |     dependencies = set(['google_settings']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(Message, stanza.NoSave) | ||||||
|  |         register_stanza_plugin(Iq, stanza.NoSaveQuery) | ||||||
|  |  | ||||||
|  |         self.xmpp.register_handler( | ||||||
|  |                 Callback('Google Nosave', | ||||||
|  |                     StanzaPath('iq@type=set/google_nosave'), | ||||||
|  |                     self._handle_nosave_change)) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Google Nosave') | ||||||
|  |  | ||||||
|  |     def enable(self, jid=None, block=True, timeout=None, callback=None): | ||||||
|  |         if jid is None: | ||||||
|  |             self.xmpp['google_settings'].update({'archiving_enabled': False}, | ||||||
|  |                     block=block, timeout=timeout, callback=callback) | ||||||
|  |         else: | ||||||
|  |             iq = self.xmpp.Iq() | ||||||
|  |             iq['type'] = 'set' | ||||||
|  |             iq['google_nosave']['item']['jid'] = jid | ||||||
|  |             iq['google_nosave']['item']['value'] = True | ||||||
|  |             return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def disable(self, jid=None, block=True, timeout=None, callback=None): | ||||||
|  |         if jid is None: | ||||||
|  |             self.xmpp['google_settings'].update({'archiving_enabled': True}, | ||||||
|  |                     block=block, timeout=timeout, callback=callback) | ||||||
|  |         else: | ||||||
|  |             iq = self.xmpp.Iq() | ||||||
|  |             iq['type'] = 'set' | ||||||
|  |             iq['google_nosave']['item']['jid'] = jid | ||||||
|  |             iq['google_nosave']['item']['value'] = False | ||||||
|  |             return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def get(self, block=True, timeout=None, callback=None): | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['type'] = 'get' | ||||||
|  |         iq.enable('google_nosave') | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def _handle_nosave_change(self, iq): | ||||||
|  |         reply = self.xmpp.Iq() | ||||||
|  |         reply['type'] = 'result' | ||||||
|  |         reply['id'] = iq['id'] | ||||||
|  |         reply['to'] = iq['from'] | ||||||
|  |         reply.send() | ||||||
|  |         self.xmpp.event('google_nosave_change', iq) | ||||||
							
								
								
									
										59
									
								
								sleekxmpp/plugins/google/nosave/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								sleekxmpp/plugins/google/nosave/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.jid import JID | ||||||
|  | from sleekxmpp.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): | ||||||
|  |         self._set_attr('source', str(value)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_stanza_plugin(NoSaveQuery, Item) | ||||||
							
								
								
									
										10
									
								
								sleekxmpp/plugins/google/settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								sleekxmpp/plugins/google/settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.google.settings import stanza | ||||||
|  | from sleekxmpp.plugins.google.settings.settings import GoogleSettings | ||||||
							
								
								
									
										65
									
								
								sleekxmpp/plugins/google/settings/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								sleekxmpp/plugins/google/settings/settings.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Iq | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.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, block=True, timeout=None, callback=None): | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['type'] = 'get' | ||||||
|  |         iq.enable('google_settings') | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def update(self, settings, block=True, 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(block=block, 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) | ||||||
							
								
								
									
										110
									
								
								sleekxmpp/plugins/google/settings/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								sleekxmpp/plugins/google/settings/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserSettings(ElementBase): | ||||||
|  |     name = 'usersetting' | ||||||
|  |     namespace = 'google:setting' | ||||||
|  |     plugin_attrib = 'google_settings' | ||||||
|  |     interfaces = set(['auto_accept_suggestions', | ||||||
|  |                       'mail_notifications', | ||||||
|  |                       'archiving_enabled', | ||||||
|  |                       'gmail', | ||||||
|  |                       'email_verified', | ||||||
|  |                       'domain_privacy_notice', | ||||||
|  |                       'display_name']) | ||||||
|  |  | ||||||
|  |     def _get_setting(self, setting): | ||||||
|  |         xml = self.xml.find('{%s}%s' % (self.namespace, setting)) | ||||||
|  |         if xml is not None: | ||||||
|  |             return xml.attrib.get('value', '') == 'true' | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     def _set_setting(self, setting, value): | ||||||
|  |         self._del_setting(setting) | ||||||
|  |         if value in (True, False): | ||||||
|  |             xml = ET.Element('{%s}%s' % (self.namespace, setting)) | ||||||
|  |             xml.attrib['value'] = 'true' if value else 'false' | ||||||
|  |             self.xml.append(xml) | ||||||
|  |  | ||||||
|  |     def _del_setting(self, setting): | ||||||
|  |         xml = self.xml.find('{%s}%s' % (self.namespace, setting)) | ||||||
|  |         if xml is not None: | ||||||
|  |             self.xml.remove(xml) | ||||||
|  |  | ||||||
|  |     def get_display_name(self): | ||||||
|  |         xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname')) | ||||||
|  |         if xml is not None: | ||||||
|  |             return xml.attrib.get('value', '') | ||||||
|  |         return '' | ||||||
|  |  | ||||||
|  |     def set_display_name(self, value): | ||||||
|  |         self._del_setting(setting) | ||||||
|  |         if value: | ||||||
|  |             xml = ET.Element('{%s}%s' % (self.namespace, 'displayname')) | ||||||
|  |             xml.attrib['value'] = value | ||||||
|  |             self.xml.append(xml) | ||||||
|  |  | ||||||
|  |     def del_display_name(self): | ||||||
|  |         self._del_setting('displayname') | ||||||
|  |  | ||||||
|  |     def get_auto_accept_suggestions(self): | ||||||
|  |         return self._get_setting('autoacceptsuggestions') | ||||||
|  |  | ||||||
|  |     def get_mail_notifications(self): | ||||||
|  |         return self._get_setting('mailnotifications') | ||||||
|  |  | ||||||
|  |     def get_archiving_enabled(self): | ||||||
|  |         return self._get_setting('archivingenabled') | ||||||
|  |  | ||||||
|  |     def get_gmail(self): | ||||||
|  |         return self._get_setting('gmail') | ||||||
|  |  | ||||||
|  |     def get_email_verified(self): | ||||||
|  |         return self._get_setting('emailverified') | ||||||
|  |  | ||||||
|  |     def get_domain_privacy_notice(self): | ||||||
|  |         return self._get_setting('domainprivacynotice') | ||||||
|  |  | ||||||
|  |     def set_auto_accept_suggestions(self, value): | ||||||
|  |         self._set_setting('autoacceptsuggestions', value) | ||||||
|  |  | ||||||
|  |     def set_mail_notifications(self, value): | ||||||
|  |         self._set_setting('mailnotifications', value) | ||||||
|  |  | ||||||
|  |     def set_archiving_enabled(self, value): | ||||||
|  |         self._set_setting('archivingenabled', value) | ||||||
|  |  | ||||||
|  |     def set_gmail(self, value): | ||||||
|  |         self._set_setting('gmail', value) | ||||||
|  |  | ||||||
|  |     def set_email_verified(self, value): | ||||||
|  |         self._set_setting('emailverified', value) | ||||||
|  |  | ||||||
|  |     def set_domain_privacy_notice(self, value): | ||||||
|  |         self._set_setting('domainprivacynotice', value) | ||||||
|  |  | ||||||
|  |     def del_auto_accept_suggestions(self): | ||||||
|  |         self._del_setting('autoacceptsuggestions') | ||||||
|  |  | ||||||
|  |     def del_mail_notifications(self): | ||||||
|  |         self._del_setting('mailnotifications') | ||||||
|  |  | ||||||
|  |     def del_archiving_enabled(self): | ||||||
|  |         self._del_setting('archivingenabled') | ||||||
|  |  | ||||||
|  |     def del_gmail(self): | ||||||
|  |         self._del_setting('gmail') | ||||||
|  |  | ||||||
|  |     def del_email_verified(self): | ||||||
|  |         self._del_setting('emailverified') | ||||||
|  |  | ||||||
|  |     def del_domain_privacy_notice(self): | ||||||
|  |         self._del_setting('domainprivacynotice') | ||||||
| @@ -1,49 +0,0 @@ | |||||||
| from . import base |  | ||||||
| import logging |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class jobs(base.base_plugin): |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = 'pubsubjob' |  | ||||||
| 		self.description = "Job distribution over Pubsub" |  | ||||||
|  |  | ||||||
| 	def post_init(self): |  | ||||||
| 		pass |  | ||||||
| 		#TODO add event |  | ||||||
|  |  | ||||||
| 	def createJobNode(self, host, jid, node, config=None): |  | ||||||
| 		pass |  | ||||||
|  |  | ||||||
| 	def createJob(self, host, node, jobid=None, payload=None): |  | ||||||
| 		return self.xmpp.plugin['xep_0060'].setItem(host, node, ((jobid, payload),)) |  | ||||||
|  |  | ||||||
| 	def claimJob(self, host, node, jobid, ifrom=None): |  | ||||||
| 		return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed')) |  | ||||||
|  |  | ||||||
| 	def unclaimJob(self, host, node, jobid): |  | ||||||
| 		return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed')) |  | ||||||
|  |  | ||||||
| 	def finishJob(self, host, node, jobid, payload=None): |  | ||||||
| 		finished = ET.Element('{http://andyet.net/protocol/pubsubjob}finished') |  | ||||||
| 		if payload is not None: |  | ||||||
| 			finished.append(payload) |  | ||||||
| 		return self._setState(host, node, jobid, finished) |  | ||||||
|  |  | ||||||
| 	def _setState(self, host, node, jobid, state, ifrom=None): |  | ||||||
| 		iq = self.xmpp.Iq() |  | ||||||
| 		iq['to'] = host |  | ||||||
| 		if ifrom: iq['from'] = ifrom |  | ||||||
| 		iq['type'] = 'set' |  | ||||||
| 		iq['psstate']['node'] = node |  | ||||||
| 		iq['psstate']['item'] = jobid |  | ||||||
| 		iq['psstate']['payload'] = state |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or type(result) == bool or result['type'] != 'result': |  | ||||||
| 			log.error("Unable to change %s:%s to %s", node, jobid, state) |  | ||||||
| 			return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| @@ -1,421 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
| from . import base |  | ||||||
| import logging |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
| import copy |  | ||||||
| import logging |  | ||||||
| #TODO support item groups and results |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class old_0004(base.base_plugin): |  | ||||||
|  |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = '0004' |  | ||||||
| 		self.description = '*Deprecated Data Forms' |  | ||||||
| 		self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form') |  | ||||||
|  |  | ||||||
| 	def post_init(self): |  | ||||||
| 		base.base_plugin.post_init(self) |  | ||||||
| 		self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') |  | ||||||
| 		log.warning("This implementation of XEP-0004 is deprecated.") |  | ||||||
|  |  | ||||||
| 	def handler_message_xform(self, xml): |  | ||||||
| 		object = self.handle_form(xml) |  | ||||||
| 		self.xmpp.event("message_form", object) |  | ||||||
|  |  | ||||||
| 	def handler_presence_xform(self, xml): |  | ||||||
| 		object = self.handle_form(xml) |  | ||||||
| 		self.xmpp.event("presence_form", object) |  | ||||||
|  |  | ||||||
| 	def handle_form(self, xml): |  | ||||||
| 		xmlform = xml.find('{jabber:x:data}x') |  | ||||||
| 		object = self.buildForm(xmlform) |  | ||||||
| 		self.xmpp.event("message_xform", object) |  | ||||||
| 		return object |  | ||||||
|  |  | ||||||
| 	def buildForm(self, xml): |  | ||||||
| 		form = Form(ftype=xml.attrib['type']) |  | ||||||
| 		form.fromXML(xml) |  | ||||||
| 		return form |  | ||||||
|  |  | ||||||
| 	def makeForm(self, ftype='form', title='', instructions=''): |  | ||||||
| 		return Form(self.xmpp, ftype, title, instructions) |  | ||||||
|  |  | ||||||
| class FieldContainer(object): |  | ||||||
| 	def __init__(self, stanza = 'form'): |  | ||||||
| 		self.fields = [] |  | ||||||
| 		self.field = {} |  | ||||||
| 		self.stanza = stanza |  | ||||||
|  |  | ||||||
| 	def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): |  | ||||||
| 		self.field[var] = FormField(var, ftype, label, desc, required, value) |  | ||||||
| 		self.fields.append(self.field[var]) |  | ||||||
| 		return self.field[var] |  | ||||||
|  |  | ||||||
| 	def buildField(self, xml): |  | ||||||
| 		self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) |  | ||||||
| 		self.fields.append(self.field[xml.get('var', '__unnamed__')]) |  | ||||||
| 		self.field[xml.get('var', '__unnamed__')].buildField(xml) |  | ||||||
|  |  | ||||||
| 	def buildContainer(self, xml): |  | ||||||
| 		self.stanza = xml.tag |  | ||||||
| 		for field in xml.findall('{jabber:x:data}field'): |  | ||||||
| 			self.buildField(field) |  | ||||||
|  |  | ||||||
| 	def getXML(self, ftype): |  | ||||||
| 		container = ET.Element(self.stanza) |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			container.append(field.getXML(ftype)) |  | ||||||
| 		return container |  | ||||||
|  |  | ||||||
| class Form(FieldContainer): |  | ||||||
| 	types = ('form', 'submit', 'cancel', 'result') |  | ||||||
| 	def __init__(self, xmpp=None, ftype='form', title='', instructions=''): |  | ||||||
| 		if not ftype in self.types: |  | ||||||
| 			raise ValueError("Invalid Form Type") |  | ||||||
| 		FieldContainer.__init__(self) |  | ||||||
| 		self.xmpp = xmpp |  | ||||||
| 		self.type = ftype |  | ||||||
| 		self.title = title |  | ||||||
| 		self.instructions = instructions |  | ||||||
| 		self.reported = [] |  | ||||||
| 		self.items = [] |  | ||||||
|  |  | ||||||
| 	def merge(self, form2): |  | ||||||
| 		form1 = Form(ftype=self.type) |  | ||||||
| 		form1.fromXML(self.getXML(self.type)) |  | ||||||
| 		for field in form2.fields: |  | ||||||
| 			if not field.var in form1.field: |  | ||||||
| 				form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value) |  | ||||||
| 			else: |  | ||||||
| 				form1.field[field.var].value = field.value |  | ||||||
| 			for option, label in field.options: |  | ||||||
| 				if (option, label) not in form1.field[field.var].options: |  | ||||||
| 					form1.fields[field.var].addOption(option, label) |  | ||||||
| 		return form1 |  | ||||||
|  |  | ||||||
| 	def copy(self): |  | ||||||
| 		newform = Form(ftype=self.type) |  | ||||||
| 		newform.fromXML(self.getXML(self.type)) |  | ||||||
| 		return newform |  | ||||||
|  |  | ||||||
| 	def update(self, form): |  | ||||||
| 		values = form.getValues() |  | ||||||
| 		for var in values: |  | ||||||
| 			if var in self.fields: |  | ||||||
| 				self.fields[var].setValue(self.fields[var]) |  | ||||||
|  |  | ||||||
| 	def getValues(self): |  | ||||||
| 		result = {} |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			value = field.value |  | ||||||
| 			if len(value) == 1: |  | ||||||
| 				value = value[0] |  | ||||||
| 			result[field.var] = value |  | ||||||
| 		return result |  | ||||||
|  |  | ||||||
| 	def setValues(self, values={}): |  | ||||||
| 		for field in values: |  | ||||||
| 			if field in self.field: |  | ||||||
| 				if isinstance(values[field], list) or isinstance(values[field], tuple): |  | ||||||
| 					for value in values[field]: |  | ||||||
| 						self.field[field].setValue(value) |  | ||||||
| 				else: |  | ||||||
| 					self.field[field].setValue(values[field]) |  | ||||||
|  |  | ||||||
| 	def fromXML(self, xml): |  | ||||||
| 		self.buildForm(xml) |  | ||||||
|  |  | ||||||
| 	def addItem(self): |  | ||||||
| 		newitem = FieldContainer('item') |  | ||||||
| 		self.items.append(newitem) |  | ||||||
| 		return newitem |  | ||||||
|  |  | ||||||
| 	def buildItem(self, xml): |  | ||||||
| 		newitem = self.addItem() |  | ||||||
| 		newitem.buildContainer(xml) |  | ||||||
|  |  | ||||||
| 	def addReported(self): |  | ||||||
| 		reported = FieldContainer('reported') |  | ||||||
| 		self.reported.append(reported) |  | ||||||
| 		return reported |  | ||||||
|  |  | ||||||
| 	def buildReported(self, xml): |  | ||||||
| 		reported = self.addReported() |  | ||||||
| 		reported.buildContainer(xml) |  | ||||||
|  |  | ||||||
| 	def setTitle(self, title): |  | ||||||
| 		self.title = title |  | ||||||
|  |  | ||||||
| 	def setInstructions(self, instructions): |  | ||||||
| 		self.instructions = instructions |  | ||||||
|  |  | ||||||
| 	def setType(self, ftype): |  | ||||||
| 		self.type = ftype |  | ||||||
|  |  | ||||||
| 	def getXMLMessage(self, to): |  | ||||||
| 		msg = self.xmpp.makeMessage(to) |  | ||||||
| 		msg.append(self.getXML()) |  | ||||||
| 		return msg |  | ||||||
|  |  | ||||||
| 	def buildForm(self, xml): |  | ||||||
| 		self.type = xml.get('type', 'form') |  | ||||||
| 		if xml.find('{jabber:x:data}title') is not None: |  | ||||||
| 			self.setTitle(xml.find('{jabber:x:data}title').text) |  | ||||||
| 		if xml.find('{jabber:x:data}instructions') is not None: |  | ||||||
| 			self.setInstructions(xml.find('{jabber:x:data}instructions').text) |  | ||||||
| 		for field in xml.findall('{jabber:x:data}field'): |  | ||||||
| 			self.buildField(field) |  | ||||||
| 		for reported in xml.findall('{jabber:x:data}reported'): |  | ||||||
| 			self.buildReported(reported) |  | ||||||
| 		for item in xml.findall('{jabber:x:data}item'): |  | ||||||
| 			self.buildItem(item) |  | ||||||
|  |  | ||||||
| 	#def getXML(self, tostring = False): |  | ||||||
| 	def getXML(self, ftype=None): |  | ||||||
| 		if ftype: |  | ||||||
| 			self.type = ftype |  | ||||||
| 		form = ET.Element('{jabber:x:data}x') |  | ||||||
| 		form.attrib['type'] = self.type |  | ||||||
| 		if self.title and self.type in ('form', 'result'): |  | ||||||
| 			title = ET.Element('{jabber:x:data}title') |  | ||||||
| 			title.text = self.title |  | ||||||
| 			form.append(title) |  | ||||||
| 		if self.instructions and self.type == 'form': |  | ||||||
| 			instructions = ET.Element('{jabber:x:data}instructions') |  | ||||||
| 			instructions.text = self.instructions |  | ||||||
| 			form.append(instructions) |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			form.append(field.getXML(self.type)) |  | ||||||
| 		for reported in self.reported: |  | ||||||
| 			form.append(reported.getXML('{jabber:x:data}reported')) |  | ||||||
| 		for item in self.items: |  | ||||||
| 			form.append(item.getXML(self.type)) |  | ||||||
| 		#if tostring: |  | ||||||
| 		#	form = self.xmpp.tostring(form) |  | ||||||
| 		return form |  | ||||||
|  |  | ||||||
| 	def getXHTML(self): |  | ||||||
| 		form = ET.Element('{http://www.w3.org/1999/xhtml}form') |  | ||||||
| 		if self.title: |  | ||||||
| 			title = ET.Element('h2') |  | ||||||
| 			title.text = self.title |  | ||||||
| 			form.append(title) |  | ||||||
| 		if self.instructions: |  | ||||||
| 			instructions = ET.Element('p') |  | ||||||
| 			instructions.text = self.instructions |  | ||||||
| 			form.append(instructions) |  | ||||||
| 		for field in self.fields: |  | ||||||
| 			form.append(field.getXHTML()) |  | ||||||
| 		for field in self.reported: |  | ||||||
| 			form.append(field.getXHTML()) |  | ||||||
| 		for field in self.items: |  | ||||||
| 			form.append(field.getXHTML()) |  | ||||||
| 		return form |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	def makeSubmit(self): |  | ||||||
| 		self.setType('submit') |  | ||||||
|  |  | ||||||
| class FormField(object): |  | ||||||
| 	types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single') |  | ||||||
| 	listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single') |  | ||||||
| 	lbtypes = ('fixed', 'text-multi') |  | ||||||
| 	def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None): |  | ||||||
| 		if not ftype in self.types: |  | ||||||
| 			raise ValueError("Invalid Field Type") |  | ||||||
| 		self.type = ftype |  | ||||||
| 		self.var = var |  | ||||||
| 		self.label = label |  | ||||||
| 		self.desc = desc |  | ||||||
| 		self.options = [] |  | ||||||
| 		self.required = False |  | ||||||
| 		self.value = [] |  | ||||||
| 		if self.type in self.listtypes: |  | ||||||
| 			self.islist = True |  | ||||||
| 		else: |  | ||||||
| 			self.islist = False |  | ||||||
| 		if self.type in self.lbtypes: |  | ||||||
| 			self.islinebreak = True |  | ||||||
| 		else: |  | ||||||
| 			self.islinebreak = False |  | ||||||
| 		if value: |  | ||||||
| 			self.setValue(value) |  | ||||||
|  |  | ||||||
| 	def addOption(self, value, label): |  | ||||||
| 		if self.islist: |  | ||||||
| 			self.options.append((value, label)) |  | ||||||
| 		else: |  | ||||||
| 			raise ValueError("Cannot add options to non-list type field.") |  | ||||||
|  |  | ||||||
| 	def setTrue(self): |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			self.value = [True] |  | ||||||
|  |  | ||||||
| 	def setFalse(self): |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			self.value = [False] |  | ||||||
|  |  | ||||||
| 	def require(self): |  | ||||||
| 		self.required = True |  | ||||||
|  |  | ||||||
| 	def setDescription(self, desc): |  | ||||||
| 		self.desc = desc |  | ||||||
|  |  | ||||||
| 	def setValue(self, value): |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			if value in ('1', 1, True, 'true', 'True', 'yes'): |  | ||||||
| 				value = True |  | ||||||
| 			else: |  | ||||||
| 				value = False |  | ||||||
| 		if self.islinebreak and value is not None: |  | ||||||
| 			self.value += value.split('\n') |  | ||||||
| 		else: |  | ||||||
| 			if len(self.value) and (not self.islist or self.type == 'list-single'): |  | ||||||
| 				self.value = [value] |  | ||||||
| 			else: |  | ||||||
| 				self.value.append(value) |  | ||||||
|  |  | ||||||
| 	def delValue(self, value): |  | ||||||
| 		if type(self.value) == type([]): |  | ||||||
| 			try: |  | ||||||
| 				idx = self.value.index(value) |  | ||||||
| 				if idx != -1: |  | ||||||
| 					self.value.pop(idx) |  | ||||||
| 			except ValueError: |  | ||||||
| 				pass |  | ||||||
| 		else: |  | ||||||
| 			self.value = '' |  | ||||||
|  |  | ||||||
| 	def setAnswer(self, value): |  | ||||||
| 		self.setValue(value) |  | ||||||
|  |  | ||||||
| 	def buildField(self, xml): |  | ||||||
| 		self.type = xml.get('type', 'text-single') |  | ||||||
| 		self.label = xml.get('label', '') |  | ||||||
| 		for option in xml.findall('{jabber:x:data}option'): |  | ||||||
| 			self.addOption(option.find('{jabber:x:data}value').text, option.get('label', '')) |  | ||||||
| 		for value in xml.findall('{jabber:x:data}value'): |  | ||||||
| 			self.setValue(value.text) |  | ||||||
| 		if xml.find('{jabber:x:data}required') is not None: |  | ||||||
| 			self.require() |  | ||||||
| 		if xml.find('{jabber:x:data}desc') is not None: |  | ||||||
| 			self.setDescription(xml.find('{jabber:x:data}desc').text) |  | ||||||
|  |  | ||||||
| 	def getXML(self, ftype): |  | ||||||
| 		field = ET.Element('{jabber:x:data}field') |  | ||||||
| 		if ftype != 'result': |  | ||||||
| 			field.attrib['type'] = self.type |  | ||||||
| 		if self.type != 'fixed': |  | ||||||
| 			if self.var: |  | ||||||
| 				field.attrib['var'] = self.var |  | ||||||
| 			if self.label: |  | ||||||
| 				field.attrib['label'] = self.label |  | ||||||
| 		if ftype == 'form': |  | ||||||
| 			for option in self.options: |  | ||||||
| 				optionxml = ET.Element('{jabber:x:data}option') |  | ||||||
| 				optionxml.attrib['label'] = option[1] |  | ||||||
| 				optionval = ET.Element('{jabber:x:data}value') |  | ||||||
| 				optionval.text = option[0] |  | ||||||
| 				optionxml.append(optionval) |  | ||||||
| 				field.append(optionxml) |  | ||||||
| 			if self.required: |  | ||||||
| 				required = ET.Element('{jabber:x:data}required') |  | ||||||
| 				field.append(required) |  | ||||||
| 			if self.desc: |  | ||||||
| 				desc = ET.Element('{jabber:x:data}desc') |  | ||||||
| 				desc.text = self.desc |  | ||||||
| 				field.append(desc) |  | ||||||
| 		for value in self.value: |  | ||||||
| 			valuexml = ET.Element('{jabber:x:data}value') |  | ||||||
| 			if value is True or value is False: |  | ||||||
| 				if value: |  | ||||||
| 					valuexml.text = '1' |  | ||||||
| 				else: |  | ||||||
| 					valuexml.text = '0' |  | ||||||
| 			else: |  | ||||||
| 				valuexml.text = value |  | ||||||
| 			field.append(valuexml) |  | ||||||
| 		return field |  | ||||||
|  |  | ||||||
| 	def getXHTML(self): |  | ||||||
| 		field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) |  | ||||||
| 		if self.label: |  | ||||||
| 			label = ET.Element('p') |  | ||||||
| 			label.text = "%s: " % self.label |  | ||||||
| 		else: |  | ||||||
| 			label = ET.Element('p') |  | ||||||
| 			label.text = "%s: " % self.var |  | ||||||
| 		field.append(label) |  | ||||||
| 		if self.type == 'boolean': |  | ||||||
| 			formf = ET.Element('input', {'type': 'checkbox', 'name': self.var}) |  | ||||||
| 			if len(self.value) and self.value[0] in (True, 'true', '1'): |  | ||||||
| 				formf.attrib['checked'] = 'checked' |  | ||||||
| 		elif self.type == 'fixed': |  | ||||||
| 			formf = ET.Element('p') |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 			field.append(formf) |  | ||||||
| 			formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		elif self.type == 'hidden': |  | ||||||
| 			formf = ET.Element('input', {'type': 'hidden', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		elif self.type in ('jid-multi', 'list-multi'): |  | ||||||
| 			formf = ET.Element('select', {'name': self.var}) |  | ||||||
| 			for option in self.options: |  | ||||||
| 				optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'}) |  | ||||||
| 				optf.text = option[1] |  | ||||||
| 				if option[1] in self.value: |  | ||||||
| 					optf.attrib['selected'] = 'selected' |  | ||||||
| 				formf.append(option) |  | ||||||
| 		elif self.type in ('jid-single', 'text-single'): |  | ||||||
| 			formf = ET.Element('input', {'type': 'text', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.attrib['value'] = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		elif self.type == 'list-single': |  | ||||||
| 			formf = ET.Element('select', {'name': self.var}) |  | ||||||
| 			for option in self.options: |  | ||||||
| 				optf = ET.Element('option', {'value': option[0]}) |  | ||||||
| 				optf.text = option[1] |  | ||||||
| 				if not optf.text: |  | ||||||
| 					optf.text = option[0] |  | ||||||
| 				if option[1] in self.value: |  | ||||||
| 					optf.attrib['selected'] = 'selected' |  | ||||||
| 				formf.append(optf) |  | ||||||
| 		elif self.type == 'text-multi': |  | ||||||
| 			formf = ET.Element('textarea', {'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.text = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 			if not formf.text: |  | ||||||
| 				formf.text = ' ' |  | ||||||
| 		elif self.type == 'text-private': |  | ||||||
| 			formf = ET.Element('input', {'type': 'password', 'name': self.var}) |  | ||||||
| 			try: |  | ||||||
| 				formf.attrib['value'] = ', '.join(self.value) |  | ||||||
| 			except: |  | ||||||
| 				pass |  | ||||||
| 		label.append(formf) |  | ||||||
| 		return field |  | ||||||
|  |  | ||||||
| @@ -1,277 +0,0 @@ | |||||||
| """ |  | ||||||
| XEP-0009 XMPP Remote Procedure Calls |  | ||||||
| """ |  | ||||||
| from __future__ import with_statement |  | ||||||
| from . import base |  | ||||||
| import logging |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
| import copy |  | ||||||
| import time |  | ||||||
| import base64 |  | ||||||
|  |  | ||||||
| def py2xml(*args): |  | ||||||
| 	params = ET.Element("params") |  | ||||||
| 	for x in args: |  | ||||||
| 		param = ET.Element("param") |  | ||||||
| 		param.append(_py2xml(x)) |  | ||||||
| 		params.append(param) #<params><param>... |  | ||||||
| 	return params |  | ||||||
|  |  | ||||||
| def _py2xml(*args): |  | ||||||
| 	for x in args: |  | ||||||
| 		val = ET.Element("value") |  | ||||||
| 		if type(x) is int: |  | ||||||
| 			i4 = ET.Element("i4") |  | ||||||
| 			i4.text = str(x) |  | ||||||
| 			val.append(i4) |  | ||||||
| 		if type(x) is bool: |  | ||||||
| 			boolean = ET.Element("boolean") |  | ||||||
| 			boolean.text = str(int(x)) |  | ||||||
| 			val.append(boolean) |  | ||||||
| 		elif type(x) is str: |  | ||||||
| 			string = ET.Element("string") |  | ||||||
| 			string.text = x |  | ||||||
| 			val.append(string) |  | ||||||
| 		elif type(x) is float: |  | ||||||
| 			double = ET.Element("double") |  | ||||||
| 			double.text = str(x) |  | ||||||
| 			val.append(double) |  | ||||||
| 		elif type(x) is rpcbase64: |  | ||||||
| 			b64 = ET.Element("Base64") |  | ||||||
| 			b64.text = x.encoded() |  | ||||||
| 			val.append(b64) |  | ||||||
| 		elif type(x) is rpctime: |  | ||||||
| 			iso = ET.Element("dateTime.iso8601") |  | ||||||
| 			iso.text = str(x) |  | ||||||
| 			val.append(iso) |  | ||||||
| 		elif type(x) is list: |  | ||||||
| 			array = ET.Element("array") |  | ||||||
| 			data = ET.Element("data") |  | ||||||
| 			for y in x: |  | ||||||
| 				data.append(_py2xml(y)) |  | ||||||
| 			array.append(data) |  | ||||||
| 			val.append(array) |  | ||||||
| 		elif type(x) is dict: |  | ||||||
| 			struct = ET.Element("struct") |  | ||||||
| 			for y in x.keys(): |  | ||||||
| 				member = ET.Element("member") |  | ||||||
| 				name = ET.Element("name") |  | ||||||
| 				name.text = y |  | ||||||
| 				member.append(name) |  | ||||||
| 				member.append(_py2xml(x[y])) |  | ||||||
| 				struct.append(member) |  | ||||||
| 			val.append(struct) |  | ||||||
| 		return val |  | ||||||
|  |  | ||||||
| def xml2py(params): |  | ||||||
| 	vals = [] |  | ||||||
| 	for param in params.findall('param'): |  | ||||||
| 		vals.append(_xml2py(param.find('value'))) |  | ||||||
| 	return vals |  | ||||||
|  |  | ||||||
| def _xml2py(value): |  | ||||||
| 	if value.find('i4') is not None: |  | ||||||
| 		return int(value.find('i4').text) |  | ||||||
| 	if value.find('int') is not None: |  | ||||||
| 		return int(value.find('int').text) |  | ||||||
| 	if value.find('boolean') is not None: |  | ||||||
| 		return bool(value.find('boolean').text) |  | ||||||
| 	if value.find('string') is not None: |  | ||||||
| 		return value.find('string').text |  | ||||||
| 	if value.find('double') is not None: |  | ||||||
| 		return float(value.find('double').text) |  | ||||||
| 	if value.find('Base64') is not None: |  | ||||||
| 		return rpcbase64(value.find('Base64').text) |  | ||||||
| 	if value.find('dateTime.iso8601') is not None: |  | ||||||
| 		return rpctime(value.find('dateTime.iso8601')) |  | ||||||
| 	if value.find('struct') is not None: |  | ||||||
| 		struct = {} |  | ||||||
| 		for member in value.find('struct').findall('member'): |  | ||||||
| 			struct[member.find('name').text] = _xml2py(member.find('value')) |  | ||||||
| 		return struct |  | ||||||
| 	if value.find('array') is not None: |  | ||||||
| 		array = [] |  | ||||||
| 		for val in value.find('array').find('data').findall('value'): |  | ||||||
| 			array.append(_xml2py(val)) |  | ||||||
| 		return array |  | ||||||
| 	raise ValueError() |  | ||||||
|  |  | ||||||
| class rpcbase64(object): |  | ||||||
| 	def __init__(self, data): |  | ||||||
| 		#base 64 encoded string |  | ||||||
| 		self.data = data |  | ||||||
|  |  | ||||||
| 	def decode(self): |  | ||||||
| 		return base64.decodestring(data) |  | ||||||
|  |  | ||||||
| 	def __str__(self): |  | ||||||
| 		return self.decode() |  | ||||||
|  |  | ||||||
| 	def encoded(self): |  | ||||||
| 		return self.data |  | ||||||
|  |  | ||||||
| class rpctime(object): |  | ||||||
| 	def __init__(self,data=None): |  | ||||||
| 		#assume string data is in iso format YYYYMMDDTHH:MM:SS |  | ||||||
| 		if type(data) is str: |  | ||||||
| 			self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S") |  | ||||||
| 		elif type(data) is time.struct_time: |  | ||||||
| 			self.timestamp = data |  | ||||||
| 		elif data is None: |  | ||||||
| 			self.timestamp = time.gmtime() |  | ||||||
| 		else: |  | ||||||
| 			raise ValueError() |  | ||||||
|  |  | ||||||
| 	def iso8601(self): |  | ||||||
| 		#return a iso8601 string |  | ||||||
| 		return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp) |  | ||||||
|  |  | ||||||
| 	def __str__(self): |  | ||||||
| 		return self.iso8601() |  | ||||||
|  |  | ||||||
| class JabberRPCEntry(object): |  | ||||||
| 	def __init__(self,call): |  | ||||||
| 		self.call = call |  | ||||||
| 		self.result = None |  | ||||||
| 		self.error = None |  | ||||||
| 		self.allow = {} #{'<jid>':['<resource1>',...],...} |  | ||||||
| 		self.deny = {} |  | ||||||
|  |  | ||||||
| 	def check_acl(self, jid, resource): |  | ||||||
| 		#Check for deny |  | ||||||
| 		if jid in self.deny.keys(): |  | ||||||
| 			if self.deny[jid] == None or resource in self.deny[jid]: |  | ||||||
| 				return False |  | ||||||
| 		#Check for allow |  | ||||||
| 		if allow == None: |  | ||||||
| 			return True |  | ||||||
| 		if jid in self.allow.keys(): |  | ||||||
| 			if self.allow[jid] == None or resource in self.allow[jid]: |  | ||||||
| 				return True |  | ||||||
| 		return False |  | ||||||
|  |  | ||||||
| 	def acl_allow(self, jid, resource): |  | ||||||
| 		if jid == None: |  | ||||||
| 			self.allow = None |  | ||||||
| 		elif resource == None: |  | ||||||
| 			self.allow[jid] = None |  | ||||||
| 		elif jid in self.allow.keys(): |  | ||||||
| 			self.allow[jid].append(resource) |  | ||||||
| 		else: |  | ||||||
| 			self.allow[jid] = [resource] |  | ||||||
| 		 |  | ||||||
| 	def acl_deny(self, jid, resource): |  | ||||||
| 		if jid == None: |  | ||||||
| 			self.deny = None |  | ||||||
| 		elif resource == None: |  | ||||||
| 			self.deny[jid] = None |  | ||||||
| 		elif jid in self.deny.keys(): |  | ||||||
| 			self.deny[jid].append(resource) |  | ||||||
| 		else: |  | ||||||
| 			self.deny[jid] = [resource] |  | ||||||
|  |  | ||||||
| 	def call_method(self, args): |  | ||||||
| 		ret = self.call(*args) |  | ||||||
|  |  | ||||||
| class xep_0009(base.base_plugin): |  | ||||||
|  |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = '0009' |  | ||||||
| 		self.description = 'Jabber-RPC' |  | ||||||
| 		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",  |  | ||||||
|                                       self._callMethod, name='Jabber RPC Call') |  | ||||||
| 		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",  |  | ||||||
|                                       self._callResult, name='Jabber RPC Result') |  | ||||||
| 		self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",  |  | ||||||
|                                       self._callError, name='Jabber RPC Error') |  | ||||||
| 		self.entries = {} |  | ||||||
| 		self.activeCalls = [] |  | ||||||
|  |  | ||||||
| 	def post_init(self): |  | ||||||
| 		base.base_plugin.post_init(self) |  | ||||||
| 		self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc') |  | ||||||
| 		self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc') |  | ||||||
|  |  | ||||||
| 	def register_call(self, method, name=None): |  | ||||||
| 		#@returns an string that can be used in acl commands. |  | ||||||
| 		with self.lock: |  | ||||||
| 			if name is None: |  | ||||||
| 				self.entries[method.__name__] = JabberRPCEntry(method) |  | ||||||
| 				return method.__name__ |  | ||||||
| 			else: |  | ||||||
| 				self.entries[name] = JabberRPCEntry(method) |  | ||||||
| 				return name |  | ||||||
|  |  | ||||||
| 	def acl_allow(self, entry, jid=None, resource=None): |  | ||||||
| 		#allow the method entry to be called by the given jid and resource. |  | ||||||
| 		#if jid is None it will allow any jid/resource. |  | ||||||
| 		#if resource is None it will allow any resource belonging to the jid. |  | ||||||
| 		with self.lock: |  | ||||||
| 			if self.entries[entry]: |  | ||||||
| 				self.entries[entry].acl_allow(jid,resource) |  | ||||||
| 			else: |  | ||||||
| 				raise ValueError() |  | ||||||
| 	 |  | ||||||
| 	def acl_deny(self, entry, jid=None, resource=None): |  | ||||||
| 		#Note: by default all requests are denied unless allowed with acl_allow. |  | ||||||
| 		#If you deny an entry it will not be allowed regardless of acl_allow |  | ||||||
| 		with self.lock: |  | ||||||
| 			if self.entries[entry]: |  | ||||||
| 				self.entries[entry].acl_deny(jid,resource) |  | ||||||
| 			else: |  | ||||||
| 				raise ValueError() |  | ||||||
| 	 |  | ||||||
| 	def unregister_call(self, entry): |  | ||||||
| 		#removes the registered call |  | ||||||
| 		with self.lock: |  | ||||||
| 			if self.entries[entry]: |  | ||||||
| 				del self.entries[entry] |  | ||||||
| 			else: |  | ||||||
| 				raise ValueError() |  | ||||||
|  |  | ||||||
| 	def makeMethodCallQuery(self,pmethod,params): |  | ||||||
| 		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc") |  | ||||||
| 		methodCall = ET.Element('methodCall') |  | ||||||
| 		methodName = ET.Element('methodName') |  | ||||||
| 		methodName.text = pmethod |  | ||||||
| 		methodCall.append(methodName) |  | ||||||
| 		methodCall.append(params) |  | ||||||
| 		query.append(methodCall) |  | ||||||
| 		return query |  | ||||||
|   |  | ||||||
| 	def makeIqMethodCall(self,pto,pmethod,params): |  | ||||||
| 		iq = self.xmpp.makeIqSet() |  | ||||||
| 		iq.set('to',pto) |  | ||||||
| 		iq.append(self.makeMethodCallQuery(pmethod,params)) |  | ||||||
| 		return iq |  | ||||||
| 	 |  | ||||||
| 	def makeIqMethodResponse(self,pto,pid,params): |  | ||||||
| 		iq = self.xmpp.makeIqResult(pid) |  | ||||||
| 		iq.set('to',pto) |  | ||||||
| 		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc") |  | ||||||
| 		methodResponse = ET.Element('methodResponse') |  | ||||||
| 		methodResponse.append(params) |  | ||||||
| 		query.append(methodResponse) |  | ||||||
| 		return iq |  | ||||||
|  |  | ||||||
| 	def makeIqMethodError(self,pto,id,pmethod,params,condition): |  | ||||||
| 		iq = self.xmpp.makeIqError(id) |  | ||||||
| 		iq.set('to',pto) |  | ||||||
| 		iq.append(self.makeMethodCallQuery(pmethod,params)) |  | ||||||
| 		iq.append(self.xmpp['xep_0086'].makeError(condition)) |  | ||||||
| 		return iq |  | ||||||
| 	 |  | ||||||
| 		 |  | ||||||
|  |  | ||||||
| 	def call_remote(self, pto, pmethod, *args): |  | ||||||
| 		#calls a remote method. Returns the id of the Iq. |  | ||||||
| 		pass |  | ||||||
|  |  | ||||||
| 	def _callMethod(self,xml): |  | ||||||
| 		pass |  | ||||||
|  |  | ||||||
| 	def _callResult(self,xml): |  | ||||||
| 		pass |  | ||||||
|  |  | ||||||
| 	def _callError(self,xml): |  | ||||||
| 		pass |  | ||||||
| @@ -1,133 +0,0 @@ | |||||||
| """ |  | ||||||
|     SleekXMPP: The Sleek XMPP Library |  | ||||||
|     Copyright (C) 2010 Nathanael C. Fritz |  | ||||||
|     This file is part of SleekXMPP. |  | ||||||
|      |  | ||||||
|     See the file LICENSE for copying permission. |  | ||||||
| """ |  | ||||||
| from __future__ import with_statement |  | ||||||
| from . import base |  | ||||||
| import logging |  | ||||||
| from xml.etree import cElementTree as ET |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| class old_0050(base.base_plugin): |  | ||||||
| 	""" |  | ||||||
| 	XEP-0050 Ad-Hoc Commands |  | ||||||
| 	""" |  | ||||||
| 	 |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = '0050' |  | ||||||
| 		self.description = 'Ad-Hoc Commands' |  | ||||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None') |  | ||||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute') |  | ||||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True) |  | ||||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel') |  | ||||||
| 		self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete') |  | ||||||
| 		self.commands = {} |  | ||||||
| 		self.sessions = {} |  | ||||||
| 		self.sd = self.xmpp.plugin['xep_0030'] |  | ||||||
| 	 |  | ||||||
| 	def post_init(self): |  | ||||||
| 		base.base_plugin.post_init(self) |  | ||||||
| 		self.sd.add_feature('http://jabber.org/protocol/commands') |  | ||||||
|  |  | ||||||
| 	def addCommand(self, node, name, form, pointer=None, multi=False): |  | ||||||
| 		self.sd.add_item(None, name, 'http://jabber.org/protocol/commands', node) |  | ||||||
| 		self.sd.add_identity('automation', 'command-node', name, node) |  | ||||||
| 		self.sd.add_feature('http://jabber.org/protocol/commands', node) |  | ||||||
| 		self.sd.add_feature('jabber:x:data', node) |  | ||||||
| 		self.commands[node] = (name, form, pointer, multi) |  | ||||||
| 	 |  | ||||||
| 	def getNewSession(self): |  | ||||||
| 		return str(time.time()) + '-' + self.xmpp.getNewId() |  | ||||||
| 	 |  | ||||||
| 	def handler_command(self, xml): |  | ||||||
| 		in_command = xml.find('{http://jabber.org/protocol/commands}command') |  | ||||||
| 		sessionid = in_command.get('sessionid', None) |  | ||||||
| 		node = in_command.get('node') |  | ||||||
| 		sessionid = self.getNewSession() |  | ||||||
| 		name, form, pointer, multi = self.commands[node] |  | ||||||
| 		self.sessions[sessionid] = {} |  | ||||||
| 		self.sessions[sessionid]['jid'] = xml.get('from') |  | ||||||
| 		self.sessions[sessionid]['to'] = xml.get('to') |  | ||||||
| 		self.sessions[sessionid]['past'] = [(form, None)] |  | ||||||
| 		self.sessions[sessionid]['next'] = pointer |  | ||||||
| 		npointer = pointer |  | ||||||
| 		if multi: |  | ||||||
| 			actions = ['next'] |  | ||||||
| 			status = 'executing' |  | ||||||
| 		else: |  | ||||||
| 			if pointer is None: |  | ||||||
| 				status = 'completed' |  | ||||||
| 				actions = [] |  | ||||||
| 			else: |  | ||||||
| 				status = 'executing' |  | ||||||
| 				actions = ['complete'] |  | ||||||
| 		self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions)) |  | ||||||
| 	 |  | ||||||
| 	def handler_command_complete(self, xml): |  | ||||||
| 		in_command = xml.find('{http://jabber.org/protocol/commands}command') |  | ||||||
| 		sessionid = in_command.get('sessionid', None) |  | ||||||
| 		pointer = self.sessions[sessionid]['next'] |  | ||||||
| 		results = self.xmpp.plugin['old_0004'].makeForm('result') |  | ||||||
| 		results.fromXML(in_command.find('{jabber:x:data}x')) |  | ||||||
| 		pointer(results,sessionid) |  | ||||||
| 		self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[])) |  | ||||||
| 		del self.sessions[in_command.get('sessionid')] |  | ||||||
| 		 |  | ||||||
| 	 |  | ||||||
| 	def handler_command_next(self, xml): |  | ||||||
| 		in_command = xml.find('{http://jabber.org/protocol/commands}command') |  | ||||||
| 		sessionid = in_command.get('sessionid', None) |  | ||||||
| 		pointer = self.sessions[sessionid]['next'] |  | ||||||
| 		results = self.xmpp.plugin['old_0004'].makeForm('result') |  | ||||||
| 		results.fromXML(in_command.find('{jabber:x:data}x')) |  | ||||||
| 		form, npointer, next = pointer(results,sessionid) |  | ||||||
| 		self.sessions[sessionid]['next'] = npointer |  | ||||||
| 		self.sessions[sessionid]['past'].append((form, pointer)) |  | ||||||
| 		actions = [] |  | ||||||
| 		actions.append('prev') |  | ||||||
| 		if npointer is None: |  | ||||||
| 			status = 'completed' |  | ||||||
| 		else: |  | ||||||
| 			status = 'executing' |  | ||||||
| 			if next: |  | ||||||
| 				actions.append('next') |  | ||||||
| 			else: |  | ||||||
| 				actions.append('complete') |  | ||||||
| 		self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions)) |  | ||||||
| 		 |  | ||||||
| 	def handler_command_cancel(self, xml): |  | ||||||
| 		command = xml.find('{http://jabber.org/protocol/commands}command') |  | ||||||
| 		try: |  | ||||||
| 			del self.sessions[command.get('sessionid')] |  | ||||||
| 		except: |  | ||||||
| 			pass |  | ||||||
| 		self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled')) |  | ||||||
|  |  | ||||||
| 	def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]): |  | ||||||
| 		if not id: |  | ||||||
| 			id = self.xmpp.getNewId() |  | ||||||
| 		iq = self.xmpp.makeIqResult(id) |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		iq.attrib['to'] = to |  | ||||||
| 		command = ET.Element('{http://jabber.org/protocol/commands}command') |  | ||||||
| 		command.attrib['node'] = node |  | ||||||
| 		command.attrib['status'] = status |  | ||||||
| 		xmlactions = ET.Element('actions') |  | ||||||
| 		for action in actions: |  | ||||||
| 			xmlactions.append(ET.Element(action)) |  | ||||||
| 		if xmlactions: |  | ||||||
| 			command.append(xmlactions) |  | ||||||
| 		if not sessionid: |  | ||||||
| 			sessionid = self.getNewSession() |  | ||||||
| 		else: |  | ||||||
| 			iq.attrib['from'] = self.sessions[sessionid]['to'] |  | ||||||
| 		command.attrib['sessionid'] = sessionid |  | ||||||
| 		if form is not None: |  | ||||||
| 			if hasattr(form,'getXML'): |  | ||||||
| 				form = form.getXML() |  | ||||||
| 			command.append(form) |  | ||||||
| 		iq.append(command) |  | ||||||
| 		return iq |  | ||||||
| @@ -1,313 +0,0 @@ | |||||||
| from __future__ import with_statement |  | ||||||
| from . import base |  | ||||||
| import logging |  | ||||||
| #from xml.etree import cElementTree as ET |  | ||||||
| from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET |  | ||||||
| from . import stanza_pubsub |  | ||||||
| from . xep_0004 import Form |  | ||||||
|  |  | ||||||
|  |  | ||||||
| log = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class xep_0060(base.base_plugin): |  | ||||||
| 	""" |  | ||||||
| 	XEP-0060 Publish Subscribe |  | ||||||
| 	""" |  | ||||||
|  |  | ||||||
| 	def plugin_init(self): |  | ||||||
| 		self.xep = '0060' |  | ||||||
| 		self.description = 'Publish-Subscribe' |  | ||||||
|  |  | ||||||
| 	def create_node(self, jid, node, config=None, collection=False, ntype=None): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		create = ET.Element('create') |  | ||||||
| 		create.set('node', node) |  | ||||||
| 		pubsub.append(create) |  | ||||||
| 		configure = ET.Element('configure') |  | ||||||
| 		if collection: |  | ||||||
| 			ntype = 'collection' |  | ||||||
| 		#if config is None: |  | ||||||
| 		#	submitform = self.xmpp.plugin['xep_0004'].makeForm('submit') |  | ||||||
| 		#else: |  | ||||||
| 		if config is not None: |  | ||||||
| 			submitform = config |  | ||||||
| 			if 'FORM_TYPE' in submitform.field: |  | ||||||
| 				submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config') |  | ||||||
| 			else: |  | ||||||
| 				submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config') |  | ||||||
| 			if ntype: |  | ||||||
| 				if 'pubsub#node_type' in submitform.field: |  | ||||||
| 					submitform.field['pubsub#node_type'].setValue(ntype) |  | ||||||
| 				else: |  | ||||||
| 					submitform.addField('pubsub#node_type', value=ntype) |  | ||||||
| 			else: |  | ||||||
| 				if 'pubsub#node_type' in submitform.field: |  | ||||||
| 					submitform.field['pubsub#node_type'].setValue('leaf') |  | ||||||
| 				else: |  | ||||||
| 					submitform.addField('pubsub#node_type', value='leaf') |  | ||||||
| 			submitform['type'] = 'submit' |  | ||||||
| 			configure.append(submitform.xml) |  | ||||||
| 		pubsub.append(configure) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def subscribe(self, jid, node, bare=True, subscribee=None): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		subscribe = ET.Element('subscribe') |  | ||||||
| 		subscribe.attrib['node'] = node |  | ||||||
| 		if subscribee is None: |  | ||||||
| 			if bare: |  | ||||||
| 				subscribe.attrib['jid'] = self.xmpp.boundjid.bare |  | ||||||
| 			else: |  | ||||||
| 				subscribe.attrib['jid'] = self.xmpp.boundjid.full |  | ||||||
| 		else: |  | ||||||
| 			subscribe.attrib['jid'] = subscribee |  | ||||||
| 		pubsub.append(subscribe) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def unsubscribe(self, jid, node, bare=True, subscribee=None): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		unsubscribe = ET.Element('unsubscribe') |  | ||||||
| 		unsubscribe.attrib['node'] = node |  | ||||||
| 		if subscribee is None: |  | ||||||
| 			if bare: |  | ||||||
| 				unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare |  | ||||||
| 			else: |  | ||||||
| 				unsubscribe.attrib['jid'] = self.xmpp.boundjid.full |  | ||||||
| 		else: |  | ||||||
| 			unsubscribe.attrib['jid'] = subscribee |  | ||||||
| 		pubsub.append(unsubscribe) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is False or result is None or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def getNodeConfig(self, jid, node=None): # if no node, then grab default |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		if node is not None: |  | ||||||
| 			configure = ET.Element('configure') |  | ||||||
| 			configure.attrib['node'] = node |  | ||||||
| 		else: |  | ||||||
| 			configure = ET.Element('default') |  | ||||||
| 		pubsub.append(configure) |  | ||||||
| 		#TODO: Add configure support. |  | ||||||
| 		iq = self.xmpp.makeIqGet() |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse) |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result == False or result['type'] == 'error': |  | ||||||
| 			log.warning("got error instead of config") |  | ||||||
| 			return False |  | ||||||
| 		if node is not None: |  | ||||||
| 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x') |  | ||||||
| 		else: |  | ||||||
| 			form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x') |  | ||||||
| 		if not form or form is None: |  | ||||||
| 			log.error("No form found.") |  | ||||||
| 			return False |  | ||||||
| 		return Form(xml=form) |  | ||||||
|  |  | ||||||
| 	def getNodeSubscriptions(self, jid, node): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		subscriptions = ET.Element('subscriptions') |  | ||||||
| 		subscriptions.attrib['node'] = node |  | ||||||
| 		pubsub.append(subscriptions) |  | ||||||
| 		iq = self.xmpp.makeIqGet() |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result == False or result['type'] == 'error': |  | ||||||
| 			log.warning("got error instead of config") |  | ||||||
| 			return False |  | ||||||
| 		else: |  | ||||||
| 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription') |  | ||||||
| 			if results is None: |  | ||||||
| 				return False |  | ||||||
| 			subs = {} |  | ||||||
| 			for sub in results: |  | ||||||
| 				subs[sub.get('jid')] = sub.get('subscription') |  | ||||||
| 			return subs |  | ||||||
|  |  | ||||||
| 	def getNodeAffiliations(self, jid, node): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		affiliations = ET.Element('affiliations') |  | ||||||
| 		affiliations.attrib['node'] = node |  | ||||||
| 		pubsub.append(affiliations) |  | ||||||
| 		iq = self.xmpp.makeIqGet() |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result == False or result['type'] == 'error': |  | ||||||
| 			log.warning("got error instead of config") |  | ||||||
| 			return False |  | ||||||
| 		else: |  | ||||||
| 			results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation') |  | ||||||
| 			if results is None: |  | ||||||
| 				return False |  | ||||||
| 			subs = {} |  | ||||||
| 			for sub in results: |  | ||||||
| 				subs[sub.get('jid')] = sub.get('affiliation') |  | ||||||
| 			return subs |  | ||||||
|  |  | ||||||
| 	def deleteNode(self, jid, node): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		iq = self.xmpp.makeIqSet() |  | ||||||
| 		delete = ET.Element('delete') |  | ||||||
| 		delete.attrib['node'] = node |  | ||||||
| 		pubsub.append(delete) |  | ||||||
| 		iq.append(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is not None and result is not False and result['type'] != 'error': |  | ||||||
| 			return True |  | ||||||
| 		else: |  | ||||||
| 			return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	def setNodeConfig(self, jid, node, config): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		configure = ET.Element('configure') |  | ||||||
| 		configure.attrib['node'] = node |  | ||||||
| 		config = config.getXML('submit') |  | ||||||
| 		configure.append(config) |  | ||||||
| 		pubsub.append(configure) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result['type'] == 'error': |  | ||||||
| 			return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def setItem(self, jid, node, items=[]): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		publish = ET.Element('publish') |  | ||||||
| 		publish.attrib['node'] = node |  | ||||||
| 		for pub_item in items: |  | ||||||
| 			id, payload = pub_item |  | ||||||
| 			item = ET.Element('item') |  | ||||||
| 			if id is not None: |  | ||||||
| 				item.attrib['id'] = id |  | ||||||
| 			item.append(payload) |  | ||||||
| 			publish.append(item) |  | ||||||
| 		pubsub.append(publish) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result is False or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def addItem(self, jid, node, items=[]): |  | ||||||
| 		return self.setItem(jid, node, items) |  | ||||||
|  |  | ||||||
| 	def deleteItem(self, jid, node, item): |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') |  | ||||||
| 		retract = ET.Element('retract') |  | ||||||
| 		retract.attrib['node'] = node |  | ||||||
| 		itemn = ET.Element('item') |  | ||||||
| 		itemn.attrib['id'] = item |  | ||||||
| 		retract.append(itemn) |  | ||||||
| 		pubsub.append(retract) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result is False or result['type'] == 'error': return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def getNodes(self, jid): |  | ||||||
| 		response = self.xmpp.plugin['xep_0030'].getItems(jid) |  | ||||||
| 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') |  | ||||||
| 		nodes = {} |  | ||||||
| 		if items is not None and items is not False: |  | ||||||
| 			for item in items: |  | ||||||
| 				nodes[item.get('node')] = item.get('name') |  | ||||||
| 		return nodes |  | ||||||
|  |  | ||||||
| 	def getItems(self, jid, node): |  | ||||||
| 		response = self.xmpp.plugin['xep_0030'].getItems(jid, node) |  | ||||||
| 		items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') |  | ||||||
| 		nodeitems = [] |  | ||||||
| 		if items is not None and items is not False: |  | ||||||
| 			for item in items: |  | ||||||
| 				nodeitems.append(item.get('node')) |  | ||||||
| 		return nodeitems |  | ||||||
|  |  | ||||||
| 	def addNodeToCollection(self, jid, child, parent=''): |  | ||||||
| 		config = self.getNodeConfig(jid, child) |  | ||||||
| 		if not config or config is None: |  | ||||||
| 			self.lasterror = "Config Error" |  | ||||||
| 			return False |  | ||||||
| 		try: |  | ||||||
| 			config.field['pubsub#collection'].setValue(parent) |  | ||||||
| 		except KeyError: |  | ||||||
| 			log.warning("pubsub#collection doesn't exist in config, trying to add it") |  | ||||||
| 			config.addField('pubsub#collection', value=parent) |  | ||||||
| 		if not self.setNodeConfig(jid, child, config): |  | ||||||
| 			return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def modifyAffiliation(self, ps_jid, node, user_jid, affiliation): |  | ||||||
| 		if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'): |  | ||||||
| 			raise TypeError |  | ||||||
| 		pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') |  | ||||||
| 		affs = ET.Element('affiliations') |  | ||||||
| 		affs.attrib['node'] = node |  | ||||||
| 		aff = ET.Element('affiliation') |  | ||||||
| 		aff.attrib['jid'] = user_jid |  | ||||||
| 		aff.attrib['affiliation'] = affiliation |  | ||||||
| 		affs.append(aff) |  | ||||||
| 		pubsub.append(affs) |  | ||||||
| 		iq = self.xmpp.makeIqSet(pubsub) |  | ||||||
| 		iq.attrib['to'] = ps_jid |  | ||||||
| 		iq.attrib['from'] = self.xmpp.boundjid.full |  | ||||||
| 		id = iq['id'] |  | ||||||
| 		result = iq.send() |  | ||||||
| 		if result is None or result is False or result['type'] == 'error': |  | ||||||
| 		    return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def addNodeToCollection(self, jid, child, parent=''): |  | ||||||
| 		config = self.getNodeConfig(jid, child) |  | ||||||
| 		if not config or config is None: |  | ||||||
| 			self.lasterror = "Config Error" |  | ||||||
| 			return False |  | ||||||
| 		try: |  | ||||||
| 			config.field['pubsub#collection'].setValue(parent) |  | ||||||
| 		except KeyError: |  | ||||||
| 			log.warning("pubsub#collection doesn't exist in config, trying to add it") |  | ||||||
| 			config.addField('pubsub#collection', value=parent) |  | ||||||
| 		if not self.setNodeConfig(jid, child, config): |  | ||||||
| 			return False |  | ||||||
| 		return True |  | ||||||
|  |  | ||||||
| 	def removeNodeFromCollection(self, jid, child): |  | ||||||
| 		self.addNodeToCollection(jid, child, '') |  | ||||||
|  |  | ||||||
| @@ -41,10 +41,11 @@ class FormField(ElementBase): | |||||||
|             self._type = value |             self._type = value | ||||||
|  |  | ||||||
|     def add_option(self, label='', value=''): |     def add_option(self, label='', value=''): | ||||||
|         if self._type in self.option_types: |         if self._type is None or self._type in self.option_types: | ||||||
|             opt = FieldOption(parent=self) |             opt = FieldOption() | ||||||
|             opt['label'] = label |             opt['label'] = label | ||||||
|             opt['value'] = value |             opt['value'] = value | ||||||
|  |             self.append(opt) | ||||||
|         else: |         else: | ||||||
|             raise ValueError("Cannot add options to " + \ |             raise ValueError("Cannot add options to " + \ | ||||||
|                              "a %s field." % self['type']) |                              "a %s field." % self['type']) | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ class Form(ElementBase): | |||||||
|         if kwtype is None: |         if kwtype is None: | ||||||
|             kwtype = ftype |             kwtype = ftype | ||||||
|  |  | ||||||
|         field = FormField(parent=self) |         field = FormField() | ||||||
|         field['var'] = var |         field['var'] = var | ||||||
|         field['type'] = kwtype |         field['type'] = kwtype | ||||||
|         field['value'] = value |         field['value'] = value | ||||||
| @@ -77,6 +77,7 @@ class Form(ElementBase): | |||||||
|                 field['options'] = options |                 field['options'] = options | ||||||
|         else: |         else: | ||||||
|             del field['type'] |             del field['type'] | ||||||
|  |         self.append(field) | ||||||
|         return field |         return field | ||||||
|  |  | ||||||
|     def getXML(self, type='submit'): |     def getXML(self, type='submit'): | ||||||
| @@ -144,10 +145,9 @@ class Form(ElementBase): | |||||||
|  |  | ||||||
|     def get_fields(self, use_dict=False): |     def get_fields(self, use_dict=False): | ||||||
|         fields = OrderedDict() |         fields = OrderedDict() | ||||||
|         fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) |         for stanza in self['substanzas']: | ||||||
|         for fieldXML in fieldsXML: |             if isinstance(stanza, FormField): | ||||||
|             field = FormField(xml=fieldXML) |                 fields[stanza['var']] = stanza | ||||||
|             fields[field['var']] = field |  | ||||||
|         return fields |         return fields | ||||||
|  |  | ||||||
|     def get_instructions(self): |     def get_instructions(self): | ||||||
| @@ -221,6 +221,8 @@ class Form(ElementBase): | |||||||
|     def set_values(self, values): |     def set_values(self, values): | ||||||
|         fields = self['fields'] |         fields = self['fields'] | ||||||
|         for field in values: |         for field in values: | ||||||
|  |             if field not in fields: | ||||||
|  |                 fields[field] = self.add_field(var=field) | ||||||
|             fields[field]['value'] = values[field] |             fields[field]['value'] = values[field] | ||||||
|  |  | ||||||
|     def merge(self, other): |     def merge(self, other): | ||||||
|   | |||||||
| @@ -32,15 +32,15 @@ class XEP_0009(BasePlugin): | |||||||
|         register_stanza_plugin(RPCQuery, MethodCall) |         register_stanza_plugin(RPCQuery, MethodCall) | ||||||
|         register_stanza_plugin(RPCQuery, MethodResponse) |         register_stanza_plugin(RPCQuery, MethodResponse) | ||||||
|  |  | ||||||
|         self.xmpp.registerHandler( |         self.xmpp.register_handler( | ||||||
|             Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), |             Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), | ||||||
|             self._handle_method_call) |             self._handle_method_call) | ||||||
|         ) |         ) | ||||||
|         self.xmpp.registerHandler( |         self.xmpp.register_handler( | ||||||
|             Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), |             Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)), | ||||||
|             self._handle_method_response) |             self._handle_method_response) | ||||||
|         ) |         ) | ||||||
|         self.xmpp.registerHandler( |         self.xmpp.register_handler( | ||||||
|             Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)), |             Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)), | ||||||
|             self._handle_error) |             self._handle_error) | ||||||
|         ) |         ) | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0020/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0020/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0020 import stanza | ||||||
|  | from sleekxmpp.plugins.xep_0020.stanza import FeatureNegotiation | ||||||
|  | from sleekxmpp.plugins.xep_0020.feature_negotiation import XEP_0020 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0020) | ||||||
							
								
								
									
										36
									
								
								sleekxmpp/plugins/xep_0020/feature_negotiation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								sleekxmpp/plugins/xep_0020/feature_negotiation.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp import Iq, Message | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||||
|  | from sleekxmpp.plugins.xep_0020 import stanza, FeatureNegotiation | ||||||
|  | from sleekxmpp.plugins.xep_0004 import Form | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0020(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0020' | ||||||
|  |     description = 'XEP-0020: Feature Negotiation' | ||||||
|  |     dependencies = set(['xep_0004', 'xep_0030']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace) | ||||||
|  |  | ||||||
|  |         register_stanza_plugin(FeatureNegotiation, Form) | ||||||
|  |  | ||||||
|  |         register_stanza_plugin(Iq, FeatureNegotiation) | ||||||
|  |         register_stanza_plugin(Message, FeatureNegotiation) | ||||||
							
								
								
									
										17
									
								
								sleekxmpp/plugins/xep_0020/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sleekxmpp/plugins/xep_0020/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ElementBase | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FeatureNegotiation(ElementBase): | ||||||
|  |  | ||||||
|  |     name = 'feature' | ||||||
|  |     namespace = 'http://jabber.org/protocol/feature-neg' | ||||||
|  |     plugin_attrib = 'feature_neg' | ||||||
|  |     interfaces = set() | ||||||
| @@ -24,7 +24,7 @@ def _extract_data(data, kind): | |||||||
|         if not begin_headers and 'BEGIN PGP %s' % kind in line: |         if not begin_headers and 'BEGIN PGP %s' % kind in line: | ||||||
|             begin_headers = True |             begin_headers = True | ||||||
|             continue |             continue | ||||||
|         if begin_headers and line.stripped() == '': |         if begin_headers and line.strip() == '': | ||||||
|             begin_data = True |             begin_data = True | ||||||
|             continue |             continue | ||||||
|         if 'END PGP %s' % kind in line: |         if 'END PGP %s' % kind in line: | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ class Encrypted(ElementBase): | |||||||
|     def set_encrypted(self, value): |     def set_encrypted(self, value): | ||||||
|         parent = self.parent() |         parent = self.parent() | ||||||
|         xmpp = parent.stream |         xmpp = parent.stream | ||||||
|         data = xmpp['xep_0027'].encrypt(value, parent['to'].bare) |         data = xmpp['xep_0027'].encrypt(value, parent['to']) | ||||||
|         if data: |         if data: | ||||||
|             self.xml.text = data |             self.xml.text = data | ||||||
|         else: |         else: | ||||||
|   | |||||||
| @@ -324,6 +324,8 @@ class XEP_0030(BasePlugin): | |||||||
|             callback -- Optional callback to execute when a reply is |             callback -- Optional callback to execute when a reply is | ||||||
|                         received instead of blocking and waiting for |                         received instead of blocking and waiting for | ||||||
|                         the reply. |                         the reply. | ||||||
|  |             timeout_callback -- Optional callback to execute when no result  | ||||||
|  |                         has been received in timeout seconds. | ||||||
|         """ |         """ | ||||||
|         if local is None: |         if local is None: | ||||||
|             if jid is not None and not isinstance(jid, JID): |             if jid is not None and not isinstance(jid, JID): | ||||||
| @@ -364,7 +366,8 @@ class XEP_0030(BasePlugin): | |||||||
|         iq['disco_info']['node'] = node if node else '' |         iq['disco_info']['node'] = node if node else '' | ||||||
|         return iq.send(timeout=kwargs.get('timeout', None), |         return iq.send(timeout=kwargs.get('timeout', None), | ||||||
|                        block=kwargs.get('block', True), |                        block=kwargs.get('block', True), | ||||||
|                        callback=kwargs.get('callback', None)) |                        callback=kwargs.get('callback', None), | ||||||
|  |                        timeout_callback=kwargs.get('timeout_callback', None)) | ||||||
|  |  | ||||||
|     def set_info(self, jid=None, node=None, info=None): |     def set_info(self, jid=None, node=None, info=None): | ||||||
|         """ |         """ | ||||||
| @@ -405,6 +408,8 @@ class XEP_0030(BasePlugin): | |||||||
|             iterator -- If True, return a result set iterator using |             iterator -- If True, return a result set iterator using | ||||||
|                         the XEP-0059 plugin, if the plugin is loaded. |                         the XEP-0059 plugin, if the plugin is loaded. | ||||||
|                         Otherwise the parameter is ignored. |                         Otherwise the parameter is ignored. | ||||||
|  |             timeout_callback -- Optional callback to execute when no result  | ||||||
|  |                         has been received in timeout seconds. | ||||||
|         """ |         """ | ||||||
|         if local or local is None and jid is None: |         if local or local is None and jid is None: | ||||||
|             items = self.api['get_items'](jid, node, |             items = self.api['get_items'](jid, node, | ||||||
| @@ -423,7 +428,8 @@ class XEP_0030(BasePlugin): | |||||||
|         else: |         else: | ||||||
|             return iq.send(timeout=kwargs.get('timeout', None), |             return iq.send(timeout=kwargs.get('timeout', None), | ||||||
|                            block=kwargs.get('block', True), |                            block=kwargs.get('block', True), | ||||||
|                            callback=kwargs.get('callback', None)) |                            callback=kwargs.get('callback', None), | ||||||
|  |                            timeout_callback=kwargs.get('timeout_callback', None)) | ||||||
|  |  | ||||||
|     def set_items(self, jid=None, node=None, **kwargs): |     def set_items(self, jid=None, node=None, **kwargs): | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -128,9 +128,10 @@ class DiscoItems(ElementBase): | |||||||
|     def del_items(self): |     def del_items(self): | ||||||
|         """Remove all items.""" |         """Remove all items.""" | ||||||
|         self._items = set() |         self._items = set() | ||||||
|         for item in self['substanzas']: |         items = [i for i in self.iterables if isinstance(i, DiscoItem)] | ||||||
|             if isinstance(item, DiscoItem): |         for item in items: | ||||||
|             self.xml.remove(item.xml) |             self.xml.remove(item.xml) | ||||||
|  |             self.iterables.remove(item) | ||||||
|  |  | ||||||
|  |  | ||||||
| class DiscoItem(ElementBase): | class DiscoItem(ElementBase): | ||||||
|   | |||||||
| @@ -125,11 +125,12 @@ class XEP_0045(BasePlugin): | |||||||
|         self.xep = '0045' |         self.xep = '0045' | ||||||
|         # load MUC support in presence stanzas |         # load MUC support in presence stanzas | ||||||
|         register_stanza_plugin(Presence, MUCPresence) |         register_stanza_plugin(Presence, MUCPresence) | ||||||
|         self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) |         self.xmpp.register_handler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) | ||||||
|         self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) |         self.xmpp.register_handler(Callback('MUCError', MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns), self.handle_groupchat_error_message)) | ||||||
|         self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject)) |         self.xmpp.register_handler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) | ||||||
|         self.xmpp.registerHandler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change)) |         self.xmpp.register_handler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject)) | ||||||
|         self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % ( |         self.xmpp.register_handler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change)) | ||||||
|  |         self.xmpp.register_handler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % ( | ||||||
|             self.xmpp.default_ns, |             self.xmpp.default_ns, | ||||||
|             'http://jabber.org/protocol/muc#user', |             'http://jabber.org/protocol/muc#user', | ||||||
|             'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite)) |             'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite)) | ||||||
| @@ -179,6 +180,14 @@ class XEP_0045(BasePlugin): | |||||||
|         self.xmpp.event('groupchat_message', msg) |         self.xmpp.event('groupchat_message', msg) | ||||||
|         self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) |         self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) | ||||||
|  |  | ||||||
|  |     def handle_groupchat_error_message(self, msg): | ||||||
|  |         """ Handle a message error event in a muc. | ||||||
|  |         """ | ||||||
|  |         self.xmpp.event('groupchat_message_error', msg) | ||||||
|  |         self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def handle_groupchat_subject(self, msg): |     def handle_groupchat_subject(self, msg): | ||||||
|         """ Handle a message coming from a muc indicating |         """ Handle a message coming from a muc indicating | ||||||
|         a change of subject (or announcing it when joining the room) |         a change of subject (or announcing it when joining the room) | ||||||
| @@ -198,30 +207,9 @@ class XEP_0045(BasePlugin): | |||||||
|             if entry is not None and entry['jid'].full == jid: |             if entry is not None and entry['jid'].full == jid: | ||||||
|                 return nick |                 return nick | ||||||
|  |  | ||||||
|     def getRoomForm(self, room, ifrom=None): |  | ||||||
|         iq = self.xmpp.makeIqGet() |  | ||||||
|         iq['to'] = room |  | ||||||
|         if ifrom is not None: |  | ||||||
|             iq['from'] = ifrom |  | ||||||
|         query = ET.Element('{http://jabber.org/protocol/muc#owner}query') |  | ||||||
|         iq.append(query) |  | ||||||
|         # For now, swallow errors to preserve existing API |  | ||||||
|         try: |  | ||||||
|             result = iq.send() |  | ||||||
|         except IqError: |  | ||||||
|             return False |  | ||||||
|         except IqTimeout: |  | ||||||
|             return False |  | ||||||
|         xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') |  | ||||||
|         if xform is None: return False |  | ||||||
|         form = self.xmpp.plugin['old_0004'].buildForm(xform) |  | ||||||
|         return form |  | ||||||
|  |  | ||||||
|     def configureRoom(self, room, form=None, ifrom=None): |     def configureRoom(self, room, form=None, ifrom=None): | ||||||
|         if form is None: |         if form is None: | ||||||
|             form = self.getRoomForm(room, ifrom=ifrom) |             form = self.getRoomConfig(room, ifrom=ifrom) | ||||||
|             #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') |  | ||||||
|             #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') |  | ||||||
|         iq = self.xmpp.makeIqSet() |         iq = self.xmpp.makeIqSet() | ||||||
|         iq['to'] = room |         iq['to'] = room | ||||||
|         if ifrom is not None: |         if ifrom is not None: | ||||||
| @@ -310,6 +298,24 @@ class XEP_0045(BasePlugin): | |||||||
|             return False |             return False | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|  |     def setRole(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 | ||||||
|  |             sessions). | ||||||
|  |         """ | ||||||
|  |         if role not in ('moderator', 'participant', 'visitor', 'none'): | ||||||
|  |             raise TypeError | ||||||
|  |         query = ET.Element('{http://jabber.org/protocol/muc#admin}query') | ||||||
|  |         item = ET.Element('item', {'role':role, 'nick':nick})     | ||||||
|  |         query.append(item) | ||||||
|  |         iq = self.xmpp.makeIqSet(query) | ||||||
|  |         iq['to'] = room | ||||||
|  |         result = iq.send() | ||||||
|  |         if result is False or result['type'] != 'result': | ||||||
|  |             raise ValueError | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     def invite(self, room, jid, reason='', mfrom=''): |     def invite(self, room, jid, reason='', mfrom=''): | ||||||
|         """ Invite a jid to a room.""" |         """ Invite a jid to a room.""" | ||||||
|         msg = self.xmpp.makeMessage(room) |         msg = self.xmpp.makeMessage(room) | ||||||
|   | |||||||
| @@ -21,21 +21,25 @@ class XEP_0047(BasePlugin): | |||||||
|     dependencies = set(['xep_0030']) |     dependencies = set(['xep_0030']) | ||||||
|     stanza = stanza |     stanza = stanza | ||||||
|     default_config = { |     default_config = { | ||||||
|  |         'block_size': 4096, | ||||||
|         'max_block_size': 8192, |         'max_block_size': 8192, | ||||||
|         'window_size': 1, |         'window_size': 1, | ||||||
|         'auto_accept': True, |         'auto_accept': False, | ||||||
|         'accept_stream': None |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     def plugin_init(self): |     def plugin_init(self): | ||||||
|         self.streams = {} |         self._streams = {} | ||||||
|         self.pending_streams = {} |         self._pending_streams = {} | ||||||
|         self.pending_close_streams = {} |         self._pending_lock = threading.Lock() | ||||||
|         self._stream_lock = threading.Lock() |         self._stream_lock = threading.Lock() | ||||||
|  |  | ||||||
|  |         self._preauthed_sids_lock = threading.Lock() | ||||||
|  |         self._preauthed_sids = {} | ||||||
|  |  | ||||||
|         register_stanza_plugin(Iq, Open) |         register_stanza_plugin(Iq, Open) | ||||||
|         register_stanza_plugin(Iq, Close) |         register_stanza_plugin(Iq, Close) | ||||||
|         register_stanza_plugin(Iq, Data) |         register_stanza_plugin(Iq, Data) | ||||||
|  |         register_stanza_plugin(Message, Data) | ||||||
|  |  | ||||||
|         self.xmpp.register_handler(Callback( |         self.xmpp.register_handler(Callback( | ||||||
|             'IBB Open', |             'IBB Open', | ||||||
| @@ -52,27 +56,71 @@ class XEP_0047(BasePlugin): | |||||||
|             StanzaPath('iq@type=set/ibb_data'), |             StanzaPath('iq@type=set/ibb_data'), | ||||||
|             self._handle_data)) |             self._handle_data)) | ||||||
|  |  | ||||||
|  |         self.xmpp.register_handler(Callback( | ||||||
|  |             'IBB Message Data', | ||||||
|  |             StanzaPath('message/ibb_data'), | ||||||
|  |             self._handle_data)) | ||||||
|  |  | ||||||
|  |         self.api.register(self._authorized, 'authorized', default=True) | ||||||
|  |         self.api.register(self._authorized_sid, 'authorized_sid', default=True) | ||||||
|  |         self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True) | ||||||
|  |         self.api.register(self._get_stream, 'get_stream', default=True) | ||||||
|  |         self.api.register(self._set_stream, 'set_stream', default=True) | ||||||
|  |         self.api.register(self._del_stream, 'del_stream', default=True) | ||||||
|  |  | ||||||
|     def plugin_end(self): |     def plugin_end(self): | ||||||
|         self.xmpp.remove_handler('IBB Open') |         self.xmpp.remove_handler('IBB Open') | ||||||
|         self.xmpp.remove_handler('IBB Close') |         self.xmpp.remove_handler('IBB Close') | ||||||
|         self.xmpp.remove_handler('IBB Data') |         self.xmpp.remove_handler('IBB Data') | ||||||
|  |         self.xmpp.remove_handler('IBB Message Data') | ||||||
|         self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb') |         self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb') | ||||||
|  |  | ||||||
|     def session_bind(self, jid): |     def session_bind(self, jid): | ||||||
|         self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') |         self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb') | ||||||
|  |  | ||||||
|  |     def _get_stream(self, jid, sid, peer_jid, data): | ||||||
|  |         return self._streams.get((jid, sid, peer_jid), None) | ||||||
|  |  | ||||||
|  |     def _set_stream(self, jid, sid, peer_jid, stream): | ||||||
|  |         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)] | ||||||
|  |  | ||||||
|     def _accept_stream(self, iq): |     def _accept_stream(self, iq): | ||||||
|         if self.accept_stream is not None: |         receiver = iq['to'] | ||||||
|             return self.accept_stream(iq) |         sender = iq['from'] | ||||||
|  |         sid = iq['ibb_open']['sid'] | ||||||
|  |  | ||||||
|  |         if self.api['authorized_sid'](receiver, sid, sender, iq): | ||||||
|  |             return True | ||||||
|  |         return self.api['authorized'](receiver, sid, sender, iq) | ||||||
|  |  | ||||||
|  |     def _authorized(self, jid, sid, ifrom, iq): | ||||||
|         if self.auto_accept: |         if self.auto_accept: | ||||||
|             if iq['ibb_open']['block_size'] <= self.max_block_size: |             if iq['ibb_open']['block_size'] <= self.max_block_size: | ||||||
|                 return True |                 return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|     def open_stream(self, jid, block_size=4096, sid=None, window=1, |     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 | ||||||
|  |  | ||||||
|  |     def _preauthorize_sid(self, jid, sid, ifrom, data): | ||||||
|  |         with self._preauthed_sids_lock: | ||||||
|  |             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): |                     ifrom=None, block=True, timeout=None, callback=None): | ||||||
|         if sid is None: |         if sid is None: | ||||||
|             sid = str(uuid.uuid4()) |             sid = str(uuid.uuid4()) | ||||||
|  |         if block_size is None: | ||||||
|  |             block_size = self.block_size | ||||||
|  |  | ||||||
|         iq = self.xmpp.Iq() |         iq = self.xmpp.Iq() | ||||||
|         iq['type'] = 'set' |         iq['type'] = 'set' | ||||||
| @@ -83,12 +131,13 @@ class XEP_0047(BasePlugin): | |||||||
|         iq['ibb_open']['stanza'] = 'iq' |         iq['ibb_open']['stanza'] = 'iq' | ||||||
|  |  | ||||||
|         stream = IBBytestream(self.xmpp, sid, block_size, |         stream = IBBytestream(self.xmpp, sid, block_size, | ||||||
|                               iq['to'], iq['from'], window) |                               iq['from'], iq['to'], window, | ||||||
|  |                               use_messages) | ||||||
|  |  | ||||||
|         with self._stream_lock: |         with self._stream_lock: | ||||||
|             self.pending_streams[iq['id']] = stream |             self._pending_streams[iq['id']] = stream | ||||||
|  |  | ||||||
|         self.pending_streams[iq['id']] = stream |         self._pending_streams[iq['id']] = stream | ||||||
|  |  | ||||||
|         if block: |         if block: | ||||||
|             resp = iq.send(timeout=timeout) |             resp = iq.send(timeout=timeout) | ||||||
| @@ -108,49 +157,59 @@ class XEP_0047(BasePlugin): | |||||||
|     def _handle_opened_stream(self, iq): |     def _handle_opened_stream(self, iq): | ||||||
|         if iq['type'] == 'result': |         if iq['type'] == 'result': | ||||||
|             with self._stream_lock: |             with self._stream_lock: | ||||||
|                 stream = self.pending_streams.get(iq['id'], None) |                 stream = self._pending_streams.get(iq['id'], None) | ||||||
|             if stream is not None: |             if stream is not None: | ||||||
|                     stream.sender = iq['to'] |                 log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from']) | ||||||
|                     stream.receiver = iq['from'] |                 stream.self_jid = iq['to'] | ||||||
|  |                 stream.peer_jid = iq['from'] | ||||||
|                 stream.stream_started.set() |                 stream.stream_started.set() | ||||||
|                     self.streams[stream.sid] = stream |                 self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) | ||||||
|                 self.xmpp.event('ibb_stream_start', stream) |                 self.xmpp.event('ibb_stream_start', stream) | ||||||
|  |                 self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream) | ||||||
|  |  | ||||||
|         with self._stream_lock: |         with self._stream_lock: | ||||||
|             if iq['id'] in self.pending_streams: |             if iq['id'] in self._pending_streams: | ||||||
|                 del self.pending_streams[iq['id']] |                 del self._pending_streams[iq['id']] | ||||||
|  |  | ||||||
|     def _handle_open_request(self, iq): |     def _handle_open_request(self, iq): | ||||||
|         sid = iq['ibb_open']['sid'] |         sid = iq['ibb_open']['sid'] | ||||||
|         size = iq['ibb_open']['block_size'] |         size = iq['ibb_open']['block_size'] or self.block_size | ||||||
|  |  | ||||||
|  |         log.debug('Received IBB stream request from %s', iq['from']) | ||||||
|  |  | ||||||
|  |         if not sid: | ||||||
|  |             raise XMPPError(etype='modify', condition='bad-request') | ||||||
|  |  | ||||||
|         if not self._accept_stream(iq): |         if not self._accept_stream(iq): | ||||||
|             raise XMPPError('not-acceptable') |             raise XMPPError(etype='modify', condition='not-acceptable') | ||||||
|  |  | ||||||
|         if size > self.max_block_size: |         if size > self.max_block_size: | ||||||
|             raise XMPPError('resource-constraint') |             raise XMPPError('resource-constraint') | ||||||
|  |  | ||||||
|         stream = IBBytestream(self.xmpp, sid, size, |         stream = IBBytestream(self.xmpp, sid, size, | ||||||
|                               iq['from'], iq['to'], |                               iq['to'], iq['from'], | ||||||
|                               self.window_size) |                               self.window_size) | ||||||
|         stream.stream_started.set() |         stream.stream_started.set() | ||||||
|         self.streams[sid] = stream |         self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream) | ||||||
|         iq.reply() |         iq.reply() | ||||||
|         iq.send() |         iq.send() | ||||||
|  |  | ||||||
|         self.xmpp.event('ibb_stream_start', stream) |         self.xmpp.event('ibb_stream_start', stream) | ||||||
|  |         self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream) | ||||||
|  |  | ||||||
|     def _handle_data(self, iq): |     def _handle_data(self, stanza): | ||||||
|         sid = iq['ibb_data']['sid'] |         sid = stanza['ibb_data']['sid'] | ||||||
|         stream = self.streams.get(sid, None) |         stream = self.api['get_stream'](stanza['to'], sid, stanza['from']) | ||||||
|         if stream is not None and iq['from'] != stream.sender: |         if stream is not None and stanza['from'] == stream.peer_jid: | ||||||
|             stream._recv_data(iq) |             stream._recv_data(stanza) | ||||||
|         else: |         else: | ||||||
|             raise XMPPError('item-not-found') |             raise XMPPError('item-not-found') | ||||||
|  |  | ||||||
|     def _handle_close(self, iq): |     def _handle_close(self, iq): | ||||||
|         sid = iq['ibb_close']['sid'] |         sid = iq['ibb_close']['sid'] | ||||||
|         stream = self.streams.get(sid, None) |         stream = self.api['get_stream'](iq['to'], sid, iq['from']) | ||||||
|         if stream is not None and iq['from'] != stream.sender: |         if stream is not None and iq['from'] == stream.peer_jid: | ||||||
|             stream._closed(iq) |             stream._closed(iq) | ||||||
|  |             self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid) | ||||||
|         else: |         else: | ||||||
|             raise XMPPError('item-not-found') |             raise XMPPError('item-not-found') | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import socket | |||||||
| import threading | import threading | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Iq | ||||||
| from sleekxmpp.util import Queue | from sleekxmpp.util import Queue | ||||||
| from sleekxmpp.exceptions import XMPPError | from sleekxmpp.exceptions import XMPPError | ||||||
|  |  | ||||||
| @@ -11,14 +12,17 @@ log = logging.getLogger(__name__) | |||||||
|  |  | ||||||
| class IBBytestream(object): | class IBBytestream(object): | ||||||
|  |  | ||||||
|     def __init__(self, xmpp, sid, block_size, to, ifrom, window_size=1): |     def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False): | ||||||
|         self.xmpp = xmpp |         self.xmpp = xmpp | ||||||
|         self.sid = sid |         self.sid = sid | ||||||
|         self.block_size = block_size |         self.block_size = block_size | ||||||
|         self.window_size = window_size |         self.window_size = window_size | ||||||
|  |         self.use_messages = use_messages | ||||||
|  |  | ||||||
|         self.receiver = to |         if jid is None: | ||||||
|         self.sender = ifrom |             jid = xmpp.boundjid | ||||||
|  |         self.self_jid = jid | ||||||
|  |         self.peer_jid = peer | ||||||
|  |  | ||||||
|         self.send_seq = -1 |         self.send_seq = -1 | ||||||
|         self.recv_seq = -1 |         self.recv_seq = -1 | ||||||
| @@ -46,10 +50,21 @@ class IBBytestream(object): | |||||||
|         with self._send_seq_lock: |         with self._send_seq_lock: | ||||||
|             self.send_seq = (self.send_seq + 1) % 65535 |             self.send_seq = (self.send_seq + 1) % 65535 | ||||||
|             seq = self.send_seq |             seq = self.send_seq | ||||||
|  |         if self.use_messages: | ||||||
|  |             msg = self.xmpp.Message() | ||||||
|  |             msg['to'] = self.peer_jid | ||||||
|  |             msg['from'] = self.self_jid | ||||||
|  |             msg['id'] = self.xmpp.new_id() | ||||||
|  |             msg['ibb_data']['sid'] = self.sid | ||||||
|  |             msg['ibb_data']['seq'] = seq | ||||||
|  |             msg['ibb_data']['data'] = data | ||||||
|  |             msg.send() | ||||||
|  |             self.send_window.release() | ||||||
|  |         else: | ||||||
|             iq = self.xmpp.Iq() |             iq = self.xmpp.Iq() | ||||||
|             iq['type'] = 'set' |             iq['type'] = 'set' | ||||||
|         iq['to'] = self.receiver |             iq['to'] = self.peer_jid | ||||||
|         iq['from'] = self.sender |             iq['from'] = self.self_jid | ||||||
|             iq['ibb_data']['sid'] = self.sid |             iq['ibb_data']['sid'] = self.sid | ||||||
|             iq['ibb_data']['seq'] = seq |             iq['ibb_data']['seq'] = seq | ||||||
|             iq['ibb_data']['data'] = data |             iq['ibb_data']['data'] = data | ||||||
| @@ -71,23 +86,25 @@ class IBBytestream(object): | |||||||
|         if iq['type'] == 'error': |         if iq['type'] == 'error': | ||||||
|             self.close() |             self.close() | ||||||
|  |  | ||||||
|     def _recv_data(self, iq): |     def _recv_data(self, stanza): | ||||||
|         with self._recv_seq_lock: |         with self._recv_seq_lock: | ||||||
|             new_seq = iq['ibb_data']['seq'] |             new_seq = stanza['ibb_data']['seq'] | ||||||
|             if new_seq != (self.recv_seq + 1) % 65535: |             if new_seq != (self.recv_seq + 1) % 65535: | ||||||
|                 self.close() |                 self.close() | ||||||
|                 raise XMPPError('unexpected-request') |                 raise XMPPError('unexpected-request') | ||||||
|             self.recv_seq = new_seq |             self.recv_seq = new_seq | ||||||
|  |  | ||||||
|         data = iq['ibb_data']['data'] |         data = stanza['ibb_data']['data'] | ||||||
|         if len(data) > self.block_size: |         if len(data) > self.block_size: | ||||||
|             self.close() |             self.close() | ||||||
|             raise XMPPError('not-acceptable') |             raise XMPPError('not-acceptable') | ||||||
|  |  | ||||||
|         self.recv_queue.put(data) |         self.recv_queue.put(data) | ||||||
|         self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data}) |         self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data}) | ||||||
|         iq.reply() |  | ||||||
|         iq.send() |         if isinstance(stanza, Iq): | ||||||
|  |             stanza.reply() | ||||||
|  |             stanza.send() | ||||||
|  |  | ||||||
|     def recv(self, *args, **kwargs): |     def recv(self, *args, **kwargs): | ||||||
|         return self.read(block=True) |         return self.read(block=True) | ||||||
| @@ -106,8 +123,8 @@ class IBBytestream(object): | |||||||
|     def close(self): |     def close(self): | ||||||
|         iq = self.xmpp.Iq() |         iq = self.xmpp.Iq() | ||||||
|         iq['type'] = 'set' |         iq['type'] = 'set' | ||||||
|         iq['to'] = self.receiver |         iq['to'] = self.peer_jid | ||||||
|         iq['from'] = self.sender |         iq['from'] = self.self_jid | ||||||
|         iq['ibb_close']['sid'] = self.sid |         iq['ibb_close']['sid'] = self.sid | ||||||
|         self.stream_out_closed.set() |         self.stream_out_closed.set() | ||||||
|         iq.send(block=False, |         iq.send(block=False, | ||||||
| @@ -117,9 +134,6 @@ class IBBytestream(object): | |||||||
|     def _closed(self, iq): |     def _closed(self, iq): | ||||||
|         self.stream_in_closed.set() |         self.stream_in_closed.set() | ||||||
|         self.stream_out_closed.set() |         self.stream_out_closed.set() | ||||||
|         while not self.window_empty.is_set(): |  | ||||||
|             log.info('waiting for send window to empty') |  | ||||||
|             self.window_empty.wait(timeout=1) |  | ||||||
|         iq.reply() |         iq.reply() | ||||||
|         iq.send() |         iq.send() | ||||||
|         self.xmpp.event('ibb_stream_end', self) |         self.xmpp.event('ibb_stream_end', self) | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0048/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0048/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0048.stanza import Bookmarks, Conference, URL | ||||||
|  | from sleekxmpp.plugins.xep_0048.bookmarks import XEP_0048 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0048) | ||||||
							
								
								
									
										76
									
								
								sleekxmpp/plugins/xep_0048/bookmarks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								sleekxmpp/plugins/xep_0048/bookmarks.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp import Iq | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.exceptions import XMPPError | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins.xep_0048 import stanza, Bookmarks, Conference, URL | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0048(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0048' | ||||||
|  |     description = 'XEP-0048: Bookmarks' | ||||||
|  |     dependencies = set(['xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223']) | ||||||
|  |     stanza = stanza | ||||||
|  |     default_config = { | ||||||
|  |         'auto_join': False, | ||||||
|  |         'storage_method': 'xep_0049' | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, Bookmarks) | ||||||
|  |  | ||||||
|  |         self.xmpp['xep_0049'].register(Bookmarks) | ||||||
|  |         self.xmpp['xep_0163'].register_pep('bookmarks', Bookmarks) | ||||||
|  |  | ||||||
|  |         self.xmpp.add_event_handler('session_start', self._autojoin) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.del_event_handler('session_start', self._autojoin) | ||||||
|  |  | ||||||
|  |     def _autojoin(self, __): | ||||||
|  |         if not self.auto_join: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             result = self.get_bookmarks(method=self.storage_method) | ||||||
|  |         except XMPPError: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         if self.storage_method == 'xep_0223': | ||||||
|  |             bookmarks = result['pubsub']['items']['item']['bookmarks'] | ||||||
|  |         else: | ||||||
|  |             bookmarks = result['private']['bookmarks'] | ||||||
|  |  | ||||||
|  |         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'], | ||||||
|  |                         password=conf['password']) | ||||||
|  |  | ||||||
|  |     def set_bookmarks(self, bookmarks, method=None, **iqargs): | ||||||
|  |         if not method: | ||||||
|  |             method = self.storage_method | ||||||
|  |         return self.xmpp[method].store(bookmarks, **iqargs) | ||||||
|  |  | ||||||
|  |     def get_bookmarks(self, method=None, **iqargs): | ||||||
|  |         if not method: | ||||||
|  |             method = self.storage_method | ||||||
|  |  | ||||||
|  |         loc = 'storage:bookmarks' if method == 'xep_0223' else 'bookmarks' | ||||||
|  |  | ||||||
|  |         return self.xmpp[method].retrieve(loc, **iqargs) | ||||||
							
								
								
									
										65
									
								
								sleekxmpp/plugins/xep_0048/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								sleekxmpp/plugins/xep_0048/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Bookmarks(ElementBase): | ||||||
|  |     name = 'storage' | ||||||
|  |     namespace = 'storage:bookmarks' | ||||||
|  |     plugin_attrib = 'bookmarks' | ||||||
|  |     interfaces = set() | ||||||
|  |  | ||||||
|  |     def add_conference(self, jid, nick, name=None, autojoin=None, password=None): | ||||||
|  |         conf = Conference() | ||||||
|  |         conf['jid'] = jid | ||||||
|  |         conf['nick'] = nick | ||||||
|  |         if name is None: | ||||||
|  |             name = jid | ||||||
|  |         conf['name'] = name | ||||||
|  |         conf['autojoin'] = autojoin | ||||||
|  |         conf['password'] = password | ||||||
|  |         self.append(conf) | ||||||
|  |  | ||||||
|  |     def add_url(self, url, name=None): | ||||||
|  |         saved_url = URL() | ||||||
|  |         saved_url['url'] = url | ||||||
|  |         if name is None: | ||||||
|  |             name = url | ||||||
|  |         saved_url['name'] = name | ||||||
|  |         self.append(saved_url) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Conference(ElementBase): | ||||||
|  |     name = 'conference' | ||||||
|  |     namespace = 'storage:bookmarks' | ||||||
|  |     plugin_attrib = 'conference' | ||||||
|  |     plugin_multi_attrib = 'conferences' | ||||||
|  |     interfaces = set(['nick', 'password', 'autojoin', 'jid', 'name']) | ||||||
|  |     sub_interfaces = set(['nick', 'password']) | ||||||
|  |  | ||||||
|  |     def get_autojoin(self): | ||||||
|  |         value = self._get_attr('autojoin') | ||||||
|  |         return value in ('1', 'true') | ||||||
|  |  | ||||||
|  |     def set_autojoin(self, value): | ||||||
|  |         del self['autojoin'] | ||||||
|  |         if value in ('1', 'true', True): | ||||||
|  |             self._set_attr('autojoin', 'true') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class URL(ElementBase): | ||||||
|  |     name = 'url' | ||||||
|  |     namespace = 'storage:bookmarks' | ||||||
|  |     plugin_attrib = 'url' | ||||||
|  |     plugin_multi_attrib = 'urls' | ||||||
|  |     interfaces = set(['url', 'name']) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_stanza_plugin(Bookmarks, Conference, iterable=True) | ||||||
|  | register_stanza_plugin(Bookmarks, URL, iterable=True) | ||||||
| @@ -267,8 +267,9 @@ class XEP_0050(BasePlugin): | |||||||
|             iq -- The command continuation request. |             iq -- The command continuation request. | ||||||
|         """ |         """ | ||||||
|         sessionid = iq['command']['sessionid'] |         sessionid = iq['command']['sessionid'] | ||||||
|         session = self.sessions[sessionid] |         session = self.sessions.get(sessionid) | ||||||
|  |  | ||||||
|  |         if session: | ||||||
|             handler = session['next'] |             handler = session['next'] | ||||||
|             interfaces = session['interfaces'] |             interfaces = session['interfaces'] | ||||||
|             results = [] |             results = [] | ||||||
| @@ -281,6 +282,35 @@ class XEP_0050(BasePlugin): | |||||||
|             session = handler(results, session) |             session = handler(results, session) | ||||||
|  |  | ||||||
|             self._process_command_response(iq, session) |             self._process_command_response(iq, session) | ||||||
|  |         else: | ||||||
|  |             raise XMPPError('item-not-found') | ||||||
|  |  | ||||||
|  |     def _handle_command_prev(self, iq): | ||||||
|  |         """ | ||||||
|  |         Process a request for the prev step in the workflow | ||||||
|  |         for a command with multiple steps. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             iq -- The command continuation request. | ||||||
|  |         """ | ||||||
|  |         sessionid = iq['command']['sessionid'] | ||||||
|  |         session = self.sessions.get(sessionid) | ||||||
|  |  | ||||||
|  |         if session: | ||||||
|  |             handler = session['prev'] | ||||||
|  |             interfaces = session['interfaces'] | ||||||
|  |             results = [] | ||||||
|  |             for stanza in iq['command']['substanzas']: | ||||||
|  |                 if stanza.plugin_attrib in interfaces: | ||||||
|  |                     results.append(stanza) | ||||||
|  |             if len(results) == 1: | ||||||
|  |                 results = results[0] | ||||||
|  |  | ||||||
|  |             session = handler(results, session) | ||||||
|  |  | ||||||
|  |             self._process_command_response(iq, session) | ||||||
|  |         else: | ||||||
|  |             raise XMPPError('item-not-found') | ||||||
|  |  | ||||||
|     def _process_command_response(self, iq, session): |     def _process_command_response(self, iq, session): | ||||||
|         """ |         """ | ||||||
| @@ -348,23 +378,23 @@ class XEP_0050(BasePlugin): | |||||||
|         """ |         """ | ||||||
|         node = iq['command']['node'] |         node = iq['command']['node'] | ||||||
|         sessionid = iq['command']['sessionid'] |         sessionid = iq['command']['sessionid'] | ||||||
|         session = self.sessions[sessionid] |  | ||||||
|         handler = session['cancel'] |  | ||||||
|  |  | ||||||
|  |         session = self.sessions.get(sessionid) | ||||||
|  |  | ||||||
|  |         if session: | ||||||
|  |             handler = session['cancel'] | ||||||
|             if handler: |             if handler: | ||||||
|                 handler(iq, session) |                 handler(iq, session) | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             del self.sessions[sessionid] |             del self.sessions[sessionid] | ||||||
|         except: |  | ||||||
|             pass |  | ||||||
|  |  | ||||||
|             iq.reply() |             iq.reply() | ||||||
|             iq['command']['node'] = node |             iq['command']['node'] = node | ||||||
|             iq['command']['sessionid'] = sessionid |             iq['command']['sessionid'] = sessionid | ||||||
|             iq['command']['status'] = 'canceled' |             iq['command']['status'] = 'canceled' | ||||||
|             iq['command']['notes'] = session['notes'] |             iq['command']['notes'] = session['notes'] | ||||||
|             iq.send() |             iq.send() | ||||||
|  |         else: | ||||||
|  |             raise XMPPError('item-not-found') | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _handle_command_complete(self, iq): |     def _handle_command_complete(self, iq): | ||||||
|         """ |         """ | ||||||
| @@ -378,7 +408,9 @@ class XEP_0050(BasePlugin): | |||||||
|         """ |         """ | ||||||
|         node = iq['command']['node'] |         node = iq['command']['node'] | ||||||
|         sessionid = iq['command']['sessionid'] |         sessionid = iq['command']['sessionid'] | ||||||
|         session = self.sessions[sessionid] |         session = self.sessions.get(sessionid) | ||||||
|  |  | ||||||
|  |         if session: | ||||||
|             handler = session['next'] |             handler = session['next'] | ||||||
|             interfaces = session['interfaces'] |             interfaces = session['interfaces'] | ||||||
|             results = [] |             results = [] | ||||||
| @@ -391,6 +423,8 @@ class XEP_0050(BasePlugin): | |||||||
|             if handler: |             if handler: | ||||||
|                 handler(results, session) |                 handler(results, session) | ||||||
|  |  | ||||||
|  |             del self.sessions[sessionid] | ||||||
|  |  | ||||||
|             iq.reply() |             iq.reply() | ||||||
|             iq['command']['node'] = node |             iq['command']['node'] = node | ||||||
|             iq['command']['sessionid'] = sessionid |             iq['command']['sessionid'] = sessionid | ||||||
| @@ -398,8 +432,8 @@ class XEP_0050(BasePlugin): | |||||||
|             iq['command']['status'] = 'completed' |             iq['command']['status'] = 'completed' | ||||||
|             iq['command']['notes'] = session['notes'] |             iq['command']['notes'] = session['notes'] | ||||||
|             iq.send() |             iq.send() | ||||||
|  |         else: | ||||||
|         del self.sessions[sessionid] |             raise XMPPError('item-not-found') | ||||||
|  |  | ||||||
|     # ================================================================= |     # ================================================================= | ||||||
|     # Client side (command user) API |     # Client side (command user) API | ||||||
| @@ -537,7 +571,7 @@ class XEP_0050(BasePlugin): | |||||||
|         else: |         else: | ||||||
|             iq.send(block=False, callback=self._handle_command_result) |             iq.send(block=False, callback=self._handle_command_result) | ||||||
|  |  | ||||||
|     def continue_command(self, session): |     def continue_command(self, session, direction='next'): | ||||||
|         """ |         """ | ||||||
|         Execute the next action of the command. |         Execute the next action of the command. | ||||||
|  |  | ||||||
| @@ -551,7 +585,7 @@ class XEP_0050(BasePlugin): | |||||||
|         self.send_command(session['jid'], |         self.send_command(session['jid'], | ||||||
|                           session['node'], |                           session['node'], | ||||||
|                           ifrom=session.get('from', None), |                           ifrom=session.get('from', None), | ||||||
|                           action='next', |                           action=direction, | ||||||
|                           payload=session.get('payload', None), |                           payload=session.get('payload', None), | ||||||
|                           sessionid=session['id'], |                           sessionid=session['id'], | ||||||
|                           flow=True, |                           flow=True, | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| from sleekxmpp import Iq | from sleekxmpp import JID, Iq | ||||||
| from sleekxmpp.exceptions import XMPPError | from sleekxmpp.exceptions import XMPPError | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
| from sleekxmpp.xmlstream.handler import Callback | from sleekxmpp.xmlstream.handler import Callback | ||||||
| @@ -59,9 +59,19 @@ class XEP_0054(BasePlugin): | |||||||
|     def make_vcard(self): |     def make_vcard(self): | ||||||
|         return VCardTemp() |         return VCardTemp() | ||||||
|  |  | ||||||
|     def get_vcard(self, jid=None, ifrom=None, local=False, cached=False, |     def get_vcard(self, jid=None, ifrom=None, local=None, cached=False, | ||||||
|                   block=True, callback=None, timeout=None): |                   block=True, callback=None, timeout=None): | ||||||
|         if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain: |         if local is None: | ||||||
|  |             if jid is not None and not isinstance(jid, JID): | ||||||
|  |                 jid = JID(jid) | ||||||
|  |                 if self.xmpp.is_component: | ||||||
|  |                     if jid.domain == self.xmpp.boundjid.domain: | ||||||
|  |                         local = True | ||||||
|  |                 else: | ||||||
|  |                     if str(jid) == str(self.xmpp.boundjid): | ||||||
|  |                         local = True | ||||||
|  |                 jid = jid.full | ||||||
|  |             elif jid in (None, ''): | ||||||
|                 local = True |                 local = True | ||||||
|  |  | ||||||
|         if local: |         if local: | ||||||
|   | |||||||
| @@ -423,7 +423,7 @@ class XEP_0060(BasePlugin): | |||||||
|                         callback=None, timeout=None): |                         callback=None, timeout=None): | ||||||
|         iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') |         iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set') | ||||||
|         iq['pubsub_owner']['configure']['node'] = node |         iq['pubsub_owner']['configure']['node'] = node | ||||||
|         iq['pubsub_owner']['configure']['form'].values = config.values |         iq['pubsub_owner']['configure'].append(config) | ||||||
|         return iq.send(block=block, callback=callback, timeout=timeout) |         return iq.send(block=block, callback=callback, timeout=timeout) | ||||||
|  |  | ||||||
|     def publish(self, jid, node, id=None, payload=None, options=None, |     def publish(self, jid, node, id=None, payload=None, options=None, | ||||||
|   | |||||||
| @@ -74,7 +74,12 @@ class Item(ElementBase): | |||||||
|  |  | ||||||
|     def set_payload(self, value): |     def set_payload(self, value): | ||||||
|         del self['payload'] |         del self['payload'] | ||||||
|         self.append(value) |         if isinstance(value, ElementBase): | ||||||
|  |             if value.tag_name() in self.plugin_tag_map: | ||||||
|  |                 self.init_plugin(value.plugin_attrib, existing_xml=value.xml) | ||||||
|  |             self.xml.append(value.xml) | ||||||
|  |         else: | ||||||
|  |             self.xml.append(value) | ||||||
|  |  | ||||||
|     def get_payload(self): |     def get_payload(self): | ||||||
|         childs = list(self.xml) |         childs = list(self.xml) | ||||||
| @@ -243,39 +248,6 @@ class PublishOptions(ElementBase): | |||||||
|         self.parent().xml.remove(self.xml) |         self.parent().xml.remove(self.xml) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PubsubState(ElementBase): |  | ||||||
|     """This is an experimental pubsub extension.""" |  | ||||||
|     namespace = 'http://jabber.org/protocol/psstate' |  | ||||||
|     name = 'state' |  | ||||||
|     plugin_attrib = 'psstate' |  | ||||||
|     interfaces = set(('node', 'item', 'payload')) |  | ||||||
|  |  | ||||||
|     def set_payload(self, value): |  | ||||||
|         self.xml.append(value) |  | ||||||
|  |  | ||||||
|     def get_payload(self): |  | ||||||
|         childs = list(self.xml) |  | ||||||
|         if len(childs) > 0: |  | ||||||
|             return childs[0] |  | ||||||
|  |  | ||||||
|     def del_payload(self): |  | ||||||
|         for child in self.xml: |  | ||||||
|             self.xml.remove(child) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PubsubStateEvent(ElementBase): |  | ||||||
|     """This is an experimental pubsub extension.""" |  | ||||||
|     namespace = 'http://jabber.org/protocol/psstate#event' |  | ||||||
|     name = 'event' |  | ||||||
|     plugin_attrib = 'psstate_event' |  | ||||||
|     intefaces = set(tuple()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| register_stanza_plugin(Iq, PubsubState) |  | ||||||
| register_stanza_plugin(Message, PubsubStateEvent) |  | ||||||
| register_stanza_plugin(PubsubStateEvent, PubsubState) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| register_stanza_plugin(Iq, Pubsub) | register_stanza_plugin(Iq, Pubsub) | ||||||
| register_stanza_plugin(Pubsub, Affiliations) | register_stanza_plugin(Pubsub, Affiliations) | ||||||
| register_stanza_plugin(Pubsub, Configure) | register_stanza_plugin(Pubsub, Configure) | ||||||
|   | |||||||
| @@ -34,7 +34,8 @@ class DefaultConfig(ElementBase): | |||||||
|         return self['form'] |         return self['form'] | ||||||
|  |  | ||||||
|     def set_config(self, value): |     def set_config(self, value): | ||||||
|         self['form'].values = value.values |         del self['from'] | ||||||
|  |         self.append(value) | ||||||
|         return self |         return self | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -93,7 +94,9 @@ class OwnerRedirect(ElementBase): | |||||||
|  |  | ||||||
|  |  | ||||||
| class OwnerSubscriptions(Subscriptions): | class OwnerSubscriptions(Subscriptions): | ||||||
|  |     name = 'subscriptions' | ||||||
|     namespace = 'http://jabber.org/protocol/pubsub#owner' |     namespace = 'http://jabber.org/protocol/pubsub#owner' | ||||||
|  |     plugin_attrib = name | ||||||
|     interfaces = set(('node',)) |     interfaces = set(('node',)) | ||||||
|  |  | ||||||
|     def append(self, subscription): |     def append(self, subscription): | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								sleekxmpp/plugins/xep_0065/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								sleekxmpp/plugins/xep_0065/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0065.stanza import Socks5 | ||||||
|  | from sleekxmpp.plugins.xep_0065.proxy import XEP_0065 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0065) | ||||||
							
								
								
									
										292
									
								
								sleekxmpp/plugins/xep_0065/proxy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								sleekxmpp/plugins/xep_0065/proxy.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,292 @@ | |||||||
|  | import logging | ||||||
|  | import threading | ||||||
|  | import socket | ||||||
|  |  | ||||||
|  | from hashlib import sha1 | ||||||
|  | from uuid import uuid4 | ||||||
|  |  | ||||||
|  | from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5 | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Iq | ||||||
|  | from sleekxmpp.exceptions import XMPPError | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.plugins.base import base_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0065 import stanza, Socks5 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0065(base_plugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0065' | ||||||
|  |     description = "Socks5 Bytestreams" | ||||||
|  |     dependencies = set(['xep_0030']) | ||||||
|  |     default_config = { | ||||||
|  |         'auto_accept': False | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(Iq, Socks5) | ||||||
|  |  | ||||||
|  |         self._proxies = {} | ||||||
|  |         self._sessions = {} | ||||||
|  |         self._sessions_lock = threading.Lock() | ||||||
|  |  | ||||||
|  |         self._preauthed_sids_lock = threading.Lock() | ||||||
|  |         self._preauthed_sids = {} | ||||||
|  |  | ||||||
|  |         self.xmpp.register_handler( | ||||||
|  |             Callback('Socks5 Bytestreams', | ||||||
|  |                      StanzaPath('iq@type=set/socks/streamhost'), | ||||||
|  |                      self._handle_streamhost)) | ||||||
|  |  | ||||||
|  |         self.api.register(self._authorized, 'authorized', default=True) | ||||||
|  |         self.api.register(self._authorized_sid, 'authorized_sid', default=True) | ||||||
|  |         self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(Socks5.namespace) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('Socks5 Bytestreams') | ||||||
|  |         self.xmpp.remove_handler('Socks5 Streamhost Used') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace) | ||||||
|  |  | ||||||
|  |     def get_socket(self, sid): | ||||||
|  |         """Returns the socket associated to the SID.""" | ||||||
|  |         return self._sessions.get(sid, None) | ||||||
|  |  | ||||||
|  |     def handshake(self, to, ifrom=None, sid=None, timeout=None): | ||||||
|  |         """ Starts the handshake to establish the socks5 bytestreams | ||||||
|  |         connection. | ||||||
|  |         """ | ||||||
|  |         if not self._proxies: | ||||||
|  |             self._proxies = self.discover_proxies() | ||||||
|  |  | ||||||
|  |         if sid is None: | ||||||
|  |             sid = uuid4().hex | ||||||
|  |  | ||||||
|  |         used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout) | ||||||
|  |         proxy = used['socks']['streamhost_used']['jid'] | ||||||
|  |  | ||||||
|  |         if proxy not in self._proxies: | ||||||
|  |             log.warning('Received unknown SOCKS5 proxy: %s', proxy) | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         with self._sessions_lock: | ||||||
|  |             self._sessions[sid] = self._connect_proxy( | ||||||
|  |                     sid, | ||||||
|  |                     self.xmpp.boundjid, | ||||||
|  |                     to, | ||||||
|  |                     self._proxies[proxy][0], | ||||||
|  |                     self._proxies[proxy][1], | ||||||
|  |                     peer=to) | ||||||
|  |  | ||||||
|  |         # Request that the proxy activate the session with the target. | ||||||
|  |         self.activate(proxy, sid, to, timeout=timeout) | ||||||
|  |         socket = self.get_socket(sid) | ||||||
|  |         self.xmpp.event('stream:%s:%s' % (sid, to), socket) | ||||||
|  |         return socket | ||||||
|  |  | ||||||
|  |     def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None): | ||||||
|  |         if sid is None: | ||||||
|  |             sid = uuid4().hex | ||||||
|  |  | ||||||
|  |         # Requester initiates S5B negotiation with Target by sending | ||||||
|  |         # IQ-set that includes the JabberID and network address of | ||||||
|  |         # StreamHost as well as the StreamID (SID) of the proposed | ||||||
|  |         # bytestream. | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['to'] = to | ||||||
|  |         iq['from'] = ifrom | ||||||
|  |         iq['type'] = 'set' | ||||||
|  |         iq['socks']['sid'] = sid | ||||||
|  |         for proxy, (host, port) in self._proxies.items(): | ||||||
|  |             iq['socks'].add_streamhost(proxy, host, port) | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def discover_proxies(self, jid=None, ifrom=None, timeout=None): | ||||||
|  |         """Auto-discover the JIDs of SOCKS5 proxies on an XMPP server.""" | ||||||
|  |         if jid is None: | ||||||
|  |             if self.xmpp.is_component: | ||||||
|  |                 jid = self.xmpp.server | ||||||
|  |             else: | ||||||
|  |                 jid = self.xmpp.boundjid.server | ||||||
|  |  | ||||||
|  |         discovered = set() | ||||||
|  |  | ||||||
|  |         disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout) | ||||||
|  |  | ||||||
|  |         for item in disco_items['disco_items']['items']: | ||||||
|  |             try: | ||||||
|  |                 disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout) | ||||||
|  |             except XMPPError: | ||||||
|  |                 continue | ||||||
|  |             else: | ||||||
|  |                 # Verify that the identity is a bytestream proxy. | ||||||
|  |                 identities = disco_info['disco_info']['identities'] | ||||||
|  |                 for identity in identities: | ||||||
|  |                     if identity[0] == 'proxy' and identity[1] == 'bytestreams': | ||||||
|  |                         discovered.add(disco_info['from']) | ||||||
|  |  | ||||||
|  |         for jid in discovered: | ||||||
|  |             try: | ||||||
|  |                 addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout) | ||||||
|  |                 self._proxies[jid] = (addr['socks']['streamhost']['host'], | ||||||
|  |                                       addr['socks']['streamhost']['port']) | ||||||
|  |             except XMPPError: | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |         return self._proxies | ||||||
|  |  | ||||||
|  |     def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None): | ||||||
|  |         """Get the network information of a proxy.""" | ||||||
|  |         iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom) | ||||||
|  |         iq.enable('socks') | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def _handle_streamhost(self, iq): | ||||||
|  |         """Handle incoming SOCKS5 session request.""" | ||||||
|  |         sid = iq['socks']['sid'] | ||||||
|  |         if not sid: | ||||||
|  |             raise XMPPError(etype='modify', condition='bad-request') | ||||||
|  |  | ||||||
|  |         if not self._accept_stream(iq): | ||||||
|  |             raise XMPPError(etype='modify', condition='not-acceptable') | ||||||
|  |  | ||||||
|  |         streamhosts = iq['socks']['streamhosts'] | ||||||
|  |         conn = None | ||||||
|  |         used_streamhost = None | ||||||
|  |  | ||||||
|  |         sender = iq['from'] | ||||||
|  |         for streamhost in streamhosts: | ||||||
|  |             try: | ||||||
|  |                 conn = self._connect_proxy(sid, | ||||||
|  |                     sender, | ||||||
|  |                     self.xmpp.boundjid, | ||||||
|  |                     streamhost['host'], | ||||||
|  |                     streamhost['port'], | ||||||
|  |                     peer=sender) | ||||||
|  |                 used_streamhost = streamhost['jid'] | ||||||
|  |                 break | ||||||
|  |             except socket.error: | ||||||
|  |                 continue | ||||||
|  |         else: | ||||||
|  |             raise XMPPError(etype='cancel', condition='item-not-found') | ||||||
|  |  | ||||||
|  |         iq.reply() | ||||||
|  |         with self._sessions_lock: | ||||||
|  |             self._sessions[sid] = conn | ||||||
|  |         iq['socks']['sid'] = sid | ||||||
|  |         iq['socks']['streamhost_used']['jid'] = used_streamhost | ||||||
|  |         iq.send() | ||||||
|  |         self.xmpp.event('socks5_stream', conn) | ||||||
|  |         self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn) | ||||||
|  |  | ||||||
|  |     def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None): | ||||||
|  |         """Activate the socks5 session that has been negotiated.""" | ||||||
|  |         iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom) | ||||||
|  |         iq['socks']['sid'] = sid | ||||||
|  |         iq['socks']['activate'] = target | ||||||
|  |         iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|  |  | ||||||
|  |     def deactivate(self, sid): | ||||||
|  |         """Closes the proxy socket associated with this SID.""" | ||||||
|  |         sock = self._sessions.get(sid) | ||||||
|  |         if sock: | ||||||
|  |             try: | ||||||
|  |                 # sock.close() will also delete sid from self._sessions (see _connect_proxy) | ||||||
|  |                 sock.close() | ||||||
|  |             except socket.error: | ||||||
|  |                 pass | ||||||
|  |             # Though this should not be neccessary remove the closed session anyway | ||||||
|  |             with self._sessions_lock: | ||||||
|  |                 if sid in self._sessions: | ||||||
|  |                     log.warn(('SOCKS5 session with sid = "%s" was not ' +  | ||||||
|  |                               'removed from _sessions by sock.close()') % sid) | ||||||
|  |                     del self._sessions[sid] | ||||||
|  |  | ||||||
|  |     def close(self): | ||||||
|  |         """Closes all proxy sockets.""" | ||||||
|  |         for sid, sock in self._sessions.items(): | ||||||
|  |             sock.close() | ||||||
|  |         with self._sessions_lock: | ||||||
|  |             self._sessions = {} | ||||||
|  |  | ||||||
|  |     def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None): | ||||||
|  |         """ Establishes a connection between the client and the server-side | ||||||
|  |         Socks5 proxy. | ||||||
|  |  | ||||||
|  |         sid        : The StreamID. <str> | ||||||
|  |         requester  : The JID of the requester. <str> | ||||||
|  |         target     : The JID of the target. <str> | ||||||
|  |         proxy_host : The hostname or the IP of the proxy. <str> | ||||||
|  |         proxy_port : The port of the proxy. <str> or <int> | ||||||
|  |         peer       : The JID for the other side of the stream, regardless | ||||||
|  |                      of target or requester status. | ||||||
|  |         """ | ||||||
|  |         # Because the xep_0065 plugin uses the proxy_port as string, | ||||||
|  |         # the Proxy class accepts the proxy_port argument as a string | ||||||
|  |         # or an integer. Here, we force to use the port as an integer. | ||||||
|  |         proxy_port = int(proxy_port) | ||||||
|  |  | ||||||
|  |         sock = socksocket() | ||||||
|  |         sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port) | ||||||
|  |  | ||||||
|  |         # The hostname MUST be SHA1(SID + Requester JID + Target JID) | ||||||
|  |         # where the output is hexadecimal-encoded (not binary). | ||||||
|  |         digest = sha1() | ||||||
|  |         digest.update(sid) | ||||||
|  |         digest.update(str(requester)) | ||||||
|  |         digest.update(str(target)) | ||||||
|  |  | ||||||
|  |         dest = digest.hexdigest() | ||||||
|  |  | ||||||
|  |         # The port MUST be 0. | ||||||
|  |         sock.connect((dest, 0)) | ||||||
|  |         log.info('Socket connected.') | ||||||
|  |  | ||||||
|  |         _close = sock.close | ||||||
|  |         def close(*args, **kwargs): | ||||||
|  |             with self._sessions_lock: | ||||||
|  |                 if sid in self._sessions: | ||||||
|  |                     del self._sessions[sid] | ||||||
|  |             _close() | ||||||
|  |             log.info('Socket closed.') | ||||||
|  |         sock.close = close | ||||||
|  |  | ||||||
|  |         sock.peer_jid = peer | ||||||
|  |         sock.self_jid = target if requester == peer else requester | ||||||
|  |  | ||||||
|  |         self.xmpp.event('socks_connected', sid) | ||||||
|  |         return sock | ||||||
|  |  | ||||||
|  |     def _accept_stream(self, iq): | ||||||
|  |         receiver = iq['to'] | ||||||
|  |         sender = iq['from'] | ||||||
|  |         sid = iq['socks']['sid'] | ||||||
|  |  | ||||||
|  |         if self.api['authorized_sid'](receiver, sid, sender, iq): | ||||||
|  |             return True | ||||||
|  |         return self.api['authorized'](receiver, sid, sender, iq) | ||||||
|  |  | ||||||
|  |     def _authorized(self, jid, sid, ifrom, iq): | ||||||
|  |         return self.auto_accept | ||||||
|  |  | ||||||
|  |     def _authorized_sid(self, jid, sid, ifrom, iq): | ||||||
|  |         with self._preauthed_sids_lock: | ||||||
|  |             log.debug('>>> authed sids: %s', self._preauthed_sids) | ||||||
|  |             log.debug('>>> lookup: %s %s %s', jid, sid, ifrom) | ||||||
|  |             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): | ||||||
|  |         log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data) | ||||||
|  |         with self._preauthed_sids_lock: | ||||||
|  |             self._preauthed_sids[(jid, sid, ifrom)] = True | ||||||
							
								
								
									
										47
									
								
								sleekxmpp/plugins/xep_0065/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								sleekxmpp/plugins/xep_0065/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | from sleekxmpp.jid import JID | ||||||
|  | from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Socks5(ElementBase): | ||||||
|  |     name = 'query' | ||||||
|  |     namespace = 'http://jabber.org/protocol/bytestreams' | ||||||
|  |     plugin_attrib = 'socks' | ||||||
|  |     interfaces = set(['sid', 'activate']) | ||||||
|  |     sub_interfaces = set(['activate']) | ||||||
|  |  | ||||||
|  |     def add_streamhost(self, jid, host, port): | ||||||
|  |         sh = StreamHost(parent=self) | ||||||
|  |         sh['jid'] = jid | ||||||
|  |         sh['host'] = host | ||||||
|  |         sh['port'] = port | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StreamHost(ElementBase): | ||||||
|  |     name = 'streamhost' | ||||||
|  |     namespace = 'http://jabber.org/protocol/bytestreams' | ||||||
|  |     plugin_attrib = 'streamhost' | ||||||
|  |     plugin_multi_attrib = 'streamhosts' | ||||||
|  |     interfaces = set(['host', 'jid', 'port']) | ||||||
|  |  | ||||||
|  |     def set_jid(self, value): | ||||||
|  |         return self._set_attr('jid', str(value)) | ||||||
|  |  | ||||||
|  |     def get_jid(self): | ||||||
|  |         return JID(self._get_attr('jid')) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class StreamHostUsed(ElementBase): | ||||||
|  |     name = 'streamhost-used' | ||||||
|  |     namespace = 'http://jabber.org/protocol/bytestreams' | ||||||
|  |     plugin_attrib = 'streamhost_used' | ||||||
|  |     interfaces = set(['jid']) | ||||||
|  |  | ||||||
|  |     def set_jid(self, value): | ||||||
|  |         return self._set_attr('jid', str(value)) | ||||||
|  |  | ||||||
|  |     def get_jid(self): | ||||||
|  |         return JID(self._get_attr('jid')) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_stanza_plugin(Socks5, StreamHost, iterable=True) | ||||||
|  | register_stanza_plugin(Socks5, StreamHostUsed) | ||||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0071/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0071/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permissio | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0071.stanza import XHTML_IM | ||||||
|  | from sleekxmpp.plugins.xep_0071.xhtml_im import XEP_0071 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0071) | ||||||
							
								
								
									
										81
									
								
								sleekxmpp/plugins/xep_0071/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								sleekxmpp/plugins/xep_0071/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2012  Nathanael C. Fritz | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Message | ||||||
|  | from sleekxmpp.util import unicode | ||||||
|  | from sleekxmpp.thirdparty import OrderedDict | ||||||
|  | from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring | ||||||
|  |  | ||||||
|  |  | ||||||
|  | XHTML_NS = 'http://www.w3.org/1999/xhtml' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XHTML_IM(ElementBase): | ||||||
|  |  | ||||||
|  |     namespace = 'http://jabber.org/protocol/xhtml-im' | ||||||
|  |     name = 'html' | ||||||
|  |     interfaces = set(['body']) | ||||||
|  |     lang_interfaces = set(['body']) | ||||||
|  |     plugin_attrib = name | ||||||
|  |  | ||||||
|  |     def set_body(self, content, lang=None): | ||||||
|  |         if lang is None: | ||||||
|  |             lang = self.get_lang() | ||||||
|  |         self.del_body(lang) | ||||||
|  |         if lang == '*': | ||||||
|  |             for sublang, subcontent in content.items(): | ||||||
|  |                 self.set_body(subcontent, sublang) | ||||||
|  |         else: | ||||||
|  |             if isinstance(content, type(ET.Element('test'))): | ||||||
|  |                 content = unicode(ET.tostring(content)) | ||||||
|  |             else: | ||||||
|  |                 content = unicode(content) | ||||||
|  |             header = '<body xmlns="%s"' % XHTML_NS | ||||||
|  |             if lang: | ||||||
|  |                 header = '%s xml:lang="%s"' % (header, lang) | ||||||
|  |             content = '%s>%s</body>' % (header, content) | ||||||
|  |             xhtml = ET.fromstring(content) | ||||||
|  |             self.xml.append(xhtml) | ||||||
|  |  | ||||||
|  |     def get_body(self, lang=None): | ||||||
|  |         """Return the contents of the HTML body.""" | ||||||
|  |         if lang is None: | ||||||
|  |             lang = self.get_lang() | ||||||
|  |  | ||||||
|  |         bodies = self.xml.findall('{%s}body' % XHTML_NS) | ||||||
|  |  | ||||||
|  |         if lang == '*': | ||||||
|  |             result = OrderedDict() | ||||||
|  |             for body in bodies: | ||||||
|  |                 body_lang = body.attrib.get('{%s}lang' % self.xml_ns, '') | ||||||
|  |                 body_result = [] | ||||||
|  |                 body_result.append(body.text if body.text else '') | ||||||
|  |                 for child in body: | ||||||
|  |                     body_result.append(tostring(child, xmlns=XHTML_NS)) | ||||||
|  |                 body_result.append(body.tail if body.tail else '') | ||||||
|  |                 result[body_lang] = ''.join(body_result) | ||||||
|  |             return result | ||||||
|  |         else: | ||||||
|  |             for body in bodies: | ||||||
|  |                 if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang: | ||||||
|  |                     result = [] | ||||||
|  |                     result.append(body.text if body.text else '') | ||||||
|  |                     for child in body: | ||||||
|  |                         result.append(tostring(child, xmlns=XHTML_NS)) | ||||||
|  |                     result.append(body.tail if body.tail else '') | ||||||
|  |                     return ''.join(result) | ||||||
|  |         return '' | ||||||
|  |  | ||||||
|  |     def del_body(self, lang=None): | ||||||
|  |         if lang is None: | ||||||
|  |             lang = self.get_lang() | ||||||
|  |         bodies = self.xml.findall('{%s}body' % XHTML_NS) | ||||||
|  |         for body in bodies: | ||||||
|  |             if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang: | ||||||
|  |                 self.xml.remove(body) | ||||||
|  |                 return | ||||||
							
								
								
									
										30
									
								
								sleekxmpp/plugins/xep_0071/xhtml_im.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								sleekxmpp/plugins/xep_0071/xhtml_im.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Message | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins.xep_0071 import stanza, XHTML_IM | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0071(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0071' | ||||||
|  |     description = 'XEP-0071: XHTML-IM' | ||||||
|  |     dependencies = set(['xep_0030']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(Message, XHTML_IM) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(feature=XHTML_IM.namespace) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=XHTML_IM.namespace) | ||||||
| @@ -7,6 +7,7 @@ | |||||||
| """ | """ | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  | import ssl | ||||||
|  |  | ||||||
| from sleekxmpp.stanza import StreamFeatures, Iq | from sleekxmpp.stanza import StreamFeatures, Iq | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin, JID | from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||||
| @@ -29,6 +30,7 @@ class XEP_0077(BasePlugin): | |||||||
|     stanza = stanza |     stanza = stanza | ||||||
|     default_config = { |     default_config = { | ||||||
|         'create_account': True, |         'create_account': True, | ||||||
|  |         'force_registration': False, | ||||||
|         'order': 50 |         'order': 50 | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -45,10 +47,29 @@ class XEP_0077(BasePlugin): | |||||||
|         register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form) |         register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form) | ||||||
|         register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB) |         register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB) | ||||||
|  |  | ||||||
|  |         self.xmpp.add_event_handler('connected', self._force_registration) | ||||||
|  |  | ||||||
|     def plugin_end(self): |     def plugin_end(self): | ||||||
|         if not self.xmpp.is_component: |         if not self.xmpp.is_component: | ||||||
|             self.xmpp.unregister_feature('register', self.order) |             self.xmpp.unregister_feature('register', self.order) | ||||||
|  |  | ||||||
|  |     def _force_registration(self, event): | ||||||
|  |         if self.force_registration: | ||||||
|  |             self.xmpp.add_filter('in', self._force_stream_feature) | ||||||
|  |  | ||||||
|  |     def _force_stream_feature(self, stanza): | ||||||
|  |         if isinstance(stanza, StreamFeatures): | ||||||
|  |             if self.xmpp.use_tls or self.xmpp.use_ssl: | ||||||
|  |                 if 'starttls' not in self.xmpp.features: | ||||||
|  |                     return stanza | ||||||
|  |                 elif not isinstance(self.xmpp.socket, ssl.SSLSocket): | ||||||
|  |                     return stanza | ||||||
|  |             if 'mechanisms' not in self.xmpp.features: | ||||||
|  |                 log.debug('Forced adding in-band registration stream feature') | ||||||
|  |                 stanza.enable('register') | ||||||
|  |                 self.xmpp.del_filter('in', self._force_stream_feature) | ||||||
|  |         return stanza | ||||||
|  |  | ||||||
|     def _handle_register_feature(self, features): |     def _handle_register_feature(self, features): | ||||||
|         if 'mechanisms' in self.xmpp.features: |         if 'mechanisms' in self.xmpp.features: | ||||||
|             # We have already logged in with an account |             # We have already logged in with an account | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
|     See the file LICENSE for copying permission. |     See the file LICENSE for copying permission. | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  | import uuid | ||||||
| import logging | import logging | ||||||
| import hashlib | import hashlib | ||||||
| import random | import random | ||||||
| @@ -98,7 +99,7 @@ class XEP_0078(BasePlugin): | |||||||
|         # A resource is required, so create a random one if necessary |         # A resource is required, so create a random one if necessary | ||||||
|         resource = self.xmpp.requested_jid.resource |         resource = self.xmpp.requested_jid.resource | ||||||
|         if not resource: |         if not resource: | ||||||
|             resource = uuid.uuid4() |             resource = str(uuid.uuid4()) | ||||||
|  |  | ||||||
|         iq['auth']['resource'] = resource |         iq['auth']['resource'] = resource | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								sleekxmpp/plugins/xep_0079/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								sleekxmpp/plugins/xep_0079/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0079.stanza import ( | ||||||
|  |         AMP, Rule, InvalidRules, UnsupportedConditions, | ||||||
|  |         UnsupportedActions, FailedRules, FailedRule, | ||||||
|  |         AMPFeature) | ||||||
|  | from sleekxmpp.plugins.xep_0079.amp import XEP_0079 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0079) | ||||||
							
								
								
									
										79
									
								
								sleekxmpp/plugins/xep_0079/amp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								sleekxmpp/plugins/xep_0079/amp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permissio | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp.stanza import Message, Error, StreamFeatures | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath, MatchMany | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.plugins.xep_0079 import stanza | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0079(BasePlugin): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     XEP-0079 Advanced Message Processing | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     name = 'xep_0079' | ||||||
|  |     description = 'XEP-0079: Advanced Message Processing' | ||||||
|  |     dependencies = set(['xep_0030']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(Message, stanza.AMP) | ||||||
|  |         register_stanza_plugin(Error, stanza.InvalidRules) | ||||||
|  |         register_stanza_plugin(Error, stanza.UnsupportedConditions) | ||||||
|  |         register_stanza_plugin(Error, stanza.UnsupportedActions) | ||||||
|  |         register_stanza_plugin(Error, stanza.FailedRules) | ||||||
|  |  | ||||||
|  |         self.xmpp.register_handler( | ||||||
|  |                 Callback('AMP Response', | ||||||
|  |                     MatchMany([ | ||||||
|  |                         StanzaPath('message/error/failed_rules'), | ||||||
|  |                         StanzaPath('message/amp') | ||||||
|  |                     ]), | ||||||
|  |                     self._handle_amp_response)) | ||||||
|  |  | ||||||
|  |         if not self.xmpp.is_component: | ||||||
|  |             self.xmpp.register_feature('amp', | ||||||
|  |                     self._handle_amp_feature, | ||||||
|  |                     restart=False, | ||||||
|  |                     order=9000) | ||||||
|  |             register_stanza_plugin(StreamFeatures, stanza.AMPFeature) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('AMP Response') | ||||||
|  |  | ||||||
|  |     def _handle_amp_response(self, msg): | ||||||
|  |         log.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') | ||||||
|  |         if msg['type'] == 'error': | ||||||
|  |             self.xmpp.event('amp_error', msg) | ||||||
|  |         elif msg['amp']['status'] in ('alert', 'notify'): | ||||||
|  |             self.xmpp.event('amp_%s' % msg['amp']['status'], msg) | ||||||
|  |  | ||||||
|  |     def _handle_amp_feature(self, features): | ||||||
|  |         log.debug('Advanced Message Processing is available.') | ||||||
|  |         self.xmpp.features.add('amp') | ||||||
|  |  | ||||||
|  |     def discover_support(self, jid=None, **iqargs): | ||||||
|  |         if jid is None: | ||||||
|  |             if self.xmpp.is_component: | ||||||
|  |                 jid = self.xmpp.server_host | ||||||
|  |             else: | ||||||
|  |                 jid = self.xmpp.boundjid.host | ||||||
|  |  | ||||||
|  |         return self.xmpp['xep_0030'].get_info( | ||||||
|  |                 jid=jid, | ||||||
|  |                 node='http://jabber.org/protocol/amp', | ||||||
|  |                 **iqargs) | ||||||
							
								
								
									
										96
									
								
								sleekxmpp/plugins/xep_0079/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								sleekxmpp/plugins/xep_0079/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from __future__ import unicode_literals | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AMP(ElementBase): | ||||||
|  |     namespace = 'http://jabber.org/protocol/amp' | ||||||
|  |     name = 'amp' | ||||||
|  |     plugin_attrib = 'amp' | ||||||
|  |     interfaces = set(['from', 'to', 'status', 'per_hop']) | ||||||
|  |  | ||||||
|  |     def get_from(self): | ||||||
|  |         return JID(self._get_attr('from')) | ||||||
|  |  | ||||||
|  |     def set_from(self, value): | ||||||
|  |         return self._set_attr('from', str(value)) | ||||||
|  |  | ||||||
|  |     def get_to(self): | ||||||
|  |         return JID(self._get_attr('from')) | ||||||
|  |  | ||||||
|  |     def set_to(self, value): | ||||||
|  |         return self._set_attr('to', str(value)) | ||||||
|  |  | ||||||
|  |     def get_per_hop(self): | ||||||
|  |         return self._get_attr('per-hop') == 'true' | ||||||
|  |  | ||||||
|  |     def set_per_hop(self, value): | ||||||
|  |         if value: | ||||||
|  |             return self._set_attr('per-hop', 'true') | ||||||
|  |         else: | ||||||
|  |             return self._del_attr('per-hop') | ||||||
|  |  | ||||||
|  |     def del_per_hop(self): | ||||||
|  |         return self._del_attr('per-hop') | ||||||
|  |  | ||||||
|  |     def add_rule(self, action, condition, value): | ||||||
|  |         rule = Rule(parent=self) | ||||||
|  |         rule['action'] = action | ||||||
|  |         rule['condition'] = condition | ||||||
|  |         rule['value'] = value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Rule(ElementBase): | ||||||
|  |     namespace = 'http://jabber.org/protocol/amp' | ||||||
|  |     name = 'rule' | ||||||
|  |     plugin_attrib = name | ||||||
|  |     plugin_multi_attrib = 'rules' | ||||||
|  |     interfaces = set(['action', 'condition', 'value']) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class InvalidRules(ElementBase): | ||||||
|  |     namespace = 'http://jabber.org/protocol/amp' | ||||||
|  |     name = 'invalid-rules' | ||||||
|  |     plugin_attrib = 'invalid_rules' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UnsupportedConditions(ElementBase): | ||||||
|  |     namespace = 'http://jabber.org/protocol/amp' | ||||||
|  |     name = 'unsupported-conditions' | ||||||
|  |     plugin_attrib = 'unsupported_conditions' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UnsupportedActions(ElementBase): | ||||||
|  |     namespace = 'http://jabber.org/protocol/amp' | ||||||
|  |     name = 'unsupported-actions' | ||||||
|  |     plugin_attrib = 'unsupported_actions' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FailedRule(Rule): | ||||||
|  |     namespace = 'http://jabber.org/protocol/amp#errors' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FailedRules(ElementBase): | ||||||
|  |     namespace = 'http://jabber.org/protocol/amp#errors' | ||||||
|  |     name = 'failed-rules' | ||||||
|  |     plugin_attrib = 'failed_rules' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AMPFeature(ElementBase): | ||||||
|  |     namespace = 'http://jabber.org/features/amp' | ||||||
|  |     name = 'amp' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_stanza_plugin(AMP, Rule, iterable=True) | ||||||
|  | register_stanza_plugin(InvalidRules, Rule, iterable=True) | ||||||
|  | register_stanza_plugin(UnsupportedConditions, Rule, iterable=True) | ||||||
|  | register_stanza_plugin(UnsupportedActions, Rule, iterable=True) | ||||||
|  | register_stanza_plugin(FailedRules, FailedRule, iterable=True) | ||||||
| @@ -6,7 +6,6 @@ | |||||||
|     See the file LICENSE for copying permission. |     See the file LICENSE for copying permission. | ||||||
| """ | """ | ||||||
|  |  | ||||||
| import logging |  | ||||||
| import datetime as dt | import datetime as dt | ||||||
|  |  | ||||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | from sleekxmpp.plugins import BasePlugin, register_plugin | ||||||
|   | |||||||
| @@ -82,6 +82,7 @@ class XEP_0084(BasePlugin): | |||||||
|                 metadata.add_pointer(pointer) |                 metadata.add_pointer(pointer) | ||||||
|  |  | ||||||
|         return self.xmpp['xep_0163'].publish(metadata, |         return self.xmpp['xep_0163'].publish(metadata, | ||||||
|  |                 id=info['id'], | ||||||
|                 ifrom=ifrom, |                 ifrom=ifrom, | ||||||
|                 block=block, |                 block=block, | ||||||
|                 callback=callback, |                 callback=callback, | ||||||
|   | |||||||
| @@ -21,14 +21,15 @@ class LegacyDelay(ElementBase): | |||||||
|     interfaces = set(('from', 'stamp', 'text')) |     interfaces = set(('from', 'stamp', 'text')) | ||||||
|  |  | ||||||
|     def get_from(self): |     def get_from(self): | ||||||
|         return JID(self._get_attr('from')) |         from_ = self._get_attr('from') | ||||||
|  |         return JID(from_) if from_ else None | ||||||
|  |  | ||||||
|     def set_from(self, value): |     def set_from(self, value): | ||||||
|         self._set_attr('from', str(value)) |         self._set_attr('from', str(value)) | ||||||
|  |  | ||||||
|     def get_stamp(self): |     def get_stamp(self): | ||||||
|         timestamp = self._get_attr('stamp') |         timestamp = self._get_attr('stamp') | ||||||
|         return xep_0082.parse('%sZ' % timestamp) |         return xep_0082.parse('%sZ' % timestamp) if timestamp else None | ||||||
|  |  | ||||||
|     def set_stamp(self, value): |     def set_stamp(self, value): | ||||||
|         if isinstance(value, dt.datetime): |         if isinstance(value, dt.datetime): | ||||||
|   | |||||||
| @@ -70,7 +70,7 @@ class XEP_0092(BasePlugin): | |||||||
|         iq['software_version']['os'] = self.os |         iq['software_version']['os'] = self.os | ||||||
|         iq.send() |         iq.send() | ||||||
|  |  | ||||||
|     def get_version(self, jid, ifrom=None): |     def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None): | ||||||
|         """ |         """ | ||||||
|         Retrieve the software version of a remote agent. |         Retrieve the software version of a remote agent. | ||||||
|  |  | ||||||
| @@ -82,14 +82,4 @@ class XEP_0092(BasePlugin): | |||||||
|         iq['from'] = ifrom |         iq['from'] = ifrom | ||||||
|         iq['type'] = 'get' |         iq['type'] = 'get' | ||||||
|         iq['query'] = Version.namespace |         iq['query'] = Version.namespace | ||||||
|  |         return iq.send(block=block, timeout=timeout, callback=callback) | ||||||
|         result = iq.send() |  | ||||||
|  |  | ||||||
|         if result and result['type'] != 'error': |  | ||||||
|             values = result['software_version'].values |  | ||||||
|             del values['lang'] |  | ||||||
|             return values |  | ||||||
|         return False |  | ||||||
|  |  | ||||||
|  |  | ||||||
| XEP_0092.getVersion = XEP_0092.get_version |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0095/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0095/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0095 import stanza | ||||||
|  | from sleekxmpp.plugins.xep_0095.stanza import SI | ||||||
|  | from sleekxmpp.plugins.xep_0095.stream_initiation import XEP_0095 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0095) | ||||||
							
								
								
									
										25
									
								
								sleekxmpp/plugins/xep_0095/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								sleekxmpp/plugins/xep_0095/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ElementBase | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SI(ElementBase): | ||||||
|  |     name = 'si' | ||||||
|  |     namespace = 'http://jabber.org/protocol/si' | ||||||
|  |     plugin_attrib = 'si' | ||||||
|  |     interfaces = set(['id', 'mime_type', 'profile']) | ||||||
|  |  | ||||||
|  |     def get_mime_type(self): | ||||||
|  |         return self._get_attr('mime-type', 'application/octet-stream') | ||||||
|  |  | ||||||
|  |     def set_mime_type(self, value): | ||||||
|  |         self._set_attr('mime-type', value) | ||||||
|  |  | ||||||
|  |     def del_mime_type(self): | ||||||
|  |         self._del_attr('mime-type') | ||||||
							
								
								
									
										214
									
								
								sleekxmpp/plugins/xep_0095/stream_initiation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								sleekxmpp/plugins/xep_0095/stream_initiation.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,214 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | import threading | ||||||
|  |  | ||||||
|  | from uuid import uuid4 | ||||||
|  |  | ||||||
|  | from sleekxmpp import Iq, Message | ||||||
|  | from sleekxmpp.exceptions import XMPPError | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||||
|  | from sleekxmpp.plugins.xep_0095 import stanza, SI | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | SOCKS5 = 'http://jabber.org/protocol/bytestreams' | ||||||
|  | IBB = 'http://jabber.org/protocol/ibb' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0095(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0095' | ||||||
|  |     description = 'XEP-0095: Stream Initiation' | ||||||
|  |     dependencies = set(['xep_0020', 'xep_0030', 'xep_0047', 'xep_0065']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         self._profiles = {} | ||||||
|  |         self._methods = {} | ||||||
|  |         self._methods_order = [] | ||||||
|  |         self._pending_lock = threading.Lock() | ||||||
|  |         self._pending= {} | ||||||
|  |  | ||||||
|  |         self.register_method(SOCKS5, 'xep_0065', 100) | ||||||
|  |         self.register_method(IBB, 'xep_0047', 50) | ||||||
|  |  | ||||||
|  |         register_stanza_plugin(Iq, SI) | ||||||
|  |         register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation) | ||||||
|  |  | ||||||
|  |         self.xmpp.register_handler( | ||||||
|  |             Callback('SI Request', | ||||||
|  |                 StanzaPath('iq@type=set/si'), | ||||||
|  |                 self._handle_request)) | ||||||
|  |  | ||||||
|  |         self.api.register(self._add_pending, 'add_pending', default=True) | ||||||
|  |         self.api.register(self._get_pending, 'get_pending', default=True) | ||||||
|  |         self.api.register(self._del_pending, 'del_pending', default=True) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(SI.namespace) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp.remove_handler('SI Request') | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=SI.namespace) | ||||||
|  |  | ||||||
|  |     def register_profile(self, profile_name, plugin): | ||||||
|  |         self._profiles[profile_name] = plugin | ||||||
|  |  | ||||||
|  |     def unregister_profile(self, profile_name): | ||||||
|  |         try: | ||||||
|  |             del self._profiles[profile_name] | ||||||
|  |         except KeyError: | ||||||
|  |             pass | ||||||
|  |  | ||||||
|  |     def register_method(self, method, plugin_name, order=50): | ||||||
|  |         self._methods[method] = (plugin_name, order) | ||||||
|  |         self._methods_order.append((order, method, plugin_name)) | ||||||
|  |         self._methods_order.sort() | ||||||
|  |  | ||||||
|  |     def unregister_method(self, method): | ||||||
|  |         if method in self._methods: | ||||||
|  |             plugin_name, order = self._methods[method] | ||||||
|  |             del self._methods[method] | ||||||
|  |             self._methods_order.remove((order, method, plugin_name)) | ||||||
|  |             self._methods_order.sort() | ||||||
|  |  | ||||||
|  |     def _handle_request(self, iq): | ||||||
|  |         profile = iq['si']['profile'] | ||||||
|  |         sid = iq['si']['id'] | ||||||
|  |  | ||||||
|  |         if not sid: | ||||||
|  |             raise XMPPError(etype='modify', condition='bad-request') | ||||||
|  |         if profile not in self._profiles: | ||||||
|  |             raise XMPPError( | ||||||
|  |                 etype='modify', | ||||||
|  |                 condition='bad-request', | ||||||
|  |                 extension='bad-profile', | ||||||
|  |                 extension_ns=SI.namespace) | ||||||
|  |  | ||||||
|  |         neg = iq['si']['feature_neg']['form']['fields'] | ||||||
|  |         options = neg['stream-method']['options'] or [] | ||||||
|  |         methods = [] | ||||||
|  |         for opt in options: | ||||||
|  |             methods.append(opt['value']) | ||||||
|  |         for method in methods: | ||||||
|  |             if method in self._methods: | ||||||
|  |                 supported = True | ||||||
|  |                 break | ||||||
|  |         else: | ||||||
|  |             raise XMPPError('bad-request', | ||||||
|  |                     extension='no-valid-streams', | ||||||
|  |                     extension_ns=SI.namespace) | ||||||
|  |  | ||||||
|  |         selected_method = None | ||||||
|  |         log.debug('Available: %s', methods) | ||||||
|  |         for order, method, plugin in self._methods_order: | ||||||
|  |             log.debug('Testing: %s', method) | ||||||
|  |             if method in methods: | ||||||
|  |                 selected_method = method | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |         receiver = iq['to'] | ||||||
|  |         sender = iq['from'] | ||||||
|  |  | ||||||
|  |         self.api['add_pending'](receiver, sid, sender, { | ||||||
|  |             'response_id': iq['id'], | ||||||
|  |             'method': selected_method, | ||||||
|  |             'profile': profile | ||||||
|  |         }) | ||||||
|  |         self.xmpp.event('si_request', iq) | ||||||
|  |  | ||||||
|  |     def offer(self, jid, sid=None, mime_type=None, profile=None, | ||||||
|  |                     methods=None, payload=None, ifrom=None, | ||||||
|  |                     **iqargs): | ||||||
|  |         if sid is None: | ||||||
|  |             sid = uuid4().hex | ||||||
|  |         if methods is None: | ||||||
|  |             methods = list(self._methods.keys()) | ||||||
|  |         if not isinstance(methods, (list, tuple, set)): | ||||||
|  |             methods = [methods] | ||||||
|  |  | ||||||
|  |         si = self.xmpp.Iq() | ||||||
|  |         si['to'] = jid | ||||||
|  |         si['from'] = ifrom | ||||||
|  |         si['type'] = 'set' | ||||||
|  |         si['si']['id'] = sid | ||||||
|  |         si['si']['mime_type'] = mime_type | ||||||
|  |         si['si']['profile'] = profile | ||||||
|  |         if not isinstance(payload, (list, tuple, set)): | ||||||
|  |             payload = [payload] | ||||||
|  |         for item in payload: | ||||||
|  |             si['si'].append(item) | ||||||
|  |         si['si']['feature_neg']['form'].add_field( | ||||||
|  |                 var='stream-method', | ||||||
|  |                 ftype='list-single', | ||||||
|  |                 options=methods) | ||||||
|  |         return si.send(**iqargs) | ||||||
|  |  | ||||||
|  |     def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None): | ||||||
|  |         stream = self.api['get_pending'](ifrom, sid, jid) | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['id'] = stream['response_id'] | ||||||
|  |         iq['to'] = jid | ||||||
|  |         iq['from'] = ifrom | ||||||
|  |         iq['type'] = 'result' | ||||||
|  |         if payload: | ||||||
|  |             iq['si'].append(payload) | ||||||
|  |         iq['si']['feature_neg']['form']['type'] = 'submit' | ||||||
|  |         iq['si']['feature_neg']['form'].add_field( | ||||||
|  |                 var='stream-method', | ||||||
|  |                 ftype='list-single', | ||||||
|  |                 value=stream['method']) | ||||||
|  |  | ||||||
|  |         if ifrom is None: | ||||||
|  |             ifrom = self.xmpp.boundjid | ||||||
|  |  | ||||||
|  |         method_plugin = self._methods[stream['method']][0] | ||||||
|  |         self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid) | ||||||
|  |  | ||||||
|  |         self.api['del_pending'](ifrom, sid, jid) | ||||||
|  |  | ||||||
|  |         if stream_handler: | ||||||
|  |             self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid), | ||||||
|  |                     stream_handler, | ||||||
|  |                     threaded=True, | ||||||
|  |                     disposable=True) | ||||||
|  |         return iq.send() | ||||||
|  |  | ||||||
|  |     def decline(self, jid, sid, ifrom=None): | ||||||
|  |         stream = self.api['get_pending'](ifrom, sid, jid) | ||||||
|  |         if not stream: | ||||||
|  |             return | ||||||
|  |         iq = self.xmpp.Iq() | ||||||
|  |         iq['id'] = stream['response_id'] | ||||||
|  |         iq['to'] = jid | ||||||
|  |         iq['from'] = ifrom | ||||||
|  |         iq['type'] = 'error' | ||||||
|  |         iq['error']['condition'] = 'forbidden' | ||||||
|  |         iq['error']['text'] = 'Offer declined' | ||||||
|  |         self.api['del_pending'](ifrom, sid, jid) | ||||||
|  |         return iq.send() | ||||||
|  |  | ||||||
|  |     def _add_pending(self, jid, node, ifrom, data): | ||||||
|  |         with self._pending_lock: | ||||||
|  |             self._pending[(jid, node, ifrom)] = data | ||||||
|  |  | ||||||
|  |     def _get_pending(self, jid, node, ifrom, data): | ||||||
|  |         with self._pending_lock: | ||||||
|  |             return self._pending.get((jid, node, ifrom), None) | ||||||
|  |  | ||||||
|  |     def _del_pending(self, jid, node, ifrom, data): | ||||||
|  |         with self._pending_lock: | ||||||
|  |             if (jid, node, ifrom) in self._pending: | ||||||
|  |                 del self._pending[(jid, node, ifrom)] | ||||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0096/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0096/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0096 import stanza | ||||||
|  | from sleekxmpp.plugins.xep_0096.stanza import File | ||||||
|  | from sleekxmpp.plugins.xep_0096.file_transfer import XEP_0096 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0096) | ||||||
							
								
								
									
										58
									
								
								sleekxmpp/plugins/xep_0096/file_transfer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								sleekxmpp/plugins/xep_0096/file_transfer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp import Iq, Message | ||||||
|  | from sleekxmpp.plugins import BasePlugin | ||||||
|  | from sleekxmpp.xmlstream.handler import Callback | ||||||
|  | from sleekxmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||||
|  | from sleekxmpp.plugins.xep_0096 import stanza, File | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0096(BasePlugin): | ||||||
|  |  | ||||||
|  |     name = 'xep_0096' | ||||||
|  |     description = 'XEP-0096: SI File Transfer' | ||||||
|  |     dependencies = set(['xep_0095']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_init(self): | ||||||
|  |         register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File) | ||||||
|  |  | ||||||
|  |         self.xmpp['xep_0095'].register_profile(File.namespace, self) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0030'].add_feature(File.namespace) | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=File.namespace) | ||||||
|  |         self.xmpp['xep_0095'].unregister_profile(File.namespace, self) | ||||||
|  |  | ||||||
|  |     def request_file_transfer(self, jid, sid=None, name=None, size=None, | ||||||
|  |                                     desc=None, hash=None, date=None, | ||||||
|  |                                     allow_ranged=False, mime_type=None, | ||||||
|  |                                     **iqargs): | ||||||
|  |         data = File() | ||||||
|  |         data['name'] = name | ||||||
|  |         data['size'] = size | ||||||
|  |         data['date'] = date | ||||||
|  |         data['desc'] = desc | ||||||
|  |         if allow_ranged: | ||||||
|  |             data.enable('range') | ||||||
|  |  | ||||||
|  |         return self.xmpp['xep_0095'].offer(jid, | ||||||
|  |                 sid=sid, | ||||||
|  |                 mime_type=mime_type, | ||||||
|  |                 profile=File.namespace, | ||||||
|  |                 payload=data, | ||||||
|  |                 **iqargs) | ||||||
							
								
								
									
										48
									
								
								sleekxmpp/plugins/xep_0096/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								sleekxmpp/plugins/xep_0096/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import datetime as dt | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||||
|  | from sleekxmpp.plugins import xep_0082 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class File(ElementBase): | ||||||
|  |     name = 'file' | ||||||
|  |     namespace = 'http://jabber.org/protocol/si/profile/file-transfer' | ||||||
|  |     plugin_attrib = 'file' | ||||||
|  |     interfaces = set(['name', 'size', 'date', 'hash', 'desc']) | ||||||
|  |     sub_interfaces = set(['desc']) | ||||||
|  |  | ||||||
|  |     def set_size(self, value): | ||||||
|  |         self._set_attr('size', str(value)) | ||||||
|  |  | ||||||
|  |     def get_date(self): | ||||||
|  |         timestamp = self._get_attr('date') | ||||||
|  |         return xep_0082.parse(timestamp) | ||||||
|  |  | ||||||
|  |     def set_date(self, value): | ||||||
|  |         if isinstance(value, dt.datetime): | ||||||
|  |             value = xep_0082.format_datetime(value) | ||||||
|  |         self._set_attr('date', value) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Range(ElementBase): | ||||||
|  |     name = 'range' | ||||||
|  |     namespace = 'http://jabber.org/protocol/si/profile/file-transfer' | ||||||
|  |     plugin_attrib = 'range' | ||||||
|  |     interfaces = set(['length', 'offset']) | ||||||
|  |  | ||||||
|  |     def set_length(self, value): | ||||||
|  |         self._set_attr('length', str(value)) | ||||||
|  |  | ||||||
|  |     def set_offset(self, value): | ||||||
|  |         self._set_attr('offset', str(value)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_stanza_plugin(File, Range) | ||||||
| @@ -9,8 +9,9 @@ | |||||||
| import logging | import logging | ||||||
| import hashlib | import hashlib | ||||||
| import base64 | import base64 | ||||||
|  | import threading | ||||||
|  |  | ||||||
| import sleekxmpp | from sleekxmpp import __version__ | ||||||
| from sleekxmpp.stanza import StreamFeatures, Presence, Iq | from sleekxmpp.stanza import StreamFeatures, Presence, Iq | ||||||
| from sleekxmpp.xmlstream import register_stanza_plugin, JID | from sleekxmpp.xmlstream import register_stanza_plugin, JID | ||||||
| from sleekxmpp.xmlstream.handler import Callback | from sleekxmpp.xmlstream.handler import Callback | ||||||
| @@ -45,8 +46,7 @@ class XEP_0115(BasePlugin): | |||||||
|                        'md5': hashlib.md5} |                        'md5': hashlib.md5} | ||||||
|  |  | ||||||
|         if self.caps_node is None: |         if self.caps_node is None: | ||||||
|             ver = sleekxmpp.__version__ |             self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__ | ||||||
|             self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver |  | ||||||
|  |  | ||||||
|         register_stanza_plugin(Presence, stanza.Capabilities) |         register_stanza_plugin(Presence, stanza.Capabilities) | ||||||
|         register_stanza_plugin(StreamFeatures, stanza.Capabilities) |         register_stanza_plugin(StreamFeatures, stanza.Capabilities) | ||||||
| @@ -90,6 +90,9 @@ class XEP_0115(BasePlugin): | |||||||
|         disco.assign_verstring = self.assign_verstring |         disco.assign_verstring = self.assign_verstring | ||||||
|         disco.get_verstring = self.get_verstring |         disco.get_verstring = self.get_verstring | ||||||
|  |  | ||||||
|  |         self._processing_lock = threading.Lock() | ||||||
|  |         self._processing = set() | ||||||
|  |  | ||||||
|     def plugin_end(self): |     def plugin_end(self): | ||||||
|         self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace) |         self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace) | ||||||
|         self.xmpp.del_filter('out', self._filter_add_caps) |         self.xmpp.del_filter('out', self._filter_add_caps) | ||||||
| @@ -135,17 +138,22 @@ class XEP_0115(BasePlugin): | |||||||
|  |  | ||||||
|     def _process_caps(self, pres): |     def _process_caps(self, pres): | ||||||
|         if not pres['caps']['hash']: |         if not pres['caps']['hash']: | ||||||
|             log.debug("Received unsupported legacy caps.") |             log.debug("Received unsupported legacy caps: %s, %s, %s", | ||||||
|  |                     pres['caps']['node'], | ||||||
|  |                     pres['caps']['ver'], | ||||||
|  |                     pres['caps']['ext']) | ||||||
|             self.xmpp.event('entity_caps_legacy', pres) |             self.xmpp.event('entity_caps_legacy', pres) | ||||||
|             return |             return | ||||||
|  |  | ||||||
|  |         ver = pres['caps']['ver'] | ||||||
|  |  | ||||||
|         existing_verstring = self.get_verstring(pres['from'].full) |         existing_verstring = self.get_verstring(pres['from'].full) | ||||||
|         if str(existing_verstring) == str(pres['caps']['ver']): |         if str(existing_verstring) == str(ver): | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         existing_caps = self.get_caps(verstring=pres['caps']['ver']) |         existing_caps = self.get_caps(verstring=ver) | ||||||
|         if existing_caps is not None: |         if existing_caps is not None: | ||||||
|             self.assign_verstring(pres['from'], pres['caps']['ver']) |             self.assign_verstring(pres['from'], ver) | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         if pres['caps']['hash'] not in self.hashes: |         if pres['caps']['hash'] not in self.hashes: | ||||||
| @@ -156,9 +164,16 @@ class XEP_0115(BasePlugin): | |||||||
|             except XMPPError: |             except XMPPError: | ||||||
|                 return |                 return | ||||||
|  |  | ||||||
|         log.debug("New caps verification string: %s", pres['caps']['ver']) |         # Only lookup the same caps once at a time. | ||||||
|  |         with self._processing_lock: | ||||||
|  |             if ver in self._processing: | ||||||
|  |                 log.debug('Already processing verstring %s' % ver) | ||||||
|  |                 return | ||||||
|  |             self._processing.add(ver) | ||||||
|  |  | ||||||
|  |         log.debug("New caps verification string: %s", ver) | ||||||
|         try: |         try: | ||||||
|             node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver']) |             node = '%s#%s' % (pres['caps']['node'], ver) | ||||||
|             caps = self.xmpp['xep_0030'].get_info(pres['from'], node) |             caps = self.xmpp['xep_0030'].get_info(pres['from'], node) | ||||||
|  |  | ||||||
|             if isinstance(caps, Iq): |             if isinstance(caps, Iq): | ||||||
| @@ -168,7 +183,10 @@ class XEP_0115(BasePlugin): | |||||||
|                                          pres['caps']['ver']): |                                          pres['caps']['ver']): | ||||||
|                 self.assign_verstring(pres['from'], pres['caps']['ver']) |                 self.assign_verstring(pres['from'], pres['caps']['ver']) | ||||||
|         except XMPPError: |         except XMPPError: | ||||||
|             log.debug("Could not retrieve disco#info results for caps") |             log.debug("Could not retrieve disco#info results for caps for %s", node) | ||||||
|  |  | ||||||
|  |         with self._processing_lock: | ||||||
|  |             self._processing.remove(ver) | ||||||
|  |  | ||||||
|     def _validate_caps(self, caps, hash, check_verstring): |     def _validate_caps(self, caps, hash, check_verstring): | ||||||
|         # Check Identities |         # Check Identities | ||||||
| @@ -179,7 +197,6 @@ class XEP_0115(BasePlugin): | |||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         # Check Features |         # Check Features | ||||||
|  |  | ||||||
|         full_features = caps.get_features(dedupe=False) |         full_features = caps.get_features(dedupe=False) | ||||||
|         deduped_features = caps.get_features() |         deduped_features = caps.get_features() | ||||||
|         if len(full_features) != len(deduped_features): |         if len(full_features) != len(deduped_features): | ||||||
| @@ -190,7 +207,10 @@ class XEP_0115(BasePlugin): | |||||||
|         form_types = [] |         form_types = [] | ||||||
|         deduped_form_types = set() |         deduped_form_types = set() | ||||||
|         for stanza in caps['substanzas']: |         for stanza in caps['substanzas']: | ||||||
|             if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): |             if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): | ||||||
|  |                 log.debug("Non form extension found, ignoring for caps") | ||||||
|  |                 caps.xml.remove(stanza.xml) | ||||||
|  |                 continue | ||||||
|             if 'FORM_TYPE' in stanza['fields']: |             if 'FORM_TYPE' in stanza['fields']: | ||||||
|                 f_type = tuple(stanza['fields']['FORM_TYPE']['value']) |                 f_type = tuple(stanza['fields']['FORM_TYPE']['value']) | ||||||
|                 form_types.append(f_type) |                 form_types.append(f_type) | ||||||
| @@ -272,7 +292,7 @@ class XEP_0115(BasePlugin): | |||||||
|         binary = hash(S.encode('utf8')).digest() |         binary = hash(S.encode('utf8')).digest() | ||||||
|         return base64.b64encode(binary).decode('utf-8') |         return base64.b64encode(binary).decode('utf-8') | ||||||
|  |  | ||||||
|     def update_caps(self, jid=None, node=None): |     def update_caps(self, jid=None, node=None, preserve=False): | ||||||
|         try: |         try: | ||||||
|             info = self.xmpp['xep_0030'].get_info(jid, node, local=True) |             info = self.xmpp['xep_0030'].get_info(jid, node, local=True) | ||||||
|             if isinstance(info, Iq): |             if isinstance(info, Iq): | ||||||
| @@ -286,19 +306,11 @@ class XEP_0115(BasePlugin): | |||||||
|             self.assign_verstring(jid, ver) |             self.assign_verstring(jid, ver) | ||||||
|  |  | ||||||
|             if self.xmpp.session_started_event.is_set() and self.broadcast: |             if self.xmpp.session_started_event.is_set() and self.broadcast: | ||||||
|                 # Check if we've sent directed presence. If we haven't, we |                 if self.xmpp.is_component or preserve: | ||||||
|                 # can just send a normal presence stanza. If we have, then |  | ||||||
|                 # we will send presence to each contact individually so |  | ||||||
|                 # that we don't clobber existing statuses. |  | ||||||
|                 directed = False |  | ||||||
|                 for contact in self.xmpp.roster[jid]: |  | ||||||
|                     if self.xmpp.roster[jid][contact].last_status is not None: |  | ||||||
|                         directed = True |  | ||||||
|                 if not directed: |  | ||||||
|                     self.xmpp.roster[jid].send_last_presence() |  | ||||||
|                 else: |  | ||||||
|                     for contact in self.xmpp.roster[jid]: |                     for contact in self.xmpp.roster[jid]: | ||||||
|                         self.xmpp.roster[jid][contact].send_last_presence() |                         self.xmpp.roster[jid][contact].send_last_presence() | ||||||
|  |                 else: | ||||||
|  |                     self.xmpp.roster[jid].send_last_presence() | ||||||
|         except XMPPError: |         except XMPPError: | ||||||
|             return |             return | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0152/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0152/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import register_plugin | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.xep_0152 import stanza | ||||||
|  | from sleekxmpp.plugins.xep_0152.stanza import Reachability | ||||||
|  | from sleekxmpp.plugins.xep_0152.reachability import XEP_0152 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_plugin(XEP_0152) | ||||||
							
								
								
									
										93
									
								
								sleekxmpp/plugins/xep_0152/reachability.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								sleekxmpp/plugins/xep_0152/reachability.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from sleekxmpp.plugins.base import BasePlugin | ||||||
|  | from sleekxmpp.plugins.xep_0152 import stanza, Reachability | ||||||
|  |  | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class XEP_0152(BasePlugin): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     XEP-0152: Reachability Addresses | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     name = 'xep_0152' | ||||||
|  |     description = 'XEP-0152: Reachability Addresses' | ||||||
|  |     dependencies = set(['xep_0163']) | ||||||
|  |     stanza = stanza | ||||||
|  |  | ||||||
|  |     def plugin_end(self): | ||||||
|  |         self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace) | ||||||
|  |         self.xmpp['xep_0163'].remove_interest(Reachability.namespace) | ||||||
|  |  | ||||||
|  |     def session_bind(self, jid): | ||||||
|  |         self.xmpp['xep_0163'].register_pep('reachability', Reachability) | ||||||
|  |  | ||||||
|  |     def publish_reachability(self, addresses, options=None, | ||||||
|  |                         ifrom=None, block=True, callback=None, timeout=None): | ||||||
|  |         """ | ||||||
|  |         Publish alternative addresses where the user can be reached. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             addresses -- A list of dictionaries containing the URI and | ||||||
|  |                          optional description for each address. | ||||||
|  |             options   -- Optional form of publish options. | ||||||
|  |             ifrom     -- Specify the sender's JID. | ||||||
|  |             block     -- Specify if the send call will block until a response | ||||||
|  |                          is received, or a timeout occurs. Defaults to True. | ||||||
|  |             timeout   -- The length of time (in seconds) to wait for a response | ||||||
|  |                          before exiting the send call if blocking is used. | ||||||
|  |                          Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT | ||||||
|  |             callback  -- Optional reference to a stream handler function. Will | ||||||
|  |                          be executed when a reply stanza is received. | ||||||
|  |         """ | ||||||
|  |         if not isinstance(addresses, (list, tuple)): | ||||||
|  |             addresses = [addresses] | ||||||
|  |         reach = Reachability() | ||||||
|  |         for address in addresses: | ||||||
|  |             if not hasattr(address, 'items'): | ||||||
|  |                 address = {'uri': address} | ||||||
|  |  | ||||||
|  |             addr = stanza.Address() | ||||||
|  |             for key, val in address.items(): | ||||||
|  |                 addr[key] = val | ||||||
|  |             reach.append(addr) | ||||||
|  |         return self.xmpp['xep_0163'].publish(reach, | ||||||
|  |                 node=Reachability.namespace, | ||||||
|  |                 options=options, | ||||||
|  |                 ifrom=ifrom, | ||||||
|  |                 block=block, | ||||||
|  |                 callback=callback, | ||||||
|  |                 timeout=timeout) | ||||||
|  |  | ||||||
|  |     def stop(self, ifrom=None, block=True, callback=None, timeout=None): | ||||||
|  |         """ | ||||||
|  |         Clear existing user activity information to stop notifications. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             ifrom    -- Specify the sender's JID. | ||||||
|  |             block    -- Specify if the send call will block until a response | ||||||
|  |                         is received, or a timeout occurs. Defaults to True. | ||||||
|  |             timeout  -- The length of time (in seconds) to wait for a response | ||||||
|  |                         before exiting the send call if blocking is used. | ||||||
|  |                         Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT | ||||||
|  |             callback -- Optional reference to a stream handler function. Will | ||||||
|  |                         be executed when a reply stanza is received. | ||||||
|  |         """ | ||||||
|  |         reach = Reachability() | ||||||
|  |         return self.xmpp['xep_0163'].publish(reach, | ||||||
|  |                 node=Reachability.namespace, | ||||||
|  |                 ifrom=ifrom, | ||||||
|  |                 block=block, | ||||||
|  |                 callback=callback, | ||||||
|  |                 timeout=timeout) | ||||||
							
								
								
									
										29
									
								
								sleekxmpp/plugins/xep_0152/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								sleekxmpp/plugins/xep_0152/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | """ | ||||||
|  |     SleekXMPP: The Sleek XMPP Library | ||||||
|  |     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||||
|  |     This file is part of SleekXMPP. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Reachability(ElementBase): | ||||||
|  |     name = 'reach' | ||||||
|  |     namespace = 'urn:xmpp:reach:0' | ||||||
|  |     plugin_attrib = 'reach' | ||||||
|  |     interfaces = set() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Address(ElementBase): | ||||||
|  |     name = 'addr' | ||||||
|  |     namespace = 'urn:xmpp:reach:0' | ||||||
|  |     plugin_attrib = 'address' | ||||||
|  |     plugin_multi_attrib = 'addresses' | ||||||
|  |     interfaces = set(['uri', 'desc']) | ||||||
|  |     lang_interfaces = set(['desc']) | ||||||
|  |     sub_interfaces = set(['desc']) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | register_stanza_plugin(Reachability, Address, iterable=True) | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user