Compare commits
	
		
			514 Commits
		
	
	
		
			1.1.5
			...
			sleek-1.2.
		
	
	| 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 | ||
| 
						 | 
					f89df6e70c | ||
| 
						 | 
					250d28e870 | ||
| 
						 | 
					19f65c8510 | ||
| 
						 | 
					f70b49882f | ||
| 
						 | 
					a7b092a305 | ||
| 
						 | 
					daa73a3f3c | ||
| 
						 | 
					0b51afe87a | ||
| 
						 | 
					2b298766c9 | ||
| 
						 | 
					10664d723b | ||
| 
						 | 
					c012208a8f | ||
| 
						 | 
					0953896d2d | ||
| 
						 | 
					cf9e89d0ae | ||
| 
						 | 
					48dd01b0bb | ||
| 
						 | 
					7247efe055 | ||
| 
						 | 
					8def3758e4 | ||
| 
						 | 
					1851ab6f5f | ||
| 
						 | 
					289b052338 | ||
| 
						 | 
					26147f5ae0 | ||
| 
						 | 
					ae01f1071a | ||
| 
						 | 
					dcdf5dcd09 | ||
| 
						 | 
					c59a6d0f51 | ||
| 
						 | 
					2cd936318d | ||
| 
						 | 
					2f38857681 | ||
| 
						 | 
					39505ae1ff | ||
| 
						 | 
					44ee0633f2 | ||
| 
						 | 
					b52d2768b0 | ||
| 
						 | 
					cf24b870b1 | ||
| 
						 | 
					69cffce7dc | ||
| 
						 | 
					a14979375b | ||
| 
						 | 
					40ef4a16b1 | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
*.pyc
 | 
			
		||||
*.py[co]
 | 
			
		||||
build/
 | 
			
		||||
dist/
 | 
			
		||||
MANIFEST
 | 
			
		||||
@@ -7,3 +7,8 @@ docs/_build/
 | 
			
		||||
.tox/
 | 
			
		||||
.coverage
 | 
			
		||||
sleekxmpp.egg-info/
 | 
			
		||||
.ropeproject/
 | 
			
		||||
4913
 | 
			
		||||
*~
 | 
			
		||||
.baboon/
 | 
			
		||||
.DS_STORE
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								LICENSE
									
									
									
									
									
								
							@@ -69,8 +69,8 @@ modification, are permitted provided that the following conditions are met:
 | 
			
		||||
    * Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
      notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
      documentation and/or other materials provided with the distribution.
 | 
			
		||||
    * Neither the name of Red Innovation nor the names of its contributors 
 | 
			
		||||
      may be used to endorse or promote products derived from this software 
 | 
			
		||||
    * Neither the name of Red Innovation nor the names of its contributors
 | 
			
		||||
      may be used to endorse or promote products derived from this software
 | 
			
		||||
      without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
 | 
			
		||||
@@ -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.5 <http://github.com/fritzy/SleekXMPP/zipball/1.1.5>`_
 | 
			
		||||
    - `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,8 +71,8 @@ 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.
 | 
			
		||||
 | 
			
		||||
    entity_time
 | 
			
		||||
@@ -93,16 +99,16 @@ 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``',
 | 
			
		||||
        or '``xa``', then this event is triggered as well.
 | 
			
		||||
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
    groupchat_invite
 | 
			
		||||
@@ -110,7 +116,7 @@ Event Index
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
    groupchat_direct_invite
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Message` 
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.Message`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
 | 
			
		||||
 | 
			
		||||
    groupchat_message
 | 
			
		||||
@@ -147,18 +153,18 @@ Event Index
 | 
			
		||||
        sure to check the message type in order to handle error messages.
 | 
			
		||||
 | 
			
		||||
    message_form
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
			
		||||
        
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
 | 
			
		||||
 | 
			
		||||
        Currently the same as :term:`message_xform`.
 | 
			
		||||
 | 
			
		||||
    message_xform
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
			
		||||
        
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
 | 
			
		||||
 | 
			
		||||
        Triggered whenever a data form is received inside a message.
 | 
			
		||||
 | 
			
		||||
    mucc::[room]::got_offline
 | 
			
		||||
    muc::[room]::got_offline
 | 
			
		||||
        - **Data:**
 | 
			
		||||
        - **Source:**
 | 
			
		||||
 | 
			
		||||
@@ -187,8 +193,8 @@ Event Index
 | 
			
		||||
        A presence stanza with a type of '``error``' is received.
 | 
			
		||||
 | 
			
		||||
    presence_form
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
 | 
			
		||||
        
 | 
			
		||||
        This event is present in the XEP-0004 plugin code, but is currently not used.
 | 
			
		||||
 | 
			
		||||
@@ -229,22 +235,20 @@ Event Index
 | 
			
		||||
        A presence stanza with a type of '``unsubscribed``' is received.
 | 
			
		||||
 | 
			
		||||
    roster_update
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.stanza.Roster` 
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.ClientXMPP` 
 | 
			
		||||
        - **Data:** :py:class:`~sleekxmpp.stanza.Roster`
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.ClientXMPP`
 | 
			
		||||
        
 | 
			
		||||
        An IQ result containing roster entries is received.
 | 
			
		||||
 | 
			
		||||
    sent_presence
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
 | 
			
		||||
        - **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
 | 
			
		||||
@@ -256,14 +260,14 @@ Event Index
 | 
			
		||||
 | 
			
		||||
    session_start
 | 
			
		||||
        - **Data:** ``{}``
 | 
			
		||||
        - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`, 
 | 
			
		||||
        - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
 | 
			
		||||
          :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
 | 
			
		||||
          :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
 | 
			
		||||
 | 
			
		||||
        Signal that a connection to the XMPP server has been made and a session has been established.
 | 
			
		||||
 | 
			
		||||
    socket_error
 | 
			
		||||
        - **Data:** ``Socket`` exception object 
 | 
			
		||||
        - **Data:** ``Socket`` exception object
 | 
			
		||||
        - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
 | 
			
		||||
 | 
			
		||||
    stream_error
 | 
			
		||||
 
 | 
			
		||||
@@ -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,13 +51,13 @@ 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))
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('custom_action', 
 | 
			
		||||
                self._handle_action_event, 
 | 
			
		||||
        self.add_event_handler('custom_action',
 | 
			
		||||
                self._handle_action_event,
 | 
			
		||||
                threaded=True)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, 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,50 +84,54 @@ class Disco(sleekxmpp.ClientXMPP):
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
 | 
			
		||||
        if self.get in self.info_types:
 | 
			
		||||
            # By using block=True, the result stanza will be
 | 
			
		||||
            # returned. Execution will block until the reply is
 | 
			
		||||
            # received. Non-blocking options would be to listen
 | 
			
		||||
            # for the disco_info event, or passing a handler
 | 
			
		||||
            # function using the callback parameter.
 | 
			
		||||
            info = self['xep_0030'].get_info(jid=self.target_jid,
 | 
			
		||||
                                             node=self.target_node,
 | 
			
		||||
                                             block=True)
 | 
			
		||||
        if self.get in self.items_types:
 | 
			
		||||
            # The same applies from above. Listen for the
 | 
			
		||||
            # disco_items event or pass a callback function
 | 
			
		||||
            # if you need to process a non-blocking request.
 | 
			
		||||
            items = self['xep_0030'].get_items(jid=self.target_jid,
 | 
			
		||||
                                               node=self.target_node,
 | 
			
		||||
                                               block=True)
 | 
			
		||||
        try:
 | 
			
		||||
            if self.get in self.info_types:
 | 
			
		||||
                # By using block=True, the result stanza will be
 | 
			
		||||
                # returned. Execution will block until the reply is
 | 
			
		||||
                # received. Non-blocking options would be to listen
 | 
			
		||||
                # for the disco_info event, or passing a handler
 | 
			
		||||
                # function using the callback parameter.
 | 
			
		||||
                info = self['xep_0030'].get_info(jid=self.target_jid,
 | 
			
		||||
                                                 node=self.target_node,
 | 
			
		||||
                                                 block=True)
 | 
			
		||||
            elif self.get in self.items_types:
 | 
			
		||||
                # The same applies from above. Listen for the
 | 
			
		||||
                # disco_items event or pass a callback function
 | 
			
		||||
                # if you need to process a non-blocking request.
 | 
			
		||||
                items = self['xep_0030'].get_items(jid=self.target_jid,
 | 
			
		||||
                                                   node=self.target_node,
 | 
			
		||||
                                                   block=True)
 | 
			
		||||
            else:
 | 
			
		||||
                logging.error("Invalid disco request type.")
 | 
			
		||||
                return
 | 
			
		||||
        except IqError as e:
 | 
			
		||||
            logging.error("Entity returned an error: %s" % e.iq['error']['condition'])
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            logging.error("No response received.")
 | 
			
		||||
        else:
 | 
			
		||||
            logging.error("Invalid disco request type.")
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        header = 'XMPP Service Discovery: %s' % self.target_jid
 | 
			
		||||
        print(header)
 | 
			
		||||
        print('-' * len(header))
 | 
			
		||||
        if self.target_node != '':
 | 
			
		||||
            print('Node: %s' % self.target_node)
 | 
			
		||||
            header = 'XMPP Service Discovery: %s' % self.target_jid
 | 
			
		||||
            print(header)
 | 
			
		||||
            print('-' * len(header))
 | 
			
		||||
            if self.target_node != '':
 | 
			
		||||
                print('Node: %s' % self.target_node)
 | 
			
		||||
                print('-' * len(header))
 | 
			
		||||
 | 
			
		||||
        if self.get in self.identity_types:
 | 
			
		||||
            print('Identities:')
 | 
			
		||||
            for identity in info['disco_info']['identities']:
 | 
			
		||||
                print('  - %s' % str(identity))
 | 
			
		||||
            if self.get in self.identity_types:
 | 
			
		||||
                print('Identities:')
 | 
			
		||||
                for identity in info['disco_info']['identities']:
 | 
			
		||||
                    print('  - %s' % str(identity))
 | 
			
		||||
 | 
			
		||||
        if self.get in self.feature_types:
 | 
			
		||||
            print('Features:')
 | 
			
		||||
            for feature in info['disco_info']['features']:
 | 
			
		||||
                print('  - %s' % feature)
 | 
			
		||||
            if self.get in self.feature_types:
 | 
			
		||||
                print('Features:')
 | 
			
		||||
                for feature in info['disco_info']['features']:
 | 
			
		||||
                    print('  - %s' % feature)
 | 
			
		||||
 | 
			
		||||
        if self.get in self.items_types:
 | 
			
		||||
            print('Items:')
 | 
			
		||||
            for item in items['disco_items']['items']:
 | 
			
		||||
                print('  - %s' % str(item))
 | 
			
		||||
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
            if self.get in self.items_types:
 | 
			
		||||
                print('Items:')
 | 
			
		||||
                for item in items['disco_items']['items']:
 | 
			
		||||
                    print('  - %s' % str(item))
 | 
			
		||||
        finally:
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								examples/pubsub_client.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										11
									
								
								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,15 +15,15 @@ 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PubsubClient(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, server, 
 | 
			
		||||
    def __init__(self, jid, password, server,
 | 
			
		||||
                       node=None, action='list', data=''):
 | 
			
		||||
        super(PubsubClient, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
@@ -28,7 +31,7 @@ class PubsubClient(sleekxmpp.ClientXMPP):
 | 
			
		||||
        self.register_plugin('xep_0059')
 | 
			
		||||
        self.register_plugin('xep_0060')
 | 
			
		||||
 | 
			
		||||
        self.actions = ['nodes', 'create', 'delete', 
 | 
			
		||||
        self.actions = ['nodes', 'create', 'delete',
 | 
			
		||||
                        'publish', 'get', 'retract',
 | 
			
		||||
                        'purge', 'subscribe', 'unsubscribe']
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								examples/pubsub_events.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										9
									
								
								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
 | 
			
		||||
 | 
			
		||||
@@ -77,7 +80,7 @@ class PubsubEvents(sleekxmpp.ClientXMPP):
 | 
			
		||||
        """Handle receiving a node deletion event."""
 | 
			
		||||
        print('Deleted node %s' % (
 | 
			
		||||
           msg['pubsub_event']['delete']['node']))
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
    def _config(self, msg):
 | 
			
		||||
        """Handle receiving a node configuration event."""
 | 
			
		||||
        print('Configured node %s:' % (
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								examples/register_account.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										12
									
								
								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
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +51,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        # The register event provides an Iq result stanza with
 | 
			
		||||
        # a registration form from the server. This may include
 | 
			
		||||
        # the basic registration fields, a data form, an 
 | 
			
		||||
        # the basic registration fields, a data form, an
 | 
			
		||||
        # out-of-band URL, or any combination. For more advanced
 | 
			
		||||
        # cases, you will need to examine the fields provided
 | 
			
		||||
        # and respond accordingly. SleekXMPP provides plugins
 | 
			
		||||
@@ -104,7 +104,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
            resp.send(now=True)
 | 
			
		||||
            logging.info("Account created for %s!" % self.boundjid)
 | 
			
		||||
        except IqError as e:
 | 
			
		||||
            logging.error("Could not register account: %s" % 
 | 
			
		||||
            logging.error("Could not register account: %s" %
 | 
			
		||||
                    e.iq['error']['text'])
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								examples/rpc_async.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										29
									
								
								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
 | 
			
		||||
@@ -11,34 +14,34 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
class Boomerang(Endpoint):
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'boomerang'
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
    @remote
 | 
			
		||||
    def throw(self):
 | 
			
		||||
        print "Duck!"
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
 | 
			
		||||
    session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
 | 
			
		||||
 | 
			
		||||
    session.new_handler(ANY_ALL, Boomerang)    
 | 
			
		||||
    
 | 
			
		||||
    session.new_handler(ANY_ALL, Boomerang)
 | 
			
		||||
 | 
			
		||||
    boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    callback = Future()
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    boomerang.async(callback).throw()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    time.sleep(10)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    session.close()
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								examples/rpc_client_side.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										29
									
								
								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
 | 
			
		||||
@@ -12,18 +15,18 @@ import threading
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
class Thermostat(Endpoint):
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'thermostat'
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def __init__(self, initial_temperature):
 | 
			
		||||
        self._temperature = initial_temperature
 | 
			
		||||
        self._event = threading.Event()        
 | 
			
		||||
    
 | 
			
		||||
        self._event = threading.Event()
 | 
			
		||||
 | 
			
		||||
    @remote
 | 
			
		||||
    def set_temperature(self, temperature):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    @remote
 | 
			
		||||
    def get_temperature(self):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
@@ -31,23 +34,23 @@ class Thermostat(Endpoint):
 | 
			
		||||
    @remote(False)
 | 
			
		||||
    def release(self):
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
 | 
			
		||||
    session = Remote.new_session('operator@xmpp.org/rpc', '*****')
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    print("Current temperature is %s" % thermostat.get_temperature())
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    thermostat.set_temperature(20)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    time.sleep(10)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    session.close()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								examples/rpc_server_side.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										31
									
								
								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
 | 
			
		||||
@@ -11,42 +14,42 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
			
		||||
import threading
 | 
			
		||||
 | 
			
		||||
class Thermostat(Endpoint):
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'thermostat'
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def __init__(self, initial_temperature):
 | 
			
		||||
        self._temperature = initial_temperature
 | 
			
		||||
        self._event = threading.Event()        
 | 
			
		||||
    
 | 
			
		||||
        self._event = threading.Event()
 | 
			
		||||
 | 
			
		||||
    @remote
 | 
			
		||||
    def set_temperature(self, temperature):
 | 
			
		||||
        print("Setting temperature to %s" % temperature)
 | 
			
		||||
        self._temperature = temperature
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    @remote
 | 
			
		||||
    def get_temperature(self):
 | 
			
		||||
        return self._temperature
 | 
			
		||||
 | 
			
		||||
    @remote(False)
 | 
			
		||||
    def release(self):
 | 
			
		||||
        self._event.set() 
 | 
			
		||||
        
 | 
			
		||||
        self._event.set()
 | 
			
		||||
 | 
			
		||||
    def wait_for_release(self):
 | 
			
		||||
        self._event.wait()        
 | 
			
		||||
    
 | 
			
		||||
        self._event.wait()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
 | 
			
		||||
    session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    thermostat.wait_for_release()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    session.close()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
@@ -42,7 +42,7 @@ class APIRegistry(object):
 | 
			
		||||
        self._handlers = {}
 | 
			
		||||
        self._handler_defaults = {}
 | 
			
		||||
        self.xmpp = xmpp
 | 
			
		||||
        self.settings = {} 
 | 
			
		||||
        self.settings = {}
 | 
			
		||||
 | 
			
		||||
    def _setup(self, ctype, op):
 | 
			
		||||
        """Initialize the API callback dictionaries.
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -138,8 +140,8 @@ class APIRegistry(object):
 | 
			
		||||
        """Register an API callback, with JID+node specificity.
 | 
			
		||||
 | 
			
		||||
        The API callback can later be executed based on the
 | 
			
		||||
        specificity of the provided JID+node combination. 
 | 
			
		||||
        
 | 
			
		||||
        specificity of the provided JID+node combination.
 | 
			
		||||
 | 
			
		||||
        See :meth:`~ApiRegistry.run` for more details.
 | 
			
		||||
 | 
			
		||||
        :param string ctype: The name of the API to use.
 | 
			
		||||
 
 | 
			
		||||
@@ -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,23 +99,34 @@ 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:
 | 
			
		||||
        #: 
 | 
			
		||||
        #:
 | 
			
		||||
        #:   - An API name, e.g. xep_0030
 | 
			
		||||
        #:   - The name of an action, e.g. get_info
 | 
			
		||||
        #:   - The JID that will be affected
 | 
			
		||||
@@ -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.
 | 
			
		||||
@@ -199,20 +231,13 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
                    Defaults to ``True``. This does **not** mean that no
 | 
			
		||||
                    threads are used at all if ``threaded=False``.
 | 
			
		||||
 | 
			
		||||
        Regardless of these threading options, these threads will 
 | 
			
		||||
        Regardless of these threading options, these threads will
 | 
			
		||||
        always exist:
 | 
			
		||||
 | 
			
		||||
        - The event queue processor
 | 
			
		||||
        - 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,18 +307,20 @@ 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.
 | 
			
		||||
 | 
			
		||||
        :param id: An ideally unique ID value for this stanza thread.
 | 
			
		||||
                   Defaults to 0.
 | 
			
		||||
        :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID` 
 | 
			
		||||
        :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                      to use for this stanza.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
        :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type, 
 | 
			
		||||
        :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
 | 
			
		||||
                      one of: ``'get'``, ``'set'``, ``'result'``,
 | 
			
		||||
                      or ``'error'``.
 | 
			
		||||
        :param iquery: Optional namespace for adding a query element.
 | 
			
		||||
@@ -329,7 +358,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type 
 | 
			
		||||
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
 | 
			
		||||
        ``'result'`` with the given ID value.
 | 
			
		||||
 | 
			
		||||
        :param id: An ideally unique ID value. May use :meth:`new_id()`.
 | 
			
		||||
@@ -359,10 +388,10 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        Optionally, a substanza may be given to use as the
 | 
			
		||||
        stanza's payload.
 | 
			
		||||
 | 
			
		||||
        :param sub: Either an 
 | 
			
		||||
        :param sub: Either an
 | 
			
		||||
                    :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
 | 
			
		||||
                    stanza object or an
 | 
			
		||||
                    :class:`~xml.etree.ElementTree.Element` XML object 
 | 
			
		||||
                    :class:`~xml.etree.ElementTree.Element` XML object
 | 
			
		||||
                    to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
@@ -389,9 +418,9 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
 | 
			
		||||
 | 
			
		||||
        :param id: An ideally unique ID value. May use :meth:`new_id()`.
 | 
			
		||||
        :param type: The type of the error, such as ``'cancel'`` or 
 | 
			
		||||
        :param type: The type of the error, such as ``'cancel'`` or
 | 
			
		||||
                     ``'modify'``. Defaults to ``'cancel'``.
 | 
			
		||||
        :param condition: The error condition. Defaults to 
 | 
			
		||||
        :param condition: The error condition. Defaults to
 | 
			
		||||
                          ``'feature-not-implemented'``.
 | 
			
		||||
        :param text: A message describing the cause of the error.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
@@ -415,7 +444,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza 
 | 
			
		||||
        Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
 | 
			
		||||
        to use the given query namespace.
 | 
			
		||||
 | 
			
		||||
        :param iq: Optionally use an existing stanza instead
 | 
			
		||||
@@ -448,7 +477,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def make_message(self, mto, mbody=None, msubject=None, mtype=None,
 | 
			
		||||
                     mhtml=None, mfrom=None, mnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create and initialize a new 
 | 
			
		||||
        Create and initialize a new
 | 
			
		||||
        :class:`~sleekxmpp.stanza.message.Message` stanza.
 | 
			
		||||
 | 
			
		||||
        :param mto: The recipient of the message.
 | 
			
		||||
@@ -474,7 +503,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def make_presence(self, pshow=None, pstatus=None, ppriority=None,
 | 
			
		||||
                      pto=None, ptype=None, pfrom=None, pnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create and initialize a new 
 | 
			
		||||
        Create and initialize a new
 | 
			
		||||
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.
 | 
			
		||||
 | 
			
		||||
        :param pshow: The presence's show value.
 | 
			
		||||
@@ -498,7 +527,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def send_message(self, mto, mbody, msubject=None, mtype=None,
 | 
			
		||||
                     mhtml=None, mfrom=None, mnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a new 
 | 
			
		||||
        Create, initialize, and send a new
 | 
			
		||||
        :class:`~sleekxmpp.stanza.message.Message` stanza.
 | 
			
		||||
 | 
			
		||||
        :param mto: The recipient of the message.
 | 
			
		||||
@@ -518,7 +547,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def send_presence(self, pshow=None, pstatus=None, ppriority=None,
 | 
			
		||||
                      pto=None, pfrom=None, ptype=None, pnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a new 
 | 
			
		||||
        Create, initialize, and send a new
 | 
			
		||||
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.
 | 
			
		||||
 | 
			
		||||
        :param pshow: The presence's show value.
 | 
			
		||||
@@ -529,23 +558,13 @@ 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):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a new 
 | 
			
		||||
        Create, initialize, and send a new
 | 
			
		||||
        :class:`~sleekxmpp.stanza.presence.Presence` stanza of
 | 
			
		||||
        type ``'subscribe'``.
 | 
			
		||||
 | 
			
		||||
@@ -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,
 | 
			
		||||
                                     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()
 | 
			
		||||
        self.make_presence(ptype=ptype,
 | 
			
		||||
                           pfrom=pfrom,
 | 
			
		||||
                           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.
 | 
			
		||||
@@ -750,7 +788,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    def exception(self, exception):
 | 
			
		||||
        """Process any uncaught exceptions, notably 
 | 
			
		||||
        """Process any uncaught exceptions, notably
 | 
			
		||||
        :class:`~sleekxmpp.exceptions.IqError` and
 | 
			
		||||
        :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -54,30 +54,32 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
    :param password: The password for the XMPP user account.
 | 
			
		||||
    :param ssl: **Deprecated.**
 | 
			
		||||
    :param plugin_config: A dictionary of plugin configurations.
 | 
			
		||||
    :param plugin_whitelist: A list of approved plugins that 
 | 
			
		||||
                    will be loaded when calling 
 | 
			
		||||
    :param plugin_whitelist: A list of approved plugins that
 | 
			
		||||
                    will be loaded when calling
 | 
			
		||||
                    :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
 | 
			
		||||
    :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.
 | 
			
		||||
@@ -186,18 +195,28 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                      occurs. Defaults to ``True``.
 | 
			
		||||
        :param timeout: The length of time (in seconds) to wait
 | 
			
		||||
                        for a response before continuing if blocking
 | 
			
		||||
                        is used. Defaults to 
 | 
			
		||||
                        is used. Defaults to
 | 
			
		||||
            :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
 | 
			
		||||
        :param callback: Optional reference to a stream handler function.
 | 
			
		||||
                         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)
 | 
			
		||||
 | 
			
		||||
    def del_roster_item(self, jid):
 | 
			
		||||
        """Remove an item from the roster.
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        This is done by setting its subscription status to ``'remove'``.
 | 
			
		||||
 | 
			
		||||
        :param jid: The JID of the item to remove.
 | 
			
		||||
@@ -212,7 +231,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                      Defaults to ``True``.
 | 
			
		||||
        :param timeout: The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before continuing if blocking is used.
 | 
			
		||||
                        Defaults to 
 | 
			
		||||
                        Defaults to
 | 
			
		||||
            :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
 | 
			
		||||
        :param callback: Optional reference to a stream handler function. Will
 | 
			
		||||
                         be executed when the roster is received.
 | 
			
		||||
@@ -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)
 | 
			
		||||
        if block:
 | 
			
		||||
            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]
 | 
			
		||||
            roster[jid]['name'] = item['name']
 | 
			
		||||
            roster[jid]['groups'] = item['groups']
 | 
			
		||||
            roster[jid]['from'] = item['subscription'] in ['from', 'both']
 | 
			
		||||
            roster[jid]['to'] = item['subscription'] in ['to', 'both']
 | 
			
		||||
            roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
 | 
			
		||||
        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]['pending_out'] = (item['ask'] == 'subscribe')
 | 
			
		||||
 | 
			
		||||
                roster[jid].save(remove=(item['subscription'] == 'remove'))
 | 
			
		||||
 | 
			
		||||
            roster[jid].save(remove=(item['subscription'] == 'remove'))
 | 
			
		||||
                 
 | 
			
		||||
        self.event("roster_update", iq)
 | 
			
		||||
        if iq['type'] == 'set':
 | 
			
		||||
            resp = self.Iq(stype='result',
 | 
			
		||||
                           sto=iq['from'],
 | 
			
		||||
 
 | 
			
		||||
@@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
    :param host: The server accepting the component.
 | 
			
		||||
    :param port: The port used to connect to the server.
 | 
			
		||||
    :param plugin_config: A dictionary of plugin configurations.
 | 
			
		||||
    :param plugin_whitelist: A list of approved plugins that 
 | 
			
		||||
                    will be loaded when calling 
 | 
			
		||||
    :param plugin_whitelist: A list of approved plugins that
 | 
			
		||||
                    will be loaded when calling
 | 
			
		||||
                    :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
 | 
			
		||||
    :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
 | 
			
		||||
                      should be used instead of the standard
 | 
			
		||||
@@ -78,7 +78,7 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        self.add_event_handler('presence_probe',
 | 
			
		||||
                               self._handle_probe)
 | 
			
		||||
 | 
			
		||||
    def connect(self, host=None, port=None, use_ssl=False, 
 | 
			
		||||
    def connect(self, host=None, port=None, use_ssl=False,
 | 
			
		||||
                      use_tls=False, reattempt=True):
 | 
			
		||||
        """Connect to the server.
 | 
			
		||||
 | 
			
		||||
@@ -123,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):
 | 
			
		||||
@@ -156,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:
 | 
			
		||||
@@ -69,10 +69,11 @@ class IqTimeout(XMPPError):
 | 
			
		||||
                condition='remote-server-timeout',
 | 
			
		||||
                etype='cancel')
 | 
			
		||||
 | 
			
		||||
        #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response 
 | 
			
		||||
        #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
 | 
			
		||||
        #: did not arrive before the timeout expired.
 | 
			
		||||
        self.iq = iq
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IqError(XMPPError):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,10 @@
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'feature_starttls', 
 | 
			
		||||
    'feature_mechanisms', 
 | 
			
		||||
    'feature_bind', 
 | 
			
		||||
    'feature_starttls',
 | 
			
		||||
    '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:
 | 
			
		||||
            self.mech_list = set(features['mechanisms'])
 | 
			
		||||
        else:
 | 
			
		||||
            self.mech_list = set([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'])
 | 
			
		||||
 | 
			
		||||
        return self._send_auth()
 | 
			
		||||
 | 
			
		||||
    def _send_auth(self):
 | 
			
		||||
        mech_list = self.mech_list - self.attempted_mechs
 | 
			
		||||
        self.mech = self.sasl.choose_mechanism(mech_list)
 | 
			
		||||
 | 
			
		||||
        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:
 | 
			
		||||
                self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
                self._send_auth()
 | 
			
		||||
            except SASLError:
 | 
			
		||||
                self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
                self._send_auth()
 | 
			
		||||
            except SASLPrepFailure:
 | 
			
		||||
                log.exception("A credential value did not pass SASLprep.")
 | 
			
		||||
                self.xmpp.disconnect()
 | 
			
		||||
            else:
 | 
			
		||||
                resp.send(now=True)
 | 
			
		||||
        else:
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
        resp = stanza.Auth(self.xmpp)
 | 
			
		||||
        resp['mechanism'] = self.mech.name
 | 
			
		||||
        try:
 | 
			
		||||
            resp['value'] = self.mech.process()
 | 
			
		||||
        except sasl.SASLCancelled:
 | 
			
		||||
            self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
            self._send_auth()
 | 
			
		||||
        except sasl.SASLFailed:
 | 
			
		||||
            self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
            self._send_auth()
 | 
			
		||||
        except sasl.SASLMutualAuthFailed:
 | 
			
		||||
            log.error("Mutual authentication failed! " + \
 | 
			
		||||
                      "A security breach is possible.")
 | 
			
		||||
            self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
            self.xmpp.disconnect()
 | 
			
		||||
        else:
 | 
			
		||||
            resp.send(now=True)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _handle_challenge(self, stanza):
 | 
			
		||||
@@ -147,20 +205,33 @@ 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."""
 | 
			
		||||
        self.attempted_mechs = set()
 | 
			
		||||
        self.xmpp.authenticated = True
 | 
			
		||||
        self.xmpp.features.add('mechanisms')
 | 
			
		||||
        self.xmpp.event('auth_success', stanza, direct=True)
 | 
			
		||||
        raise RestartStream()
 | 
			
		||||
        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')
 | 
			
		||||
            self.xmpp.event('auth_success', stanza, direct=True)
 | 
			
		||||
            raise RestartStream()
 | 
			
		||||
 | 
			
		||||
    def _handle_fail(self, stanza):
 | 
			
		||||
        """SASL authentication failed. Disconnect and shutdown."""
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,7 @@
 | 
			
		||||
 | 
			
		||||
import base64
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty.suelta.util import bytes
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.util import bytes
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +22,7 @@ class Auth(StanzaBase):
 | 
			
		||||
    interfaces = set(('mechanism', 'value'))
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
 | 
			
		||||
    #: Some SASL mechs require sending values as is, 
 | 
			
		||||
    #: Some SASL mechs require sending values as is,
 | 
			
		||||
    #: without converting base64.
 | 
			
		||||
    plain_mechs = set(['X-MESSENGER-OAUTH2'])
 | 
			
		||||
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
@@ -31,10 +32,10 @@ log = logging.getLogger(__name__)
 | 
			
		||||
PLUGIN_REGISTRY = {}
 | 
			
		||||
 | 
			
		||||
#: In order to do cascading plugin disabling, reverse dependencies
 | 
			
		||||
#: must be tracked. 
 | 
			
		||||
#: must be tracked.
 | 
			
		||||
PLUGIN_DEPENDENTS = {}
 | 
			
		||||
 | 
			
		||||
#: Only allow one thread to manipulate the plugin registry at a time. 
 | 
			
		||||
#: Only allow one thread to manipulate the plugin registry at a time.
 | 
			
		||||
REGISTRY_LOCK = threading.RLock()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -75,7 +76,7 @@ def load_plugin(name, module=None):
 | 
			
		||||
                     plugins are in packages matching their name,
 | 
			
		||||
                     even though the plugin class name does not
 | 
			
		||||
                     have to match.
 | 
			
		||||
    :param str module: The name of the base module to search 
 | 
			
		||||
    :param str module: The name of the base module to search
 | 
			
		||||
                       for the plugin.
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
@@ -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,11 +104,11 @@ 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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PluginManager(object): 
 | 
			
		||||
class PluginManager(object):
 | 
			
		||||
    def __init__(self, xmpp, config=None):
 | 
			
		||||
        #: We will track all enabled plugins in a set so that we
 | 
			
		||||
        #: can enable plugins in batches and pull in dependencies
 | 
			
		||||
@@ -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:
 | 
			
		||||
@@ -181,7 +181,7 @@ class PluginManager(object):
 | 
			
		||||
 | 
			
		||||
    def enable_all(self, names=None, config=None):
 | 
			
		||||
        """Enable all registered plugins.
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        :param list names: A list of plugin names to enable. If
 | 
			
		||||
                           none are provided, all registered plugins
 | 
			
		||||
                           will be 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,9 +345,13 @@ 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.
 | 
			
		||||
       
 | 
			
		||||
 | 
			
		||||
        Only needed if the plugin has circular dependencies.
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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,7 +201,8 @@ class Form(ElementBase):
 | 
			
		||||
        del self['instructions']
 | 
			
		||||
        if instructions in [None, '']:
 | 
			
		||||
            return
 | 
			
		||||
        instructions = instructions.split('\n')
 | 
			
		||||
        if not isinstance(instructions, list):
 | 
			
		||||
            instructions = instructions.split('\n')
 | 
			
		||||
        for instruction in instructions:
 | 
			
		||||
            inst = ET.Element('{%s}instructions' % self.namespace)
 | 
			
		||||
            inst.text = instruction
 | 
			
		||||
@@ -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,24 +324,27 @@ 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 jid is not None and not isinstance(jid, JID):
 | 
			
		||||
            jid = JID(jid)
 | 
			
		||||
            if self.xmpp.is_component:
 | 
			
		||||
                if jid.domain == self.xmpp.boundjid.domain:
 | 
			
		||||
                    local = True
 | 
			
		||||
            else:
 | 
			
		||||
                if str(jid) == str(self.xmpp.boundjid):
 | 
			
		||||
                    local = True
 | 
			
		||||
            jid = jid.full
 | 
			
		||||
        elif jid in (None, ''):
 | 
			
		||||
            local = True
 | 
			
		||||
        if local is None:
 | 
			
		||||
            if jid is not None and not isinstance(jid, JID):
 | 
			
		||||
                jid = JID(jid)
 | 
			
		||||
                if self.xmpp.is_component:
 | 
			
		||||
                    if jid.domain == self.xmpp.boundjid.domain:
 | 
			
		||||
                        local = True
 | 
			
		||||
                else:
 | 
			
		||||
                    if str(jid) == str(self.xmpp.boundjid):
 | 
			
		||||
                        local = True
 | 
			
		||||
                jid = jid.full
 | 
			
		||||
            elif jid in (None, ''):
 | 
			
		||||
                local = True
 | 
			
		||||
 | 
			
		||||
        if local:
 | 
			
		||||
            log.debug("Looking up local disco#info data " + \
 | 
			
		||||
                      "for %s, node %s.", jid, node)
 | 
			
		||||
            info = self.api['get_info'](jid, node, 
 | 
			
		||||
                    kwargs.get('ifrom', None), 
 | 
			
		||||
            info = self.api['get_info'](jid, node,
 | 
			
		||||
                    kwargs.get('ifrom', None),
 | 
			
		||||
                    kwargs)
 | 
			
		||||
            info = self._fix_default_info(info)
 | 
			
		||||
            return self._wrap(kwargs.get('ifrom', None), jid, info)
 | 
			
		||||
@@ -348,8 +352,8 @@ class XEP_0030(BasePlugin):
 | 
			
		||||
        if cached:
 | 
			
		||||
            log.debug("Looking up cached disco#info data " + \
 | 
			
		||||
                      "for %s, node %s.", jid, node)
 | 
			
		||||
            info = self.api['get_cached_info'](jid, node, 
 | 
			
		||||
                    kwargs.get('ifrom', None), 
 | 
			
		||||
            info = self.api['get_cached_info'](jid, node,
 | 
			
		||||
                    kwargs.get('ifrom', None),
 | 
			
		||||
                    kwargs)
 | 
			
		||||
            if info is not None:
 | 
			
		||||
                return self._wrap(kwargs.get('ifrom', None), jid, info)
 | 
			
		||||
@@ -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,10 +408,12 @@ 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:
 | 
			
		||||
            items = self.api['get_items'](jid, node, 
 | 
			
		||||
                    kwargs.get('ifrom', None), 
 | 
			
		||||
        if local or local is None and jid is None:
 | 
			
		||||
            items = self.api['get_items'](jid, node,
 | 
			
		||||
                    kwargs.get('ifrom', None),
 | 
			
		||||
                    kwargs)
 | 
			
		||||
            return self._wrap(kwargs.get('ifrom', None), jid, items)
 | 
			
		||||
 | 
			
		||||
@@ -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):
 | 
			
		||||
                self.xml.remove(item.xml)
 | 
			
		||||
        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):
 | 
			
		||||
 
 | 
			
		||||
@@ -237,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:
 | 
			
		||||
@@ -424,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