Compare commits
	
		
			526 Commits
		
	
	
		
			sleek-1.1.
			...
			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 | ||
|   | 12e8bb6ddc | ||
|   | 06a690a259 | ||
|   | 52feabbe76 | ||
|   | 14c9e9a9cc | ||
|   | a22ca228cc | ||
|   | d0666a5eb6 | ||
|   | 931d49560a | ||
|   | 2a4e435228 | ||
|   | 3655827ef2 | ||
|   | c5046b9c91 | ||
|   | 4598031dd2 | ||
|   | 12e0e1a16b | ||
|   | 5e9266ba90 | ||
|   | 0d448b8221 | ||
|   | e6c95f0a2a | ||
|   | 63b58edda1 | ||
|   | af9632519c | ||
|   | d367fb938d | ||
|   | 77f2a339e1 | ||
|   | 4190027a78 | ||
|   | ef48a8c4d9 | ||
|   | 829b225053 | ||
|   | 747a6e94e6 | ||
|   | cebc798e72 | ||
|   | 7c485c6a8b | ||
|   | e2e8c4b5dc | ||
|   | 675c0112ac | ||
|   | 4dd2c15775 | ||
|   | 9f6decdbc1 | ||
|   | fc07e23ff8 | ||
|   | 4ea328b9f2 | ||
|   | 84a2fc382b | ||
|   | 098714b3c4 | ||
|   | cf2c94d974 | ||
|   | 657102e938 | ||
|   | 44e7585bf8 | ||
|   | 94488fa2ea | ||
|   | a2c60a4911 | ||
|   | ee9c4abd08 | ||
|   | b5b1c932c7 | ||
|   | b8f04983e1 | ||
|   | 90807dd973 | ||
|   | ef974114ea | ||
|   | f6e1fecdf8 | ||
|   | 94e8b2becf | ||
|   | a6ca6701a0 | ||
|   | c4edb9724b | ||
|   | b5c669bdff | ||
|   | e449dce65c | ||
|   | 73ce9a5ecc | ||
|   | 671f680bb3 | ||
|   | dfff19ffbf | ||
|   | a4abdf9fa6 | ||
|   | 6c57bb0553 | ||
|   | d385b9e708 | ||
|   | c2ae1ee891 | ||
|   | 67147570e9 | ||
|   | fb3e6b7e35 | ||
|   | cf28d4586d | ||
|   | f65eb5eeea | ||
|   | 26fa9bd87e | ||
|   | 0016d9a638 | ||
|   | a88b9737ff | ||
|   | df9ac58d05 | ||
|   | 357406d801 | ||
|   | 19a78f63f4 | ||
|   | c7ec6a72cd | ||
|   | e68b07dbce | ||
|   | e20610ab80 | ||
|   | 1ca0c46333 | ||
|   | e510875f64 | ||
|   | f52a10b061 | ||
|   | 7d382a2bfd | ||
|   | 09bec1c4fe | ||
|   | ff28b0a005 | ||
|   | 8a03bd72ae | ||
|   | a249f8736a | ||
|   | f0e1fc5aad | ||
|   | f09adf0014 | ||
|   | c6ac64ed2d | ||
|   | 04dc68f5f6 | ||
|   | 92be051450 | ||
|   | 5c25208fb5 | ||
|   | 779c258e27 | ||
|   | 962dfad216 | ||
|   | f7a710e55b | ||
|   | 814a50e36f | ||
|   | 230465b946 | ||
|   | d11a67702e | ||
|   | 4e12e228cb | ||
|   | 4a94aeba49 | ||
|   | 14aa831169 | ||
|   | 295d23ccf3 | ||
|   | 75d904ed01 | ||
|   | aebcf6ff82 | ||
|   | 8c2ece3bca | ||
|   | 80a90a6221 | ||
|   | f81d5e4bd6 | ||
|   | 2324c90232 | ||
|   | 2f65fdbc76 | ||
|   | 59ff08174f | ||
|   | 2f4149c7d0 | ||
|   | b84e359770 | ||
|   | fb4275648c | ||
|   | 475ccfa8dc | ||
|   | 267c24c8ef | ||
|   | 06a9d9fc30 | ||
|   | 1383ca19b5 | ||
|   | 4c3ff2abab | ||
|   | 7c6ef18e4f | ||
|   | f8856467d5 | ||
|   | 3bd84b8d27 | ||
|   | bc8b5774ac | ||
|   | 8009b0485e | ||
|   | 8742a56b3e | ||
|   | a792bcdafe | ||
|   | 167d1ce97b | ||
|   | 695cd95657 | ||
|   | 44ce01a70b | ||
|   | e4b4c67637 | ||
|   | 422e77ae40 | ||
|   | 5ae6c8f8fa | ||
|   | 54656b331a | ||
|   | 9047b627a4 | ||
|   | 6645a3be40 | ||
|   | c2189b4ecd | ||
|   | e3fab66dfb | ||
|   | 5867f08bf1 | ||
|   | a06fa2de67 | ||
|   | c9b2cf6043 | ||
|   | 35396d2977 | ||
|   | 3bff743d9f | ||
|   | 5a878f829b | ||
|   | 16ec0f151a | ||
|   | 26dc6e90ea | ||
|   | 94c749fd5a | ||
|   | 7b80ed0807 | ||
|   | 98b7e8b10a | ||
|   | c42f1ad4c7 | ||
|   | 9d8de7fc15 | ||
|   | 70883086b7 | ||
|   | 9a08dfc7d4 | ||
|   | 3e43b36a9d | ||
|   | 352ee2f2fd | ||
|   | 78aa5c3dfa | ||
|   | 613323b5fb | ||
|   | 6c4b01db8a | ||
|   | d06897a635 | ||
|   | 1600bb0aaf | ||
|   | b5c9c98a8b | ||
|   | e4e18a416f | ||
|   | 01cc0e6def | ||
|   | a3ec1af205 | ||
|   | d571d691a7 | ||
|   | 2e580304f9 | ||
|   | fb221a8dc0 | ||
|   | 459e1ed345 | ||
|   | 6680c244f5 | ||
|   | 06423964ec | ||
|   | 5492e9028d | ||
|   | 474390fa00 | ||
|   | 81d3723084 | ||
|   | 32e798967e | ||
|   | 060c9ab679 | ||
|   | acd9c32a9f | ||
|   | b8581b0278 | ||
|   | 917faecdcb | ||
|   | 78f0325398 | ||
|   | f6edaa56a6 | ||
|   | 51fee28bf4 | ||
|   | e8a3e92ceb | ||
|   | 5df3839b7a | ||
|   | 8dcb441f44 | ||
|   | a347cf625a | ||
|   | 46f49c7a12 | ||
|   | 99701c947e | ||
|   | 1baae1b81e | ||
|   | 7d20f0e9a6 | ||
|   | fbad22a1cd | ||
|   | 5af2f62c04 | ||
|   | 4a4a03858e | ||
|   | 1efe049959 | ||
|   | 2393148908 | ||
|   | 6819b57353 | ||
|   | 88b5e60807 | ||
|   | c7594b3ef0 | ||
|   | b210870f48 | ||
|   | a26a8bd79c | ||
|   | 9307a6915f | ||
|   | 5d6019a962 | ||
|   | 85ef2d8d0b | ||
|   | c2c7cc032b | ||
|   | e4911e9391 | ||
|   | b11e1ee92d | ||
|   | 5027d00c10 | ||
|   | 69ddeceb49 | ||
|   | 82698672bb | ||
|   | 9cec284947 | ||
|   | dc501d1902 | ||
|   | 100e504b7f | ||
|   | eb5df1aa37 | ||
|   | 8a745c5e81 | ||
|   | bf0a157c5d | ||
|   | f49818be06 | ||
|   | 1ad171dfe5 | ||
|   | 2a78570d65 | ||
|   | 546066d677 | ||
|   | 7a112f2523 | ||
|   | 3234596974 | ||
|   | e86444e5fb | ||
|   | 5820d49cd4 | ||
|   | 36c11ad9de | ||
|   | 019a4b20ae | ||
|   | 433ee08687 | ||
|   | 7858d969d8 | ||
|   | 8119551049 | ||
|   | 061489f03a | ||
|   | d92aa05b5c | ||
|   | f7a74d960e | ||
|   | 95a0e51b41 | ||
|   | 110e45e187 | ||
|   | 534aaf2b2a | ||
|   | 4cc20fdd05 | ||
|   | f3fae192a8 | ||
|   | 7d59a8a0ad | ||
|   | 8da387a38a | ||
|   | ff6fc44215 | ||
|   | 62391a895a | ||
|   | 9bcdd7d18f | ||
|   | 5c4f7bfe8b | ||
|   | 0b7f134021 | ||
|   | 378a42889f | ||
|   | f824950552 | ||
|   | 3d2d11f169 | ||
|   | 181aea737d | ||
|   | 1ab66e5767 | ||
|   | aab2682f9a | ||
|   | 55d332bcc8 | ||
|   | ee702f4071 | ||
|   | a08c2161a7 | ||
|   | f89df6e70c | ||
|   | 0e36a01354 | ||
|   | c39ad7dfbb | ||
|   | b92ae706e9 | ||
|   | 250d28e870 | ||
|   | 19f65c8510 | ||
|   | f70b49882f | ||
|   | a7b092a305 | ||
|   | daa73a3f3c | ||
|   | 6997261c6b | ||
|   | 0b51afe87a | ||
|   | 6cfb5cb14c | ||
|   | 8567d6034f | ||
|   | e06368f8cd | ||
|   | 4b37a4706f | ||
|   | 7b1564947d | ||
|   | 2b298766c9 | ||
|   | 10664d723b | ||
|   | c012208a8f | ||
|   | 0953896d2d | ||
|   | cf9e89d0ae | ||
|   | 48dd01b0bb | ||
|   | 7247efe055 | ||
|   | 8def3758e4 | ||
|   | 1851ab6f5f | ||
|   | 289b052338 | ||
|   | 26147f5ae0 | ||
|   | ae01f1071a | ||
|   | dcdf5dcd09 | ||
|   | c59a6d0f51 | ||
|   | 2cd936318d | ||
|   | 2f38857681 | ||
|   | 39505ae1ff | ||
|   | 44ee0633f2 | ||
|   | b52d2768b0 | ||
|   | cf24b870b1 | ||
|   | 69cffce7dc | ||
|   | a14979375b | ||
|   | 40ef4a16b1 | ||
|   | f5652a667b | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| *.pyc | ||||
| *.py[co] | ||||
| build/ | ||||
| dist/ | ||||
| MANIFEST | ||||
| @@ -7,3 +7,8 @@ docs/_build/ | ||||
| .tox/ | ||||
| .coverage | ||||
| 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" | ||||
|  | ||||
| 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 | ||||
| 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 | ||||
| OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | ||||
| ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  | ||||
| socksipy: A Python SOCKS client module. | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| Copyright 2006 Dan-Haim. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without modification, | ||||
| are permitted provided that the following conditions are met: | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
|    list of conditions and the following disclaimer. | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|    this list of conditions and the following disclaimer in the documentation | ||||
|    and/or other materials provided with the distribution. | ||||
| 3. Neither the name of Dan Haim nor the names of his contributors may be used | ||||
|    to endorse or promote products derived from this software without specific | ||||
|    prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED | ||||
| WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||||
| MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | ||||
| EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA | ||||
| OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||
| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | ||||
| OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. | ||||
|   | ||||
| @@ -45,13 +45,11 @@ The latest source code for SleekXMPP may be found on `Github | ||||
| ``develop`` branch. | ||||
|  | ||||
| **Latest Release** | ||||
|     - `1.1.2 <http://github.com/fritzy/SleekXMPP/zipball/1.1.2>`_ | ||||
|     - `1.2.5 <http://github.com/fritzy/SleekXMPP/zipball/1.2.5>`_ | ||||
|  | ||||
| **Develop Releases** | ||||
|     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||
|  | ||||
| **Older Stable Releases** | ||||
|     - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_ | ||||
|  | ||||
| Installing DNSPython | ||||
| --------------------- | ||||
| @@ -74,6 +72,7 @@ help with SleekXMPP. | ||||
| **Chat** | ||||
|     `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ | ||||
|  | ||||
|  | ||||
| Documentation and Testing | ||||
| ------------------------- | ||||
| Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``. | ||||
|   | ||||
| @@ -222,7 +222,7 @@ handler function to process registration requests. | ||||
|         self.description = "In-Band Registration" | ||||
|         self.xep = "0077" | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|         self.xmpp.register_handler( | ||||
|           Callback('In-Band Registration', | ||||
|             MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), | ||||
|             self.__handleRegistration)) | ||||
| @@ -601,7 +601,7 @@ with some additional registration fields implemented. | ||||
|             self.form_instructions = "" | ||||
|             self.backend = UserStore() | ||||
|  | ||||
|             self.xmpp.registerHandler( | ||||
|             self.xmpp.register_handler( | ||||
|                 Callback('In-Band Registration', | ||||
|                          MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns), | ||||
|                          self.__handleRegistration)) | ||||
|   | ||||
| @@ -6,14 +6,20 @@ Event Index | ||||
|  | ||||
|     connected | ||||
|         - **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 | ||||
|         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 | ||||
|         - **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 | ||||
|         different than the last presence stanza from the same JID. | ||||
| @@ -65,7 +71,7 @@ Event Index | ||||
|  | ||||
|     disconnected | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`~sleekxmpp.ClientXMPP` | ||||
|         - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` | ||||
|  | ||||
|         Signal that the connection with the XMPP server has been lost. | ||||
|  | ||||
| @@ -93,7 +99,7 @@ Event Index | ||||
|  | ||||
|     got_online | ||||
|         - **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 | ||||
|         offline, and the presence has a show type of '``chat``', '``dnd``', '``away``', | ||||
| @@ -101,7 +107,7 @@ Event Index | ||||
|  | ||||
|     got_offline | ||||
|         - **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. | ||||
|  | ||||
| @@ -158,7 +164,7 @@ Event Index | ||||
|  | ||||
|         Triggered whenever a data form is received inside a message. | ||||
|  | ||||
|     mucc::[room]::got_offline | ||||
|     muc::[room]::got_offline | ||||
|         - **Data:** | ||||
|         - **Source:** | ||||
|  | ||||
| @@ -236,15 +242,13 @@ Event Index | ||||
|  | ||||
|     sent_presence | ||||
|         - **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. | ||||
|  | ||||
|     session_end | ||||
|         - **Data:** ``{}`` | ||||
|         - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,  | ||||
|           :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>` | ||||
|           :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>` | ||||
|         - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream` | ||||
|  | ||||
|         Signal that a connection to the XMPP server has been lost and the current | ||||
|         stream session has ended. Currently equivalent to :term:`disconnected`, but | ||||
|   | ||||
| @@ -69,8 +69,8 @@ use ASCII. We can get Python to use UTF-8 as the default encoding by including: | ||||
| .. code-block:: python | ||||
|  | ||||
|     if sys.version_info < (3, 0): | ||||
|         reload(sys) | ||||
|         sys.setdefaultencoding('utf8') | ||||
|         from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|         setdefaultencoding('utf8') | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|   | ||||
							
								
								
									
										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" | ||||
|  | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										178
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										178
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| # by default. To ensure that Unicode is handled properly | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class AdminCommands(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A simple SleekXMPP bot that uses admin commands to | ||||
|     add a new user to a server. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, command): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.command = command | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         def command_success(iq, session): | ||||
|             print('Command completed') | ||||
|             if iq['command']['form']: | ||||
|                 for var, field in iq['command']['form']['fields'].items(): | ||||
|                     print('%s: %s' % (var, field['value'])) | ||||
|             if iq['command']['notes']: | ||||
|                 print('Command Notes:') | ||||
|                 for note in iq['command']['notes']: | ||||
|                     print('%s: %s' % note) | ||||
|             self.disconnect() | ||||
|  | ||||
|         def command_error(iq, session): | ||||
|             print('Error completing command') | ||||
|             print('%s: %s' % (iq['error']['condition'], | ||||
|                               iq['error']['text'])) | ||||
|             self['xep_0050'].terminate_command(session) | ||||
|             self.disconnect() | ||||
|  | ||||
|         def process_form(iq, session): | ||||
|             form = iq['command']['form'] | ||||
|             answers = {} | ||||
|             for var, field in form['fields'].items(): | ||||
|                 if var != 'FORM_TYPE': | ||||
|                     if field['type'] == 'boolean': | ||||
|                         answers[var] = raw_input('%s (y/n): ' % field['label']) | ||||
|                         if answers[var].lower() in ('1', 'true', 'y', 'yes'): | ||||
|                             answers[var] = '1' | ||||
|                         else: | ||||
|                             answers[var] = '0' | ||||
|                     else: | ||||
|                         answers[var] = raw_input('%s: ' % field['label']) | ||||
|                 else: | ||||
|                     answers['FORM_TYPE'] = field['value'] | ||||
|             form['type'] = 'submit' | ||||
|             form['values'] = answers | ||||
|  | ||||
|             session['next'] = command_success | ||||
|             session['payload'] = form | ||||
|  | ||||
|             self['xep_0050'].complete_command(session) | ||||
|  | ||||
|         session = {'next': process_form, | ||||
|                    'error': command_error} | ||||
|  | ||||
|         command = self.command.replace('-', '_') | ||||
|         handler = getattr(self['xep_0133'], command, None) | ||||
|  | ||||
|         if handler: | ||||
|             handler(session={ | ||||
|                 'next': process_form, | ||||
|                 'error': command_error | ||||
|             }) | ||||
|         else: | ||||
|             print('Invalid command name: %s' % self.command) | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.ERROR, default=logging.INFO) | ||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.DEBUG, default=logging.INFO) | ||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=5, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|     optp.add_option("-c", "--command", dest="command", | ||||
|                     help="admin command to use") | ||||
|  | ||||
|     opts, args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|     if opts.command is None: | ||||
|         opts.command = raw_input("Admin command: ") | ||||
|  | ||||
|     # Setup the CommandBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = AdminCommands(opts.jid, opts.password, opts.command) | ||||
|     xmpp.register_plugin('xep_0133') # Service Administration | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
| @@ -28,8 +28,8 @@ from stanza import Action | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -51,7 +51,7 @@ class ActionBot(sleekxmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|         self.registerHandler( | ||||
|         self.register_handler( | ||||
|           Callback('Some custom iq', | ||||
|             StanzaPath('iq@type=set/action'), | ||||
|             self._handle_action)) | ||||
|   | ||||
| @@ -26,8 +26,8 @@ from stanza import Action | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| @@ -22,8 +23,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -83,6 +84,7 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         try: | ||||
|             if self.get in self.info_types: | ||||
|                 # By using block=True, the result stanza will be | ||||
|                 # returned. Execution will block until the reply is | ||||
| @@ -92,7 +94,7 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|                 info = self['xep_0030'].get_info(jid=self.target_jid, | ||||
|                                                  node=self.target_node, | ||||
|                                                  block=True) | ||||
|         if self.get in self.items_types: | ||||
|             elif self.get in self.items_types: | ||||
|                 # The same applies from above. Listen for the | ||||
|                 # disco_items event or pass a callback function | ||||
|                 # if you need to process a non-blocking request. | ||||
| @@ -101,9 +103,12 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|                                                    block=True) | ||||
|             else: | ||||
|                 logging.error("Invalid disco request type.") | ||||
|             self.disconnect() | ||||
|                 return | ||||
|  | ||||
|         except IqError as e: | ||||
|             logging.error("Entity returned an error: %s" % e.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
|             logging.error("No response received.") | ||||
|         else: | ||||
|             header = 'XMPP Service Discovery: %s' % self.target_jid | ||||
|             print(header) | ||||
|             print('-' * len(header)) | ||||
| @@ -125,7 +130,7 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|                 print('Items:') | ||||
|                 for item in items['disco_items']['items']: | ||||
|                     print('  - %s' % str(item)) | ||||
|  | ||||
|         finally: | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,184 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| import threading | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
|  | ||||
|  | ||||
| # 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 | ||||
|  | ||||
|  | ||||
| FILE_TYPES = { | ||||
|     'image/png': 'png', | ||||
|     'image/gif': 'gif', | ||||
|     'image/jpeg': 'jpg' | ||||
| } | ||||
|  | ||||
|  | ||||
| class AvatarDownloader(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic script for downloading the avatars for a user's contacts. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|         self.add_event_handler("session_start", self.start, threaded=True) | ||||
|         self.add_event_handler("changed_status", self.wait_for_presences) | ||||
|  | ||||
|         self.add_event_handler('vcard_avatar_update', self.on_vcard_avatar) | ||||
|         self.add_event_handler('avatar_metadata_publish', self.on_avatar) | ||||
|  | ||||
|         self.received = set() | ||||
|         self.presences_received = threading.Event() | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         print('Waiting for presence updates...\n') | ||||
|         self.presences_received.wait(15) | ||||
|         self.disconnect(wait=True) | ||||
|  | ||||
|     def on_vcard_avatar(self, pres): | ||||
|         print("Received vCard avatar update from %s" % pres['from'].bare) | ||||
|         try: | ||||
|             result = self['xep_0054'].get_vcard(pres['from'], cached=True) | ||||
|         except XMPPError: | ||||
|             print("Error retrieving avatar for %s" % pres['from']) | ||||
|             return | ||||
|         avatar = result['vcard_temp']['PHOTO'] | ||||
|  | ||||
|         filetype = FILE_TYPES.get(avatar['TYPE'], 'png') | ||||
|         filename = 'vcard_avatar_%s_%s.%s' % ( | ||||
|                 pres['from'].bare, | ||||
|                 pres['vcard_temp_update']['photo'], | ||||
|                 filetype) | ||||
|         with open(filename, 'w+') as img: | ||||
|             img.write(avatar['BINVAL']) | ||||
|  | ||||
|     def on_avatar(self, msg): | ||||
|         print("Received avatar update from %s" % msg['from']) | ||||
|         metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] | ||||
|         for info in metadata['items']: | ||||
|             if not info['url']: | ||||
|                 try: | ||||
|                     result = self['xep_0084'].retrieve_avatar(msg['from'], info['id']) | ||||
|                 except XMPPError: | ||||
|                     print("Error retrieving avatar for %s" % msg['from']) | ||||
|                     return | ||||
|  | ||||
|                 avatar = result['pubsub']['items']['item']['avatar_data'] | ||||
|  | ||||
|                 filetype = FILE_TYPES.get(metadata['type'], 'png') | ||||
|                 filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) | ||||
|                 with open(filename, 'w+') as img: | ||||
|                     img.write(avatar['value']) | ||||
|             else: | ||||
|                 # We could retrieve the avatar via HTTP, etc here instead. | ||||
|                 pass | ||||
|  | ||||
|     def wait_for_presences(self, pres): | ||||
|         """ | ||||
|         Wait to receive updates from all roster contacts. | ||||
|         """ | ||||
|         self.received.add(pres['from'].bare) | ||||
|         if len(self.received) >= len(self.client_roster.keys()): | ||||
|             self.presences_received.set() | ||||
|         else: | ||||
|             self.presences_received.clear() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|     optp.add_option('-q','--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.ERROR, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-d','--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.DEBUG, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-v','--verbose', help='set logging to COMM', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=5, | ||||
|                     default=logging.ERROR) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|     opts,args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|  | ||||
|     xmpp = AvatarDownloader(opts.jid, opts.password) | ||||
|     xmpp.register_plugin('xep_0054') | ||||
|     xmpp.register_plugin('xep_0153') | ||||
|     xmpp.register_plugin('xep_0084') | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -122,6 +122,19 @@ if __name__ == '__main__': | ||||
|     xmpp.register_plugin('xep_0060') # PubSub | ||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|     # 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 | ||||
|   | ||||
| @@ -22,8 +22,8 @@ from sleekxmpp.componentxmpp import ComponentXMPP | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										165
									
								
								examples/gtalk_custom_domain.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										165
									
								
								examples/gtalk_custom_domain.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| import ssl | ||||
| from sleekxmpp.xmlstream import cert | ||||
|  | ||||
|  | ||||
| # 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 | ||||
|  | ||||
|  | ||||
| class GTalkBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A demonstration of using SleekXMPP with accounts from a Google Apps | ||||
|     account with a custom domain, because it requires custom certificate | ||||
|     validation. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
|         # and the XML streams are ready for use. We want to | ||||
|         # listen for this event so that we we can initialize | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|         # The message event is triggered whenever a message | ||||
|         # stanza is received. Be aware that that includes | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|         # Using a Google Apps custom domain, the certificate | ||||
|         # does not contain the custom domain, just the GTalk | ||||
|         # server name. So we will need to process invalid | ||||
|         # certifcates ourselves and check that it really | ||||
|         # is from Google. | ||||
|         self.add_event_handler("ssl_invalid_cert", self.invalid_cert) | ||||
|  | ||||
|     def invalid_cert(self, pem_cert): | ||||
|         der_cert = ssl.PEM_cert_to_DER_cert(pem_cert) | ||||
|         try: | ||||
|             cert.verify('talk.google.com', der_cert) | ||||
|             logging.debug("CERT: Found GTalk certificate") | ||||
|         except cert.CertificateError as err: | ||||
|             log.error(err.message) | ||||
|             self.disconnect(send_close=False) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|         Process incoming message stanzas. Be aware that this also | ||||
|         includes MUC messages and error messages. It is usually | ||||
|         a good idea to check the messages's type before processing | ||||
|         or sending replies. | ||||
|  | ||||
|         Arguments: | ||||
|             msg -- The received message stanza. See the documentation | ||||
|                    for stanza objects and the Message stanza to see | ||||
|                    how it may be used. | ||||
|         """ | ||||
|         if msg['type'] in ('chat', 'normal'): | ||||
|             msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.ERROR, default=logging.INFO) | ||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.DEBUG, default=logging.INFO) | ||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=5, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|  | ||||
|     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: ") | ||||
|  | ||||
|     # Setup the GTalkBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = GTalkBot(opts.jid, opts.password) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0060') # PubSub | ||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										147
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										147
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,147 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| # by default. To ensure that Unicode is handled properly | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class IBBReceiver(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') # Service Discovery | ||||
|         self.register_plugin('xep_0047', { | ||||
|             'auto_accept': True | ||||
|         }) # In-band Bytestreams | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
|         # and the XML streams are ready for use. We want to | ||||
|         # listen for this event so that we we can initialize | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|         self.add_event_handler("ibb_stream_start", self.stream_opened, threaded=True) | ||||
|         self.add_event_handler("ibb_stream_data", self.stream_data) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def accept_stream(self, iq): | ||||
|         """ | ||||
|         Check that it is ok to accept a stream request. | ||||
|  | ||||
|         Controlling stream acceptance can be done via either: | ||||
|             - setting 'auto_accept' to False in the plugin | ||||
|               configuration. The default is True. | ||||
|             - setting 'accept_stream' to a function which accepts | ||||
|               an Iq stanza as its argument, like this one. | ||||
|  | ||||
|         The accept_stream function will be used if it exists, and the | ||||
|         auto_accept value will be used otherwise. | ||||
|         """ | ||||
|         return True | ||||
|  | ||||
|     def stream_opened(self, stream): | ||||
|         print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) | ||||
|  | ||||
|         # You could run a loop reading from the stream using stream.recv(), | ||||
|         # or use the ibb_stream_data event. | ||||
|  | ||||
|     def stream_data(self, event): | ||||
|         print(event['data']) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.ERROR, default=logging.INFO) | ||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.DEBUG, default=logging.INFO) | ||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=5, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|  | ||||
|     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 = IBBReceiver(opts.jid, opts.password) | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										145
									
								
								examples/ibb_transfer/ibb_sender.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										145
									
								
								examples/ibb_transfer/ibb_sender.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
|  | ||||
| # Python versions before 3.0 do not use UTF-8 encoding | ||||
| # by default. To ensure that Unicode is handled properly | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|  | ||||
| class IBBSender(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, receiver, filename): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.receiver = receiver | ||||
|         self.filename = filename | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
|         # and the XML streams are ready for use. We want to | ||||
|         # listen for this event so that we we can initialize | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         # For the purpose of demonstration, we'll set a very small block | ||||
|         # size. The default block size is 4096. We'll also use a window | ||||
|         # allowing sending multiple blocks at a time; in this case, three | ||||
|         # block transfers may be in progress at any time. | ||||
|         stream = self['xep_0047'].open_stream(self.receiver) | ||||
|  | ||||
|         with open(self.filename) as f: | ||||
|             data = f.read() | ||||
|             stream.sendall(data) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.ERROR, default=logging.INFO) | ||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=logging.DEBUG, default=logging.INFO) | ||||
|     optp.add_option('-v', '--verbose', help='set logging to COMM', | ||||
|                     action='store_const', dest='loglevel', | ||||
|                     const=5, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|     optp.add_option("-r", "--receiver", dest="receiver", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-f", "--file", dest="filename", | ||||
|                     help="JID to use") | ||||
|  | ||||
|     opts, args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|     if opts.receiver is None: | ||||
|         opts.receiver = raw_input("Receiver: ") | ||||
|     if opts.filename is None: | ||||
|         opts.filename = raw_input("File path: ") | ||||
|  | ||||
|     # Setup the EchoBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = IBBSender(opts.jid, opts.password, opts.receiver, opts.filename) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0047') # In-band Bytestreams | ||||
|     xmpp.register_plugin('xep_0060') # PubSub | ||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|         print("Done") | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										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) | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -37,7 +37,7 @@ class PingTest(sleekxmpp.ClientXMPP): | ||||
|     def __init__(self, jid, password, pingjid): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|         if pingjid is None: | ||||
|             pingjid = self.jid | ||||
|             pingjid = self.boundjid.bare | ||||
|         self.pingjid = pingjid | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
| @@ -62,16 +62,18 @@ class PingTest(sleekxmpp.ClientXMPP): | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         result = self['xep_0199'].send_ping(self.pingjid, | ||||
|                                             timeout=10, | ||||
|                                             errorfalse=True) | ||||
|         logging.info("Pinging...") | ||||
|         if result is False: | ||||
|             logging.info("Couldn't ping.") | ||||
|             self.disconnect() | ||||
|             sys.exit(1) | ||||
|         else: | ||||
|             logging.info("Success! RTT: %s", str(result)) | ||||
|  | ||||
|         try: | ||||
|             rtt = self['xep_0199'].ping(self.pingjid, | ||||
|                                         timeout=10) | ||||
|             logging.info("Success! RTT: %s", rtt) | ||||
|         except IqError as e: | ||||
|             logging.info("Error pinging %s: %s", | ||||
|                     self.pingjid, | ||||
|                     e.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
|             logging.info("No response from %s", self.pingjid) | ||||
|         finally: | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										7
									
								
								examples/pubsub_client.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										7
									
								
								examples/pubsub_client.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,3 +1,6 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| @@ -12,8 +15,8 @@ from sleekxmpp.xmlstream import ET, tostring | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										7
									
								
								examples/pubsub_events.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										7
									
								
								examples/pubsub_events.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -1,3 +1,6 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| @@ -14,8 +17,8 @@ from sleekxmpp.xmlstream.handler import Callback | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										8
									
								
								examples/register_account.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										8
									
								
								examples/register_account.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -22,8 +22,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
| @@ -153,6 +153,10 @@ if __name__ == '__main__': | ||||
|     xmpp.register_plugin('xep_0066') # Out-of-band Data | ||||
|     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 | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|   | ||||
							
								
								
									
										4
									
								
								examples/roster_browser.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										4
									
								
								examples/roster_browser.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -24,8 +24,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 | ||||
|     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 | ||||
|     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 | ||||
|     Copyright (C) 2011  Dann Martens | ||||
|   | ||||
| @@ -21,8 +21,8 @@ import sleekxmpp | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,174 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| import imghdr | ||||
| import logging | ||||
| import getpass | ||||
| import threading | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
|  | ||||
|  | ||||
| # 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 | ||||
|  | ||||
|  | ||||
| class AvatarSetter(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic script for downloading the avatars for a user's contacts. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, filepath): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start, threaded=True) | ||||
|  | ||||
|         self.filepath = filepath | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         avatar_file = None | ||||
|         try: | ||||
|             avatar_file = open(os.path.expanduser(self.filepath)) | ||||
|         except IOError: | ||||
|             print('Could not find file: %s' % self.filepath) | ||||
|             return self.disconnect() | ||||
|  | ||||
|         avatar = avatar_file.read() | ||||
|  | ||||
|         avatar_type = 'image/%s' % imghdr.what('', avatar) | ||||
|         avatar_id = self['xep_0084'].generate_id(avatar) | ||||
|         avatar_bytes = len(avatar) | ||||
|  | ||||
|         avatar_file.close() | ||||
|  | ||||
|         used_xep84 = False | ||||
|         try: | ||||
|             print('Publish XEP-0084 avatar data') | ||||
|             self['xep_0084'].publish_avatar(avatar) | ||||
|             used_xep84 = True | ||||
|         except XMPPError: | ||||
|             print('Could not publish XEP-0084 avatar') | ||||
|  | ||||
|         try: | ||||
|             print('Update vCard with avatar') | ||||
|             self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) | ||||
|         except XMPPError: | ||||
|             print('Could not set vCard avatar') | ||||
|  | ||||
|         if used_xep84: | ||||
|             try: | ||||
|                 print('Advertise XEP-0084 avatar metadata') | ||||
|                 self['xep_0084'].publish_avatar_metadata([ | ||||
|                     {'id': avatar_id, | ||||
|                      'type': avatar_type, | ||||
|                      'bytes': avatar_bytes} | ||||
|                     # We could advertise multiple avatars to provide | ||||
|                     # options in image type, source (HTTP vs pubsub), | ||||
|                     # size, etc. | ||||
|                     # {'id': ....} | ||||
|                 ]) | ||||
|             except XMPPError: | ||||
|                 print('Could not publish XEP-0084 metadata') | ||||
|  | ||||
|         print('Wait for presence updates to propagate...') | ||||
|         self.schedule('end', 5, self.disconnect, kwargs={'wait': True}) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|     optp.add_option('-q','--quiet', help='set logging to ERROR', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.ERROR, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-d','--debug', help='set logging to DEBUG', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=logging.DEBUG, | ||||
|                     default=logging.ERROR) | ||||
|     optp.add_option('-v','--verbose', help='set logging to COMM', | ||||
|                     action='store_const', | ||||
|                     dest='loglevel', | ||||
|                     const=5, | ||||
|                     default=logging.ERROR) | ||||
|  | ||||
|     # JID and password options. | ||||
|     optp.add_option("-j", "--jid", dest="jid", | ||||
|                     help="JID to use") | ||||
|     optp.add_option("-p", "--password", dest="password", | ||||
|                     help="password to use") | ||||
|     optp.add_option("-f", "--file", dest="filepath", | ||||
|                     help="path to the avatar file") | ||||
|     opts,args = optp.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=opts.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|     if opts.filepath is None: | ||||
|         opts.filepath = raw_input("Avatar file location: ") | ||||
|  | ||||
|     xmpp = AvatarSetter(opts.jid, opts.password, opts.filepath) | ||||
|     xmpp.register_plugin('xep_0054') | ||||
|     xmpp.register_plugin('xep_0153') | ||||
|     xmpp.register_plugin('xep_0084') | ||||
|  | ||||
|     # If you are working with an OpenFire server, you may need | ||||
|     # to adjust the SSL version used: | ||||
|     # xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|     # If you want to verify the SSL certificates offered by a server: | ||||
|     # xmpp.ca_certs = "path/to/ca/cert" | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     if xmpp.connect(): | ||||
|         # If you do not have the dnspython library installed, you will need | ||||
|         # to manually specify the name of the server if it does not match | ||||
|         # the one in the JID. For example, to use Google Talk you would | ||||
|         # need to use: | ||||
|         # | ||||
|         # if xmpp.connect(('talk.google.com', 5222)): | ||||
|         #     ... | ||||
|         xmpp.process(block=True) | ||||
|     else: | ||||
|         print("Unable to connect.") | ||||
							
								
								
									
										4
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										4
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @@ -29,8 +29,8 @@ from sleekxmpp.xmlstream import JID | ||||
| # throughout SleekXMPP, we will set the default encoding | ||||
| # ourselves to UTF-8. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
| else: | ||||
|     raw_input = input | ||||
|  | ||||
|   | ||||
							
								
								
									
										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
									
								
							
							
								
								
									
										42
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								setup.py
									
									
									
									
									
								
							| @@ -42,6 +42,7 @@ CLASSIFIERS      = [ 'Intended Audience :: Developers', | ||||
|                      'Programming Language :: Python :: 2.7', | ||||
|                      'Programming Language :: Python :: 3.1', | ||||
|                      'Programming Language :: Python :: 3.2', | ||||
|                      'Programming Language :: Python :: 3.3', | ||||
|                      'Topic :: Software Development :: Libraries :: Python Modules', | ||||
|                    ] | ||||
|  | ||||
| @@ -49,6 +50,8 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/stanza', | ||||
|                  'sleekxmpp/test', | ||||
|                  'sleekxmpp/roster', | ||||
|                  'sleekxmpp/util', | ||||
|                  'sleekxmpp/util/sasl', | ||||
|                  'sleekxmpp/xmlstream', | ||||
|                  'sleekxmpp/xmlstream/matcher', | ||||
|                  'sleekxmpp/xmlstream/handler', | ||||
| @@ -58,37 +61,73 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0009', | ||||
|                  'sleekxmpp/plugins/xep_0009/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0012', | ||||
|                  'sleekxmpp/plugins/xep_0013', | ||||
|                  'sleekxmpp/plugins/xep_0016', | ||||
|                  'sleekxmpp/plugins/xep_0020', | ||||
|                  'sleekxmpp/plugins/xep_0027', | ||||
|                  'sleekxmpp/plugins/xep_0030', | ||||
|                  'sleekxmpp/plugins/xep_0030/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0033', | ||||
|                  'sleekxmpp/plugins/xep_0047', | ||||
|                  'sleekxmpp/plugins/xep_0048', | ||||
|                  'sleekxmpp/plugins/xep_0049', | ||||
|                  'sleekxmpp/plugins/xep_0050', | ||||
|                  'sleekxmpp/plugins/xep_0054', | ||||
|                  'sleekxmpp/plugins/xep_0059', | ||||
|                  'sleekxmpp/plugins/xep_0060', | ||||
|                  'sleekxmpp/plugins/xep_0060/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0065', | ||||
|                  'sleekxmpp/plugins/xep_0066', | ||||
|                  'sleekxmpp/plugins/xep_0071', | ||||
|                  'sleekxmpp/plugins/xep_0077', | ||||
|                  'sleekxmpp/plugins/xep_0078', | ||||
|                  'sleekxmpp/plugins/xep_0080', | ||||
|                  'sleekxmpp/plugins/xep_0084', | ||||
|                  'sleekxmpp/plugins/xep_0085', | ||||
|                  'sleekxmpp/plugins/xep_0086', | ||||
|                  'sleekxmpp/plugins/xep_0091', | ||||
|                  'sleekxmpp/plugins/xep_0092', | ||||
|                  'sleekxmpp/plugins/xep_0095', | ||||
|                  'sleekxmpp/plugins/xep_0096', | ||||
|                  'sleekxmpp/plugins/xep_0107', | ||||
|                  'sleekxmpp/plugins/xep_0108', | ||||
|                  'sleekxmpp/plugins/xep_0115', | ||||
|                  'sleekxmpp/plugins/xep_0118', | ||||
|                  'sleekxmpp/plugins/xep_0128', | ||||
|                  'sleekxmpp/plugins/xep_0131', | ||||
|                  'sleekxmpp/plugins/xep_0152', | ||||
|                  'sleekxmpp/plugins/xep_0153', | ||||
|                  'sleekxmpp/plugins/xep_0172', | ||||
|                  'sleekxmpp/plugins/xep_0184', | ||||
|                  'sleekxmpp/plugins/xep_0186', | ||||
|                  'sleekxmpp/plugins/xep_0191', | ||||
|                  'sleekxmpp/plugins/xep_0196', | ||||
|                  'sleekxmpp/plugins/xep_0198', | ||||
|                  'sleekxmpp/plugins/xep_0199', | ||||
|                  'sleekxmpp/plugins/xep_0202', | ||||
|                  'sleekxmpp/plugins/xep_0203', | ||||
|                  'sleekxmpp/plugins/xep_0221', | ||||
|                  'sleekxmpp/plugins/xep_0224', | ||||
|                  'sleekxmpp/plugins/xep_0231', | ||||
|                  'sleekxmpp/plugins/xep_0235', | ||||
|                  'sleekxmpp/plugins/xep_0249', | ||||
|                  'sleekxmpp/plugins/xep_0257', | ||||
|                  'sleekxmpp/plugins/xep_0258', | ||||
|                  'sleekxmpp/plugins/xep_0279', | ||||
|                  'sleekxmpp/plugins/xep_0280', | ||||
|                  'sleekxmpp/plugins/xep_0297', | ||||
|                  'sleekxmpp/plugins/xep_0308', | ||||
|                  '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/feature_mechanisms', | ||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', | ||||
| @@ -96,9 +135,8 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/features/feature_bind', | ||||
|                  'sleekxmpp/features/feature_session', | ||||
|                  'sleekxmpp/features/feature_rosterver', | ||||
|                  'sleekxmpp/features/feature_preapproval', | ||||
|                  'sleekxmpp/thirdparty', | ||||
|                  'sleekxmpp/thirdparty/suelta', | ||||
|                  'sleekxmpp/thirdparty/suelta/mechanisms', | ||||
|                  ] | ||||
|  | ||||
| setup( | ||||
|   | ||||
| @@ -6,13 +6,25 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.basexmpp import BaseXMPP | ||||
| from sleekxmpp.clientxmpp import ClientXMPP | ||||
| from sleekxmpp.componentxmpp import ComponentXMPP | ||||
| import logging | ||||
| if hasattr(logging, 'NullHandler'): | ||||
|     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.jid import JID, InvalidJID | ||||
| from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| 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__ | ||||
|   | ||||
| @@ -16,24 +16,24 @@ class APIWrapper(object): | ||||
|         elif attr == 'settings': | ||||
|             return self.api.settings[self.name] | ||||
|         elif attr == 'register': | ||||
|             def curried_handler(handler, op, jid=None, node=None, default=False): | ||||
|             def partial(handler, op, jid=None, node=None, default=False): | ||||
|                 register = getattr(self.api, attr) | ||||
|                 return register(handler, self.name, op, jid, node, default) | ||||
|             return curried_handler | ||||
|             return partial | ||||
|         elif attr == 'register_default': | ||||
|             def curried_handler(handler, op, jid=None, node=None): | ||||
|             def partial(handler, op, jid=None, node=None): | ||||
|                 return getattr(self.api, attr)(handler, self.name, op) | ||||
|             return curried_handler | ||||
|             return partial | ||||
|         elif attr in ('run', 'restore_default', 'unregister'): | ||||
|             def curried_handler(*args, **kwargs): | ||||
|             def partial(*args, **kwargs): | ||||
|                 return getattr(self.api, attr)(self.name, *args, **kwargs) | ||||
|             return curried_handler | ||||
|             return partial | ||||
|         return None | ||||
|  | ||||
|     def __getitem__(self, attr): | ||||
|         def curried_handler(jid=None, node=None, ifrom=None, args=None): | ||||
|         def partial(jid=None, node=None, ifrom=None, args=None): | ||||
|             return self.api.run(self.name, attr, jid, node, ifrom, args) | ||||
|         return curried_handler | ||||
|         return partial | ||||
|  | ||||
|  | ||||
| class APIRegistry(object): | ||||
| @@ -99,10 +99,12 @@ class APIRegistry(object): | ||||
|         """ | ||||
|         self._setup(ctype, op) | ||||
|  | ||||
|         if jid in (None, ''): | ||||
|         if not jid: | ||||
|             jid = self.xmpp.boundjid | ||||
|         if jid and not isinstance(jid, JID): | ||||
|         elif jid and not isinstance(jid, JID): | ||||
|             jid = JID(jid) | ||||
|         elif jid == JID(''): | ||||
|             jid = self.xmpp.boundjid | ||||
|  | ||||
|         if node is None: | ||||
|             node = '' | ||||
| @@ -113,7 +115,7 @@ class APIRegistry(object): | ||||
|             else: | ||||
|                 jid = jid.full | ||||
|         else: | ||||
|             if self.settings[ctype].get('client_bare', True): | ||||
|             if self.settings[ctype].get('client_bare', False): | ||||
|                 jid = jid.bare | ||||
|             else: | ||||
|                 jid = jid.full | ||||
|   | ||||
| @@ -16,9 +16,9 @@ from __future__ import with_statement, unicode_literals | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp import plugins, features, roster | ||||
| from sleekxmpp import plugins, roster, stanza | ||||
| from sleekxmpp.api import APIRegistry | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
| @@ -31,9 +31,9 @@ from sleekxmpp.xmlstream import XMLStream, JID | ||||
| from sleekxmpp.xmlstream import ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import MatchXPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.stanzabase import XML_NS | ||||
|  | ||||
| from sleekxmpp.features import * | ||||
| from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin | ||||
| from sleekxmpp.plugins import PluginManager, load_plugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
| @@ -41,8 +41,8 @@ log = logging.getLogger(__name__) | ||||
| # In order to make sure that Unicode is handled properly | ||||
| # in Python 2.x, reset the default encoding. | ||||
| if sys.version_info < (3, 0): | ||||
|     reload(sys) | ||||
|     sys.setdefaultencoding('utf8') | ||||
|     from sleekxmpp.util.misc_ops import setdefaultencoding | ||||
|     setdefaultencoding('utf8') | ||||
|  | ||||
|  | ||||
| class BaseXMPP(XMLStream): | ||||
| @@ -66,9 +66,22 @@ class BaseXMPP(XMLStream): | ||||
|         #: An identifier for the stream as given by the server. | ||||
|         self.stream_id = None | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection.  | ||||
|         self.boundjid = JID(jid) | ||||
|         #: The JabberID (JID) requested for this connection. | ||||
|         self.requested_jid = JID(jid, cache_lock=True) | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection, | ||||
|         #: as set after session binding. This may even be a | ||||
|         #: different bare JID than what was requested. | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|  | ||||
|         self._expected_server_name = self.boundjid.host | ||||
|         self._redirect_attempts = 0 | ||||
|  | ||||
|         #: The maximum number of consecutive see-other-host | ||||
|         #: redirections that will be followed before quitting. | ||||
|         self.max_redirects = 5 | ||||
|  | ||||
|         self.session_bind_event = threading.Event() | ||||
|  | ||||
|         #: A dictionary mapping plugin names to plugins. | ||||
|         self.plugin = PluginManager(self) | ||||
| @@ -86,19 +99,30 @@ class BaseXMPP(XMLStream): | ||||
|         #: owner JIDs, as in the case for components. For clients | ||||
|         #: which only have a single JID, see :attr:`client_roster`. | ||||
|         self.roster = roster.Roster(self) | ||||
|         self.roster.add(self.boundjid.bare) | ||||
|         self.roster.add(self.boundjid) | ||||
|  | ||||
|         #: The single roster for the bound JID. This is the | ||||
|         #: equivalent of:: | ||||
|         #: | ||||
|         #:     self.roster[self.boundjid.bare] | ||||
|         self.client_roster = self.roster[self.boundjid.bare] | ||||
|         self.client_roster = self.roster[self.boundjid] | ||||
|  | ||||
|         #: The distinction between clients and components can be | ||||
|         #: important, primarily for choosing how to handle the | ||||
|         #: ``'to'`` and ``'from'`` JIDs of stanzas. | ||||
|         self.is_component = False | ||||
|  | ||||
|         #: Messages may optionally be tagged with ID values. Setting | ||||
|         #: :attr:`use_message_ids` to `True` will assign all outgoing | ||||
|         #: messages an ID. Some plugin features require enabling | ||||
|         #: this option. | ||||
|         self.use_message_ids = False | ||||
|  | ||||
|         #: Presence updates may optionally be tagged with ID values. | ||||
|         #: Setting :attr:`use_message_ids` to `True` will assign all | ||||
|         #: outgoing messages an ID. | ||||
|         self.use_presence_ids = False | ||||
|  | ||||
|         #: The API registry is a way to process callbacks based on | ||||
|         #: JID+node combinations. Each callback in the registry is | ||||
|         #: marked with: | ||||
| @@ -122,7 +146,7 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         #: A reference to :mod:`sleekxmpp.stanza` to make accessing | ||||
|         #: stanza classes easier. | ||||
|         self.stanza = sleekxmpp.stanza | ||||
|         self.stanza = stanza | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('IM', | ||||
| @@ -133,11 +157,14 @@ class BaseXMPP(XMLStream): | ||||
|             Callback('Presence', | ||||
|                      MatchXPath("{%s}presence" % self.default_ns), | ||||
|                      self._handle_presence)) | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('Stream Error', | ||||
|                      MatchXPath("{%s}error" % self.stream_ns), | ||||
|                      self._handle_stream_error)) | ||||
|  | ||||
|         self.add_event_handler('session_start', | ||||
|                                self._handle_session_start) | ||||
|         self.add_event_handler('disconnected', | ||||
|                                self._handle_disconnected) | ||||
|         self.add_event_handler('presence_available', | ||||
| @@ -172,7 +199,6 @@ class BaseXMPP(XMLStream): | ||||
|         # Initialize a few default stanza plugins. | ||||
|         register_stanza_plugin(Iq, Roster) | ||||
|         register_stanza_plugin(Message, Nick) | ||||
|         register_stanza_plugin(Message, HTMLIM) | ||||
|  | ||||
|     def start_stream_handler(self, xml): | ||||
|         """Save the stream ID once the streams have been established. | ||||
| @@ -180,6 +206,12 @@ class BaseXMPP(XMLStream): | ||||
|         :param xml: The incoming stream's root element. | ||||
|         """ | ||||
|         self.stream_id = xml.get('id', '') | ||||
|         self.stream_version = xml.get('version', '') | ||||
|         self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None) | ||||
|  | ||||
|         if not self.is_component and not self.stream_version: | ||||
|             log.warning('Legacy XMPP 0.9 protocol detected.') | ||||
|             self.event('legacy_protocol') | ||||
|  | ||||
|     def process(self, *args, **kwargs): | ||||
|         """Initialize plugins and begin processing the XML stream. | ||||
| @@ -206,13 +238,6 @@ class BaseXMPP(XMLStream): | ||||
|         - The send queue processor | ||||
|         - The scheduler | ||||
|         """ | ||||
|         if 'xep_0115' in self.plugin: | ||||
|             name = 'xep_0115' | ||||
|             if not hasattr(self.plugin[name], 'post_inited'): | ||||
|                 if hasattr(self.plugin[name], 'post_init'): | ||||
|                     self.plugin[name].post_init() | ||||
|                 self.plugin[name].post_inited = True | ||||
|  | ||||
|         for name in self.plugin: | ||||
|             if not hasattr(self.plugin[name], 'post_inited'): | ||||
|                 if hasattr(self.plugin[name], 'post_init'): | ||||
| @@ -272,7 +297,9 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def Message(self, *args, **kwargs): | ||||
|         """Create a Message stanza associated with this stream.""" | ||||
|         return Message(self, *args, **kwargs) | ||||
|         msg = Message(self, *args, **kwargs) | ||||
|         msg['lang'] = self.default_lang | ||||
|         return msg | ||||
|  | ||||
|     def Iq(self, *args, **kwargs): | ||||
|         """Create an Iq stanza associated with this stream.""" | ||||
| @@ -280,7 +307,9 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def Presence(self, *args, **kwargs): | ||||
|         """Create a Presence stanza associated with this stream.""" | ||||
|         return Presence(self, *args, **kwargs) | ||||
|         pres = Presence(self, *args, **kwargs) | ||||
|         pres['lang'] = self.default_lang | ||||
|         return pres | ||||
|  | ||||
|     def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None): | ||||
|         """Create a new Iq stanza with a given Id and from JID. | ||||
| @@ -529,18 +558,8 @@ class BaseXMPP(XMLStream): | ||||
|         :param pfrom: The sender of the presence. | ||||
|         :param pnick: Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         # Python2.6 chokes on Unicode strings for dict keys. | ||||
|         args = {str('pto'): pto, | ||||
|                 str('ptype'): ptype, | ||||
|                 str('pshow'): pshow, | ||||
|                 str('pstatus'): pstatus, | ||||
|                 str('ppriority'): ppriority, | ||||
|                 str('pnick'): pnick} | ||||
|  | ||||
|         if self.is_component: | ||||
|             self.roster[pfrom].send_presence(**args) | ||||
|         else: | ||||
|             self.client_roster.send_presence(**args) | ||||
|         self.make_presence(pshow, pstatus, ppriority, pto, | ||||
|                            ptype, pfrom, pnick).send() | ||||
|  | ||||
|     def send_presence_subscription(self, pto, pfrom=None, | ||||
|                                    ptype='subscribe', pnick=None): | ||||
| @@ -554,14 +573,10 @@ class BaseXMPP(XMLStream): | ||||
|         :param ptype: The type of presence, such as ``'subscribe'``. | ||||
|         :param pnick: Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         presence = self.makePresence(ptype=ptype, | ||||
|         self.make_presence(ptype=ptype, | ||||
|                            pfrom=pfrom, | ||||
|                                      pto=self.getjidbare(pto)) | ||||
|         if pnick: | ||||
|             nick = ET.Element('{http://jabber.org/protocol/nick}nick') | ||||
|             nick.text = pnick | ||||
|             presence.append(nick) | ||||
|         presence.send() | ||||
|                            pto=JID(pto).bare, | ||||
|                            pnick=pnick).send() | ||||
|  | ||||
|     @property | ||||
|     def jid(self): | ||||
| @@ -593,7 +608,7 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     @resource.setter | ||||
|     def resource(self, value): | ||||
|         log.warning("fulljid property deprecated. Use boundjid.full") | ||||
|         log.warning("fulljid property deprecated. Use boundjid.resource") | ||||
|         self.boundjid.resource = value | ||||
|  | ||||
|     @property | ||||
| @@ -647,7 +662,7 @@ class BaseXMPP(XMLStream): | ||||
|     def set_jid(self, jid): | ||||
|         """Rip a JID apart and claim it as our own.""" | ||||
|         log.debug("setting jid to %s", jid) | ||||
|         self.boundjid.full = jid | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|  | ||||
|     def getjidresource(self, fulljid): | ||||
|         if '/' in fulljid: | ||||
| @@ -658,30 +673,61 @@ class BaseXMPP(XMLStream): | ||||
|     def getjidbare(self, fulljid): | ||||
|         return fulljid.split('/', 1)[0] | ||||
|  | ||||
|     def _handle_session_start(self, event): | ||||
|         """Reset redirection attempt count.""" | ||||
|         self._redirect_attempts = 0 | ||||
|  | ||||
|     def _handle_disconnected(self, event): | ||||
|         """When disconnected, reset the roster""" | ||||
|         self.roster.reset() | ||||
|         self.session_bind_event.clear() | ||||
|  | ||||
|     def _handle_stream_error(self, error): | ||||
|         self.event('stream_error', error) | ||||
|  | ||||
|         if error['condition'] == 'see-other-host': | ||||
|             other_host = error['see_other_host'] | ||||
|             if not other_host: | ||||
|                 log.warning("No other host specified.") | ||||
|                 return | ||||
|  | ||||
|             if self._redirect_attempts > self.max_redirects: | ||||
|                 log.error("Exceeded maximum number of redirection attempts.") | ||||
|                 return | ||||
|  | ||||
|             self._redirect_attempts += 1 | ||||
|  | ||||
|             host = other_host | ||||
|             port = 5222 | ||||
|  | ||||
|             if '[' in other_host and ']' in other_host: | ||||
|                 host = other_host.split(']')[0][1:] | ||||
|             elif ':' in other_host: | ||||
|                 host = other_host.split(':')[0] | ||||
|  | ||||
|             port_sec = other_host.split(']')[-1] | ||||
|             if ':' in port_sec: | ||||
|                 port = int(port_sec.split(':')[1]) | ||||
|  | ||||
|             self.address = (host, port) | ||||
|             self.default_domain = host | ||||
|             self.dns_records = None | ||||
|             self.reconnect_delay = None | ||||
|             self.reconnect() | ||||
|  | ||||
|     def _handle_message(self, msg): | ||||
|         """Process incoming message stanzas.""" | ||||
|         if not self.is_component and not msg['to'].bare: | ||||
|             msg['to'] = self.boundjid | ||||
|         self.event('message', msg) | ||||
|  | ||||
|     def _handle_available(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_available(presence) | ||||
|     def _handle_available(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_available(pres) | ||||
|  | ||||
|     def _handle_unavailable(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unavailable(presence) | ||||
|     def _handle_unavailable(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unavailable(pres) | ||||
|  | ||||
|     def _handle_new_subscription(self, stanza): | ||||
|     def _handle_new_subscription(self, pres): | ||||
|         """Attempt to automatically handle subscription requests. | ||||
|  | ||||
|         Subscriptions will be approved if the request is from | ||||
| @@ -693,10 +739,12 @@ class BaseXMPP(XMLStream): | ||||
|         If a subscription is accepted, a request for a mutual | ||||
|         subscription will be sent if :attr:`auto_subscribe` is ``True``. | ||||
|         """ | ||||
|         roster = self.roster[stanza['to'].bare] | ||||
|         item = self.roster[stanza['to'].bare][stanza['from'].bare] | ||||
|         roster = self.roster[pres['to']] | ||||
|         item = self.roster[pres['to']][pres['from']] | ||||
|         if item['whitelisted']: | ||||
|             item.authorize() | ||||
|             if roster.auto_subscribe: | ||||
|                 item.subscribe() | ||||
|         elif roster.auto_authorize: | ||||
|             item.authorize() | ||||
|             if roster.auto_subscribe: | ||||
| @@ -704,30 +752,20 @@ class BaseXMPP(XMLStream): | ||||
|         elif roster.auto_authorize == False: | ||||
|             item.unauthorize() | ||||
|  | ||||
|     def _handle_removed_subscription(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].unauthorize() | ||||
|     def _handle_removed_subscription(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unauthorize(pres) | ||||
|  | ||||
|     def _handle_subscribe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_subscribe(presence) | ||||
|     def _handle_subscribe(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_subscribe(pres) | ||||
|  | ||||
|     def _handle_subscribed(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_subscribed(presence) | ||||
|     def _handle_subscribed(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_subscribed(pres) | ||||
|  | ||||
|     def _handle_unsubscribe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unsubscribe(presence) | ||||
|     def _handle_unsubscribe(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unsubscribe(pres) | ||||
|  | ||||
|     def _handle_unsubscribed(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_unsubscribed(presence) | ||||
|     def _handle_unsubscribed(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_unsubscribed(pres) | ||||
|  | ||||
|     def _handle_presence(self, presence): | ||||
|         """Process incoming presence stanzas. | ||||
| @@ -737,7 +775,8 @@ class BaseXMPP(XMLStream): | ||||
|         if not self.is_component and not presence['to'].bare: | ||||
|             presence['to'] = self.boundjid | ||||
|  | ||||
|         self.event("presence_%s" % presence['type'], presence) | ||||
|         self.event('presence', presence) | ||||
|         self.event('presence_%s' % presence['type'], presence) | ||||
|  | ||||
|         # Check for changes in subscription state. | ||||
|         if presence['type'] in ('subscribe', 'subscribed', | ||||
|   | ||||
| @@ -60,24 +60,26 @@ class ClientXMPP(BaseXMPP): | ||||
|     :param escape_quotes: **Deprecated.** | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, ssl=False, plugin_config={}, | ||||
|                  plugin_whitelist=[], escape_quotes=True, sasl_mech=None): | ||||
|     def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], | ||||
|                  escape_quotes=True, sasl_mech=None, lang='en'): | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||
|  | ||||
|         self.set_jid(jid) | ||||
|         self.escape_quotes = escape_quotes | ||||
|         self.plugin_config = plugin_config | ||||
|         self.plugin_whitelist = plugin_whitelist | ||||
|         self.default_port = 5222 | ||||
|         self.default_lang = lang | ||||
|  | ||||
|         self.credentials = {} | ||||
|  | ||||
|         self.password = password | ||||
|  | ||||
|         self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( | ||||
|         self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % ( | ||||
|                 self.boundjid.host, | ||||
|                 "xmlns:stream='%s'" % self.stream_ns, | ||||
|                 "xmlns='%s'" % self.default_ns) | ||||
|                 "xmlns='%s'" % self.default_ns, | ||||
|                 "xml:lang='%s'" % self.default_lang, | ||||
|                 "version='1.0'") | ||||
|         self.stream_footer = "</stream:stream>" | ||||
|  | ||||
|         self.features = set() | ||||
| @@ -92,8 +94,9 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.bound = False | ||||
|         self.bindfail = False | ||||
|  | ||||
|         self.add_event_handler('connected', self._handle_connected) | ||||
|         self.add_event_handler('connected', self._reset_connection_state) | ||||
|         self.add_event_handler('session_bind', self._handle_session_bind) | ||||
|         self.add_event_handler('roster_update', self._handle_roster) | ||||
|  | ||||
|         self.register_stanza(StreamFeatures) | ||||
|  | ||||
| @@ -104,15 +107,18 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.register_handler( | ||||
|                 Callback('Roster Update', | ||||
|                          StanzaPath('iq@type=set/roster'), | ||||
|                          self._handle_roster)) | ||||
|                          lambda iq: self.event('roster_update', iq))) | ||||
|  | ||||
|         # Setup default stream features | ||||
|         self.register_plugin('feature_starttls') | ||||
|         self.register_plugin('feature_bind') | ||||
|         self.register_plugin('feature_session') | ||||
|         self.register_plugin('feature_mechanisms', | ||||
|                 pconfig={'use_mech': sasl_mech} if sasl_mech else None) | ||||
|         self.register_plugin('feature_rosterver') | ||||
|         self.register_plugin('feature_preapproval') | ||||
|         self.register_plugin('feature_mechanisms') | ||||
|  | ||||
|         if sasl_mech: | ||||
|             self['feature_mechanisms'].use_mech = sasl_mech | ||||
|  | ||||
|     @property | ||||
|     def password(self): | ||||
| @@ -130,7 +136,7 @@ class ClientXMPP(BaseXMPP): | ||||
|         be attempted. If that fails, the server user in the JID | ||||
|         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 | ||||
|                          error occurs. Defaults to ``True``. | ||||
|         :param use_tls: Indicates if TLS should be used for the | ||||
| @@ -149,8 +155,6 @@ class ClientXMPP(BaseXMPP): | ||||
|             address = (self.boundjid.host, 5222) | ||||
|             self.dns_service = 'xmpp-client' | ||||
|  | ||||
|         self._expected_server_name = self.boundjid.host | ||||
|  | ||||
|         return XMLStream.connect(self, address[0], address[1], | ||||
|                                  use_tls=use_tls, use_ssl=use_ssl, | ||||
|                                  reattempt=reattempt) | ||||
| @@ -170,8 +174,13 @@ class ClientXMPP(BaseXMPP): | ||||
|         self._stream_feature_order.append((order, name)) | ||||
|         self._stream_feature_order.sort() | ||||
|  | ||||
|     def update_roster(self, jid, name=None, subscription=None, groups=[], | ||||
|                             block=True, timeout=None, callback=None): | ||||
|     def unregister_feature(self, name, order): | ||||
|         if name in self._stream_feature_handlers: | ||||
|             del self._stream_feature_handlers[name] | ||||
|         self._stream_feature_order.remove((order, name)) | ||||
|         self._stream_feature_order.sort() | ||||
|  | ||||
|     def update_roster(self, jid, **kwargs): | ||||
|         """Add or change a roster item. | ||||
|  | ||||
|         :param jid: The JID of the entry to modify. | ||||
| @@ -192,6 +201,16 @@ class ClientXMPP(BaseXMPP): | ||||
|                          Will be executed when the roster is received. | ||||
|                          Implies ``block=False``. | ||||
|         """ | ||||
|         current = self.client_roster[jid] | ||||
|  | ||||
|         name = kwargs.get('name', current['name']) | ||||
|         subscription = kwargs.get('subscription', current['subscription']) | ||||
|         groups = kwargs.get('groups', current['groups']) | ||||
|  | ||||
|         block = kwargs.get('block', True) | ||||
|         timeout = kwargs.get('timeout', None) | ||||
|         callback = kwargs.get('callback', None) | ||||
|  | ||||
|         return self.client_roster.update(jid, name, subscription, groups, | ||||
|                                          block, timeout, callback) | ||||
|  | ||||
| @@ -224,17 +243,25 @@ class ClientXMPP(BaseXMPP): | ||||
|         if 'rosterver' in self.features: | ||||
|             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) | ||||
|         self.event('roster_received', response) | ||||
|  | ||||
|         if block: | ||||
|             self._handle_roster(response) | ||||
|             self.event('roster_update', response) | ||||
|             return response | ||||
|  | ||||
|     def _handle_connected(self, event=None): | ||||
|     def _reset_connection_state(self, event=None): | ||||
|         #TODO: Use stream state here | ||||
|         self.authenticated = False | ||||
|         self.sessionstarted = False | ||||
| @@ -254,6 +281,8 @@ class ClientXMPP(BaseXMPP): | ||||
|                     # Don't continue if the feature requires | ||||
|                     # restarting the XML stream. | ||||
|                     return True | ||||
|         log.debug('Finished processing stream features.') | ||||
|         self.event('stream_negotiated') | ||||
|  | ||||
|     def _handle_roster(self, iq): | ||||
|         """Update the roster after receiving a roster stanza. | ||||
| @@ -267,17 +296,19 @@ class ClientXMPP(BaseXMPP): | ||||
|         roster = self.client_roster | ||||
|         if iq['roster']['ver']: | ||||
|             roster.version = iq['roster']['ver'] | ||||
|         for jid in iq['roster']['items']: | ||||
|             item = iq['roster']['items'][jid] | ||||
|         items = iq['roster']['items'] | ||||
|  | ||||
|         valid_subscriptions = ('to', 'from', 'both', 'none', 'remove') | ||||
|         for jid, item in items.items(): | ||||
|             if item['subscription'] in valid_subscriptions: | ||||
|                 roster[jid]['name'] = item['name'] | ||||
|                 roster[jid]['groups'] = item['groups'] | ||||
|             roster[jid]['from'] = item['subscription'] in ['from', 'both'] | ||||
|             roster[jid]['to'] = item['subscription'] in ['to', 'both'] | ||||
|                 roster[jid]['from'] = item['subscription'] in ('from', 'both') | ||||
|                 roster[jid]['to'] = item['subscription'] in ('to', 'both') | ||||
|                 roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||
|  | ||||
|                 roster[jid].save(remove=(item['subscription'] == 'remove')) | ||||
|  | ||||
|         self.event("roster_update", iq) | ||||
|         if iq['type'] == 'set': | ||||
|             resp = self.Iq(stype='result', | ||||
|                            sto=iq['from'], | ||||
|   | ||||
| @@ -79,7 +79,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|                                self._handle_probe) | ||||
|  | ||||
|     def connect(self, host=None, port=None, use_ssl=False, | ||||
|                       use_tls=True, reattempt=True): | ||||
|                       use_tls=False, reattempt=True): | ||||
|         """Connect to the server. | ||||
|  | ||||
|         Setting ``reattempt`` to ``True`` will cause connection attempts to | ||||
| @@ -104,10 +104,13 @@ class ComponentXMPP(BaseXMPP): | ||||
|  | ||||
|         self.server_name = self.boundjid.host | ||||
|  | ||||
|         if use_tls: | ||||
|             log.info("XEP-0114 components can not use TLS") | ||||
|  | ||||
|         log.debug("Connecting to %s:%s", host, port) | ||||
|         return XMLStream.connect(self, host=host, port=port, | ||||
|                                        use_ssl=use_ssl, | ||||
|                                        use_tls=use_tls, | ||||
|                                        use_tls=False, | ||||
|                                        reattempt=reattempt) | ||||
|  | ||||
|     def incoming_filter(self, xml): | ||||
| @@ -120,12 +123,6 @@ class ComponentXMPP(BaseXMPP): | ||||
|         """ | ||||
|         if xml.tag.startswith('{jabber:client}'): | ||||
|             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 | ||||
|  | ||||
|     def start_stream_handler(self, xml): | ||||
| @@ -153,10 +150,10 @@ class ComponentXMPP(BaseXMPP): | ||||
|  | ||||
|         :param xml: The reply handshake stanza. | ||||
|         """ | ||||
|         self.session_bind_event.set() | ||||
|         self.session_started_event.set() | ||||
|         self.event("session_start") | ||||
|         self.event('session_bind', self.boundjid, direct=True) | ||||
|         self.event('session_start') | ||||
|  | ||||
|     def _handle_probe(self, presence): | ||||
|         pto = presence['to'].bare | ||||
|         pfrom = presence['from'].bare | ||||
|         self.roster[pto][pfrom].handle_probe(presence) | ||||
|     def _handle_probe(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_probe(pres) | ||||
|   | ||||
| @@ -42,7 +42,7 @@ class XMPPError(Exception): | ||||
|                   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, | ||||
|                 extension_args=None, clear=True): | ||||
|         if extension_args is None: | ||||
| @@ -73,6 +73,7 @@ class IqTimeout(XMPPError): | ||||
|         #: did not arrive before the timeout expired. | ||||
|         self.iq = iq | ||||
|  | ||||
|  | ||||
| class IqError(XMPPError): | ||||
|  | ||||
|     """ | ||||
|   | ||||
| @@ -11,5 +11,6 @@ __all__ = [ | ||||
|     'feature_mechanisms', | ||||
|     'feature_bind', | ||||
|     'feature_session', | ||||
|     'feature_rosterver' | ||||
|     'feature_rosterver', | ||||
|     'feature_preapproval' | ||||
| ] | ||||
|   | ||||
| @@ -8,10 +8,11 @@ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.jid import JID | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.features.feature_bind import stanza | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
| @@ -40,24 +41,25 @@ class FeatureBind(BasePlugin): | ||||
|         Arguments: | ||||
|             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['type'] = 'set' | ||||
|         iq.enable('bind') | ||||
|         if self.xmpp.boundjid.resource: | ||||
|             iq['bind']['resource'] = self.xmpp.boundjid.resource | ||||
|         if self.xmpp.requested_jid.resource: | ||||
|             iq['bind']['resource'] = self.xmpp.requested_jid.resource | ||||
|         response = iq.send(now=True) | ||||
|  | ||||
|         self.xmpp.set_jid(response['bind']['jid']) | ||||
|         self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) | ||||
|         self.xmpp.bound = True | ||||
|         self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True) | ||||
|         self.xmpp.session_bind_event.set() | ||||
|  | ||||
|         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']: | ||||
|             log.debug("Established Session") | ||||
|             self.xmpp.sessionstarted = True | ||||
|             self.xmpp.session_started_event.set() | ||||
|             self.xmpp.event("session_start") | ||||
|             self.xmpp.event('session_start') | ||||
|   | ||||
| @@ -6,12 +6,11 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import ssl | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.thirdparty import suelta | ||||
| from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError | ||||
| from sleekxmpp.thirdparty.suelta.exceptions import SASLPrepFailure | ||||
|  | ||||
| from sleekxmpp.util import sasl | ||||
| from sleekxmpp.util.stringprep_profiles import StringPrepError | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| @@ -29,42 +28,32 @@ class FeatureMechanisms(BasePlugin): | ||||
|     description = 'RFC 6120: Stream Feature: SASL' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'use_mech': None, | ||||
|         'use_mechs': None, | ||||
|         'min_mech': None, | ||||
|         'sasl_callback': None, | ||||
|         'security_callback': None, | ||||
|         'encrypted_plain': True, | ||||
|         'unencrypted_plain': False, | ||||
|         'unencrypted_digest': False, | ||||
|         'unencrypted_cram': False, | ||||
|         'unencrypted_scram': True, | ||||
|         'order': 100 | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.use_mech = self.config.get('use_mech', None) | ||||
|         if self.sasl_callback is None: | ||||
|             self.sasl_callback = self._default_credentials | ||||
|  | ||||
|         if not self.use_mech and not self.xmpp.boundjid.user: | ||||
|         if self.security_callback is None: | ||||
|             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' | ||||
|  | ||||
|         def tls_active(): | ||||
|             return 'starttls' in self.xmpp.features | ||||
|  | ||||
|         def basic_callback(mech, values): | ||||
|             creds = self.xmpp.credentials | ||||
|             for value in values: | ||||
|                 if value == 'username': | ||||
|                     values['username'] = self.xmpp.boundjid.user | ||||
|                 elif value == 'password': | ||||
|                     values['password'] = creds['password'] | ||||
|                 elif value == 'email': | ||||
|                     jid = self.xmpp.boundjid.bare | ||||
|                     values['email'] = creds.get('email', jid) | ||||
|                 elif value in creds: | ||||
|                     values[value] = creds[value] | ||||
|             mech.fulfill(values) | ||||
|  | ||||
|         sasl_callback = self.config.get('sasl_callback', None) | ||||
|         if sasl_callback is None: | ||||
|             sasl_callback = basic_callback | ||||
|  | ||||
|         self.mech = None | ||||
|         self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp', | ||||
|                                 username=self.xmpp.boundjid.user, | ||||
|                                 sec_query=suelta.sec_query_allow, | ||||
|                                 request_values=sasl_callback, | ||||
|                                 tls_active=tls_active, | ||||
|                                 mech=self.use_mech) | ||||
|  | ||||
|         self.mech_list = set() | ||||
|         self.attempted_mechs = set() | ||||
|  | ||||
| @@ -95,7 +84,51 @@ class FeatureMechanisms(BasePlugin): | ||||
|         self.xmpp.register_feature('mechanisms', | ||||
|                 self._handle_sasl_auth, | ||||
|                 restart=True, | ||||
|                 order=self.config.get('order', 100)) | ||||
|                 order=self.order) | ||||
|  | ||||
|     def _default_credentials(self, required_values, optional_values): | ||||
|         creds = self.xmpp.credentials | ||||
|         result = {} | ||||
|         values = required_values.union(optional_values) | ||||
|         for value in values: | ||||
|             if value == 'username': | ||||
|                 result[value] = creds.get('username', self.xmpp.requested_jid.user) | ||||
|             elif value == 'email': | ||||
|                 jid = self.xmpp.requested_jid.bare | ||||
|                 result[value] = creds.get('email', jid) | ||||
|             elif value == 'channel_binding': | ||||
|                 if hasattr(self.xmpp.socket, 'get_channel_binding'): | ||||
|                     result[value] = self.xmpp.socket.get_channel_binding() | ||||
|                 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 | ||||
|             elif value == 'host': | ||||
|                 result[value] = creds.get('host', self.xmpp.requested_jid.domain) | ||||
|             elif value == 'realm': | ||||
|                 result[value] = creds.get('realm', self.xmpp.requested_jid.domain) | ||||
|             elif value == 'service-name': | ||||
|                 result[value] = creds.get('service-name', self.xmpp._service_name) | ||||
|             elif value == 'service': | ||||
|                 result[value] = creds.get('service', 'xmpp') | ||||
|             elif value in creds: | ||||
|                 result[value] = creds[value] | ||||
|         return result | ||||
|  | ||||
|     def _default_security(self, values): | ||||
|         result = {} | ||||
|         for value in values: | ||||
|             if value == 'encrypted': | ||||
|                 if 'starttls' in self.xmpp.features: | ||||
|                     result[value] = True | ||||
|                 elif isinstance(self.xmpp.socket, ssl.SSLSocket): | ||||
|                     result[value] = True | ||||
|                 else: | ||||
|                     result[value] = False | ||||
|             else: | ||||
|                 result[value] = self.config.get(value, False) | ||||
|         return result | ||||
|  | ||||
|     def _handle_sasl_auth(self, features): | ||||
|         """ | ||||
| @@ -109,37 +142,62 @@ class FeatureMechanisms(BasePlugin): | ||||
|             # server has incorrectly offered it again. | ||||
|             return False | ||||
|  | ||||
|         if not self.use_mech: | ||||
|         enforce_limit = False | ||||
|         limited_mechs = self.use_mechs | ||||
|  | ||||
|         if limited_mechs is None: | ||||
|             limited_mechs = set() | ||||
|         elif limited_mechs and not isinstance(limited_mechs, set): | ||||
|             limited_mechs = set(limited_mechs) | ||||
|             enforce_limit = True | ||||
|  | ||||
|         if self.use_mech: | ||||
|             limited_mechs.add(self.use_mech) | ||||
|             enforce_limit = True | ||||
|  | ||||
|         if enforce_limit: | ||||
|             self.use_mechs = limited_mechs | ||||
|  | ||||
|         self.mech_list = set(features['mechanisms']) | ||||
|         else: | ||||
|             self.mech_list = set([self.use_mech]) | ||||
|  | ||||
|         return self._send_auth() | ||||
|  | ||||
|     def _send_auth(self): | ||||
|         mech_list = self.mech_list - self.attempted_mechs | ||||
|         self.mech = self.sasl.choose_mechanism(mech_list) | ||||
|         try: | ||||
|             self.mech = sasl.choose(mech_list, | ||||
|                                     self.sasl_callback, | ||||
|                                     self.security_callback, | ||||
|                                     limit=self.use_mechs, | ||||
|                                     min_mech=self.min_mech) | ||||
|         except sasl.SASLNoAppropriateMechanism: | ||||
|             log.error("No appropriate login method.") | ||||
|             self.xmpp.event("no_auth", direct=True) | ||||
|             self.xmpp.event("failed_auth", direct=True) | ||||
|             self.attempted_mechs = set() | ||||
|             return self.xmpp.disconnect() | ||||
|         except StringPrepError: | ||||
|             log.exception("A credential value did not pass SASLprep.") | ||||
|             self.xmpp.disconnect() | ||||
|  | ||||
|         if mech_list and self.mech is not None: | ||||
|         resp = stanza.Auth(self.xmpp) | ||||
|         resp['mechanism'] = self.mech.name | ||||
|         try: | ||||
|             resp['value'] = self.mech.process() | ||||
|             except SASLCancelled: | ||||
|         except sasl.SASLCancelled: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|             except SASLError: | ||||
|         except sasl.SASLFailed: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|             except SASLPrepFailure: | ||||
|                 log.exception("A credential value did not pass SASLprep.") | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             resp.send(now=True) | ||||
|         else: | ||||
|             log.error("No appropriate login method.") | ||||
|             self.xmpp.event("no_auth", direct=True) | ||||
|             self.attempted_mechs = set() | ||||
|             self.xmpp.disconnect() | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def _handle_challenge(self, stanza): | ||||
| @@ -147,15 +205,28 @@ class FeatureMechanisms(BasePlugin): | ||||
|         resp = self.stanza.Response(self.xmpp) | ||||
|         try: | ||||
|             resp['value'] = self.mech.process(stanza['value']) | ||||
|         except SASLCancelled: | ||||
|         except sasl.SASLCancelled: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except SASLError: | ||||
|         except sasl.SASLFailed: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             resp.send(now=True) | ||||
|  | ||||
|     def _handle_success(self, stanza): | ||||
|         """SASL authentication succeeded. Restart the stream.""" | ||||
|         try: | ||||
|             final = self.mech.process(stanza['value']) | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             self.attempted_mechs = set() | ||||
|             self.xmpp.authenticated = True | ||||
|             self.xmpp.features.add('mechanisms') | ||||
|   | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
| @@ -41,7 +40,7 @@ class Auth(StanzaBase): | ||||
|         if not self['mechanism'] in self.plain_mechs: | ||||
|             if values: | ||||
|                 self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') | ||||
|             else: | ||||
|             elif values == b'': | ||||
|                 self.xml.text = '=' | ||||
|         else: | ||||
|             self.xml.text = bytes(values).decode('utf-8') | ||||
|   | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class Failure(StanzaBase): | ||||
|  | ||||
|     def get_condition(self): | ||||
|         """Return the condition element's name.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if "{%s}" % self.namespace in child.tag: | ||||
|                 cond = child.tag.split('}', 1)[-1] | ||||
|                 if cond in self.conditions: | ||||
| @@ -68,7 +68,7 @@ class Failure(StanzaBase): | ||||
|  | ||||
|     def del_condition(self): | ||||
|         """Remove the condition element.""" | ||||
|         for child in self.xml.getchildren(): | ||||
|         for child in self.xml: | ||||
|             if "{%s}" % self.condition_ns in child.tag: | ||||
|                 tag = child.tag.split('}', 1)[-1] | ||||
|                 if tag in self.conditions: | ||||
|   | ||||
| @@ -8,8 +8,7 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,10 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
| class Success(StanzaBase): | ||||
|  | ||||
| @@ -16,9 +18,21 @@ class Success(StanzaBase): | ||||
|  | ||||
|     name = 'success' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set() | ||||
|     interfaces = set(['value']) | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     def setup(self, xml): | ||||
|         StanzaBase.setup(self, xml) | ||||
|         self.xml.tag = self.tag_name() | ||||
|  | ||||
|     def get_value(self): | ||||
|         return base64.b64decode(bytes(self.xml.text)) | ||||
|  | ||||
|     def set_value(self, values): | ||||
|         if values: | ||||
|             self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') | ||||
|         else: | ||||
|             self.xml.text = '=' | ||||
|  | ||||
|     def del_value(self): | ||||
|         self.xml.text = '' | ||||
|   | ||||
							
								
								
									
										15
									
								
								sleekxmpp/features/feature_preapproval/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/features/feature_preapproval/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| """ | ||||
|     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.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.features.feature_preapproval.preapproval import FeaturePreApproval | ||||
| from sleekxmpp.features.feature_preapproval.stanza import PreApproval | ||||
|  | ||||
|  | ||||
| register_plugin(FeaturePreApproval) | ||||
							
								
								
									
										42
									
								
								sleekxmpp/features/feature_preapproval/preapproval.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/features/feature_preapproval/preapproval.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2012  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.features.feature_preapproval import stanza | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.base import BasePlugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class FeaturePreApproval(BasePlugin): | ||||
|  | ||||
|     name = 'feature_preapproval' | ||||
|     description = 'RFC 6121: Stream Feature: Subscription Pre-Approval' | ||||
|     dependences = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.register_feature('preapproval', | ||||
|                 self._handle_preapproval, | ||||
|                 restart=False, | ||||
|                 order=9001) | ||||
|  | ||||
|         register_stanza_plugin(StreamFeatures, stanza.PreApproval) | ||||
|  | ||||
|     def _handle_preapproval(self, features): | ||||
|         """Save notice that the server support subscription pre-approvals. | ||||
|  | ||||
|         Arguments: | ||||
|             features -- The stream features stanza. | ||||
|         """ | ||||
|         log.debug("Server supports subscription pre-approvals.") | ||||
|         self.xmpp.features.add('preapproval') | ||||
							
								
								
									
										17
									
								
								sleekxmpp/features/feature_preapproval/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sleekxmpp/features/feature_preapproval/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| """ | ||||
|     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.xmlstream import ElementBase | ||||
|  | ||||
|  | ||||
| class PreApproval(ElementBase): | ||||
|  | ||||
|     name = 'sub' | ||||
|     namespace = 'urn:xmpp:features:pre-approval' | ||||
|     interfaces = set() | ||||
|     plugin_attrib = 'preapproval' | ||||
| @@ -8,7 +8,7 @@ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.features.feature_rosterver import stanza | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.base import BasePlugin | ||||
|   | ||||
| @@ -51,4 +51,4 @@ class FeatureSession(BasePlugin): | ||||
|         log.debug("Established Session") | ||||
|         self.xmpp.sessionstarted = True | ||||
|         self.xmpp.session_started_event.set() | ||||
|         self.xmpp.event("session_start") | ||||
|         self.xmpp.event('session_start') | ||||
|   | ||||
| @@ -54,13 +54,9 @@ class FeatureSTARTTLS(BasePlugin): | ||||
|             return False | ||||
|         elif not self.xmpp.use_tls: | ||||
|             return False | ||||
|         elif self.xmpp.ssl_support: | ||||
|         else: | ||||
|             self.xmpp.send(features['starttls'], now=True) | ||||
|             return True | ||||
|         else: | ||||
|             log.warning("The module tlslite is required to log in" + \ | ||||
|                         " to some servers, and has not been found.") | ||||
|             return False | ||||
|  | ||||
|     def _handle_starttls_proceed(self, proceed): | ||||
|         """Restart the XML stream when TLS is accepted.""" | ||||
|   | ||||
							
								
								
									
										638
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										638
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,638 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| """ | ||||
|     sleekxmpp.jid | ||||
|     ~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|     This module allows for working with Jabber IDs (JIDs). | ||||
|  | ||||
|     Part of SleekXMPP: The Sleek XMPP Library | ||||
|  | ||||
|     :copyright: (c) 2011 Nathanael C. Fritz | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import re | ||||
| import socket | ||||
| import stringprep | ||||
| import threading | ||||
| import encodings.idna | ||||
|  | ||||
| from copy import deepcopy | ||||
|  | ||||
| from sleekxmpp.util import stringprep_profiles | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
|  | ||||
| #: These characters are not allowed to appear in a JID. | ||||
| ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \ | ||||
|                 '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \ | ||||
|                 '\x1a\x1b\x1c\x1d\x1e\x1f' + \ | ||||
|                 ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f' | ||||
|  | ||||
| #: The basic regex pattern that a JID must match in order to determine | ||||
| #: the local, domain, and resource parts. This regex does NOT do any | ||||
| #: validation, which requires application of nodeprep, resourceprep, etc. | ||||
| JID_PATTERN = re.compile( | ||||
|     "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$" | ||||
| ) | ||||
|  | ||||
| #: The set of escape sequences for the characters not allowed by nodeprep. | ||||
| JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f', | ||||
|                             '\\3a', '\\3c', '\\3e', '\\40', '\\5c']) | ||||
|  | ||||
| #: A mapping of unallowed characters to their escape sequences. An escape | ||||
| #: sequence for '\' is also included since it must also be escaped in | ||||
| #: certain situations. | ||||
| JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20', | ||||
|                               '"': '\\22', | ||||
|                               '&': '\\26', | ||||
|                               "'": '\\27', | ||||
|                               '/': '\\2f', | ||||
|                               ':': '\\3a', | ||||
|                               '<': '\\3c', | ||||
|                               '>': '\\3e', | ||||
|                               '@': '\\40', | ||||
|                               '\\': '\\5c'} | ||||
|  | ||||
| #: The reverse mapping of escape sequences to their original forms. | ||||
| JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', | ||||
|                                 '\\22': '"', | ||||
|                                 '\\26': '&', | ||||
|                                 '\\27': "'", | ||||
|                                 '\\2f': '/', | ||||
|                                 '\\3a': ':', | ||||
|                                 '\\3c': '<', | ||||
|                                 '\\3e': '>', | ||||
|                                 '\\40': '@', | ||||
|                                 '\\5c': '\\'} | ||||
|  | ||||
| JID_CACHE = OrderedDict() | ||||
| JID_CACHE_LOCK = threading.Lock() | ||||
| JID_CACHE_MAX_SIZE = 1024 | ||||
|  | ||||
| def _cache(key, parts, locked): | ||||
|     JID_CACHE[key] = (parts, locked) | ||||
|     if len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|         with JID_CACHE_LOCK: | ||||
|             while len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|                 found = None | ||||
|                 for key, item in JID_CACHE.items(): | ||||
|                     if not item[1]: # if not locked | ||||
|                         found = key | ||||
|                         break | ||||
|                 if not found: # more than MAX_SIZE locked | ||||
|                     # warn? | ||||
|                     break | ||||
|                 del JID_CACHE[found] | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The nodeprep profile of stringprep used to validate the local, | ||||
| #: or username, portion of a JID. | ||||
| nodeprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[ | ||||
|         stringprep_profiles.b1_mapping, | ||||
|         stringprep.map_table_b2], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c11, | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9, | ||||
|         lambda c: c in ' \'"&/:<>@'], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The resourceprep profile of stringprep, which is used to validate | ||||
| #: the resource portion of a JID. | ||||
| resourceprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[stringprep_profiles.b1_mapping], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
|  | ||||
| def _parse_jid(data): | ||||
|     """ | ||||
|     Parse string data into the node, domain, and resource | ||||
|     components of a JID, if possible. | ||||
|  | ||||
|     :param string data: A string that is potentially a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: tuple of the validated local, domain, and resource strings | ||||
|     """ | ||||
|     match = JID_PATTERN.match(data) | ||||
|     if not match: | ||||
|         raise InvalidJID('JID could not be parsed') | ||||
|  | ||||
|     (node, domain, resource) = match.groups() | ||||
|  | ||||
|     node = _validate_node(node) | ||||
|     domain = _validate_domain(domain) | ||||
|     resource = _validate_resource(resource) | ||||
|  | ||||
|     return node, domain, resource | ||||
|  | ||||
|  | ||||
| def _validate_node(node): | ||||
|     """Validate the local, or username, portion of a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by nodeprep. | ||||
|     """ | ||||
|     try: | ||||
|         if node is not None: | ||||
|             node = nodeprep(node) | ||||
|  | ||||
|             if not node: | ||||
|                 raise InvalidJID('Localpart must not be 0 bytes') | ||||
|             if len(node) > 1023: | ||||
|                 raise InvalidJID('Localpart must be less than 1024 bytes') | ||||
|             return node | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid local part') | ||||
|  | ||||
|  | ||||
| def _validate_domain(domain): | ||||
|     """Validate the domain portion of a JID. | ||||
|  | ||||
|     IP literal addresses are left as-is, if valid. Domain names | ||||
|     are stripped of any trailing label separators (`.`), and are | ||||
|     checked with the nameprep profile of stringprep. If the given | ||||
|     domain is actually a punyencoded version of a domain name, it | ||||
|     is converted back into its original Unicode form. Domains must | ||||
|     also not start or end with a dash (`-`). | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The validated domain name | ||||
|     """ | ||||
|     ip_addr = False | ||||
|  | ||||
|     # First, check if this is an IPv4 address | ||||
|     try: | ||||
|         socket.inet_aton(domain) | ||||
|         ip_addr = True | ||||
|     except socket.error: | ||||
|         pass | ||||
|  | ||||
|     # Check if this is an IPv6 address | ||||
|     if not ip_addr and hasattr(socket, 'inet_pton'): | ||||
|         try: | ||||
|             socket.inet_pton(socket.AF_INET6, domain.strip('[]')) | ||||
|             domain = '[%s]' % domain.strip('[]') | ||||
|             ip_addr = True | ||||
|         except (socket.error, ValueError): | ||||
|             pass | ||||
|  | ||||
|     if not ip_addr: | ||||
|         # This is a domain name, which must be checked further | ||||
|  | ||||
|         if domain and domain[-1] == '.': | ||||
|             domain = domain[:-1] | ||||
|  | ||||
|         domain_parts = [] | ||||
|         for label in domain.split('.'): | ||||
|             try: | ||||
|                 label = encodings.idna.nameprep(label) | ||||
|                 encodings.idna.ToASCII(label) | ||||
|                 pass_nameprep = True | ||||
|             except UnicodeError: | ||||
|                 pass_nameprep = False | ||||
|  | ||||
|             if not pass_nameprep: | ||||
|                 raise InvalidJID('Could not encode domain as ASCII') | ||||
|  | ||||
|             if label.startswith('xn--'): | ||||
|                 label = encodings.idna.ToUnicode(label) | ||||
|  | ||||
|             for char in label: | ||||
|                 if char in ILLEGAL_CHARS: | ||||
|                     raise InvalidJID('Domain contains illegal characters') | ||||
|  | ||||
|             if '-' in (label[0], label[-1]): | ||||
|                 raise InvalidJID('Domain started or ended with -') | ||||
|  | ||||
|             domain_parts.append(label) | ||||
|         domain = '.'.join(domain_parts) | ||||
|  | ||||
|     if not domain: | ||||
|         raise InvalidJID('Domain must not be 0 bytes') | ||||
|     if len(domain) > 1023: | ||||
|         raise InvalidJID('Domain must be less than 1024 bytes') | ||||
|  | ||||
|     return domain | ||||
|  | ||||
|  | ||||
| def _validate_resource(resource): | ||||
|     """Validate the resource portion of a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by resourceprep. | ||||
|     """ | ||||
|     try: | ||||
|         if resource is not None: | ||||
|             resource = resourceprep(resource) | ||||
|  | ||||
|             if not resource: | ||||
|                 raise InvalidJID('Resource must not be 0 bytes') | ||||
|             if len(resource) > 1023: | ||||
|                 raise InvalidJID('Resource must be less than 1024 bytes') | ||||
|             return resource | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid resource') | ||||
|  | ||||
|  | ||||
| def _escape_node(node): | ||||
|     """Escape the local portion of a JID.""" | ||||
|     result = [] | ||||
|  | ||||
|     for i, char in enumerate(node): | ||||
|         if char == '\\': | ||||
|             if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES: | ||||
|                 result.append('\\5c') | ||||
|                 continue | ||||
|         result.append(char) | ||||
|  | ||||
|     for i, char in enumerate(result): | ||||
|         if char != '\\': | ||||
|             result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char) | ||||
|  | ||||
|     escaped = ''.join(result) | ||||
|  | ||||
|     if escaped.startswith('\\20') or escaped.endswith('\\20'): | ||||
|         raise InvalidJID('Escaped local part starts or ends with "\\20"') | ||||
|  | ||||
|     _validate_node(escaped) | ||||
|  | ||||
|     return escaped | ||||
|  | ||||
|  | ||||
| def _unescape_node(node): | ||||
|     """Unescape a local portion of a JID. | ||||
|  | ||||
|     .. note:: | ||||
|         The unescaped local portion is meant ONLY for presentation, | ||||
|         and should not be used for other purposes. | ||||
|     """ | ||||
|     unescaped = [] | ||||
|     seq = '' | ||||
|     for i, char in enumerate(node): | ||||
|         if char == '\\': | ||||
|             seq = node[i:i+3] | ||||
|             if seq not in JID_ESCAPE_SEQUENCES: | ||||
|                 seq = '' | ||||
|         if seq: | ||||
|             if len(seq) == 3: | ||||
|                 unescaped.append(JID_UNESCAPE_TRANSFORMATIONS.get(seq, char)) | ||||
|  | ||||
|             # Pop character off the escape sequence, and ignore it | ||||
|             seq = seq[1:] | ||||
|         else: | ||||
|             unescaped.append(char) | ||||
|     unescaped = ''.join(unescaped) | ||||
|  | ||||
|     return unescaped | ||||
|  | ||||
|  | ||||
| def _format_jid(local=None, domain=None, resource=None): | ||||
|     """Format the given JID components into a full or bare JID. | ||||
|  | ||||
|     :param string local: Optional. The local portion of the JID. | ||||
|     :param string domain: Required. The domain name portion of the JID. | ||||
|     :param strin resource: Optional. The resource portion of the JID. | ||||
|  | ||||
|     :return: A full or bare JID string. | ||||
|     """ | ||||
|     result = [] | ||||
|     if local: | ||||
|         result.append(local) | ||||
|         result.append('@') | ||||
|     if domain: | ||||
|         result.append(domain) | ||||
|     if resource: | ||||
|         result.append('/') | ||||
|         result.append(resource) | ||||
|     return ''.join(result) | ||||
|  | ||||
|  | ||||
| class InvalidJID(ValueError): | ||||
|     """ | ||||
|     Raised when attempting to create a JID that does not pass validation. | ||||
|  | ||||
|     It can also be raised if modifying an existing JID in such a way as | ||||
|     to make it invalid, such trying to remove the domain from an existing | ||||
|     full JID while the local and resource portions still exist. | ||||
|     """ | ||||
|  | ||||
| # pylint: disable=R0903 | ||||
| class UnescapedJID(object): | ||||
|  | ||||
|     """ | ||||
|     .. versionadded:: 1.1.10 | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, local, domain, resource): | ||||
|         self._jid = (local, domain, resource) | ||||
|  | ||||
|     # pylint: disable=R0911 | ||||
|     def __getattr__(self, name): | ||||
|         """Retrieve the given JID component. | ||||
|  | ||||
|         :param name: one of: user, server, domain, resource, | ||||
|                      full, or bare. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             return self._jid[2] or '' | ||||
|         elif name in ('user', 'username', 'local', 'node'): | ||||
|             return self._jid[0] or '' | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             return self._jid[1] or '' | ||||
|         elif name in ('full', 'jid'): | ||||
|             return _format_jid(*self._jid) | ||||
|         elif name == 'bare': | ||||
|             return _format_jid(self._jid[0], self._jid[1]) | ||||
|         elif name == '_jid': | ||||
|             return getattr(super(JID, self), '_jid') | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|  | ||||
|  | ||||
| class JID(object): | ||||
|  | ||||
|     """ | ||||
|     A representation of a Jabber ID, or JID. | ||||
|  | ||||
|     Each JID may have three components: a user, a domain, and an optional | ||||
|     resource. For example: user@domain/resource | ||||
|  | ||||
|     When a resource is not used, the JID is called a bare JID. | ||||
|     The JID is a full JID otherwise. | ||||
|  | ||||
|     **JID Properties:** | ||||
|         :jid: Alias for ``full``. | ||||
|         :full: The string value of the full JID. | ||||
|         :bare: The string value of the bare JID. | ||||
|         :user: The username portion of the JID. | ||||
|         :username: Alias for ``user``. | ||||
|         :local: Alias for ``user``. | ||||
|         :node: Alias for ``user``. | ||||
|         :domain: The domain name portion of the JID. | ||||
|         :server: Alias for ``domain``. | ||||
|         :host: Alias for ``domain``. | ||||
|         :resource: The resource portion of the JID. | ||||
|  | ||||
|     :param string jid: | ||||
|         A string of the form ``'[user@]domain[/resource]'``. | ||||
|     :param string local: | ||||
|         Optional. Specify the local, or username, portion | ||||
|         of the JID. If provided, it will override the local | ||||
|         value provided by the `jid` parameter. The given | ||||
|         local value will also be escaped if necessary. | ||||
|     :param string domain: | ||||
|         Optional. Specify the domain of the JID. If | ||||
|         provided, it will override the domain given by | ||||
|         the `jid` parameter. | ||||
|     :param string resource: | ||||
|         Optional. Specify the resource value of the JID. | ||||
|         If provided, it will override the domain given | ||||
|         by the `jid` parameter. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|     """ | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __init__(self, jid=None, **kwargs): | ||||
|         locked = kwargs.get('cache_lock', False) | ||||
|         in_local = kwargs.get('local', None) | ||||
|         in_domain = kwargs.get('domain', None) | ||||
|         in_resource = kwargs.get('resource', None) | ||||
|         parts = None | ||||
|         if in_local or in_domain or in_resource: | ||||
|             parts = (in_local, in_domain, in_resource) | ||||
|  | ||||
|         # only check cache if there is a jid string, or parts, not if there | ||||
|         # are both | ||||
|         self._jid = None | ||||
|         key = None | ||||
|         if (jid is not None) and (parts is None): | ||||
|             if isinstance(jid, JID): | ||||
|                 # it's already good to go, and there are no additions | ||||
|                 self._jid = jid._jid | ||||
|                 return | ||||
|             key = jid | ||||
|             self._jid, locked = JID_CACHE.get(jid, (None, locked)) | ||||
|         elif jid is None and parts is not None: | ||||
|             key = parts | ||||
|             self._jid, locked = JID_CACHE.get(parts, (None, locked)) | ||||
|         if not self._jid: | ||||
|             if not jid: | ||||
|                 parsed_jid = (None, None, None) | ||||
|             elif not isinstance(jid, JID): | ||||
|                 parsed_jid = _parse_jid(jid) | ||||
|             else: | ||||
|                 parsed_jid = jid._jid | ||||
|  | ||||
|             local, domain, resource = parsed_jid | ||||
|  | ||||
|             if 'local' in kwargs: | ||||
|                 local = _escape_node(in_local) | ||||
|             if 'domain' in kwargs: | ||||
|                 domain = _validate_domain(in_domain) | ||||
|             if 'resource' in kwargs: | ||||
|                 resource = _validate_resource(in_resource) | ||||
|  | ||||
|             self._jid = (local, domain, resource) | ||||
|             if key: | ||||
|                 _cache(key, self._jid, locked) | ||||
|  | ||||
|     def unescape(self): | ||||
|         """Return an unescaped JID object. | ||||
|  | ||||
|         Using an unescaped JID is preferred for displaying JIDs | ||||
|         to humans, and they should NOT be used for any other | ||||
|         purposes than for presentation. | ||||
|  | ||||
|         :return: :class:`UnescapedJID` | ||||
|  | ||||
|         .. versionadded:: 1.1.10 | ||||
|         """ | ||||
|         return UnescapedJID(_unescape_node(self._jid[0]), | ||||
|                             self._jid[1], | ||||
|                             self._jid[2]) | ||||
|  | ||||
|     def regenerate(self): | ||||
|         """No-op | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def reset(self, data): | ||||
|         """Start fresh from a new JID string. | ||||
|  | ||||
|         :param string data: A string of the form ``'[user@]domain[/resource]'``. | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|         """ | ||||
|         self._jid = JID(data)._jid | ||||
|  | ||||
|     @property | ||||
|     def resource(self): | ||||
|         return self._jid[2] or '' | ||||
|  | ||||
|     @property | ||||
|     def user(self): | ||||
|         return self._jid[0] or '' | ||||
|  | ||||
|     @property | ||||
|     def local(self): | ||||
|         return self._jid[0] or '' | ||||
|  | ||||
|     @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]) | ||||
|  | ||||
|     @property | ||||
|     def server(self): | ||||
|         return self._jid[1] or '' | ||||
|  | ||||
|     @property | ||||
|     def domain(self): | ||||
|         return self._jid[1] or '' | ||||
|  | ||||
|     @property | ||||
|     def host(self): | ||||
|         return self._jid[1] or '' | ||||
|  | ||||
|     @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 | ||||
|  | ||||
|     @user.setter | ||||
|     def user(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @username.setter | ||||
|     def username(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @local.setter | ||||
|     def local(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @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 | ||||
|  | ||||
|     @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 | ||||
|  | ||||
|     @jid.setter | ||||
|     def jid(self, value): | ||||
|         self._jid = JID(value)._jid | ||||
|  | ||||
|     @bare.setter | ||||
|     def bare(self, value): | ||||
|         parsed = JID(value)._jid | ||||
|         self._jid = (parsed[0], parsed[1], self._jid[2]) | ||||
|  | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __eq__(self, other): | ||||
|         """Two JIDs are equal if they have the same full JID value.""" | ||||
|         if isinstance(other, UnescapedJID): | ||||
|             return False | ||||
|  | ||||
|         other = JID(other) | ||||
|         return self._jid == other._jid | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __ne__(self, other): | ||||
|         """Two JIDs are considered unequal if they are not equal.""" | ||||
|         return not self == other | ||||
|  | ||||
|     def __hash__(self): | ||||
|         """Hash a JID based on the string version of its full JID.""" | ||||
|         return hash(self.__str__()) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         """Generate a duplicate JID.""" | ||||
|         return JID(self) | ||||
|  | ||||
|     def __deepcopy__(self, memo): | ||||
|         """Generate a duplicate JID.""" | ||||
|         return JID(deepcopy(str(self), memo)) | ||||
| @@ -11,44 +11,76 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin | ||||
|  | ||||
|  | ||||
| __all__ = [ | ||||
|     # Non-standard | ||||
|     'gmail_notify',  # Gmail searching and notifications | ||||
|  | ||||
|     # XEPS | ||||
|     'xep_0004',  # Data Forms | ||||
|     'xep_0009',  # Jabber-RPC | ||||
|     'xep_0012',  # Last Activity | ||||
|     'xep_0013',  # Flexible Offline Message Retrieval | ||||
|     'xep_0016',  # Privacy Lists | ||||
|     'xep_0020',  # Feature Negotiation | ||||
|     'xep_0027',  # Current Jabber OpenPGP Usage | ||||
|     'xep_0030',  # Service Discovery | ||||
|     'xep_0033',  # Extended Stanza Addresses | ||||
|     'xep_0045',  # Multi-User Chat (Client) | ||||
|     'xep_0047',  # In-Band Bytestreams | ||||
|     'xep_0048',  # Bookmarks | ||||
|     'xep_0049',  # Private XML Storage | ||||
|     'xep_0050',  # Ad-hoc Commands | ||||
|     'xep_0054',  # vcard-temp | ||||
|     'xep_0059',  # Result Set Management | ||||
|     'xep_0060',  # Pubsub (Client) | ||||
|     'xep_0065',  # SOCKS5 Bytestreams | ||||
|     'xep_0066',  # Out of Band Data | ||||
|     'xep_0071',  # XHTML-IM | ||||
|     'xep_0077',  # In-Band Registration | ||||
| #   'xep_0078',  # Non-SASL auth. Don't automatically load | ||||
|     'xep_0079',  # Advanced Message Processing | ||||
|     'xep_0080',  # User Location | ||||
|     'xep_0082',  # XMPP Date and Time Profiles | ||||
|     'xep_0084',  # User Avatar | ||||
|     'xep_0085',  # Chat State Notifications | ||||
|     'xep_0086',  # Legacy Error Codes | ||||
|     'xep_0091',  # Legacy Delayed Delivery | ||||
|     'xep_0092',  # Software Version | ||||
|     'xep_0106',  # JID Escaping | ||||
|     'xep_0107',  # User Mood | ||||
|     'xep_0108',  # User Activity | ||||
|     'xep_0115',  # Entity Capabilities | ||||
|     'xep_0118',  # User Tune | ||||
|     'xep_0128',  # Extended Service Discovery | ||||
|     'xep_0131',  # Standard Headers and Internet Metadata | ||||
|     'xep_0133',  # Service Administration | ||||
|     'xep_0152',  # Reachability Addresses | ||||
|     'xep_0153',  # vCard-Based Avatars | ||||
|     'xep_0163',  # Personal Eventing Protocol | ||||
|     'xep_0172',  # User Nickname | ||||
|     'xep_0184',  # Message Receipts | ||||
|     'xep_0186',  # Invisible Command | ||||
|     'xep_0191',  # Blocking Command | ||||
|     'xep_0196',  # User Gaming | ||||
|     'xep_0198',  # Stream Management | ||||
|     'xep_0199',  # Ping | ||||
|     'xep_0202',  # Entity Time | ||||
|     'xep_0203',  # Delayed Delivery | ||||
|     'xep_0221',  # Data Forms Media Element | ||||
|     'xep_0222',  # Persistent Storage of Public Data via Pubsub | ||||
|     'xep_0223',  # Persistent Storage of Private Data via Pubsub | ||||
|     'xep_0224',  # Attention | ||||
|     'xep_0231',  # Bits of Binary | ||||
|     'xep_0235',  # OAuth Over XMPP | ||||
|     'xep_0242',  # XMPP Client Compliance 2009 | ||||
|     'xep_0249',  # Direct MUC Invitations | ||||
|     'xep_0256',  # Last Activity in Presence | ||||
|     'xep_0257',  # Client Certificate Management for SASL EXTERNAL | ||||
|     'xep_0258',  # Security Labels in XMPP | ||||
|     'xep_0270',  # XMPP Compliance Suites 2010 | ||||
|     'xep_0279',  # Server IP Check | ||||
|     'xep_0280',  # Message Carbons | ||||
|     'xep_0297',  # Stanza Forwarding | ||||
|     'xep_0302',  # XMPP Compliance Suites 2012 | ||||
|     'xep_0308',  # Last Message Correction | ||||
|     'xep_0313',  # Message Archive Management | ||||
|     'xep_0319',  # Last User Interaction in Presence | ||||
|     'xep_0323',  # IoT Systems Sensor Data | ||||
|     'xep_0325',  # IoT Systems Control | ||||
| ] | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import copy | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| @@ -84,7 +85,7 @@ def load_plugin(name, module=None): | ||||
|                 module = 'sleekxmpp.plugins.%s' % name | ||||
|                 __import__(module) | ||||
|                 mod = sys.modules[module] | ||||
|             except: | ||||
|             except ImportError: | ||||
|                 module = 'sleekxmpp.features.%s' % name | ||||
|                 __import__(module) | ||||
|                 mod = sys.modules[module] | ||||
| @@ -103,7 +104,7 @@ def load_plugin(name, module=None): | ||||
|                 # we can work around dependency issues. | ||||
|                 plugin.old_style = True | ||||
|             register_plugin(plugin, name) | ||||
|     except: | ||||
|     except ImportError: | ||||
|         log.exception("Unable to load plugin: %s", name) | ||||
|  | ||||
|  | ||||
| @@ -167,8 +168,7 @@ class PluginManager(object): | ||||
|                 self._plugins[name] = plugin | ||||
|                 for dep in plugin.dependencies: | ||||
|                     self.enable(dep, enabled=enabled) | ||||
|                 plugin.plugin_init() | ||||
|                 log.debug("Loaded Plugin: %s", plugin.description) | ||||
|                 plugin._init() | ||||
|  | ||||
|         if top_level: | ||||
|             for name in enabled: | ||||
| @@ -229,7 +229,7 @@ class PluginManager(object): | ||||
|                     raise PluginNotFound(name) | ||||
|                 for dep in PLUGIN_DEPENDENTS[name]: | ||||
|                     self.disable(dep, _disabled) | ||||
|                 plugin.plugin_end() | ||||
|                 plugin._end() | ||||
|                 if name in self._enabled: | ||||
|                     self._enabled.remove(name) | ||||
|                 del self._plugins[name] | ||||
| @@ -273,6 +273,14 @@ class BasePlugin(object): | ||||
|     #: be initialized as needed if this plugin is enabled. | ||||
|     dependencies = set() | ||||
|  | ||||
|     #: The basic, standard configuration for the plugin, which may | ||||
|     #: be overridden when initializing the plugin. The configuration | ||||
|     #: fields included here may be accessed directly as attributes of | ||||
|     #: the plugin. For example, including the configuration field 'foo' | ||||
|     #: would mean accessing `plugin.foo` returns the current value of | ||||
|     #: `plugin.config['foo']`. | ||||
|     default_config = {} | ||||
|  | ||||
|     def __init__(self, xmpp, config=None): | ||||
|         self.xmpp = xmpp | ||||
|         if self.xmpp: | ||||
| @@ -280,7 +288,54 @@ class BasePlugin(object): | ||||
|  | ||||
|         #: A plugin's behaviour may be configurable, in which case those | ||||
|         #: configuration settings will be provided as a dictionary. | ||||
|         self.config = config if config is not None else {} | ||||
|         self.config = copy.copy(self.default_config) | ||||
|         if config: | ||||
|             self.config.update(config) | ||||
|  | ||||
|     def __getattr__(self, key): | ||||
|         """Provide direct access to configuration fields. | ||||
|  | ||||
|         If the standard configuration includes the option `'foo'`, then | ||||
|         accessing `self.foo` should be the same as `self.config['foo']`. | ||||
|         """ | ||||
|         if key in self.default_config: | ||||
|             return self.config.get(key, None) | ||||
|         else: | ||||
|             return object.__getattribute__(self, key) | ||||
|  | ||||
|     def __setattr__(self, key, value): | ||||
|         """Provide direct assignment to configuration fields. | ||||
|  | ||||
|         If the standard configuration includes the option `'foo'`, then | ||||
|         assigning to `self.foo` should be the same as assigning to | ||||
|         `self.config['foo']`. | ||||
|         """ | ||||
|         if key in self.default_config: | ||||
|             self.config[key] = value | ||||
|         else: | ||||
|             super(BasePlugin, self).__setattr__(key, value) | ||||
|  | ||||
|     def _init(self): | ||||
|         """Initialize plugin state, such as registering event handlers. | ||||
|  | ||||
|         Also sets up required event handlers. | ||||
|         """ | ||||
|         if self.xmpp is not None: | ||||
|             self.xmpp.add_event_handler('session_bind', self.session_bind) | ||||
|             if self.xmpp.session_bind_event.is_set(): | ||||
|                 self.session_bind(self.xmpp.boundjid.full) | ||||
|         self.plugin_init() | ||||
|         log.debug('Loaded Plugin: %s', self.description) | ||||
|  | ||||
|     def _end(self): | ||||
|         """Cleanup plugin state, and prepare for plugin removal. | ||||
|  | ||||
|         Also removes required event handlers. | ||||
|         """ | ||||
|         if self.xmpp is not None: | ||||
|             self.xmpp.del_event_handler('session_bind', self.session_bind) | ||||
|         self.plugin_end() | ||||
|         log.debug('Disabled Plugin: %s' % self.description) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """Initialize plugin state, such as registering event handlers.""" | ||||
| @@ -290,6 +345,10 @@ class BasePlugin(object): | ||||
|         """Cleanup plugin state, and prepare for plugin removal.""" | ||||
|         pass | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         """Initialize plugin state based on the bound JID.""" | ||||
|         pass | ||||
|  | ||||
|     def post_init(self): | ||||
|         """Initialize any cross-plugin state. | ||||
|  | ||||
|   | ||||
							
								
								
									
										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, '') | ||||
|  | ||||
| @@ -27,7 +27,7 @@ class XEP_0004(BasePlugin): | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.registerHandler( | ||||
|         self.xmpp.register_handler( | ||||
|             Callback('Data Form', | ||||
|                  StanzaPath('message/form'), | ||||
|                  self.handle_form)) | ||||
| @@ -36,6 +36,11 @@ class XEP_0004(BasePlugin): | ||||
|         register_stanza_plugin(Form, FormField, iterable=True) | ||||
|         register_stanza_plugin(Message, Form) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Data Form') | ||||
|         self.xmpp['xep_0030'].del_feature(feature='jabber:x:data') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('jabber:x:data') | ||||
|  | ||||
|     def make_form(self, ftype='form', title='', instructions=''): | ||||
|   | ||||
| @@ -41,10 +41,11 @@ class FormField(ElementBase): | ||||
|             self._type = value | ||||
|  | ||||
|     def add_option(self, label='', value=''): | ||||
|         if self._type in self.option_types: | ||||
|             opt = FieldOption(parent=self) | ||||
|         if self._type is None or self._type in self.option_types: | ||||
|             opt = FieldOption() | ||||
|             opt['label'] = label | ||||
|             opt['value'] = value | ||||
|             self.append(opt) | ||||
|         else: | ||||
|             raise ValueError("Cannot add options to " + \ | ||||
|                              "a %s field." % self['type']) | ||||
|   | ||||
| @@ -65,7 +65,7 @@ class Form(ElementBase): | ||||
|         if kwtype is None: | ||||
|             kwtype = ftype | ||||
|  | ||||
|         field = FormField(parent=self) | ||||
|         field = FormField() | ||||
|         field['var'] = var | ||||
|         field['type'] = kwtype | ||||
|         field['value'] = value | ||||
| @@ -77,6 +77,7 @@ class Form(ElementBase): | ||||
|                 field['options'] = options | ||||
|         else: | ||||
|             del field['type'] | ||||
|         self.append(field) | ||||
|         return field | ||||
|  | ||||
|     def getXML(self, type='submit'): | ||||
| @@ -144,10 +145,9 @@ class Form(ElementBase): | ||||
|  | ||||
|     def get_fields(self, use_dict=False): | ||||
|         fields = OrderedDict() | ||||
|         fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) | ||||
|         for fieldXML in fieldsXML: | ||||
|             field = FormField(xml=fieldXML) | ||||
|             fields[field['var']] = field | ||||
|         for stanza in self['substanzas']: | ||||
|             if isinstance(stanza, FormField): | ||||
|                 fields[stanza['var']] = stanza | ||||
|         return fields | ||||
|  | ||||
|     def get_instructions(self): | ||||
| @@ -201,6 +201,7 @@ class Form(ElementBase): | ||||
|         del self['instructions'] | ||||
|         if instructions in [None, '']: | ||||
|             return | ||||
|         if not isinstance(instructions, list): | ||||
|             instructions = instructions.split('\n') | ||||
|         for instruction in instructions: | ||||
|             inst = ET.Element('{%s}instructions' % self.namespace) | ||||
| @@ -220,6 +221,8 @@ class Form(ElementBase): | ||||
|     def set_values(self, values): | ||||
|         fields = self['fields'] | ||||
|         for field in values: | ||||
|             if field not in fields: | ||||
|                 fields[field] = self.add_field(var=field) | ||||
|             fields[field]['value'] = values[field] | ||||
|  | ||||
|     def merge(self, other): | ||||
|   | ||||
| @@ -32,15 +32,15 @@ class XEP_0009(BasePlugin): | ||||
|         register_stanza_plugin(RPCQuery, MethodCall) | ||||
|         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)), | ||||
|             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)), | ||||
|             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)), | ||||
|             self._handle_error) | ||||
|         ) | ||||
|   | ||||
| @@ -37,13 +37,11 @@ class XEP_0012(BasePlugin): | ||||
|  | ||||
|         self._last_activities = {} | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|         self.xmpp.register_handler( | ||||
|             Callback('Last Activity', | ||||
|                  StanzaPath('iq@type=get/last_activity'), | ||||
|                  self._handle_get_last_activity)) | ||||
|  | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last') | ||||
|  | ||||
|         self.api.register(self._default_get_last_activity, | ||||
|                 'get_last_activity', | ||||
|                 default=True) | ||||
| @@ -54,6 +52,13 @@ class XEP_0012(BasePlugin): | ||||
|                 'del_last_activity', | ||||
|                 default=True) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Last Activity') | ||||
|         self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last') | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature('jabber:iq:last') | ||||
|  | ||||
|     def begin_idle(self, jid=None, status=None): | ||||
|         self.set_last_activity(jid, 0, status) | ||||
|  | ||||
|   | ||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0013/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0013/__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_0013.stanza import Offline | ||||
| from sleekxmpp.plugins.xep_0013.offline import XEP_0013 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0013) | ||||
							
								
								
									
										134
									
								
								sleekxmpp/plugins/xep_0013/offline.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								sleekxmpp/plugins/xep_0013/offline.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| """ | ||||
|     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 | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.stanza import Message, Iq | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.xmlstream.handler import Collector | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0013 import stanza | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0013(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0013 Flexible Offline Message Retrieval | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0013' | ||||
|     description = 'XEP-0013: Flexible Offline Message Retrieval' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, stanza.Offline) | ||||
|         register_stanza_plugin(Message, stanza.Offline) | ||||
|  | ||||
|     def get_count(self, **kwargs): | ||||
|         return self.xmpp['xep_0030'].get_info( | ||||
|                 node='http://jabber.org/protocol/offline', | ||||
|                 local=False, | ||||
|                 **kwargs) | ||||
|  | ||||
|     def get_headers(self, **kwargs): | ||||
|         return self.xmpp['xep_0030'].get_items( | ||||
|                 node='http://jabber.org/protocol/offline', | ||||
|                 local=False, | ||||
|                 **kwargs) | ||||
|  | ||||
|     def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         if not isinstance(nodes, (list, set)): | ||||
|             nodes = [nodes] | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['from'] = ifrom | ||||
|         offline = iq['offline'] | ||||
|         for node in nodes: | ||||
|             item = stanza.Item() | ||||
|             item['node'] = node | ||||
|             item['action'] = 'view' | ||||
|             offline.append(item) | ||||
|  | ||||
|         collector = Collector( | ||||
|             'Offline_Results_%s' % iq['id'], | ||||
|             StanzaPath('message/offline')) | ||||
|         self.xmpp.register_handler(collector) | ||||
|  | ||||
|         if not block and callback is not None: | ||||
|             def wrapped_cb(iq): | ||||
|                 results = collector.stop() | ||||
|                 if iq['type'] == 'result': | ||||
|                     iq['offline']['results'] = results | ||||
|                 callback(iq) | ||||
|             return iq.send(block=block, timeout=timeout, callback=wrapped_cb) | ||||
|         else: | ||||
|             try: | ||||
|                 resp = iq.send(block=block, timeout=timeout, callback=callback) | ||||
|                 resp['offline']['results'] = collector.stop() | ||||
|                 return resp | ||||
|             except XMPPError as e: | ||||
|                 collector.stop() | ||||
|                 raise e | ||||
|  | ||||
|     def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         if not isinstance(nodes, (list, set)): | ||||
|             nodes = [nodes] | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         offline = iq['offline'] | ||||
|         for node in nodes: | ||||
|             item = stanza.Item() | ||||
|             item['node'] = node | ||||
|             item['action'] = 'remove' | ||||
|             offline.append(item) | ||||
|  | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def fetch(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq['offline']['fetch'] = True | ||||
|  | ||||
|         collector = Collector( | ||||
|             'Offline_Results_%s' % iq['id'], | ||||
|             StanzaPath('message/offline')) | ||||
|         self.xmpp.register_handler(collector) | ||||
|  | ||||
|         if not block and callback is not None: | ||||
|             def wrapped_cb(iq): | ||||
|                 results = collector.stop() | ||||
|                 if iq['type'] == 'result': | ||||
|                     iq['offline']['results'] = results | ||||
|                 callback(iq) | ||||
|             return iq.send(block=block, timeout=timeout, callback=wrapped_cb) | ||||
|         else: | ||||
|             try: | ||||
|                 resp = iq.send(block=block, timeout=timeout, callback=callback) | ||||
|                 resp['offline']['results'] = collector.stop() | ||||
|                 return resp | ||||
|             except XMPPError as e: | ||||
|                 collector.stop() | ||||
|                 raise e | ||||
|  | ||||
|     def purge(self, ifrom=None, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['from'] = ifrom | ||||
|         iq['offline']['purge'] = True | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
							
								
								
									
										53
									
								
								sleekxmpp/plugins/xep_0013/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								sleekxmpp/plugins/xep_0013/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| """ | ||||
|     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.jid import JID | ||||
| from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Offline(ElementBase): | ||||
|     name = 'offline' | ||||
|     namespace = 'http://jabber.org/protocol/offline' | ||||
|     plugin_attrib = 'offline' | ||||
|     interfaces = set(['fetch', 'purge', 'results']) | ||||
|     bool_interfaces = interfaces | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         ElementBase.setup(self, xml) | ||||
|         self._results = [] | ||||
|  | ||||
|     # The results interface is meant only as an easy | ||||
|     # way to access the set of collected message responses | ||||
|     # from the query. | ||||
|  | ||||
|     def get_results(self): | ||||
|         return self._results | ||||
|  | ||||
|     def set_results(self, values): | ||||
|         self._results = values | ||||
|  | ||||
|     def del_results(self): | ||||
|         self._results = [] | ||||
|  | ||||
|  | ||||
| class Item(ElementBase): | ||||
|     name = 'item' | ||||
|     namespace = 'http://jabber.org/protocol/offline' | ||||
|     plugin_attrib = 'item' | ||||
|     interfaces = set(['action', 'node', 'jid']) | ||||
|  | ||||
|     actions = set(['view', 'remove']) | ||||
|  | ||||
|     def get_jid(self): | ||||
|         return JID(self._get_attr('jid')) | ||||
|  | ||||
|     def set_jid(self, value): | ||||
|         self._set_attr('jid', str(value)) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Offline, Item, iterable=True) | ||||
							
								
								
									
										16
									
								
								sleekxmpp/plugins/xep_0016/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								sleekxmpp/plugins/xep_0016/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| """ | ||||
|     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.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0016 import stanza | ||||
| from sleekxmpp.plugins.xep_0016.stanza import Privacy | ||||
| from sleekxmpp.plugins.xep_0016.privacy import XEP_0016 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0016) | ||||
							
								
								
									
										110
									
								
								sleekxmpp/plugins/xep_0016/privacy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								sleekxmpp/plugins/xep_0016/privacy.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0016 import stanza | ||||
| from sleekxmpp.plugins.xep_0016.stanza import Privacy, Item | ||||
|  | ||||
|  | ||||
| class XEP_0016(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0016' | ||||
|     description = 'XEP-0016: Privacy Lists' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, Privacy) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Privacy.namespace) | ||||
|  | ||||
|     def get_privacy_lists(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq.enable('privacy') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def get_list(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['privacy']['list']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def get_active(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['privacy'].enable('active') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def get_default(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq['privacy'].enable('default') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def activate(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['active']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def deactivate(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy'].enable('active') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def make_default(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['default']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def remove_default(self, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy'].enable('default') | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
|  | ||||
|     def edit_list(self, name, rules, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['list']['name'] = name | ||||
|         priv_list = iq['privacy']['list'] | ||||
|  | ||||
|         if not rules: | ||||
|             rules = [] | ||||
|  | ||||
|         for rule in rules: | ||||
|             if isinstance(rule, Item): | ||||
|                 priv_list.append(rule) | ||||
|                 continue | ||||
|  | ||||
|             priv_list.add_item( | ||||
|                 rule['value'], | ||||
|                 rule['action'], | ||||
|                 rule['order'], | ||||
|                 itype=rule.get('type', None), | ||||
|                 iq=rule.get('iq', None), | ||||
|                 message=rule.get('message', None), | ||||
|                 presence_in=rule.get('presence_in', | ||||
|                     rule.get('presence-in', None)), | ||||
|                 presence_out=rule.get('presence_out', | ||||
|                     rule.get('presence-out', None))) | ||||
|  | ||||
|     def remove_list(self, name, block=True, timeout=None, callback=None): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['privacy']['list']['name'] = name | ||||
|         return iq.send(block=block, timeout=timeout, callback=callback) | ||||
							
								
								
									
										103
									
								
								sleekxmpp/plugins/xep_0016/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								sleekxmpp/plugins/xep_0016/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Privacy(ElementBase): | ||||
|     name = 'query' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = 'privacy' | ||||
|     interfaces = set() | ||||
|  | ||||
|     def add_list(self, name): | ||||
|         priv_list = List() | ||||
|         priv_list['name'] = name | ||||
|         self.append(priv_list) | ||||
|         return priv_list | ||||
|  | ||||
|  | ||||
| class Active(ElementBase): | ||||
|     name = 'active' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     interfaces = set(['name']) | ||||
|  | ||||
|  | ||||
| class Default(ElementBase): | ||||
|     name = 'default' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     interfaces = set(['name']) | ||||
|  | ||||
|  | ||||
| class List(ElementBase): | ||||
|     name = 'list' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'lists' | ||||
|     interfaces = set(['name']) | ||||
|  | ||||
|     def add_item(self, value, action, order, itype=None, iq=False, | ||||
|                  message=False, presence_in=False, presence_out=False): | ||||
|         item = Item() | ||||
|         item.values = {'type': itype, | ||||
|                        'value': value, | ||||
|                        'action': action, | ||||
|                        'order': order, | ||||
|                        'message': message, | ||||
|                        'iq': iq, | ||||
|                        'presence_in': presence_in, | ||||
|                        'presence_out': presence_out} | ||||
|         self.append(item) | ||||
|         return item | ||||
|  | ||||
|  | ||||
| class Item(ElementBase): | ||||
|     name = 'item' | ||||
|     namespace = 'jabber:iq:privacy' | ||||
|     plugin_attrib = name | ||||
|     plugin_multi_attrib = 'items' | ||||
|     interfaces = set(['type', 'value', 'action', 'order', 'iq', | ||||
|                       'message', 'presence_in', 'presence_out']) | ||||
|     bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out']) | ||||
|  | ||||
|     type_values = ('', 'jid', 'group', 'subscription') | ||||
|     action_values = ('allow', 'deny') | ||||
|  | ||||
|     def set_type(self, value): | ||||
|         if value and value not in self.type_values: | ||||
|             raise ValueError('Unknown type value: %s' % value) | ||||
|         else: | ||||
|             self._set_attr('type', value) | ||||
|  | ||||
|     def set_action(self, value): | ||||
|         if value not in self.action_values: | ||||
|             raise ValueError('Unknown action value: %s' % value) | ||||
|         else: | ||||
|             self._set_attr('action', value) | ||||
|  | ||||
|     def set_presence_in(self, value): | ||||
|         keep = True if value else False | ||||
|         self._set_sub_text('presence-in', '', keep=keep) | ||||
|  | ||||
|     def get_presence_in(self): | ||||
|         pres = self.xml.find('{%s}presence-in' % self.namespace) | ||||
|         return pres is not None | ||||
|  | ||||
|     def del_presence_in(self): | ||||
|         self._del_sub('{%s}presence-in' % self.namespace) | ||||
|  | ||||
|     def set_presence_out(self, value): | ||||
|         keep = True if value else False | ||||
|         self._set_sub_text('presence-in', '', keep=keep) | ||||
|  | ||||
|     def get_presence_out(self): | ||||
|         pres = self.xml.find('{%s}presence-in' % self.namespace) | ||||
|         return pres is not None | ||||
|  | ||||
|     def del_presence_out(self): | ||||
|         self._del_sub('{%s}presence-in' % self.namespace) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Privacy, Active) | ||||
| register_stanza_plugin(Privacy, Default) | ||||
| register_stanza_plugin(Privacy, List, iterable=True) | ||||
| register_stanza_plugin(List, Item, iterable=True) | ||||
							
								
								
									
										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: | ||||
|             begin_headers = True | ||||
|             continue | ||||
|         if begin_headers and line == '': | ||||
|         if begin_headers and line.strip() == '': | ||||
|             begin_data = True | ||||
|             continue | ||||
|         if 'END PGP %s' % kind in line: | ||||
| @@ -40,14 +40,15 @@ class XEP_0027(BasePlugin): | ||||
|     description = 'XEP-0027: Current Jabber OpenPGP Usage' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'gpg_binary': 'gpg', | ||||
|         'gpg_home': '', | ||||
|         'use_agent': True, | ||||
|         'keyring': None, | ||||
|         'key_server': 'pgp.mit.edu' | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.gpg_binary = self.config.get('gpg_binary', 'gpg') | ||||
|         self.gpg_home = self.config.get('gpg_home', '') | ||||
|         self.use_agent = self.config.get('use_agent', True) | ||||
|         self.keyring = self.config.get('keyring', None) | ||||
|         self.key_server = self.config.get('key_server', 'pgp.mit.edu') | ||||
|  | ||||
|         self.gpg = GPG(gnupghome=self.gpg_home, | ||||
|                        gpgbinary=self.gpg_binary, | ||||
|                        use_agent=self.use_agent, | ||||
| @@ -79,9 +80,17 @@ class XEP_0027(BasePlugin): | ||||
|                     StanzaPath('message/encrypted'), | ||||
|                     self._handle_encrypted_message)) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler('Encrypted Message') | ||||
|         self.xmpp.remove_handler('Signed Presence') | ||||
|         self.xmpp.del_filter('out', self._sign_presence) | ||||
|         self.xmpp.del_event_handler('unverified_signed_presence', | ||||
|                 self._handle_unverified_signed_presence) | ||||
|  | ||||
|     def _sign_presence(self, stanza): | ||||
|         if isinstance(stanza, Presence): | ||||
|             if stanza['type'] == 'available' or stanza['type'] in Presence.showtypes: | ||||
|             if stanza['type'] == 'available' or \ | ||||
|                     stanza['type'] in Presence.showtypes: | ||||
|                 stanza['signed'] = stanza['status'] | ||||
|         return stanza | ||||
|  | ||||
|   | ||||
| @@ -39,7 +39,7 @@ class Encrypted(ElementBase): | ||||
|     def set_encrypted(self, value): | ||||
|         parent = self.parent() | ||||
|         xmpp = parent.stream | ||||
|         data = xmpp['xep_0027'].encrypt(value, parent['to'].bare) | ||||
|         data = xmpp['xep_0027'].encrypt(value, parent['to']) | ||||
|         if data: | ||||
|             self.xml.text = data | ||||
|         else: | ||||
| @@ -51,6 +51,3 @@ class Encrypted(ElementBase): | ||||
|         if self.xml.text: | ||||
|             return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) | ||||
|         return None | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -88,6 +88,10 @@ class XEP_0030(BasePlugin): | ||||
|     description = 'XEP-0030: Service Discovery' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|     default_config = { | ||||
|         'use_cache': True, | ||||
|         'wrap_results': False | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         """ | ||||
| @@ -108,9 +112,6 @@ class XEP_0030(BasePlugin): | ||||
|  | ||||
|         self.static = StaticDisco(self.xmpp, self) | ||||
|  | ||||
|         self.use_cache = self.config.get('use_cache', True) | ||||
|         self.wrap_results = self.config.get('wrap_results', False) | ||||
|  | ||||
|         self._disco_ops = [ | ||||
|                 'get_info', 'set_info', 'set_identities', 'set_features', | ||||
|                 'get_items', 'set_items', 'del_items', 'add_identity', | ||||
| @@ -287,7 +288,7 @@ class XEP_0030(BasePlugin): | ||||
|                 'cached': cached} | ||||
|         return self.api['has_identity'](jid, node, ifrom, data) | ||||
|  | ||||
|     def get_info(self, jid=None, node=None, local=False, | ||||
|     def get_info(self, jid=None, node=None, local=None, | ||||
|                        cached=None, **kwargs): | ||||
|         """ | ||||
|         Retrieve the disco#info results from a given JID/node combination. | ||||
| @@ -323,7 +324,10 @@ class XEP_0030(BasePlugin): | ||||
|             callback -- Optional callback to execute when a reply is | ||||
|                         received instead of blocking and waiting for | ||||
|                         the reply. | ||||
|             timeout_callback -- Optional callback to execute when no result  | ||||
|                         has been received in timeout seconds. | ||||
|         """ | ||||
|         if local is None: | ||||
|             if jid is not None and not isinstance(jid, JID): | ||||
|                 jid = JID(jid) | ||||
|                 if self.xmpp.is_component: | ||||
| @@ -362,7 +366,8 @@ class XEP_0030(BasePlugin): | ||||
|         iq['disco_info']['node'] = node if node else '' | ||||
|         return iq.send(timeout=kwargs.get('timeout', None), | ||||
|                        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): | ||||
|         """ | ||||
| @@ -403,8 +408,10 @@ class XEP_0030(BasePlugin): | ||||
|             iterator -- If True, return a result set iterator using | ||||
|                         the XEP-0059 plugin, if the plugin is loaded. | ||||
|                         Otherwise the parameter is ignored. | ||||
|             timeout_callback -- Optional callback to execute when no result  | ||||
|                         has been received in timeout seconds. | ||||
|         """ | ||||
|         if local or jid is None: | ||||
|         if local or local is None and jid is None: | ||||
|             items = self.api['get_items'](jid, node, | ||||
|                     kwargs.get('ifrom', None), | ||||
|                     kwargs) | ||||
| @@ -421,7 +428,8 @@ class XEP_0030(BasePlugin): | ||||
|         else: | ||||
|             return iq.send(timeout=kwargs.get('timeout', None), | ||||
|                            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): | ||||
|         """ | ||||
| @@ -622,11 +630,7 @@ class XEP_0030(BasePlugin): | ||||
|         if iq['type'] == 'get': | ||||
|             log.debug("Received disco info query from " + \ | ||||
|                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||
|             if self.xmpp.is_component: | ||||
|                 jid = iq['to'].full | ||||
|             else: | ||||
|                 jid = iq['to'].bare | ||||
|             info = self.api['get_info'](jid, | ||||
|             info = self.api['get_info'](iq['to'], | ||||
|                                         iq['disco_info']['node'], | ||||
|                                         iq['from'], | ||||
|                                         iq) | ||||
| @@ -649,7 +653,7 @@ class XEP_0030(BasePlugin): | ||||
|                     ito = iq['to'].full | ||||
|                 else: | ||||
|                     ito = None | ||||
|                 self.api['cache_info'](iq['from'].full, | ||||
|                 self.api['cache_info'](iq['from'], | ||||
|                                        iq['disco_info']['node'], | ||||
|                                        ito, | ||||
|                                        iq) | ||||
| @@ -667,13 +671,9 @@ class XEP_0030(BasePlugin): | ||||
|         if iq['type'] == 'get': | ||||
|             log.debug("Received disco items query from " + \ | ||||
|                       "<%s> to <%s>.", iq['from'], iq['to']) | ||||
|             if self.xmpp.is_component: | ||||
|                 jid = iq['to'].full | ||||
|             else: | ||||
|                 jid = iq['to'].bare | ||||
|             items = self.api['get_items'](jid, | ||||
|             items = self.api['get_items'](iq['to'], | ||||
|                                           iq['disco_items']['node'], | ||||
|                                           iq['from'].full, | ||||
|                                           iq['from'], | ||||
|                                           iq) | ||||
|             if isinstance(items, Iq): | ||||
|                 items.send() | ||||
|   | ||||
| @@ -128,9 +128,10 @@ class DiscoItems(ElementBase): | ||||
|     def del_items(self): | ||||
|         """Remove all items.""" | ||||
|         self._items = set() | ||||
|         for item in self['substanzas']: | ||||
|             if isinstance(item, DiscoItem): | ||||
|         items = [i for i in self.iterables if isinstance(i, DiscoItem)] | ||||
|         for item in items: | ||||
|             self.xml.remove(item.xml) | ||||
|             self.iterables.remove(item) | ||||
|  | ||||
|  | ||||
| class DiscoItem(ElementBase): | ||||
|   | ||||
| @@ -182,11 +182,6 @@ class StaticDisco(object): | ||||
|         data = {'local': data.get('local', False), | ||||
|                 'cached': data.get('cached', True)} | ||||
|  | ||||
|         if node in (None, ''): | ||||
|             info = self.caps.get_caps(jid) | ||||
|             if info and identity in info['identities']: | ||||
|                 return True | ||||
|  | ||||
|         try: | ||||
|             info = self.disco.get_info(jid=jid, node=node, | ||||
|                                        ifrom=ifrom, **data) | ||||
| @@ -242,7 +237,7 @@ class StaticDisco(object): | ||||
|         with self.lock: | ||||
|             if not self.node_exists(jid, node): | ||||
|                 if not node: | ||||
|                     return DiscoInfo() | ||||
|                     return DiscoItems() | ||||
|                 else: | ||||
|                     raise XMPPError(condition='item-not-found') | ||||
|             else: | ||||
| @@ -429,9 +424,6 @@ class StaticDisco(object): | ||||
|         The data parameter is not used. | ||||
|         """ | ||||
|         with self.lock: | ||||
|             if isinstance(jid, JID): | ||||
|                 jid = jid.full | ||||
|  | ||||
|             if not self.node_exists(jid, node, ifrom): | ||||
|                 return None | ||||
|             else: | ||||
|   | ||||
| @@ -1,167 +0,0 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from sleekxmpp import Message | ||||
| from sleekxmpp.xmlstream.handler.callback import Callback | ||||
| from sleekxmpp.xmlstream.matcher.xpath import MatchXPath | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| class Addresses(ElementBase): | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     name = 'addresses' | ||||
|     plugin_attrib = 'addresses' | ||||
|     interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) | ||||
|  | ||||
|     def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False): | ||||
|         address = Address(parent=self) | ||||
|         address['type'] = atype | ||||
|         address['jid'] = jid | ||||
|         address['node'] = node | ||||
|         address['uri'] = uri | ||||
|         address['desc'] = desc | ||||
|         address['delivered'] = delivered | ||||
|         return address | ||||
|  | ||||
|     def getAddresses(self, atype=None): | ||||
|         addresses = [] | ||||
|         for addrXML in self.xml.findall('{%s}address' % Address.namespace): | ||||
|             # ElementTree 1.2.6 does not support [@attr='value'] in findall | ||||
|             if atype is None or addrXML.attrib.get('type') == atype: | ||||
|                 addresses.append(Address(xml=addrXML, parent=None)) | ||||
|         return addresses | ||||
|  | ||||
|     def setAddresses(self, addresses, set_type=None): | ||||
|         self.delAddresses(set_type) | ||||
|         for addr in addresses: | ||||
|             addr = dict(addr) | ||||
|             # Remap 'type' to 'atype' to match the add method | ||||
|             if set_type is not None: | ||||
|                 addr['type'] = set_type | ||||
|             curr_type = addr.get('type', None) | ||||
|             if curr_type is not None: | ||||
|                 del addr['type'] | ||||
|                 addr['atype'] = curr_type | ||||
|             self.addAddress(**addr) | ||||
|  | ||||
|     def delAddresses(self, atype=None): | ||||
|         if atype is None: | ||||
|             return | ||||
|         for addrXML in self.xml.findall('{%s}address' % Address.namespace): | ||||
|             # ElementTree 1.2.6 does not support [@attr='value'] in findall | ||||
|             if addrXML.attrib.get('type') == atype: | ||||
|                 self.xml.remove(addrXML) | ||||
|  | ||||
|     # -------------------------------------------------------------- | ||||
|  | ||||
|     def delBcc(self): | ||||
|         self.delAddresses('bcc') | ||||
|  | ||||
|     def delCc(self): | ||||
|         self.delAddresses('cc') | ||||
|  | ||||
|     def delNoreply(self): | ||||
|         self.delAddresses('noreply') | ||||
|  | ||||
|     def delReplyroom(self): | ||||
|         self.delAddresses('replyroom') | ||||
|  | ||||
|     def delReplyto(self): | ||||
|         self.delAddresses('replyto') | ||||
|  | ||||
|     def delTo(self): | ||||
|         self.delAddresses('to') | ||||
|  | ||||
|     # -------------------------------------------------------------- | ||||
|  | ||||
|     def getBcc(self): | ||||
|         return self.getAddresses('bcc') | ||||
|  | ||||
|     def getCc(self): | ||||
|         return self.getAddresses('cc') | ||||
|  | ||||
|     def getNoreply(self): | ||||
|         return self.getAddresses('noreply') | ||||
|  | ||||
|     def getReplyroom(self): | ||||
|         return self.getAddresses('replyroom') | ||||
|  | ||||
|     def getReplyto(self): | ||||
|         return self.getAddresses('replyto') | ||||
|  | ||||
|     def getTo(self): | ||||
|         return self.getAddresses('to') | ||||
|  | ||||
|     # -------------------------------------------------------------- | ||||
|  | ||||
|     def setBcc(self, addresses): | ||||
|         self.setAddresses(addresses, 'bcc') | ||||
|  | ||||
|     def setCc(self, addresses): | ||||
|         self.setAddresses(addresses, 'cc') | ||||
|  | ||||
|     def setNoreply(self, addresses): | ||||
|         self.setAddresses(addresses, 'noreply') | ||||
|  | ||||
|     def setReplyroom(self, addresses): | ||||
|         self.setAddresses(addresses, 'replyroom') | ||||
|  | ||||
|     def setReplyto(self, addresses): | ||||
|         self.setAddresses(addresses, 'replyto') | ||||
|  | ||||
|     def setTo(self, addresses): | ||||
|         self.setAddresses(addresses, 'to') | ||||
|  | ||||
|  | ||||
| class Address(ElementBase): | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     name = 'address' | ||||
|     plugin_attrib = 'address' | ||||
|     interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri')) | ||||
|     address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) | ||||
|  | ||||
|     def getDelivered(self): | ||||
|         return self.xml.attrib.get('delivered', False) | ||||
|  | ||||
|     def setDelivered(self, delivered): | ||||
|         if delivered: | ||||
|             self.xml.attrib['delivered'] = "true" | ||||
|         else: | ||||
|             del self['delivered'] | ||||
|  | ||||
|     def setUri(self, uri): | ||||
|         if uri: | ||||
|             del self['jid'] | ||||
|             del self['node'] | ||||
|             self.xml.attrib['uri'] = uri | ||||
|         elif 'uri' in self.xml.attrib: | ||||
|             del self.xml.attrib['uri'] | ||||
|  | ||||
|  | ||||
| class XEP_0033(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0033: Extended Stanza Addressing | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0033' | ||||
|     description = 'XEP-0033: Extended Stanza Addressing' | ||||
|     dependencies = set(['xep_0033']) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xep = '0033' | ||||
|  | ||||
|         register_stanza_plugin(Message, Addresses) | ||||
|  | ||||
|         self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace) | ||||
|  | ||||
|  | ||||
| xep_0033 = XEP_0033 | ||||
| register_plugin(XEP_0033) | ||||
							
								
								
									
										20
									
								
								sleekxmpp/plugins/xep_0033/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								sleekxmpp/plugins/xep_0033/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| """ | ||||
|     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.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0033 import stanza | ||||
| from sleekxmpp.plugins.xep_0033.stanza import Addresses, Address | ||||
| from sleekxmpp.plugins.xep_0033.addresses import XEP_0033 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0033) | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0033 = XEP_0033 | ||||
| Addresses.addAddress = Addresses.add_address | ||||
							
								
								
									
										37
									
								
								sleekxmpp/plugins/xep_0033/addresses.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								sleekxmpp/plugins/xep_0033/addresses.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Message, Presence | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0033 import stanza, Addresses | ||||
|  | ||||
|  | ||||
| class XEP_0033(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0033: Extended Stanza Addressing | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0033' | ||||
|     description = 'XEP-0033: Extended Stanza Addressing' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, Addresses) | ||||
|         register_stanza_plugin(Presence, Addresses) | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace) | ||||
|  | ||||
|     def session_bind(self, jid): | ||||
|         self.xmpp['xep_0030'].add_feature(Addresses.namespace) | ||||
|  | ||||
							
								
								
									
										131
									
								
								sleekxmpp/plugins/xep_0033/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								sleekxmpp/plugins/xep_0033/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | ||||
| """ | ||||
|     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 JID, ElementBase, ET, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class Addresses(ElementBase): | ||||
|  | ||||
|     name = 'addresses' | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     plugin_attrib = 'addresses' | ||||
|     interfaces = set() | ||||
|  | ||||
|     def add_address(self, atype='to', jid='', node='', uri='', | ||||
|                           desc='', delivered=False): | ||||
|         addr = Address(parent=self) | ||||
|         addr['type'] = atype | ||||
|         addr['jid'] = jid | ||||
|         addr['node'] = node | ||||
|         addr['uri'] = uri | ||||
|         addr['desc'] = desc | ||||
|         addr['delivered'] = delivered | ||||
|  | ||||
|         return addr | ||||
|  | ||||
|     # Additional methods for manipulating sets of addresses | ||||
|     # based on type are generated below. | ||||
|  | ||||
|  | ||||
| class Address(ElementBase): | ||||
|  | ||||
|     name = 'address' | ||||
|     namespace = 'http://jabber.org/protocol/address' | ||||
|     plugin_attrib = 'address' | ||||
|     interfaces = set(['type', 'jid', 'node', 'uri', 'desc', 'delivered']) | ||||
|  | ||||
|     address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to')) | ||||
|  | ||||
|     def get_jid(self): | ||||
|         return JID(self._get_attr('jid')) | ||||
|  | ||||
|     def set_jid(self, value): | ||||
|         self._set_attr('jid', str(value)) | ||||
|  | ||||
|     def get_delivered(self): | ||||
|         value = self._get_attr('delivered', False) | ||||
|         return value and value.lower() in ('true', '1') | ||||
|  | ||||
|     def set_delivered(self, delivered): | ||||
|         if delivered: | ||||
|             self._set_attr('delivered', 'true') | ||||
|         else: | ||||
|             del self['delivered'] | ||||
|  | ||||
|     def set_uri(self, uri): | ||||
|         if uri: | ||||
|             del self['jid'] | ||||
|             del self['node'] | ||||
|             self._set_attr('uri', uri) | ||||
|         else: | ||||
|             self._del_attr('uri') | ||||
|  | ||||
|  | ||||
| # ===================================================================== | ||||
| # Auto-generate address type filters for the Addresses class. | ||||
|  | ||||
| def _addr_filter(atype): | ||||
|     def _type_filter(addr): | ||||
|         if isinstance(addr, Address): | ||||
|             if atype == 'all' or addr['type'] == atype: | ||||
|                 return True | ||||
|         return False | ||||
|     return _type_filter | ||||
|  | ||||
|  | ||||
| def _build_methods(atype): | ||||
|  | ||||
|     def get_multi(self): | ||||
|         return list(filter(_addr_filter(atype), self)) | ||||
|  | ||||
|     def set_multi(self, value): | ||||
|         del self[atype] | ||||
|         for addr in value: | ||||
|  | ||||
|             # Support assigning dictionary versions of addresses | ||||
|             # instead of full Address objects. | ||||
|             if not isinstance(addr, Address): | ||||
|                 if atype != 'all': | ||||
|                     addr['type'] = atype | ||||
|                 elif 'atype' in addr and 'type' not in addr: | ||||
|                     addr['type'] = addr['atype'] | ||||
|                 addrObj = Address() | ||||
|                 addrObj.values = addr | ||||
|                 addr = addrObj | ||||
|  | ||||
|             self.append(addr) | ||||
|  | ||||
|     def del_multi(self): | ||||
|         res = list(filter(_addr_filter(atype), self)) | ||||
|         for addr in res: | ||||
|             self.iterables.remove(addr) | ||||
|             self.xml.remove(addr.xml) | ||||
|  | ||||
|     return get_multi, set_multi, del_multi | ||||
|  | ||||
|  | ||||
| for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'): | ||||
|     get_multi, set_multi, del_multi = _build_methods(atype) | ||||
|  | ||||
|     Addresses.interfaces.add(atype) | ||||
|     setattr(Addresses, "get_%s" % atype, get_multi) | ||||
|     setattr(Addresses, "set_%s" % atype, set_multi) | ||||
|     setattr(Addresses, "del_%s" % atype, del_multi) | ||||
|  | ||||
|     # To retain backwards compatibility: | ||||
|     setattr(Addresses, "get%s" % atype.title(), get_multi) | ||||
|     setattr(Addresses, "set%s" % atype.title(), set_multi) | ||||
|     setattr(Addresses, "del%s" % atype.title(), del_multi) | ||||
|     if atype == 'all': | ||||
|         Addresses.interfaces.add('addresses') | ||||
|         setattr(Addresses, "getAddresses", get_multi) | ||||
|         setattr(Addresses, "setAddresses", set_multi) | ||||
|         setattr(Addresses, "delAddresses", del_multi) | ||||
|  | ||||
|  | ||||
| register_stanza_plugin(Addresses, 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