Compare commits
	
		
			492 Commits
		
	
	
		
			1.0
			...
			sleek-1.1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 846f2bac5f | ||
|   | 2229ad8d8e | ||
|   | 61aff9f49a | ||
|   | 67235c4214 | ||
|   | 12e8bb6ddc | ||
|   | 52feabbe76 | ||
|   | a22ca228cc | ||
|   | d0666a5eb6 | ||
|   | 2a4e435228 | ||
|   | c5046b9c91 | ||
|   | 4598031dd2 | ||
|   | 5e9266ba90 | ||
|   | e6c95f0a2a | ||
|   | 63b58edda1 | ||
|   | af9632519c | ||
|   | d367fb938d | ||
|   | 4190027a78 | ||
|   | ef48a8c4d9 | ||
|   | 829b225053 | ||
|   | 747a6e94e6 | ||
|   | cebc798e72 | ||
|   | e2e8c4b5dc | ||
|   | 675c0112ac | ||
|   | 4dd2c15775 | ||
|   | 9f6decdbc1 | ||
|   | 4ea328b9f2 | ||
|   | 098714b3c4 | ||
|   | cf2c94d974 | ||
|   | 657102e938 | ||
|   | 94488fa2ea | ||
|   | ee9c4abd08 | ||
|   | b5b1c932c7 | ||
|   | b8f04983e1 | ||
|   | 90807dd973 | ||
|   | ef974114ea | ||
|   | f6e1fecdf8 | ||
|   | 94e8b2becf | ||
|   | a6ca6701a0 | ||
|   | c4edb9724b | ||
|   | b5c669bdff | ||
|   | e449dce65c | ||
|   | 671f680bb3 | ||
|   | dfff19ffbf | ||
|   | a4abdf9fa6 | ||
|   | 6c57bb0553 | ||
|   | c2ae1ee891 | ||
|   | fb3e6b7e35 | ||
|   | cf28d4586d | ||
|   | f65eb5eeea | ||
|   | 26fa9bd87e | ||
|   | 0016d9a638 | ||
|   | a88b9737ff | ||
|   | 357406d801 | ||
|   | c7ec6a72cd | ||
|   | e68b07dbce | ||
|   | 1ca0c46333 | ||
|   | e510875f64 | ||
|   | 8a03bd72ae | ||
|   | f0e1fc5aad | ||
|   | c6ac64ed2d | ||
|   | 92be051450 | ||
|   | 779c258e27 | ||
|   | f7a710e55b | ||
|   | 814a50e36f | ||
|   | 230465b946 | ||
|   | d11a67702e | ||
|   | 4e12e228cb | ||
|   | 4a94aeba49 | ||
|   | 295d23ccf3 | ||
|   | aebcf6ff82 | ||
|   | 8c2ece3bca | ||
|   | 80a90a6221 | ||
|   | 2324c90232 | ||
|   | 59ff08174f | ||
|   | b84e359770 | ||
|   | 475ccfa8dc | ||
|   | 267c24c8ef | ||
|   | 1383ca19b5 | ||
|   | 4c3ff2abab | ||
|   | 7c6ef18e4f | ||
|   | f8856467d5 | ||
|   | 3bd84b8d27 | ||
|   | bc8b5774ac | ||
|   | 8009b0485e | ||
|   | 8742a56b3e | ||
|   | a792bcdafe | ||
|   | 167d1ce97b | ||
|   | 695cd95657 | ||
|   | e4b4c67637 | ||
|   | 422e77ae40 | ||
|   | 5ae6c8f8fa | ||
|   | 54656b331a | ||
|   | 9047b627a4 | ||
|   | 6645a3be40 | ||
|   | e3fab66dfb | ||
|   | 5867f08bf1 | ||
|   | a06fa2de67 | ||
|   | 35396d2977 | ||
|   | 3bff743d9f | ||
|   | 5a878f829b | ||
|   | 26dc6e90ea | ||
|   | 94c749fd5a | ||
|   | 7b80ed0807 | ||
|   | 98b7e8b10a | ||
|   | 9d8de7fc15 | ||
|   | 70883086b7 | ||
|   | 9a08dfc7d4 | ||
|   | 3e43b36a9d | ||
|   | 352ee2f2fd | ||
|   | 78aa5c3dfa | ||
|   | 613323b5fb | ||
|   | 6c4b01db8a | ||
|   | d06897a635 | ||
|   | 1600bb0aaf | ||
|   | b5c9c98a8b | ||
|   | e4e18a416f | ||
|   | 01cc0e6def | ||
|   | d571d691a7 | ||
|   | fb221a8dc0 | ||
|   | 459e1ed345 | ||
|   | 6680c244f5 | ||
|   | 06423964ec | ||
|   | 474390fa00 | ||
|   | 81d3723084 | ||
|   | 32e798967e | ||
|   | acd9c32a9f | ||
|   | b8581b0278 | ||
|   | 917faecdcb | ||
|   | f6edaa56a6 | ||
|   | 51fee28bf4 | ||
|   | e8a3e92ceb | ||
|   | 5df3839b7a | ||
|   | 8dcb441f44 | ||
|   | a347cf625a | ||
|   | 46f49c7a12 | ||
|   | 99701c947e | ||
|   | 1baae1b81e | ||
|   | 7d20f0e9a6 | ||
|   | fbad22a1cd | ||
|   | 5af2f62c04 | ||
|   | 4a4a03858e | ||
|   | 6819b57353 | ||
|   | 88b5e60807 | ||
|   | a26a8bd79c | ||
|   | 9307a6915f | ||
|   | 85ef2d8d0b | ||
|   | c2c7cc032b | ||
|   | e4911e9391 | ||
|   | b11e1ee92d | ||
|   | 5027d00c10 | ||
|   | 69ddeceb49 | ||
|   | 82698672bb | ||
|   | 9cec284947 | ||
|   | dc501d1902 | ||
|   | 100e504b7f | ||
|   | 8a745c5e81 | ||
|   | bf0a157c5d | ||
|   | f49818be06 | ||
|   | 1ad171dfe5 | ||
|   | 2a78570d65 | ||
|   | 7a112f2523 | ||
|   | e86444e5fb | ||
|   | 36c11ad9de | ||
|   | 019a4b20ae | ||
|   | 433ee08687 | ||
|   | 7858d969d8 | ||
|   | 8119551049 | ||
|   | 061489f03a | ||
|   | d92aa05b5c | ||
|   | f7a74d960e | ||
|   | 95a0e51b41 | ||
|   | 110e45e187 | ||
|   | 534aaf2b2a | ||
|   | 4cc20fdd05 | ||
|   | f3fae192a8 | ||
|   | 7d59a8a0ad | ||
|   | 8da387a38a | ||
|   | ff6fc44215 | ||
|   | 62391a895a | ||
|   | 9bcdd7d18f | ||
|   | 5c4f7bfe8b | ||
|   | 0b7f134021 | ||
|   | 378a42889f | ||
|   | f824950552 | ||
|   | 3d2d11f169 | ||
|   | 181aea737d | ||
|   | ee702f4071 | ||
|   | a08c2161a7 | ||
|   | 0e36a01354 | ||
|   | c39ad7dfbb | ||
|   | b92ae706e9 | ||
|   | 6997261c6b | ||
|   | 6cfb5cb14c | ||
|   | 8567d6034f | ||
|   | e06368f8cd | ||
|   | 4b37a4706f | ||
|   | 7b1564947d | ||
|   | f5652a667b | ||
|   | 3b2c865a58 | ||
|   | db0e683d01 | ||
|   | e29a9e0394 | ||
|   | edf65f4f52 | ||
|   | 98677fd602 | ||
|   | 61a4f76c8d | ||
|   | 856a826eea | ||
|   | 387ef513d6 | ||
|   | 2858dbf57f | ||
|   | 350a2b8bbc | ||
|   | c9093c9972 | ||
|   | d1ad31696e | ||
|   | f49311ef9e | ||
|   | 678e529efc | ||
|   | 6ddb430fef | ||
|   | 74d1f88146 | ||
|   | 7842c55da3 | ||
|   | f5beac2afa | ||
|   | 8a23f28dfa | ||
|   | 9c4886e746 | ||
|   | e0bcd5d722 | ||
|   | ba854e7d85 | ||
|   | 4ded34ebc9 | ||
|   | e918a86028 | ||
|   | 24234bf718 | ||
|   | ec99339140 | ||
|   | 03dedfc871 | ||
|   | 9e86a7b357 | ||
|   | 6a32417957 | ||
|   | 97a7be7dfa | ||
|   | fa86f956ef | ||
|   | a9acff5294 | ||
|   | ad5b61de50 | ||
|   | f53b815855 | ||
|   | bf8a9dc20d | ||
|   | 08716c35fd | ||
|   | fd81bab906 | ||
|   | 1cf55c14b0 | ||
|   | 8b47159788 | ||
|   | 2eeaf4d80c | ||
|   | 4d89d26a1c | ||
|   | 0cc14cee4d | ||
|   | a20a9c505d | ||
|   | 913738444e | ||
|   | 8ee30179ea | ||
|   | cb2469322b | ||
|   | 94aa6673ca | ||
|   | 4b2b2d16b8 | ||
|   | 4cd5d3b3b5 | ||
|   | e48e50c6ff | ||
|   | 01189376e2 | ||
|   | 60195cf2dc | ||
|   | 15ef273141 | ||
|   | eed6da538a | ||
|   | d3e8993e22 | ||
|   | 8a8926c5e8 | ||
|   | f9d0ee824b | ||
|   | af099737ab | ||
|   | 9ffde5ab37 | ||
|   | 272ddf9f01 | ||
|   | 259c84e99a | ||
|   | 7391288668 | ||
|   | 7734aee7ad | ||
|   | 9f855b9679 | ||
|   | aedbecd673 | ||
|   | 83c5a4cd2f | ||
|   | 9c61c2882f | ||
|   | e0dd9c3618 | ||
|   | 4921c44d0a | ||
|   | 3161f104c7 | ||
|   | 898f5f4b51 | ||
|   | 3ee3fdca91 | ||
|   | 488f7ed886 | ||
|   | 51e5aee830 | ||
|   | af13bea2b8 | ||
|   | cdf0b353db | ||
|   | 48504ed5e2 | ||
|   | 4d4d1e0ee5 | ||
|   | c1d36cad46 | ||
|   | aad2eb31fc | ||
|   | 1bd7824f24 | ||
|   | 912463ed6a | ||
|   | dda2473d35 | ||
|   | 94923ae898 | ||
|   | f1fde07eb9 | ||
|   | a1ddd88208 | ||
|   | ee6a9b981a | ||
|   | 9879c7af59 | ||
|   | fa4c52e499 | ||
|   | d5484808a7 | ||
|   | 1c83391948 | ||
|   | 59d1b8e131 | ||
|   | 859822ff05 | ||
|   | 3acc7d0914 | ||
|   | b077ef9150 | ||
|   | e2ce5ae222 | ||
|   | 73cabcb6ae | ||
|   | fbdf2bed49 | ||
|   | 33d01fb694 | ||
|   | ab2e43d052 | ||
|   | 0c24fbdb06 | ||
|   | eb25998e72 | ||
|   | eafd2aee93 | ||
|   | a6f3d740a2 | ||
|   | 19a6f61b44 | ||
|   | 58e0f1e6c3 | ||
|   | 96ff2d43c0 | ||
|   | 1b00b7e8df | ||
|   | 7284ceb90c | ||
|   | 24ec448b7f | ||
|   | ed5a2f400d | ||
|   | 9596616b42 | ||
|   | 8d38fb511b | ||
|   | 5a2cbbb731 | ||
|   | 32d6f85649 | ||
|   | a2b47e5749 | ||
|   | 14d4062f4a | ||
|   | 67972c5e84 | ||
|   | 3467ac18cc | ||
|   | cabf27424f | ||
|   | 162e955bd6 | ||
|   | 57d761b8a2 | ||
|   | 8b2023225c | ||
|   | f8f2b541db | ||
|   | 9d645ad5cd | ||
|   | 610d366bdb | ||
|   | 64c46562d3 | ||
|   | 87d6ade06d | ||
|   | 4a009515c1 | ||
|   | 6497857495 | ||
|   | 5a324c01de | ||
|   | 17279de4a3 | ||
|   | 34a7a62c35 | ||
|   | 2305cc61fd | ||
|   | 2f677c98f8 | ||
|   | 3fda053606 | ||
|   | 6d855ec06c | ||
|   | 23cc62fe7c | ||
|   | 26ea67d211 | ||
|   | d43cd9fa54 | ||
|   | 6f337b5425 | ||
|   | d104a5fe75 | ||
|   | cdd69c6842 | ||
|   | 4a3a9067d4 | ||
|   | 1aecb2293a | ||
|   | ad8fd91b7a | ||
|   | 1f5a3a4445 | ||
|   | be363e0b46 | ||
|   | a104cd6dae | ||
|   | e287282782 | ||
|   | 8b06d10415 | ||
|   | 1a153487c3 | ||
|   | 01b2499915 | ||
|   | 9f43d31bf5 | ||
|   | a318beded4 | ||
|   | 5f4b528e6b | ||
|   | f759b0ada1 | ||
|   | 7d89fa27a8 | ||
|   | 10ec92f7c6 | ||
|   | 58d2f317a0 | ||
|   | 34b094561f | ||
|   | 91155444c0 | ||
|   | 7f71ac7e0a | ||
|   | e5fc59a4c6 | ||
|   | 549a9ab472 | ||
|   | 09720dcf42 | ||
|   | ec044affd4 | ||
|   | af39945009 | ||
|   | 78a50d0237 | ||
|   | 861d279b08 | ||
|   | eb1a32fc90 | ||
|   | 4610a6615c | ||
|   | 4cb8a8d389 | ||
|   | a71823dc04 | ||
|   | d41ada6b66 | ||
|   | fdfe2cd64f | ||
|   | 7b51c6f5cc | ||
|   | be7f07ad12 | ||
|   | 830db11b41 | ||
|   | 53bcd33e1d | ||
|   | e3d596c9fa | ||
|   | ecd6ad6930 | ||
|   | c36073b40e | ||
|   | afe0d16797 | ||
|   | 977fcc0632 | ||
|   | 94b57d232d | ||
|   | 7cdedb2ec0 | ||
|   | 676324805e | ||
|   | 7d74a7b027 | ||
|   | 9d5eb864d1 | ||
|   | 86a482e032 | ||
|   | c43c7be86c | ||
|   | c58462f154 | ||
|   | 31d3e3b2b6 | ||
|   | fb2582e53b | ||
|   | d807613117 | ||
|   | 6d922d00c3 | ||
|   | 61ea84093b | ||
|   | e76d6a481f | ||
|   | c1357717d9 | ||
|   | ca5145c210 | ||
|   | 1a272fd276 | ||
|   | 952260b423 | ||
|   | caa967105c | ||
|   | d565e4be20 | ||
|   | 85dd005abc | ||
|   | 021c57205f | ||
|   | 261a501afc | ||
|   | 9a38a101d2 | ||
|   | 4665c5cf1a | ||
|   | bd52a5e6c1 | ||
|   | f98e5a03de | ||
|   | 2217c69757 | ||
|   | 5a4df56836 | ||
|   | 3ab7c8bcc3 | ||
|   | 8f25acd0f3 | ||
|   | 999f1932cc | ||
|   | 69940a8ab9 | ||
|   | 13158e3cdf | ||
|   | f06589c913 | ||
|   | 2735b680b9 | ||
|   | 5f1d4ce433 | ||
|   | 25f87607aa | ||
|   | f81fb6af44 | ||
|   | bb0a5186d6 | ||
|   | baad907422 | ||
|   | 1022fc0060 | ||
|   | 3a22d798f8 | ||
|   | 71ea430c62 | ||
|   | 0d2125e737 | ||
|   | 02f4006153 | ||
|   | b25668b5b7 | ||
|   | bb3080e829 | ||
|   | bd85e95398 | ||
|   | 22cc194ed8 | ||
|   | 79b71228c1 | ||
|   | fd515d807c | ||
|   | 4f4c121d9b | ||
|   | 72e1ab47fc | ||
|   | 3575084640 | ||
|   | 1e01903072 | ||
|   | 3672856ab4 | ||
|   | 86d8736dcc | ||
|   | 2923f56561 | ||
|   | 4274f49ada | ||
|   | a4b27ff031 | ||
|   | f49b6fa79f | ||
|   | 7b854a190e | ||
|   | 947d1ffbb3 | ||
|   | de35848500 | ||
|   | 1ae219025a | ||
|   | e8b2dd6698 | ||
|   | c0074f95b1 | ||
|   | a79ce1c35e | ||
|   | 1eb69f7075 | ||
|   | a86935a42f | ||
|   | 1674bd753e | ||
|   | 6b9a55e62d | ||
|   | c578ddeb1a | ||
|   | 8ef7188dae | ||
|   | 738ec92b8e | ||
|   | be9e26b4a3 | ||
|   | b345c227b2 | ||
|   | c7e95c8dec | ||
|   | 3a4e3d3f51 | ||
|   | 8fd2efa2fa | ||
|   | 97378998a5 | ||
|   | 6b6995bb0b | ||
|   | 27c658922e | ||
|   | 35954cdc90 | ||
|   | fa912aeb84 | ||
|   | 9a5e2ae768 | ||
|   | e0545bf0bc | ||
|   | d817d64c65 | ||
|   | 8a29ec67ac | ||
|   | 6722b0224a | ||
|   | 8eb225bdec | ||
|   | a7df76a275 | ||
|   | efae8f3369 | ||
|   | a11e6c0b77 | ||
|   | 1bb0b38868 | ||
|   | 4df1641689 | ||
|   | 5ef0b96d5c | ||
|   | d979b5f2b9 | ||
|   | 1a61bdb302 | ||
|   | e8545dd2bc | ||
|   | 2f2ebb37e4 | ||
|   | 8f9d1bcfe0 | ||
|   | a7a2fd1d5b | ||
|   | f6e30edbc4 | ||
|   | 45ed68006f | ||
|   | dcb0d8b00e | ||
|   | cb635dcd5a | ||
|   | eff3330e75 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,3 +6,4 @@ docs/_build/ | ||||
| *.swp | ||||
| .tox/ | ||||
| .coverage | ||||
| sleekxmpp.egg-info/ | ||||
|   | ||||
							
								
								
									
										29
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -138,3 +138,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
|  | ||||
|  | ||||
| python-gnupg: A Python wrapper for the GNU Privacy Guard | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| Copyright (c) 2008-2012 by Vinay Sajip. | ||||
| All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
|  | ||||
|     * Redistributions of source code must retain the above copyright notice, | ||||
|       this list of conditions and the following disclaimer. | ||||
|     * Redistributions in binary form must reproduce the above copyright notice, | ||||
|       this list of conditions and the following disclaimer in the documentation | ||||
|       and/or other materials provided with the distribution. | ||||
|     * The name(s) of the copyright holder(s) may not be used to endorse or | ||||
|       promote products derived from this software without specific prior | ||||
|       written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR | ||||
| IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||||
| MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | ||||
| EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||||
| OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | ||||
| ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|   | ||||
							
								
								
									
										41
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								README.rst
									
									
									
									
									
								
							| @@ -45,21 +45,11 @@ The latest source code for SleekXMPP may be found on `Github | ||||
| ``develop`` branch. | ||||
|  | ||||
| **Latest Release** | ||||
|     - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_ | ||||
|     - `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_ | ||||
|  | ||||
| **Develop Releases** | ||||
|     - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||
|  | ||||
| **Older Stable Releases** | ||||
|     - `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_   | ||||
|     - `1.0 RC2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC2>`_   | ||||
|     - `1.0 RC1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC1>`_   | ||||
|     - `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_   | ||||
|     - `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_ | ||||
|     - `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_ | ||||
|     - `1.0 Beta3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta3>`_ | ||||
|     - `1.0 Beta2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta2>`_ | ||||
|     - `1.0 Beta1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta1>`_ | ||||
|  | ||||
| Installing DNSPython | ||||
| --------------------- | ||||
| @@ -82,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``. | ||||
| @@ -118,8 +109,12 @@ SleekXMPP projects:: | ||||
|             self.add_event_handler("session_start", self.session_start) | ||||
|             self.add_event_handler("message", self.message) | ||||
|  | ||||
|             self.register_plugin('xep_0030') # Service Discovery | ||||
|             self.register_plugin('xep_0199') # XMPP Ping | ||||
|             # If you wanted more functionality, here's how to register plugins: | ||||
|             # self.register_plugin('xep_0030') # Service Discovery | ||||
|             # self.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|             # Here's how to access plugins once you've registered them: | ||||
|             # self['xep_0030'].add_feature('echo_demo') | ||||
|  | ||||
|             # If you are working with an OpenFire server, you will | ||||
|             # need to use a different SSL version: | ||||
| @@ -128,18 +123,20 @@ SleekXMPP projects:: | ||||
|  | ||||
|         def session_start(self, event): | ||||
|             self.send_presence() | ||||
|             self.get_roster() | ||||
|  | ||||
|             # Most get_*/set_* methods from plugins use Iq stanzas, which | ||||
|             # can generate IqError and IqTimeout exceptions | ||||
|             try: | ||||
|                 self.get_roster() | ||||
|             except IqError as err: | ||||
|                 logging.error('There was an error getting the roster') | ||||
|                 logging.error(err.iq['error']['condition']) | ||||
|                 self.disconnect() | ||||
|             except IqTimeout: | ||||
|                 logging.error('Server is taking too long to respond') | ||||
|                 self.disconnect() | ||||
|             # | ||||
|             # try: | ||||
|             #     self.get_roster() | ||||
|             # except IqError as err: | ||||
|             #     logging.error('There was an error getting the roster') | ||||
|             #     logging.error(err.iq['error']['condition']) | ||||
|             #     self.disconnect() | ||||
|             # except IqTimeout: | ||||
|             #     logging.error('Server is taking too long to respond') | ||||
|             #     self.disconnect() | ||||
|  | ||||
|         def message(self, msg): | ||||
|             if msg['type'] in ('chat', 'normal'): | ||||
|   | ||||
| @@ -1,171 +0,0 @@ | ||||
| import logging | ||||
| import sleekxmpp | ||||
| from optparse import OptionParser | ||||
| from xml.etree import cElementTree as ET | ||||
| import os | ||||
| import time | ||||
| import sys | ||||
| import unittest | ||||
| import sleekxmpp.plugins.xep_0004 | ||||
| from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath | ||||
| from sleekxmpp.xmlstream.handler.waiter import Waiter | ||||
| try: | ||||
| 	import configparser | ||||
| except ImportError: | ||||
| 	import ConfigParser as configparser | ||||
| try: | ||||
| 	import queue | ||||
| except ImportError: | ||||
| 	import Queue as queue | ||||
|  | ||||
| class TestClient(sleekxmpp.ClientXMPP): | ||||
| 	def __init__(self, jid, password): | ||||
| 		sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
| 		self.add_event_handler("session_start", self.start) | ||||
| 		#self.add_event_handler("message", self.message) | ||||
| 		self.waitforstart = queue.Queue() | ||||
| 	 | ||||
| 	def start(self, event): | ||||
| 		self.getRoster() | ||||
| 		self.sendPresence() | ||||
| 		self.waitforstart.put(True) | ||||
|  | ||||
|  | ||||
| class TestPubsubServer(unittest.TestCase): | ||||
| 	statev = {} | ||||
|  | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		unittest.TestCase.__init__(self, *args, **kwargs) | ||||
|  | ||||
| 	def setUp(self): | ||||
| 		pass | ||||
|  | ||||
| 	def test001getdefaultconfig(self): | ||||
| 		"""Get the default node config""" | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2') | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3') | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4') | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5') | ||||
| 		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) | ||||
| 		self.statev['defaultconfig'] = result | ||||
| 		self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form)) | ||||
| 	 | ||||
| 	def test002createdefaultnode(self): | ||||
| 		"""Create a node without config""" | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1')) | ||||
|  | ||||
| 	def test003deletenode(self): | ||||
| 		"""Delete recently created node""" | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1')) | ||||
| 	 | ||||
| 	def test004createnode(self): | ||||
| 		"""Create a node with a config""" | ||||
| 		self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open') | ||||
| 		self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True) | ||||
| 		self.statev['defaultconfig'].field['pubsub#persist_items'].setValue(True) | ||||
| 		self.statev['defaultconfig'].field['pubsub#presence_based_delivery'].setValue(True) | ||||
| 		p = self.xmpp2.Presence() | ||||
| 		p['to'] = self.pshost | ||||
| 		p.send() | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig'], ntype='job')) | ||||
| 	 | ||||
| 	def test005reconfigure(self): | ||||
| 		"""Retrieving node config and reconfiguring""" | ||||
| 		nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2') | ||||
| 		self.failUnless(nconfig, "No configuration returned") | ||||
| 		#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues())) | ||||
| 		self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match") | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig)) | ||||
|  | ||||
| 	def test006subscribetonode(self): | ||||
| 		"""Subscribe to node from account 2""" | ||||
| 		self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2")) | ||||
| 	 | ||||
| 	def test007publishitem(self): | ||||
| 		"""Publishing item""" | ||||
| 		item = ET.Element('{http://netflint.net/protocol/test}test') | ||||
| 		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items')) | ||||
| 		self.xmpp2.registerHandler(w) | ||||
| 		#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),)) | ||||
| 		result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test1', item) | ||||
| 		msg = w.wait(5) # got to get a result in 5 seconds | ||||
| 		self.failUnless(msg != False, "Account #2 did not get message event") | ||||
| 		#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),)) | ||||
| 		result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test2', item) | ||||
| 		w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items')) | ||||
| 		self.xmpp2.registerHandler(w) | ||||
| 		self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test1') | ||||
| 		msg = w.wait(5) # got to get a result in 5 seconds | ||||
| 		self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test2') | ||||
| 		self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test1') | ||||
| 		self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test2') | ||||
| 		print result | ||||
| 		#need to add check for update | ||||
|  | ||||
| 	def test900cleanup(self): | ||||
| 		"Cleaning up" | ||||
| 		#self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.") | ||||
| 		time.sleep(10) | ||||
| 	 | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| 	#parse command line arguements | ||||
| 	optp = OptionParser() | ||||
| 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||
| 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||
| 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||
| 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||
| 	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use") | ||||
| 	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use") | ||||
| 	opts,args = optp.parse_args() | ||||
| 	 | ||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||
|  | ||||
| 	#load xml config | ||||
| 	logging.info("Loading config file: %s" , opts.configfile) | ||||
| 	config = configparser.RawConfigParser() | ||||
| 	config.read(opts.configfile) | ||||
| 	 | ||||
| 	#init | ||||
| 	logging.info("Account 1 is %s" , config.get('account1', 'jid')) | ||||
| 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | ||||
| 	logging.info("Account 2 is %s" , config.get('account2', 'jid')) | ||||
| 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | ||||
| 	 | ||||
| 	xmpp1.registerPlugin('xep_0004') | ||||
| 	xmpp1.registerPlugin('xep_0030') | ||||
| 	xmpp1.registerPlugin('xep_0060') | ||||
| 	xmpp1.registerPlugin('xep_0199') | ||||
| 	xmpp1.registerPlugin('jobs') | ||||
| 	xmpp2.registerPlugin('xep_0004') | ||||
| 	xmpp2.registerPlugin('xep_0030') | ||||
| 	xmpp2.registerPlugin('xep_0060') | ||||
| 	xmpp2.registerPlugin('xep_0199') | ||||
| 	xmpp2.registerPlugin('jobs') | ||||
|  | ||||
| 	if not config.get('account1', 'server'): | ||||
| 		# we don't know the server, but the lib can probably figure it out | ||||
| 		xmpp1.connect()  | ||||
| 	else: | ||||
| 		xmpp1.connect((config.get('account1', 'server'), 5222)) | ||||
| 	xmpp1.process(threaded=True) | ||||
| 	 | ||||
| 	#init | ||||
| 	if not config.get('account2', 'server'): | ||||
| 		# we don't know the server, but the lib can probably figure it out | ||||
| 		xmpp2.connect()  | ||||
| 	else: | ||||
| 		xmpp2.connect((config.get('account2', 'server'), 5222)) | ||||
| 	xmpp2.process(threaded=True) | ||||
|  | ||||
| 	TestPubsubServer.xmpp1 = xmpp1 | ||||
| 	TestPubsubServer.xmpp2 = xmpp2 | ||||
| 	TestPubsubServer.pshost = config.get('settings', 'pubsub') | ||||
| 	xmpp1.waitforstart.get(True) | ||||
| 	xmpp2.waitforstart.get(True) | ||||
| 	testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer) | ||||
|  | ||||
| 	alltests_suite = unittest.TestSuite([testsuite]) | ||||
| 	result = unittest.TextTestRunner(verbosity=2).run(alltests_suite) | ||||
| 	xmpp1.disconnect() | ||||
| 	xmpp2.disconnect() | ||||
| @@ -1,233 +0,0 @@ | ||||
| import logging | ||||
| import sleekxmpp | ||||
| from optparse import OptionParser | ||||
| from xml.etree import cElementTree as ET | ||||
| import os | ||||
| import time | ||||
| import sys | ||||
| import unittest | ||||
| import sleekxmpp.plugins.xep_0004 | ||||
| from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath | ||||
| from sleekxmpp.xmlstream.handler.waiter import Waiter | ||||
| try: | ||||
| 	import configparser | ||||
| except ImportError: | ||||
| 	import ConfigParser as configparser | ||||
| try: | ||||
| 	import queue | ||||
| except ImportError: | ||||
| 	import Queue as queue | ||||
|  | ||||
| class TestClient(sleekxmpp.ClientXMPP): | ||||
| 	def __init__(self, jid, password): | ||||
| 		sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
| 		self.add_event_handler("session_start", self.start) | ||||
| 		#self.add_event_handler("message", self.message) | ||||
| 		self.waitforstart = queue.Queue() | ||||
| 	 | ||||
| 	def start(self, event): | ||||
| 		self.getRoster() | ||||
| 		self.sendPresence() | ||||
| 		self.waitforstart.put(True) | ||||
|  | ||||
|  | ||||
| class TestPubsubServer(unittest.TestCase): | ||||
| 	statev = {} | ||||
|  | ||||
| 	def __init__(self, *args, **kwargs): | ||||
| 		unittest.TestCase.__init__(self, *args, **kwargs) | ||||
|  | ||||
| 	def setUp(self): | ||||
| 		pass | ||||
|  | ||||
| 	def test001getdefaultconfig(self): | ||||
| 		"""Get the default node config""" | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2') | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3') | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4') | ||||
| 		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5') | ||||
| 		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) | ||||
| 		self.statev['defaultconfig'] = result | ||||
| 		self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form)) | ||||
| 	 | ||||
| 	def test002createdefaultnode(self): | ||||
| 		"""Create a node without config""" | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1')) | ||||
|  | ||||
| 	def test003deletenode(self): | ||||
| 		"""Delete recently created node""" | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1')) | ||||
| 	 | ||||
| 	def test004createnode(self): | ||||
| 		"""Create a node with a config""" | ||||
| 		self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open') | ||||
| 		self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True) | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig'])) | ||||
| 	 | ||||
| 	def test005reconfigure(self): | ||||
| 		"""Retrieving node config and reconfiguring""" | ||||
| 		nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2') | ||||
| 		self.failUnless(nconfig, "No configuration returned") | ||||
| 		#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues())) | ||||
| 		self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match") | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig)) | ||||
|  | ||||
| 	def test006subscribetonode(self): | ||||
| 		"""Subscribe to node from account 2""" | ||||
| 		self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2")) | ||||
| 	 | ||||
| 	def test007publishitem(self): | ||||
| 		"""Publishing item""" | ||||
| 		item = ET.Element('{http://netflint.net/protocol/test}test') | ||||
| 		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items')) | ||||
| 		self.xmpp2.registerHandler(w) | ||||
| 		result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),)) | ||||
| 		msg = w.wait(5) # got to get a result in 5 seconds | ||||
| 		self.failUnless(msg != False, "Account #2 did not get message event") | ||||
| 		self.failUnless(result) | ||||
| 		#need to add check for update | ||||
| 	 | ||||
| 	def test008updateitem(self): | ||||
| 		"""Updating item""" | ||||
| 		item = ET.Element('{http://netflint.net/protocol/test}test', {'someattr': 'hi there'}) | ||||
| 		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items')) | ||||
| 		self.xmpp2.registerHandler(w) | ||||
| 		result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),)) | ||||
| 		msg = w.wait(5) # got to get a result in 5 seconds | ||||
| 		self.failUnless(msg != False, "Account #2 did not get message event") | ||||
| 		self.failUnless(result) | ||||
| 		#need to add check for update | ||||
| 	 | ||||
| 	def test009deleteitem(self): | ||||
| 		"""Deleting item""" | ||||
| 		w = Waiter('wait retract', StanzaPath('message/pubsub_event/items@node=testnode2')) | ||||
| 		self.xmpp2.registerHandler(w) | ||||
| 		result = self.xmpp1['xep_0060'].deleteItem(self.pshost, "testnode2", "test1") | ||||
| 		self.failUnless(result, "Got error when deleting item.") | ||||
| 		msg = w.wait(1) | ||||
| 		self.failUnless(msg != False, "Did not get retract notice.") | ||||
| 	 | ||||
| 	def test010unsubscribenode(self): | ||||
| 		"Unsubscribing Account #2" | ||||
| 		self.failUnless(self.xmpp2['xep_0060'].unsubscribe(self.pshost, "testnode2"), "Got error response when unsubscribing.") | ||||
| 	 | ||||
| 	def test011createcollectionnode(self): | ||||
| 		"Create a collection node w/ Account #2" | ||||
| 		self.failUnless(self.xmpp2['xep_0060'].create_node(self.pshost, "testnode3", self.statev['defaultconfig'], True), "Could not create collection node") | ||||
| 	 | ||||
| 	def test012subscribecollection(self): | ||||
| 		"Subscribe Account #1 to collection" | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode3")) | ||||
| 	 | ||||
| 	def test013assignnodetocollection(self): | ||||
| 		"Assign node to collection" | ||||
| 		self.failUnless(self.xmpp2['xep_0060'].addNodeToCollection(self.pshost, 'testnode2', 'testnode3')) | ||||
| 	 | ||||
| 	def test014publishcollection(self): | ||||
| 		"""Publishing item to collection child""" | ||||
| 		item = ET.Element('{http://netflint.net/protocol/test}test') | ||||
| 		w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items@node=testnode2')) | ||||
| 		self.xmpp1.registerHandler(w) | ||||
| 		result = self.xmpp2['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),)) | ||||
| 		msg = w.wait(5) # got to get a result in 5 seconds | ||||
| 		self.failUnless(msg != False, "Account #1 did not get message event: perhaps node was advertised incorrectly?") | ||||
| 		self.failUnless(result) | ||||
|  | ||||
| #	def test016speedtest(self): | ||||
| #		"Uncached speed test" | ||||
| #		import time | ||||
| #		start = time.time() | ||||
| #		for y in range(0, 50000, 1000): | ||||
| #			start2 = time.time() | ||||
| #			for x in range(y, y+1000): | ||||
| #				self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode4", subscribee="testuser%s@whatever" % x)) | ||||
| #			print time.time() - start2 | ||||
| #		seconds = time.time() - start | ||||
| #		print "--", seconds | ||||
| #		print "---------" | ||||
| #		time.sleep(15) | ||||
| #		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4'), "Could not delete non-cached test node") | ||||
| 	 | ||||
| #	def test015speedtest(self): | ||||
| #		"cached speed test" | ||||
| #		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost) | ||||
| #		self.statev['defaultconfig'] = result | ||||
| #		self.statev['defaultconfig'].field['pubsub#node_type'].setValue("leaf") | ||||
| #		self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(True) | ||||
| #		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode4', self.statev['defaultconfig'])) | ||||
| #		self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(False) | ||||
| #		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode5', self.statev['defaultconfig'])) | ||||
| #		start = time.time() | ||||
| #		for y in range(0, 50000, 1000): | ||||
| #			start2 = time.time() | ||||
| #			for x in range(y, y+1000): | ||||
| #				self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode5", subscribee="testuser%s@whatever" % x)) | ||||
| #			print time.time() - start2 | ||||
| #		seconds = time.time() - start | ||||
| #		print "--", seconds | ||||
|  | ||||
| 	def test900cleanup(self): | ||||
| 		"Cleaning up" | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.") | ||||
| 		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3'), "Could not delete collection node") | ||||
| 	 | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| 	#parse command line arguements | ||||
| 	optp = OptionParser() | ||||
| 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||
| 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||
| 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||
| 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||
| 	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use") | ||||
| 	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use") | ||||
| 	opts,args = optp.parse_args() | ||||
| 	 | ||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||
|  | ||||
| 	#load xml config | ||||
| 	logging.info("Loading config file: %s" , opts.configfile) | ||||
| 	config = configparser.RawConfigParser() | ||||
| 	config.read(opts.configfile) | ||||
| 	 | ||||
| 	#init | ||||
| 	logging.info("Account 1 is %s" , config.get('account1', 'jid')) | ||||
| 	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass')) | ||||
| 	logging.info("Account 2 is %s" , config.get('account2', 'jid')) | ||||
| 	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass')) | ||||
| 	 | ||||
| 	xmpp1.registerPlugin('xep_0004') | ||||
| 	xmpp1.registerPlugin('xep_0030') | ||||
| 	xmpp1.registerPlugin('xep_0060') | ||||
| 	xmpp1.registerPlugin('xep_0199') | ||||
| 	xmpp2.registerPlugin('xep_0004') | ||||
| 	xmpp2.registerPlugin('xep_0030') | ||||
| 	xmpp2.registerPlugin('xep_0060') | ||||
| 	xmpp2.registerPlugin('xep_0199') | ||||
|  | ||||
| 	if not config.get('account1', 'server'): | ||||
| 		# we don't know the server, but the lib can probably figure it out | ||||
| 		xmpp1.connect()  | ||||
| 	else: | ||||
| 		xmpp1.connect((config.get('account1', 'server'), 5222)) | ||||
| 	xmpp1.process(threaded=True) | ||||
| 	 | ||||
| 	#init | ||||
| 	if not config.get('account2', 'server'): | ||||
| 		# we don't know the server, but the lib can probably figure it out | ||||
| 		xmpp2.connect()  | ||||
| 	else: | ||||
| 		xmpp2.connect((config.get('account2', 'server'), 5222)) | ||||
| 	xmpp2.process(threaded=True) | ||||
|  | ||||
| 	TestPubsubServer.xmpp1 = xmpp1 | ||||
| 	TestPubsubServer.xmpp2 = xmpp2 | ||||
| 	TestPubsubServer.pshost = config.get('settings', 'pubsub') | ||||
| 	xmpp1.waitforstart.get(True) | ||||
| 	xmpp2.waitforstart.get(True) | ||||
| 	testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer) | ||||
|  | ||||
| 	alltests_suite = unittest.TestSuite([testsuite]) | ||||
| 	result = unittest.TextTestRunner(verbosity=2).run(alltests_suite) | ||||
| 	xmpp1.disconnect() | ||||
| 	xmpp2.disconnect() | ||||
| @@ -1,13 +0,0 @@ | ||||
| [settings] | ||||
| enabled=true | ||||
| pubsub=pubsub.recon | ||||
|  | ||||
| [account1] | ||||
| jid=fritzy@recon | ||||
| pass=testing123 | ||||
| server= | ||||
|  | ||||
| [account2] | ||||
| jid=fritzy2@recon | ||||
| pass=testing123 | ||||
| server= | ||||
| @@ -1,350 +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. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| import sleekxmpp | ||||
| from optparse import OptionParser | ||||
| from xml.etree import cElementTree as ET | ||||
| import os | ||||
| import time | ||||
| import sys | ||||
| import Queue | ||||
| import thread | ||||
|  | ||||
|  | ||||
| class testps(sleekxmpp.ClientXMPP): | ||||
| 	def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], nodenum=0, pshost=None): | ||||
| 		sleekxmpp.ClientXMPP.__init__(self, jid, password, ssl, plugin_config, plugin_whitelist) | ||||
| 		self.registerPlugin('xep_0004') | ||||
| 		self.registerPlugin('xep_0030') | ||||
| 		self.registerPlugin('xep_0060') | ||||
| 		self.registerPlugin('xep_0092') | ||||
| 		self.add_handler("<message xmlns='jabber:client'><event xmlns='http://jabber.org/protocol/pubsub#event' /></message>", self.pubsubEventHandler, name='Pubsub Event', threaded=True) | ||||
| 		self.add_event_handler("session_start", self.start, threaded=True) | ||||
| 		self.add_handler("<iq type='error' />", self.handleError, name='Iq Error') | ||||
| 		self.events = Queue.Queue() | ||||
| 		self.default_config = None | ||||
| 		self.ps = self.plugin['xep_0060'] | ||||
| 		self.node = "pstestnode_%s" | ||||
| 		self.pshost = pshost | ||||
| 		if pshost is None: | ||||
| 			self.pshost = self.boundjid.host | ||||
| 		self.nodenum = int(nodenum) | ||||
| 		self.leafnode = self.nodenum + 1 | ||||
| 		self.collectnode = self.nodenum + 2 | ||||
| 		self.lasterror = '' | ||||
| 		self.sprintchars = 0 | ||||
| 		self.defaultconfig = None | ||||
| 		self.tests = ['test_defaultConfig', 'test_createDefaultNode', 'test_getNodes', 'test_deleteNode', 'test_createWithConfig', 'test_reconfigureNode', 'test_subscribeToNode', 'test_addItem', 'test_updateItem', 'test_deleteItem', 'test_unsubscribeNode', 'test_createCollection', 'test_subscribeCollection', 'test_addNodeCollection', 'test_deleteNodeCollection', 'test_addCollectionNode', 'test_deleteCollectionNode', 'test_unsubscribeNodeCollection', 'test_deleteCollection'] | ||||
| 		self.passed = 0 | ||||
| 		self.width = 120 | ||||
| 	 | ||||
| 	def start(self, event): | ||||
| 		#TODO: make this configurable | ||||
| 		self.getRoster() | ||||
| 		self.sendPresence(ppriority=20) | ||||
| 		thread.start_new(self.test_all, tuple()) | ||||
| 	 | ||||
| 	def sprint(self, msg, end=False, color=False): | ||||
| 		length = len(msg) | ||||
| 		if color: | ||||
| 			if color == "red": | ||||
| 				color = "1;31" | ||||
| 			elif color == "green": | ||||
| 				color = "0;32" | ||||
| 			msg = "%s%s%s" % ("\033[%sm" % color, msg, "\033[0m") | ||||
| 		if not end: | ||||
| 			sys.stdout.write(msg) | ||||
| 			self.sprintchars += length | ||||
| 		else: | ||||
| 			self.sprint("%s%s" % ("." * (self.width - self.sprintchars - length), msg)) | ||||
| 			print('') | ||||
| 			self.sprintchars = 0 | ||||
| 		sys.stdout.flush() | ||||
|  | ||||
| 	def pubsubEventHandler(self, xml): | ||||
| 		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}item'): | ||||
| 			self.events.put(item.get('id', '__unknown__')) | ||||
| 		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}retract'): | ||||
| 			self.events.put(item.get('id', '__unknown__')) | ||||
| 		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}disassociate'): | ||||
| 			self.events.put(item.get('node', '__unknown__')) | ||||
| 		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}associate'): | ||||
| 			self.events.put(item.get('node', '__unknown__')) | ||||
| 	 | ||||
| 	def handleError(self, xml): | ||||
| 		error = xml.find('{jabber:client}error') | ||||
| 		self.lasterror =  error.getchildren()[0].tag.split('}')[-1] | ||||
| 		 | ||||
| 	def test_all(self): | ||||
| 		print("Running Publish-Subscribe Tests") | ||||
| 		version = self.plugin['xep_0092'].getVersion(self.pshost) | ||||
| 		if version: | ||||
| 			print("%s %s on %s" % (version.get('name', 'Unknown Server'), version.get('version', 'v?'), version.get('os', 'Unknown OS'))) | ||||
| 		print("=" * self.width) | ||||
| 		for test in self.tests: | ||||
| 			testfunc = getattr(self, test) | ||||
| 			self.sprint("%s" % testfunc.__doc__) | ||||
| 			if testfunc(): | ||||
| 				self.sprint("Passed", True, "green") | ||||
| 				self.passed += 1 | ||||
| 			else: | ||||
| 				if not self.lasterror: | ||||
| 					self.lasterror = 'No response' | ||||
| 				self.sprint("Failed (%s)" % self.lasterror, True, "red") | ||||
| 				self.lasterror = '' | ||||
| 		print("=" * self.width) | ||||
| 		self.sprint("Cleaning up...") | ||||
| 		#self.ps.deleteNode(self.pshost, self.node % self.nodenum) | ||||
| 		self.ps.deleteNode(self.pshost, self.node % self.leafnode) | ||||
| 		#self.ps.deleteNode(self.pshost, self.node % self.collectnode) | ||||
| 		self.sprint("Done", True, "green") | ||||
| 		self.disconnect() | ||||
| 		self.sprint("%s" % self.passed, False, "green") | ||||
| 		self.sprint("/%s Passed -- " % len(self.tests)) | ||||
| 		if len(self.tests) - self.passed: | ||||
| 			self.sprint("%s" % (len(self.tests) - self.passed), False, "red") | ||||
| 		else: | ||||
| 			self.sprint("%s" % (len(self.tests) - self.passed), False, "green") | ||||
| 		self.sprint(" Failed Tests") | ||||
| 		print | ||||
| 		#print "%s/%s Passed -- %s Failed Tests" % (self.passed, len(self.tests), len(self.tests) - self.passed) | ||||
| 	 | ||||
| 	def test_defaultConfig(self): | ||||
| 		"Retreiving default configuration" | ||||
| 		result = self.ps.getNodeConfig(self.pshost) | ||||
| 		if result is False or result is None: | ||||
| 			return False | ||||
| 		else: | ||||
| 			self.defaultconfig = result | ||||
| 			try: | ||||
| 				self.defaultconfig.field['pubsub#access_model'].setValue('open') | ||||
| 			except KeyError: | ||||
| 				pass | ||||
| 			try: | ||||
| 				self.defaultconfig.field['pubsub#notify_retract'].setValue(True) | ||||
| 			except KeyError: | ||||
| 				pass | ||||
| 			return True | ||||
| 	 | ||||
| 	def test_createDefaultNode(self): | ||||
| 		"Creating default node" | ||||
| 		return self.ps.create_node(self.pshost, self.node % self.nodenum) | ||||
| 	 | ||||
| 	def test_getNodes(self): | ||||
| 		"Getting list of nodes" | ||||
| 		self.ps.getNodes(self.pshost) | ||||
| 		self.ps.getItems(self.pshost, 'blog') | ||||
| 		return True | ||||
| 	 | ||||
| 	def test_deleteNode(self): | ||||
| 		"Deleting node" | ||||
| 		return self.ps.deleteNode(self.pshost, self.node % self.nodenum) | ||||
| 	 | ||||
| 	def test_createWithConfig(self): | ||||
| 		"Creating node with config" | ||||
| 		if self.defaultconfig is None: | ||||
| 			self.lasterror = "No Avail Config" | ||||
| 			return False | ||||
| 		return self.ps.create_node(self.pshost, self.node % self.leafnode, self.defaultconfig) | ||||
| 	 | ||||
| 	def test_reconfigureNode(self): | ||||
| 		"Retrieving node config and reconfiguring" | ||||
| 		nconfig = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode) | ||||
| 		if nconfig == False: | ||||
| 			return False | ||||
| 		return self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, nconfig) | ||||
| 		 | ||||
| 	def test_subscribeToNode(self): | ||||
| 		"Subscribing to node" | ||||
| 		return self.ps.subscribe(self.pshost, self.node % self.leafnode) | ||||
| 	 | ||||
| 	def test_addItem(self): | ||||
| 		"Adding item, waiting for notification" | ||||
| 		item = ET.Element('test') | ||||
| 		result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),)) | ||||
| 		if result == False: | ||||
| 			return False | ||||
| 		try: | ||||
| 			event = self.events.get(True, 10) | ||||
| 		except Queue.Empty: | ||||
| 			return False | ||||
| 		if event == 'test_node1': | ||||
| 			return True | ||||
| 		return False | ||||
| 	 | ||||
| 	def test_updateItem(self): | ||||
| 		"Updating item, waiting for notification" | ||||
| 		item = ET.Element('test') | ||||
| 		item.attrib['crap'] = 'yup, right here' | ||||
| 		result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),)) | ||||
| 		if result == False: | ||||
| 			return False | ||||
| 		try: | ||||
| 			event = self.events.get(True, 10) | ||||
| 		except Queue.Empty: | ||||
| 			return False | ||||
| 		if event == 'test_node1': | ||||
| 			return True | ||||
| 		return False | ||||
|  | ||||
| 	def test_deleteItem(self): | ||||
| 		"Deleting item, waiting for notification" | ||||
| 		result = self.ps.deleteItem(self.pshost, self.node % self.leafnode, 'test_node1') | ||||
| 		if result == False: | ||||
| 			return False | ||||
| 		try: | ||||
| 			event = self.events.get(True, 10) | ||||
| 		except Queue.Empty: | ||||
| 			self.lasterror = "No Notification" | ||||
| 			return False | ||||
| 		if event == 'test_node1': | ||||
| 			return True | ||||
| 		return False | ||||
| 	 | ||||
| 	def test_unsubscribeNode(self): | ||||
| 		"Unsubscribing from node" | ||||
| 		return self.ps.unsubscribe(self.pshost, self.node % self.leafnode) | ||||
|  | ||||
| 	def test_createCollection(self): | ||||
| 		"Creating collection node" | ||||
| 		return self.ps.create_node(self.pshost, self.node % self.collectnode, self.defaultconfig, True) | ||||
| 	 | ||||
| 	def test_subscribeCollection(self): | ||||
| 		"Subscribing to collection node" | ||||
| 		return self.ps.subscribe(self.pshost, self.node % self.collectnode) | ||||
| 	 | ||||
| 	def test_addNodeCollection(self): | ||||
| 		"Assigning node to collection, waiting for notification" | ||||
| 		config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode) | ||||
| 		if not config or config is None: | ||||
| 			self.lasterror = "Config Error" | ||||
| 			return False | ||||
| 		try: | ||||
| 			config.field['pubsub#collection'].setValue(self.node % self.collectnode) | ||||
| 		except KeyError: | ||||
| 			self.sprint("...Missing Field...", False, "red") | ||||
| 			config.addField('pubsub#collection', value=self.node % self.collectnode) | ||||
| 		if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config): | ||||
| 			return False | ||||
| 		try: | ||||
| 			event = self.events.get(True, 10) | ||||
| 		except Queue.Empty: | ||||
| 			self.lasterror = "No Notification" | ||||
| 			return False | ||||
| 		if event == self.node % self.leafnode: | ||||
| 			return True | ||||
| 		return False | ||||
| 	 | ||||
| 	def test_deleteNodeCollection(self): | ||||
| 		"Removing node assignment to collection, waiting for notification" | ||||
| 		config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode) | ||||
| 		if not config or config is None: | ||||
| 			self.lasterror = "Config Error" | ||||
| 			return False | ||||
| 		try: | ||||
| 			config.field['pubsub#collection'].delValue(self.node % self.collectnode) | ||||
| 		except KeyError: | ||||
| 			self.sprint("...Missing Field...", False, "red") | ||||
| 			config.addField('pubsub#collection', value='') | ||||
| 		if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config): | ||||
| 			return False | ||||
| 		try: | ||||
| 			event = self.events.get(True, 10) | ||||
| 		except Queue.Empty: | ||||
| 			self.lasterror = "No Notification" | ||||
| 			return False | ||||
| 		if event == self.node % self.leafnode: | ||||
| 			return True | ||||
| 		return False | ||||
|  | ||||
| 	def test_addCollectionNode(self): | ||||
| 		"Assigning node from collection, waiting for notification" | ||||
| 		config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode) | ||||
| 		if not config or config is None: | ||||
| 			self.lasterror = "Config Error" | ||||
| 			return False | ||||
| 		try: | ||||
| 			config.field['pubsub#children'].setValue(self.node % self.leafnode) | ||||
| 		except KeyError: | ||||
| 			self.sprint("...Missing Field...", False, "red") | ||||
| 			config.addField('pubsub#children', value=self.node % self.leafnode) | ||||
| 		if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config): | ||||
| 			return False | ||||
| 		try: | ||||
| 			event = self.events.get(True, 10) | ||||
| 		except Queue.Empty: | ||||
| 			self.lasterror = "No Notification" | ||||
| 			return False | ||||
| 		if event == self.node % self.leafnode: | ||||
| 			return True | ||||
| 		return False | ||||
|  | ||||
| 	def test_deleteCollectionNode(self): | ||||
| 		"Removing node from collection, waiting for notification" | ||||
| 		config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode) | ||||
| 		if not config or config is None: | ||||
| 			self.lasterror = "Config Error" | ||||
| 			return False | ||||
| 		try: | ||||
| 			config.field['pubsub#children'].delValue(self.node % self.leafnode) | ||||
| 		except KeyError: | ||||
| 			self.sprint("...Missing Field...", False, "red") | ||||
| 			config.addField('pubsub#children', value='') | ||||
| 		if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config): | ||||
| 			return False | ||||
| 		try: | ||||
| 			event = self.events.get(True, 10) | ||||
| 		except Queue.Empty: | ||||
| 			self.lasterror = "No Notification" | ||||
| 			return False | ||||
| 		if event == self.node % self.leafnode: | ||||
| 			return True | ||||
| 		return False | ||||
| 	 | ||||
| 	def test_unsubscribeNodeCollection(self): | ||||
| 		"Unsubscribing from collection" | ||||
| 		return self.ps.unsubscribe(self.pshost, self.node % self.collectnode) | ||||
| 	 | ||||
| 	def test_deleteCollection(self): | ||||
| 		"Deleting collection" | ||||
| 		return self.ps.deleteNode(self.pshost, self.node % self.collectnode) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| 	#parse command line arguements | ||||
| 	optp = OptionParser() | ||||
| 	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) | ||||
| 	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) | ||||
| 	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) | ||||
| 	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") | ||||
| 	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use") | ||||
| 	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use") | ||||
| 	opts,args = optp.parse_args() | ||||
| 	 | ||||
| 	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') | ||||
|  | ||||
| 	#load xml config | ||||
| 	logging.info("Loading config file: %s" , opts.configfile) | ||||
| 	config = ET.parse(os.path.expanduser(opts.configfile)).find('auth') | ||||
| 	 | ||||
| 	#init | ||||
| 	logging.info("Logging in as %s" , config.attrib['jid']) | ||||
| 	 | ||||
| 	 | ||||
| 	plugin_config = {} | ||||
| 	plugin_config['xep_0092'] = {'name': 'SleekXMPP Example', 'version': '0.1-dev'} | ||||
| 	plugin_config['xep_0199'] = {'keepalive': True, 'timeout': 30, 'frequency': 300} | ||||
| 	 | ||||
| 	con = testps(config.attrib['jid'], config.attrib['pass'], plugin_config=plugin_config, plugin_whitelist=[], nodenum=opts.nodenum, pshost=opts.pubsub) | ||||
| 	if not config.get('server', None): | ||||
| 		# we don't know the server, but the lib can probably figure it out | ||||
| 		con.connect()  | ||||
| 	else: | ||||
| 		con.connect((config.attrib['server'], 5222)) | ||||
| 	con.process(threaded=False) | ||||
| 	print("") | ||||
							
								
								
									
										1
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| _build/* | ||||
							
								
								
									
										35
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							| @@ -59,9 +59,10 @@ body { | ||||
|     margin: auto; | ||||
|     padding: 0px; | ||||
|     font-family: "Helvetica Neueu", Helvetica, sans-serif; | ||||
|     min-width: 59em; | ||||
|     min-width: 30em; | ||||
|     max-width: 70em; | ||||
|     color: #444; | ||||
|     text-align: center; | ||||
| } | ||||
|  | ||||
| div.footer { | ||||
| @@ -124,21 +125,25 @@ a.headerlink:hover { | ||||
| /* basic text elements */ | ||||
|  | ||||
| div.content { | ||||
|     margin: auto; | ||||
|     margin-top: 20px; | ||||
|     margin-left: 40px; | ||||
|     margin-right: 40px; | ||||
|     margin-bottom: 50px; | ||||
|     font-size: 0.9em; | ||||
|     width: 700px; | ||||
|     text-align: left; | ||||
| } | ||||
|  | ||||
| /* heading and navigation */ | ||||
|  | ||||
| div.header { | ||||
|     position: relative; | ||||
|     margin: auto; | ||||
|     margin-top: 125px; | ||||
|     height: 85px; | ||||
|     padding: 0 40px; | ||||
|     font-family: "Yanone Kaffeesatz"; | ||||
|     text-align: left; | ||||
|     width: 750px; | ||||
| } | ||||
| div.header h1 { | ||||
|     font-size: 2.6em; | ||||
| @@ -185,12 +190,12 @@ div.topnav { | ||||
|     z-index: 0; | ||||
| } | ||||
| div.topnav p { | ||||
|     margin: auto; | ||||
|     margin-top: 0; | ||||
|     margin-left: 40px; | ||||
|     margin-right: 40px; | ||||
|     margin-bottom: 0px; | ||||
|     text-align: right; | ||||
|     font-size: 0.8em; | ||||
|     width: 750px; | ||||
| } | ||||
| div.bottomnav { | ||||
|     background: #eeeeee; | ||||
| @@ -404,3 +409,23 @@ div.viewcode-block:target { | ||||
|     padding: 0 12px; | ||||
| } | ||||
|  | ||||
| #from_andyet { | ||||
|     -webkit-box-shadow: #CCC 0px 0px 3px; | ||||
|     background: rgba(255, 255, 255, 1); | ||||
|     bottom: 0px; | ||||
|     right: 17px; | ||||
|     padding: 3px 10px; | ||||
|     position: fixed; | ||||
| } | ||||
|  | ||||
| #from_andyet h2 { | ||||
|     background-image: url("images/from_&yet.png"); | ||||
|     background-repeat: no-repeat; | ||||
|     height: 29px; | ||||
|     line-height: 0; | ||||
|     text-indent: -9999em; | ||||
|     width: 79px; | ||||
|     margin-top: 0; | ||||
|     margin: 0px; | ||||
|     padding: 0px; | ||||
| } | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| .highlight .hll { background-color: #ffffcc } | ||||
| .highlight  { background: #000000; color: #f6f3e8; } | ||||
| .highlight .c { color: #7C7C7C; } /* Comment */ | ||||
| .highlight .err { color: #f6f3e8; } /* Error */ | ||||
| .highlight .g { color: #f6f3e8; } /* Generic */ | ||||
| .highlight .k { color: #00ADEE; } /* Keyword */ | ||||
| .highlight .l { color: #f6f3e8; } /* Literal */ | ||||
| .highlight .n { color: #f6f3e8; } /* Name */ | ||||
| .highlight .o { color: #f6f3e8; } /* Operator */ | ||||
| .highlight .x { color: #f6f3e8; } /* Other */ | ||||
| .highlight .p { color: #f6f3e8; } /* Punctuation */ | ||||
| .highlight .cm { color: #7C7C7C; } /* Comment.Multiline */ | ||||
| .highlight .cp { color: #96CBFE; } /* Comment.Preproc */ | ||||
| .highlight .c1 { color: #7C7C7C; } /* Comment.Single */ | ||||
| .highlight .cs { color: #7C7C7C; } /* Comment.Special */ | ||||
| .highlight .gd { color: #f6f3e8; } /* Generic.Deleted */ | ||||
| .highlight .ge { color: #f6f3e8; } /* Generic.Emph */ | ||||
| .highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */ | ||||
| .highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */ | ||||
| .highlight .gi { color: #f6f3e8; } /* Generic.Inserted */ | ||||
| .highlight .go { color: #070707; } /* Generic.Output */ | ||||
| .highlight .gp { color: #f6f3e8; } /* Generic.Prompt */ | ||||
| .highlight .gs { color: #f6f3e8; } /* Generic.Strong */ | ||||
| .highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */ | ||||
| .highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */ | ||||
| .highlight .kc { color: #6699CC; } /* Keyword.Constant */ | ||||
| .highlight .kd { color: #6699CC; } /* Keyword.Declaration */ | ||||
| .highlight .kn { color: #6699CC; } /* Keyword.Namespace */ | ||||
| .highlight .kp { color: #6699CC; } /* Keyword.Pseudo */ | ||||
| .highlight .kr { color: #6699CC; } /* Keyword.Reserved */ | ||||
| .highlight .kt { color: #FFFFB6; } /* Keyword.Type */ | ||||
| .highlight .ld { color: #f6f3e8; } /* Literal.Date */ | ||||
| .highlight .m { color: #FF73FD; } /* Literal.Number */ | ||||
| .highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */ | ||||
| .highlight .na { color: #f6f3e8; } /* Name.Attribute */ | ||||
| .highlight .nb { color: #f6f3e8; } /* Name.Builtin */ | ||||
| .highlight .nc { color: #f6f3e8; } /* Name.Class */ | ||||
| .highlight .no { color: #99CC99; } /* Name.Constant */ | ||||
| .highlight .nd { color: #f6f3e8; } /* Name.Decorator */ | ||||
| .highlight .ni { color: #E18964; } /* Name.Entity */ | ||||
| .highlight .ne { color: #f6f3e8; } /* Name.Exception */ | ||||
| .highlight .nf { color: #F64DBA; } /* Name.Function */ | ||||
| .highlight .nl { color: #f6f3e8; } /* Name.Label */ | ||||
| .highlight .nn { color: #f6f3e8; } /* Name.Namespace */ | ||||
| .highlight .nx { color: #f6f3e8; } /* Name.Other */ | ||||
| .highlight .py { color: #f6f3e8; } /* Name.Property */ | ||||
| .highlight .nt { color: #00ADEE; } /* Name.Tag */ | ||||
| .highlight .nv { color: #C6C5FE; } /* Name.Variable */ | ||||
| .highlight .ow { color: #ffffff; } /* Operator.Word */ | ||||
| .highlight .w { color: #f6f3e8; } /* Text.Whitespace */ | ||||
| .highlight .mf { color: #FF73FD; } /* Literal.Number.Float */ | ||||
| .highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */ | ||||
| .highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */ | ||||
| .highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */ | ||||
| .highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */ | ||||
| .highlight .sc { color: #A8FF60; } /* Literal.String.Char */ | ||||
| .highlight .sd { color: #A8FF60; } /* Literal.String.Doc */ | ||||
| .highlight .s2 { color: #A8FF60; } /* Literal.String.Double */ | ||||
| .highlight .se { color: #A8FF60; } /* Literal.String.Escape */ | ||||
| .highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */ | ||||
| .highlight .si { color: #A8FF60; } /* Literal.String.Interpol */ | ||||
| .highlight .sx { color: #A8FF60; } /* Literal.String.Other */ | ||||
| .highlight .sr { color: #A8FF60; } /* Literal.String.Regex */ | ||||
| .highlight .s1 { color: #A8FF60; } /* Literal.String.Single */ | ||||
| .highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */ | ||||
| .highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */ | ||||
| .highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */ | ||||
| .highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */ | ||||
| .highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */ | ||||
| .highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */ | ||||
							
								
								
									
										70
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| {# | ||||
|     haiku/layout.html | ||||
|     ~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|     Sphinx layout template for the haiku theme. | ||||
|  | ||||
|     :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. | ||||
|     :license: BSD, see LICENSE for details. | ||||
| #} | ||||
| {% extends "basic/layout.html" %} | ||||
| {% set script_files = script_files + ['_static/theme_extras.js'] %} | ||||
| {% set css_files = css_files + ['_static/print.css'] %} | ||||
|  | ||||
| {# do not display relbars #} | ||||
| {% block relbar1 %}{% endblock %} | ||||
| {% block relbar2 %}{% endblock %} | ||||
|  | ||||
| {% macro nav() %} | ||||
|         <p> | ||||
|         {%- block haikurel1 %} | ||||
|         {%- endblock %} | ||||
|         {%- if prev %} | ||||
|         «  <a href="{{ prev.link|e }}">{{ prev.title }}</a> | ||||
|           ::   | ||||
|         {%- endif %} | ||||
|         <a class="uplink" href="{{ pathto(master_doc) }}">{{ _('Contents') }}</a> | ||||
|         {%- if next %} | ||||
|           ::   | ||||
|         <a href="{{ next.link|e }}">{{ next.title }}</a>  » | ||||
|         {%- endif %} | ||||
|         {%- block haikurel2 %} | ||||
|         {%- endblock %} | ||||
|         </p> | ||||
| {% endmacro %} | ||||
|  | ||||
| {% block content %} | ||||
|       <div class="header"> | ||||
|         {%- block haikuheader %} | ||||
|         {%- if theme_full_logo != "false" %} | ||||
|         <a href="{{ pathto('index') }}"> | ||||
|           <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/> | ||||
|         </a> | ||||
|         {%- else %} | ||||
|         {%- if logo -%} | ||||
|           <img class="rightlogo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/> | ||||
|         {%- endif -%} | ||||
|         <h1 class="heading"><a href="{{ pathto('index') }}"> | ||||
|           <span>{{ title|striptags }}</span></a></h1> | ||||
|         <h2 class="heading"><span>{{ shorttitle|e }}</span></h2> | ||||
|         {%- endif %} | ||||
|         {%- endblock %} | ||||
|       </div> | ||||
|       <div class="topnav"> | ||||
|       {{ nav() }} | ||||
|       </div> | ||||
|       <div class="content"> | ||||
|         {#{%- if display_toc %} | ||||
|         <div id="toc"> | ||||
|           <h3>Table Of Contents</h3> | ||||
|           {{ toc }} | ||||
|         </div> | ||||
|         {%- endif %}#} | ||||
|         {% block body %}{% endblock %} | ||||
|       </div> | ||||
|       <div class="bottomnav"> | ||||
|       {{ nav() }} | ||||
|       </div> | ||||
|       <a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a> | ||||
| {% endblock %} | ||||
|  | ||||
| @@ -50,7 +50,7 @@ copyright = u'2011, Nathan Fritz, Lance Stout' | ||||
| # The short X.Y version. | ||||
| version = '1.0' | ||||
| # The full version, including alpha/beta/rc tags. | ||||
| release = '1.0RC3' | ||||
| release = '1.0' | ||||
|  | ||||
| # The language for content autogenerated by Sphinx. Refer to documentation | ||||
| # for a list of supported languages. | ||||
| @@ -91,7 +91,7 @@ pygments_style = 'tango' | ||||
|  | ||||
| # The theme to use for HTML and HTML Help pages.  See the documentation for | ||||
| # a list of builtin themes. | ||||
| html_theme = 'nature' | ||||
| html_theme = 'haiku' | ||||
|  | ||||
| # Theme options are theme-specific and customize the look and feel of a theme | ||||
| # further.  For a list of options available for each theme, see the | ||||
|   | ||||
| @@ -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:: | ||||
|  | ||||
|   | ||||
| @@ -1,2 +1,208 @@ | ||||
| .. _mucbot: | ||||
|  | ||||
| ========================= | ||||
| Mulit-User Chat (MUC) Bot | ||||
| ========================= | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|  | ||||
| If you have not yet installed SleekXMPP, do so now by either checking out a version | ||||
| from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install sleekxmpp  # Or: easy_install sleekxmpp | ||||
|  | ||||
|  | ||||
| Now that you've got the basic gist of using SleekXMPP by following the | ||||
| echobot example (:ref:`echobot`), we can use one of the bundled plugins | ||||
| to create a very popular XMPP starter project: a `Multi-User Chat`_ | ||||
| (MUC) bot. Our bot will login to an XMPP server, join an MUC chat room | ||||
| and "lurk" indefinitely, responding with a generic message to anyone | ||||
| that mentions its nickname. It will also greet members as they join the | ||||
| chat room. | ||||
|  | ||||
| .. _`multi-user chat`: http://xmpp.org/extensions/xep-0045.html | ||||
|  | ||||
| Joining The Room | ||||
| ---------------- | ||||
|  | ||||
| As usual, our code will be based on the pattern explained in :ref:`echobot`. | ||||
| To start, we create an ``MUCBot`` class based on | ||||
| :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` and which accepts | ||||
| parameters for the JID of the MUC room to join, and the nick that the | ||||
| bot will use inside the chat room.  We also register an | ||||
| :term:`event handler` for the :term:`session_start` event. | ||||
|  | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import sleekxmpp | ||||
|  | ||||
|     class MUCBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password, room, nick): | ||||
|             sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|             self.room = room | ||||
|             self.nick = nick | ||||
|  | ||||
|             self.add_event_handler("session_start", self.start) | ||||
|  | ||||
| After initialization, we also need to register the MUC (XEP-0045) plugin | ||||
| so that we can make use of the group chat plugin's methods and events. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     xmpp.register_plugin('xep_0045') | ||||
|  | ||||
| Finally, we can make our bot join the chat room once an XMPP session | ||||
| has been established: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|         self.plugin['xep_0045'].joinMUC(self.room, | ||||
|                                         self.nick, | ||||
|                                         wait=True) | ||||
|  | ||||
| Note that as in :ref:`echobot`, we need to include send an initial presence and request | ||||
| the roster. Next, we want to join the group chat, so we call the | ||||
| ``joinMUC`` method of the MUC plugin. | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     The :attr:`plugin <sleekxmpp.basexmpp.BaseXMPP.plugin>` attribute is | ||||
|     dictionary that maps to instances of plugins that we have previously | ||||
|     registered, by their names. | ||||
|  | ||||
|  | ||||
| Adding Functionality | ||||
| -------------------- | ||||
|  | ||||
| Currently, our bot just sits dormantly inside the chat room, but we | ||||
| would like it to respond to two distinct events by issuing a generic | ||||
| message in each case to the chat room. In particular, when a member | ||||
| mentions the bot's nickname inside the chat room, and when a member | ||||
| joins the chat room. | ||||
|  | ||||
| Responding to Mentions | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Whenever a user mentions our bot's nickname in chat, our bot will | ||||
| respond with a generic message resembling *"I heard that, user."* We do | ||||
| this by examining all of the messages sent inside the chat and looking | ||||
| for the ones which contain the nickname string. | ||||
|  | ||||
| First, we register an event handler for the :term:`groupchat_message` | ||||
| event inside the bot's ``__init__`` function. | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     We do not register a handler for the :term:`message` event in this | ||||
|     bot, but if we did, the group chat message would have been sent to | ||||
|     both handlers. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def __init__(self, jid, password, room, nick): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.room = room | ||||
|         self.nick = nick | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|         self.add_event_handler("groupchat_message", self.muc_message) | ||||
|  | ||||
| Then, we can send our generic message whenever the bot's nickname gets | ||||
| mentioned. | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     Always check that a message is not from yourself, | ||||
|     otherwise you will create an infinite loop responding | ||||
|     to your own messages. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def muc_message(self, msg): | ||||
|         if msg['mucnick'] != self.nick and self.nick in msg['body']: | ||||
|             self.send_message(mto=msg['from'].bare, | ||||
|                               mbody="I heard that, %s." % msg['mucnick'], | ||||
|                               mtype='groupchat') | ||||
|  | ||||
|  | ||||
| Greeting Members | ||||
| ~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Now we want to greet member whenever they join the group chat. To | ||||
| do this we will use the dynamic ``muc::room@server::got_online`` [1]_ | ||||
| event so it's a good idea to register an event handler for it. | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     The groupchat_presence event is triggered whenever a | ||||
|     presence stanza is received from any chat room, including | ||||
|     any presences you send yourself. To limit event handling | ||||
|     to a single room, use the events ``muc::room@server::presence``, | ||||
|     ``muc::room@server::got_online``, or ``muc::room@server::got_offline``. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def __init__(self, jid, password, room, nick): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.room = room | ||||
|         self.nick = nick | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|         self.add_event_handler("groupchat_message", self.muc_message) | ||||
|         self.add_event_handler("muc::%s::got_online" % self.room, | ||||
|                                self.muc_online) | ||||
|  | ||||
| Now all that's left to do is to greet them: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     def muc_online(self, presence): | ||||
|         if presence['muc']['nick'] != self.nick: | ||||
|             self.send_message(mto=presence['from'].bare, | ||||
|                               mbody="Hello, %s %s" % (presence['muc']['role'], | ||||
|                                                       presence['muc']['nick']), | ||||
|                               mtype='groupchat') | ||||
|  | ||||
| .. [1] this is similar to the :term:`got_online` event and is sent by | ||||
|        the xep_0045 plugin whenever a member joins the referenced | ||||
|        MUC chat room. | ||||
|  | ||||
|  | ||||
| Final Product | ||||
| ------------- | ||||
|  | ||||
| .. compound:: | ||||
|  | ||||
|     The final step is to create a small runner script for initialising our ``MUCBot`` class and adding some | ||||
|     basic configuration options. By following the basic boilerplate pattern in :ref:`echobot`, we arrive | ||||
|     at the code below. To experiment with this example, you can use: | ||||
|  | ||||
|     .. code-block:: sh | ||||
|  | ||||
|             python muc.py -d -j jid@example.com -r room@muc.example.net -n lurkbot | ||||
|  | ||||
|     which will prompt for the password, log in, and join the group chat. To test, open | ||||
|     your regular IM client and join the same group chat that you sent the bot to. You | ||||
|     will see ``lurkbot`` as one of the members in the group chat, and that it greeted | ||||
|     you upon entry. Send a message with the string "lurkbot" inside the body text, and you | ||||
|     will also see that it responds with our pre-programmed customized message. | ||||
|  | ||||
| .. include:: ../../examples/muc.py | ||||
|     :literal: | ||||
|   | ||||
| @@ -13,7 +13,7 @@ SleekXMPP | ||||
|     ``develop`` branch. | ||||
|  | ||||
|     **Latest Stable Release** | ||||
|         - `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_   | ||||
|         - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_ | ||||
|  | ||||
|     **Develop Releases** | ||||
|         - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_ | ||||
| @@ -59,6 +59,72 @@ SleekXMPP's design goals and philosphy are: | ||||
|     sensible defaults and appropriate abstractions. XML can be ugly to work | ||||
|     with, but it doesn't have to be that way. | ||||
|  | ||||
| Here's your first SleekXMPP Bot: | ||||
| -------------------------------- | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import logging | ||||
|  | ||||
|     from sleekxmpp import ClientXMPP | ||||
|     from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
|     class EchoBot(ClientXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password): | ||||
|             ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|             self.add_event_handler("session_start", self.session_start) | ||||
|             self.add_event_handler("message", self.message) | ||||
|  | ||||
|             # If you wanted more functionality, here's how to register plugins: | ||||
|             # self.register_plugin('xep_0030') # Service Discovery | ||||
|             # self.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|             # Here's how to access plugins once you've registered them: | ||||
|             # self['xep_0030'].add_feature('echo_demo') | ||||
|  | ||||
|             # If you are working with an OpenFire server, you will | ||||
|             # need to use a different SSL version: | ||||
|             # import ssl | ||||
|             # self.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|         def session_start(self, event): | ||||
|             self.send_presence() | ||||
|             self.get_roster() | ||||
|  | ||||
|             # Most get_*/set_* methods from plugins use Iq stanzas, which | ||||
|             # can generate IqError and IqTimeout exceptions | ||||
|             # | ||||
|             # try: | ||||
|             #     self.get_roster() | ||||
|             # except IqError as err: | ||||
|             #     logging.error('There was an error getting the roster') | ||||
|             #     logging.error(err.iq['error']['condition']) | ||||
|             #     self.disconnect() | ||||
|             # except IqTimeout: | ||||
|             #     logging.error('Server is taking too long to respond') | ||||
|             #     self.disconnect() | ||||
|  | ||||
|         def message(self, msg): | ||||
|             if msg['type'] in ('chat', 'normal'): | ||||
|                 msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
|  | ||||
|     if __name__ == '__main__': | ||||
|         # Ideally use optparse or argparse to get JID,  | ||||
|         # password, and log level. | ||||
|  | ||||
|         logging.basicConfig(level=logging.DEBUG, | ||||
|                             format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|         xmpp = EchoBot('somejid@example.com', 'use_getpass') | ||||
|         xmpp.connect() | ||||
|         xmpp.process(block=True) | ||||
|  | ||||
|  | ||||
|  | ||||
| Getting Started (with Examples) | ||||
| ------------------------------- | ||||
| .. toctree:: | ||||
| @@ -156,18 +222,25 @@ Additional Info | ||||
|  | ||||
| Credits | ||||
| ------- | ||||
| **Main Author:** Nathan Fritz | ||||
|  | ||||
| **Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_ | ||||
|      `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, | ||||
|      `@fritzy <http://twitter.com/fritzy>`_ | ||||
|  | ||||
|      Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP | ||||
|     <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP | ||||
|      <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of the XMPP | ||||
|      Council. | ||||
|  | ||||
| **Co-Author:** Lance Stout | ||||
| **Co-Author:** `Lance Stout <http://andyet.net/team/lance>`_ | ||||
|      `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, | ||||
|      `@lancestout <http://twitter.com/lancestout>`_ | ||||
|  | ||||
| Both Fritzy and Lance work for `&yet <http://andyet.net>`_, which specializes in | ||||
| realtime web and XMPP applications. | ||||
|  | ||||
|     - `contact@andyet.net <mailto:contact@andyet.net>`_ | ||||
|     - `XMPP Consulting <http://xmppconsulting.com>`_ | ||||
|  | ||||
| **Contributors:** | ||||
|     - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_) | ||||
|     - Dann Martens (`dannmartens <http://github.com/dannmartens>`_) | ||||
|   | ||||
| @@ -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.") | ||||
							
								
								
									
										173
									
								
								examples/custom_stanzas/custom_stanza_provider.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										173
									
								
								examples/custom_stanzas/custom_stanza_provider.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| #!/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 | ||||
|  | ||||
| from sleekxmpp import ClientXMPP, Iq | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout, XMPPError | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from stanza import Action | ||||
|  | ||||
| # 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 ActionBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A simple SleekXMPP bot that receives a custom stanza | ||||
|     from another client. | ||||
|     """ | ||||
|  | ||||
|     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) | ||||
|  | ||||
|         self.registerHandler( | ||||
|           Callback('Some custom iq', | ||||
|             StanzaPath('iq@type=set/action'), | ||||
|             self._handle_action)) | ||||
|  | ||||
|         self.add_event_handler('custom_action', | ||||
|                 self._handle_action_event, | ||||
|                 threaded=True) | ||||
|  | ||||
|         register_stanza_plugin(Iq, Action) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def _handle_action(self, iq): | ||||
|         """ | ||||
|         Raise an event for the stanza so that it can be processed in its | ||||
|         own thread without blocking the main stanza processing loop. | ||||
|         """ | ||||
|         self.event('custom_action', iq) | ||||
|  | ||||
|     def _handle_action_event(self, iq): | ||||
|         """ | ||||
|         Respond to the custom action event. | ||||
|  | ||||
|         Since one of the actions is to disconnect, this | ||||
|         event handler needs to be run in threaded mode, by | ||||
|         using `threaded=True` in the `add_event_handler` call. | ||||
|         """ | ||||
|         method = iq['action']['method'] | ||||
|         param = iq['action']['param'] | ||||
|  | ||||
|         if method == 'is_prime' and param == '2': | ||||
|             print("got message: %s" % iq) | ||||
|             iq.reply() | ||||
|             iq['action']['status'] = 'done' | ||||
|             iq.send() | ||||
|         elif method == 'bye': | ||||
|             print("got message: %s" % iq) | ||||
|             self.disconnect() | ||||
|         else: | ||||
|             print("got message: %s" % iq) | ||||
|             iq.reply() | ||||
|             iq['action']['status'] = 'error' | ||||
|             iq.send() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     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 CommandBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = ActionBot(opts.jid, opts.password) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands | ||||
|     xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency':15}) | ||||
|  | ||||
|     # 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.") | ||||
							
								
								
									
										175
									
								
								examples/custom_stanzas/custom_stanza_user.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										175
									
								
								examples/custom_stanzas/custom_stanza_user.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| #!/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 | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
|  | ||||
| from stanza import Action | ||||
|  | ||||
| # 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 ActionUserBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A simple SleekXMPP bot that sends a custom action stanza | ||||
|     to another client. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, other): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.action_provider = other | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
|         # and the XML streams are ready for use. We want to | ||||
|         # listen for this event so that we we can initialize | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start, threaded=True) | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|         register_stanza_plugin(Iq, Action) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         self.send_custom_iq() | ||||
|  | ||||
|     def send_custom_iq(self): | ||||
|         """Create and send two custom actions. | ||||
|  | ||||
|         If the first action was successful, then send | ||||
|         a shutdown command and then disconnect. | ||||
|         """ | ||||
|         iq = self.Iq() | ||||
|         iq['to'] = self.action_provider | ||||
|         iq['type'] = 'set' | ||||
|         iq['action']['method'] = 'is_prime' | ||||
|         iq['action']['param'] = '2' | ||||
|  | ||||
|         try: | ||||
|             resp = iq.send() | ||||
|             if resp['action']['status'] == 'done': | ||||
|                 #sending bye | ||||
|                 iq2 = self.Iq() | ||||
|                 iq2['to'] = self.action_provider | ||||
|                 iq2['type'] = 'set' | ||||
|                 iq2['action']['method'] = 'bye' | ||||
|                 iq2.send(block=False) | ||||
|              | ||||
|                 # The wait=True delays the disconnect until the queue | ||||
|                 # of stanzas to be sent becomes empty. | ||||
|                 self.disconnect(wait=True) | ||||
|         except XMPPError: | ||||
|             print('There was an error sending the custom action.') | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|         Process incoming message stanzas. | ||||
|  | ||||
|         Arguments: | ||||
|             msg -- The received message stanza. | ||||
|         """ | ||||
|         logging.info(msg['body']) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     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("-o", "--other", dest="other", | ||||
|                     help="JID providing custom stanza") | ||||
|  | ||||
|     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.other is None: | ||||
|         opts.other = raw_input("JID Providing custom stanza: ") | ||||
|  | ||||
|     # Setup the CommandBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = ActionUserBot(opts.jid, opts.password, opts.other) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0050') # Adhoc Commands | ||||
|  | ||||
|     # 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.") | ||||
							
								
								
									
										56
									
								
								examples/custom_stanzas/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								examples/custom_stanzas/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
|  | ||||
| class Action(ElementBase): | ||||
|  | ||||
|     """ | ||||
|     A stanza class for XML content of the form: | ||||
|  | ||||
|     <action xmlns="sleekxmpp:custom:actions"> | ||||
|       <method>X</method> | ||||
|       <param>X</param> | ||||
|       <status>X</status> | ||||
|     </action> | ||||
|     """ | ||||
|     | ||||
|     #: The `name` field refers to the basic XML tag name of the | ||||
|     #: stanza. Here, the tag name will be 'action'. | ||||
|     name = 'action' | ||||
|  | ||||
|     #: The namespace of the main XML tag. | ||||
|     namespace = 'sleekxmpp:custom:actions' | ||||
|  | ||||
|     #: The `plugin_attrib` value is the name that can be used | ||||
|     #: with a parent stanza to access this stanza. For example | ||||
|     #: from an Iq stanza object, accessing: | ||||
|     #:  | ||||
|     #:     iq['action'] | ||||
|     #:  | ||||
|     #: would reference an Action object, and will even create | ||||
|     #: an Action object and append it to the Iq stanza if | ||||
|     #: one doesn't already exist. | ||||
|     plugin_attrib = 'action' | ||||
|  | ||||
|     #: Stanza objects expose dictionary-like interfaces for | ||||
|     #: accessing and manipulating substanzas and other values. | ||||
|     #: The set of interfaces defined here are the names of | ||||
|     #: these dictionary-like interfaces provided by this stanza | ||||
|     #: type. For example, an Action stanza object can use: | ||||
|     #: | ||||
|     #:     action['method'] = 'foo' | ||||
|     #:     print(action['param']) | ||||
|     #:     del action['status'] | ||||
|     #: | ||||
|     #: to set, get, or remove its values. | ||||
|     interfaces = set(('method', 'param', 'status')) | ||||
|  | ||||
|     #: By default, values in the `interfaces` set are mapped to | ||||
|     #: attribute values. This can be changed such that an interface | ||||
|     #: maps to a subelement's text value by adding interfaces to | ||||
|     #: the sub_interfaces set. For example, here all interfaces | ||||
|     #: are marked as sub_interfaces, and so the XML produced will | ||||
|     #: look like: | ||||
|     #:  | ||||
|     #:     <action xmlns="sleekxmpp:custom:actions"> | ||||
|     #:       <method>foo</method> | ||||
|     #:     </action> | ||||
|     sub_interfaces = interfaces | ||||
| @@ -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 | ||||
|  | ||||
| @@ -62,7 +63,7 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|         # 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("session_start", self.start, threaded=True) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
| @@ -83,6 +84,7 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         try: | ||||
|             if self.get in self.info_types: | ||||
|                 # By using block=True, the result stanza will be | ||||
|                 # returned. Execution will block until the reply is | ||||
| @@ -92,7 +94,7 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|                 info = self['xep_0030'].get_info(jid=self.target_jid, | ||||
|                                                  node=self.target_node, | ||||
|                                                  block=True) | ||||
|         if self.get in self.items_types: | ||||
|             elif self.get in self.items_types: | ||||
|                 # The same applies from above. Listen for the | ||||
|                 # disco_items event or pass a callback function | ||||
|                 # if you need to process a non-blocking request. | ||||
| @@ -101,9 +103,12 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|                                                    block=True) | ||||
|             else: | ||||
|                 logging.error("Invalid disco request type.") | ||||
|             self.disconnect() | ||||
|                 return | ||||
|  | ||||
|         except IqError as e: | ||||
|             logging.error("Entity returned an error: %s" % e.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
|             logging.error("No response received.") | ||||
|         else: | ||||
|             header = 'XMPP Service Discovery: %s' % self.target_jid | ||||
|             print(header) | ||||
|             print('-' * len(header)) | ||||
| @@ -125,7 +130,7 @@ class Disco(sleekxmpp.ClientXMPP): | ||||
|                 print('Items:') | ||||
|                 for item in items['disco_items']['items']: | ||||
|                     print('  - %s' % str(item)) | ||||
|  | ||||
|         finally: | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								examples/download_avatars.py
									
									
									
									
									
										Normal 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.") | ||||
							
								
								
									
										149
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										149
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| #!/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', { | ||||
|             'accept_stream': self.accept_stream | ||||
|         }) # 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) | ||||
|         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): | ||||
|         # NOTE: IBB streams are bi-directional, so the original sender is | ||||
|         # now the opened stream's receiver. | ||||
|         print('Stream opened: %s from %s' % (stream.sid, stream.receiver)) | ||||
|  | ||||
|         # You could run a loop reading from the stream using stream.recv(), | ||||
|         # 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.") | ||||
| @@ -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 | ||||
|  | ||||
| @@ -76,8 +76,8 @@ class MUCBot(sleekxmpp.ClientXMPP): | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.getRoster() | ||||
|         self.sendPresence() | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|         self.plugin['xep_0045'].joinMUC(self.room, | ||||
|                                         self.nick, | ||||
|                                         # If a room password is needed, use: | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| @@ -45,7 +45,7 @@ class PingTest(sleekxmpp.ClientXMPP): | ||||
|         # 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("session_start", self.start, threaded=True) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										198
									
								
								examples/pubsub_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								examples/pubsub_client.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,198 @@ | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.xmlstream import ET, tostring | ||||
|  | ||||
|  | ||||
| # 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 PubsubClient(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password, server,  | ||||
|                        node=None, action='list', data=''): | ||||
|         super(PubsubClient, self).__init__(jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0059') | ||||
|         self.register_plugin('xep_0060') | ||||
|  | ||||
|         self.actions = ['nodes', 'create', 'delete',  | ||||
|                         'publish', 'get', 'retract', | ||||
|                         'purge', 'subscribe', 'unsubscribe'] | ||||
|  | ||||
|         self.action = action | ||||
|         self.node = node | ||||
|         self.data = data | ||||
|         self.pubsub_server = server | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start, threaded=True) | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         try: | ||||
|             getattr(self, self.action)() | ||||
|         except: | ||||
|             logging.error('Could not execute: %s' % self.action) | ||||
|         self.disconnect() | ||||
|  | ||||
|     def nodes(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].get_nodes(self.pubsub_server, self.node) | ||||
|             for item in result['disco_items']['items']: | ||||
|                 print('  - %s' % str(item)) | ||||
|         except: | ||||
|             logging.error('Could not retrieve node list.') | ||||
|  | ||||
|     def create(self): | ||||
|         try: | ||||
|             self['xep_0060'].create_node(self.pubsub_server, self.node) | ||||
|         except: | ||||
|             logging.error('Could not create node: %s' % self.node) | ||||
|  | ||||
|     def delete(self): | ||||
|         try: | ||||
|             self['xep_0060'].delete_node(self.pubsub_server, self.node) | ||||
|             print('Deleted node: %s' % self.node) | ||||
|         except: | ||||
|             logging.error('Could not delete node: %s' % self.node) | ||||
|  | ||||
|     def publish(self): | ||||
|         payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data) | ||||
|         try: | ||||
|             result = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) | ||||
|             id = result['pubsub']['publish']['item']['id'] | ||||
|             print('Published at item id: %s' % id) | ||||
|         except: | ||||
|             logging.error('Could not publish to: %s' % self.node) | ||||
|  | ||||
|     def get(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) | ||||
|             for item in result['pubsub']['items']['substanzas']: | ||||
|                 print('Retrieved item %s: %s' % (item['id'], tostring(item['payload']))) | ||||
|         except: | ||||
|             logging.error('Could not retrieve item %s from node %s' % (self.data, self.node)) | ||||
|  | ||||
|     def retract(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].retract(self.pubsub_server, self.node, self.data) | ||||
|             print('Retracted item %s from node %s' % (self.data, self.node)) | ||||
|         except: | ||||
|             logging.error('Could not retract item %s from node %s' % (self.data, self.node)) | ||||
|  | ||||
|     def purge(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].purge(self.pubsub_server, self.node) | ||||
|             print('Purged all items from node %s' % self.node) | ||||
|         except: | ||||
|             logging.error('Could not purge items from node %s' % self.node) | ||||
|  | ||||
|     def subscribe(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].subscribe(self.pubsub_server, self.node) | ||||
|             print('Subscribed %s to node %s' % (self.boundjid.bare, self.node)) | ||||
|         except: | ||||
|             logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node)) | ||||
|  | ||||
|     def unsubscribe(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].unsubscribe(self.pubsub_server, self.node) | ||||
|             print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node)) | ||||
|         except: | ||||
|             logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node)) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     optp = OptionParser() | ||||
|     optp.version = '%%prog 0.1' | ||||
|     optp.usage = "Usage: %%prog [options] <jid> " + \ | ||||
|                              'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \ | ||||
|                              ' [<node> <data>]' | ||||
|  | ||||
|     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 len(args) < 2: | ||||
|         optp.print_help() | ||||
|         exit() | ||||
|  | ||||
|     if opts.jid is None: | ||||
|         opts.jid = raw_input("Username: ") | ||||
|     if opts.password is None: | ||||
|         opts.password = getpass.getpass("Password: ") | ||||
|  | ||||
|     if len(args) == 2: | ||||
|         args = (args[0], args[1], '', '', '') | ||||
|     elif len(args) == 3: | ||||
|         args = (args[0], args[1], args[2], '', '') | ||||
|     elif len(args) == 4: | ||||
|         args = (args[0], args[1], args[2], args[3], '') | ||||
|  | ||||
|  | ||||
|     # Setup the Pubsub client | ||||
|     xmpp = PubsubClient(opts.jid, opts.password, | ||||
|                         server=args[0], | ||||
|                         node=args[2], | ||||
|                         action=args[1], | ||||
|                         data=args[3]) | ||||
|  | ||||
|     # 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.") | ||||
							
								
								
									
										151
									
								
								examples/pubsub_events.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								examples/pubsub_events.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.xmlstream import ET, tostring | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
|  | ||||
|  | ||||
| # 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 PubsubEvents(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(PubsubEvents, self).__init__(jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0059') | ||||
|         self.register_plugin('xep_0060') | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|  | ||||
|         # Some services may require configuration to allow | ||||
|         # sending delete, configuration, or subscription  events. | ||||
|         self.add_event_handler('pubsub_publish', self._publish) | ||||
|         self.add_event_handler('pubsub_retract', self._retract) | ||||
|         self.add_event_handler('pubsub_purge', self._purge) | ||||
|         self.add_event_handler('pubsub_delete', self._delete) | ||||
|         self.add_event_handler('pubsub_config', self._config) | ||||
|         self.add_event_handler('pubsub_subscription', self._subscription) | ||||
|  | ||||
|         # Want to use nicer, more specific pubsub event names? | ||||
|         # self['xep_0060'].map_node_event('node_name', 'event_prefix') | ||||
|         # self.add_event_handler('event_prefix_publish', handler) | ||||
|         # self.add_event_handler('event_prefix_retract', handler) | ||||
|         # self.add_event_handler('event_prefix_purge', handler) | ||||
|         # self.add_event_handler('event_prefix_delete', handler) | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|     def _publish(self, msg): | ||||
|         """Handle receiving a publish item event.""" | ||||
|         print('Published item %s to %s:' % ( | ||||
|             msg['pubsub_event']['items']['item']['id'], | ||||
|             msg['pubsub_event']['items']['node'])) | ||||
|         data = msg['pubsub_event']['items']['item']['payload'] | ||||
|         if data is not None: | ||||
|             print(tostring(data)) | ||||
|         else: | ||||
|             print('No item content') | ||||
|  | ||||
|     def _retract(self, msg): | ||||
|         """Handle receiving a retract item event.""" | ||||
|         print('Retracted item %s from %s' % ( | ||||
|             msg['pubsub_event']['items']['retract']['id'], | ||||
|             msg['pubsub_event']['items']['node'])) | ||||
|  | ||||
|     def _purge(self, msg): | ||||
|         """Handle receiving a node purge event.""" | ||||
|         print('Purged all items from %s' % ( | ||||
|             msg['pubsub_event']['purge']['node'])) | ||||
|  | ||||
|     def _delete(self, msg): | ||||
|         """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:' % ( | ||||
|             msg['pubsub_event']['configuration']['node'])) | ||||
|         print(msg['pubsub_event']['configuration']['form']) | ||||
|  | ||||
|     def _subscription(self, msg): | ||||
|         """Handle receiving a node subscription event.""" | ||||
|         print('Subscription change for node %s:' % ( | ||||
|             msg['pubsub_event']['subscription']['node'])) | ||||
|         print(msg['pubsub_event']['subscription']) | ||||
|  | ||||
|  | ||||
| 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: ") | ||||
|  | ||||
|     logging.info("Run this in conjunction with the pubsub_client.py " + \ | ||||
|                  "example to watch events happen as you give commands.") | ||||
|  | ||||
|     # Setup the PubsubEvents listener | ||||
|     xmpp = PubsubEvents(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.") | ||||
							
								
								
									
										175
									
								
								examples/register_account.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								examples/register_account.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| #!/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 | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
| # 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 RegisterBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic bot that will attempt to register an account | ||||
|     with an XMPP server. | ||||
|  | ||||
|     NOTE: This follows the very basic registration workflow | ||||
|           from XEP-0077. More advanced server registration | ||||
|           workflows will need to check for data forms, etc. | ||||
|     """ | ||||
|  | ||||
|     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, threaded=True) | ||||
|  | ||||
|         # 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  | ||||
|         # 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 | ||||
|         # for data forms and OOB links that will make that easier. | ||||
|         self.add_event_handler("register", self.register, threaded=True) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         # We're only concerned about registering, so nothing more to do here. | ||||
|         self.disconnect() | ||||
|  | ||||
|     def register(self, iq): | ||||
|         """ | ||||
|         Fill out and submit a registration form. | ||||
|  | ||||
|         The form may be composed of basic registration fields, a data form, | ||||
|         an out-of-band link, or any combination thereof. Data forms and OOB | ||||
|         links can be checked for as so: | ||||
|  | ||||
|         if iq.match('iq/register/form'): | ||||
|             # do stuff with data form | ||||
|             # iq['register']['form']['fields'] | ||||
|         if iq.match('iq/register/oob'): | ||||
|             # do stuff with OOB URL | ||||
|             # iq['register']['oob']['url'] | ||||
|  | ||||
|         To get the list of basic registration fields, you can use: | ||||
|             iq['register']['fields'] | ||||
|         """ | ||||
|         resp = self.Iq() | ||||
|         resp['type'] = 'set' | ||||
|         resp['register']['username'] = self.boundjid.user | ||||
|         resp['register']['password'] = self.password | ||||
|  | ||||
|         try: | ||||
|             resp.send(now=True) | ||||
|             logging.info("Account created for %s!" % self.boundjid) | ||||
|         except IqError as e: | ||||
|             logging.error("Could not register account: %s" %  | ||||
|                     e.iq['error']['text']) | ||||
|             self.disconnect() | ||||
|         except IqTimeout: | ||||
|             logging.error("No response from server.") | ||||
|             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") | ||||
|  | ||||
|     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 RegisterBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = RegisterBot(opts.jid, opts.password) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data forms | ||||
|     xmpp.register_plugin('xep_0066') # Out-of-band Data | ||||
|     xmpp.register_plugin('xep_0077') # In-band Registration | ||||
|  | ||||
|     # 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.") | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class Thermostat(Endpoint): | ||||
|     def FQN(self): | ||||
|         return 'thermostat' | ||||
|      | ||||
|     def __init(self, initial_temperature): | ||||
|     def __init__(self, initial_temperature): | ||||
|         self._temperature = initial_temperature | ||||
|         self._event = threading.Event()         | ||||
|      | ||||
|   | ||||
| @@ -15,7 +15,7 @@ class Thermostat(Endpoint): | ||||
|     def FQN(self): | ||||
|         return 'thermostat' | ||||
|      | ||||
|     def __init(self, initial_temperature): | ||||
|     def __init__(self, initial_temperature): | ||||
|         self._temperature = initial_temperature | ||||
|         self._event = threading.Event()         | ||||
|      | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| @@ -47,7 +47,7 @@ class SendMsgBot(sleekxmpp.ClientXMPP): | ||||
|         # 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("session_start", self.start, threaded=True) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|   | ||||
							
								
								
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								examples/set_avatar.py
									
									
									
									
									
										Normal 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.") | ||||
							
								
								
									
										247
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,247 @@ | ||||
| #!/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 | ||||
|  | ||||
| try: | ||||
|     from httplib import HTTPSConnection | ||||
|     from urllib import urlencode | ||||
| except ImportError: | ||||
|     from urllib.parse import urlencode | ||||
|     from http.client import HTTPSConnection | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp.xmlstream import JID | ||||
|  | ||||
| # 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 ThirdPartyAuthBot(sleekxmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A simple SleekXMPP bot that will echo messages it | ||||
|     receives, along with a short thank you message. | ||||
|  | ||||
|     This version uses a thirdpary service for authentication, | ||||
|     such as Facebook or Google. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         # The X-GOOGLE-TOKEN mech is ranked lower than PLAIN | ||||
|         # due to Google only allowing a single SASL attempt per | ||||
|         # connection. So PLAIN will be used for TLS connections, | ||||
|         # and X-GOOGLE-TOKEN for non-TLS connections. To use | ||||
|         # X-GOOGLE-TOKEN with a TLS connection, explicitly select | ||||
|         # it using: | ||||
|         # | ||||
|         # sleekxmpp.ClientXMPP.__init__(self, jid, password, | ||||
|         #                               sasl_mech="X-GOOGLE-TOKEN") | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
|         # and the XML streams are ready for use. We want to | ||||
|         # listen for this event so that we we can initialize | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|         # The message event is triggered whenever a message | ||||
|         # stanza is received. Be aware that that includes | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|         Process incoming message stanzas. Be aware that this also | ||||
|         includes MUC messages and error messages. It is usually | ||||
|         a good idea to check the messages's type before processing | ||||
|         or sending replies. | ||||
|  | ||||
|         Arguments: | ||||
|             msg -- The received message stanza. See the documentation | ||||
|                    for stanza objects and the Message stanza to see | ||||
|                    how it may be used. | ||||
|         """ | ||||
|         if msg['type'] in ('chat', 'normal'): | ||||
|             msg.reply("Thanks for sending\n%(body)s" % msg).send() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     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: ") | ||||
|  | ||||
|      | ||||
|     access_token = None | ||||
|  | ||||
|     # Since documentation on how to work with Google tokens | ||||
|     # can be difficult to find, we'll demo a basic version | ||||
|     # here. Note that responses could refer to a Captcha | ||||
|     # URL that would require a browser. | ||||
|  | ||||
|     # Using Facebook or MSN's custom authentication requires | ||||
|     # a browser, but the process is the same once a token | ||||
|     # has been retrieved. | ||||
|  | ||||
|     # Request an access token from Google: | ||||
|     try: | ||||
|         conn = HTTPSConnection('www.google.com') | ||||
|     except: | ||||
|         print('Could not connect to Google') | ||||
|         sys.exit() | ||||
|  | ||||
|     params = urlencode({ | ||||
|         'accountType': 'GOOGLE', | ||||
|         'service': 'mail', | ||||
|         'Email': JID(opts.jid).bare, | ||||
|         'Passwd': opts.password | ||||
|     }) | ||||
|     headers = { | ||||
|         'Content-Type': 'application/x-www-form-urlencoded'  | ||||
|     } | ||||
|     try: | ||||
|         conn.request('POST', '/accounts/ClientLogin', params, headers) | ||||
|         resp = conn.getresponse().read() | ||||
|         data = {} | ||||
|         for line in resp.split(): | ||||
|             k, v = line.split(b'=', 1) | ||||
|             data[k] = v | ||||
|     except Exception as e: | ||||
|         print('Could not retrieve login data') | ||||
|         sys.exit() | ||||
|  | ||||
|     if b'SID' not in data: | ||||
|         print('Required data not found') | ||||
|         sys.exit() | ||||
|  | ||||
|  | ||||
|     params = urlencode({ | ||||
|         'SID': data[b'SID'], | ||||
|         'LSID': data[b'LSID'], | ||||
|         'service': 'mail' | ||||
|     }) | ||||
|     try: | ||||
|         conn.request('POST', '/accounts/IssueAuthToken', params, headers) | ||||
|         resp = conn.getresponse() | ||||
|         data = resp.read().split() | ||||
|     except: | ||||
|         print('Could not retrieve auth data') | ||||
|         sys.exit() | ||||
|  | ||||
|     if not data: | ||||
|         print('Could not retrieve token') | ||||
|         sys.exit() | ||||
|  | ||||
|     access_token = data[0] | ||||
|  | ||||
|  | ||||
|     # Setup the ThirdPartyAuthBot and register plugins. Note that while plugins | ||||
|     # may have interdependencies, the order in which you register them does not | ||||
|     # matter. | ||||
|  | ||||
|     # If using MSN, the JID should be "user@messenger.live.com", which will | ||||
|     # be overridden on session bind. | ||||
|  | ||||
|     # We're using an access token instead of a password, so we'll use `''` as | ||||
|     # a password argument filler. | ||||
|  | ||||
|     xmpp = ThirdPartyAuthBot(opts.jid, '')   | ||||
|     xmpp.credentials['access_token'] = access_token | ||||
|  | ||||
|     # The credentials dictionary is used to provide additional authentication | ||||
|     # information beyond just a password. | ||||
|      | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0060') # PubSub | ||||
|  | ||||
|     # MSN will kill connections that have been inactive for even | ||||
|     # short periods of time. So use pings to keep the session alive; | ||||
|     # whitespace keepalives do not work. | ||||
|     xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency': 60}) | ||||
|  | ||||
|     # 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. | ||||
|     # Google only allows one SASL attempt per connection, so in order to  | ||||
|     # enable the X-GOOGLE-TOKEN mechanism, we'll disable TLS. | ||||
|     if xmpp.connect(use_tls=False): | ||||
|         # 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.") | ||||
							
								
								
									
										125
									
								
								examples/user_location.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								examples/user_location.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| try: | ||||
|     import json | ||||
| except ImportError: | ||||
|     import simplejson as json | ||||
|  | ||||
| try: | ||||
|     import requests | ||||
| except ImportError: | ||||
|     print('This demo requires the requests package for using HTTP.') | ||||
|     sys.exit() | ||||
|  | ||||
| from sleekxmpp import ClientXMPP | ||||
|  | ||||
|  | ||||
| class LocationBot(ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(LocationBot, self).__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start, threaded=True) | ||||
|         self.add_event_handler('user_location_publish',  | ||||
|                                self.user_location_publish) | ||||
|  | ||||
|         self.register_plugin('xep_0004') | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0060') | ||||
|         self.register_plugin('xep_0115') | ||||
|         self.register_plugin('xep_0128') | ||||
|         self.register_plugin('xep_0163') | ||||
|         self.register_plugin('xep_0080') | ||||
|  | ||||
|         self.current_tune = None | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         self['xep_0115'].update_caps() | ||||
|  | ||||
|         print("Using freegeoip.net to get geolocation.") | ||||
|         r = requests.get('http://freegeoip.net/json/') | ||||
|         try: | ||||
|             data = json.loads(r.text) | ||||
|         except: | ||||
|             print("Could not retrieve user location.") | ||||
|             self.disconnect() | ||||
|             return | ||||
|  | ||||
|         self['xep_0080'].publish_location( | ||||
|                 lat=data['latitude'], | ||||
|                 lon=data['longitude'], | ||||
|                 locality=data['city'], | ||||
|                 region=data['region_name'], | ||||
|                 country=data['country_name'], | ||||
|                 countrycode=data['country_code'], | ||||
|                 postalcode=data['zipcode']) | ||||
|  | ||||
|     def user_location_publish(self, msg): | ||||
|         geo = msg['pubsub_event']['items']['item']['geoloc'] | ||||
|         print("%s is at:" % msg['from']) | ||||
|         for key, val in geo.values.items(): | ||||
|             if val: | ||||
|                 print("  %s: %s" % (key, val)) | ||||
|  | ||||
|  | ||||
| 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 = LocationBot(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.") | ||||
							
								
								
									
										137
									
								
								examples/user_tune.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								examples/user_tune.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| import sys | ||||
| import logging | ||||
| import getpass | ||||
| from optparse import OptionParser | ||||
|  | ||||
| try: | ||||
|     from appscript import * | ||||
| except ImportError: | ||||
|     print('This demo requires the appscript package to interact with iTunes.') | ||||
|     sys.exit() | ||||
|  | ||||
| from sleekxmpp import ClientXMPP | ||||
|  | ||||
|  | ||||
| class TuneBot(ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(TuneBot, self).__init__(jid, password) | ||||
|  | ||||
|         # Check for the current song every 5 seconds. | ||||
|         self.schedule('Check Current Tune', 5, self._update_tune, repeat=True) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|         self.add_event_handler('user_tune_publish', self.user_tune_publish) | ||||
|  | ||||
|         self.register_plugin('xep_0004') | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0060') | ||||
|         self.register_plugin('xep_0115') | ||||
|         self.register_plugin('xep_0118') | ||||
|         self.register_plugin('xep_0128') | ||||
|         self.register_plugin('xep_0163') | ||||
|  | ||||
|         self.current_tune = None | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         self['xep_0115'].update_caps() | ||||
|  | ||||
|     def _update_tune(self): | ||||
|         itunes_count = app('System Events').processes[its.name == 'iTunes'].count() | ||||
|         if itunes_count > 0: | ||||
|             iTunes = app('iTunes') | ||||
|             if iTunes.player_state.get() == k.playing: | ||||
|                 track = iTunes.current_track.get() | ||||
|                 length = track.time.get() | ||||
|                 if ':' in length: | ||||
|                     minutes, secs = map(int, length.split(':')) | ||||
|                     secs += minutes * 60 | ||||
|                 else: | ||||
|                     secs = int(length) | ||||
|  | ||||
|                 artist = track.artist.get() | ||||
|                 title = track.name.get() | ||||
|                 source = track.album.get() | ||||
|                 rating = track.rating.get() / 10 | ||||
|  | ||||
|                 tune = (artist, secs, rating, source, title) | ||||
|                 if tune != self.current_tune: | ||||
|                     self.current_tune = tune | ||||
|  | ||||
|                     # We have a new song playing, so publish it. | ||||
|                     self['xep_0118'].publish_tune( | ||||
|                             artist=artist, | ||||
|                             length=secs, | ||||
|                             title=title, | ||||
|                             rating=rating, | ||||
|                             source=source) | ||||
|             else: | ||||
|                 # No song is playing, clear the user tune. | ||||
|                 tune = None | ||||
|                 if tune != self.current_tune: | ||||
|                     self.current_tune = tune | ||||
|                     self['xep_0118'].stop() | ||||
|  | ||||
|     def user_tune_publish(self, msg): | ||||
|         tune = msg['pubsub_event']['items']['item']['tune'] | ||||
|         print("%s is listening to: %s" % (msg['from'], tune['title'])) | ||||
|  | ||||
|  | ||||
| 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 = TuneBot(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.") | ||||
							
								
								
									
										44
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								setup.py
									
									
									
									
									
								
							| @@ -8,6 +8,7 @@ | ||||
| # file, which you should have received as part of this distribution. | ||||
|  | ||||
| import sys | ||||
| import codecs | ||||
| try: | ||||
|     from setuptools import setup, Command | ||||
| except ImportError: | ||||
| @@ -31,7 +32,7 @@ from sleekxmpp.version import __version__ | ||||
|  | ||||
| VERSION          = __version__ | ||||
| DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).' | ||||
| with open('README.rst') as readme: | ||||
| with codecs.open('README.rst', 'r', encoding='UTF-8') as readme: | ||||
|     LONG_DESCRIPTION = ''.join(readme) | ||||
|  | ||||
| CLASSIFIERS      = [ 'Intended Audience :: Developers', | ||||
| @@ -48,6 +49,8 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/stanza', | ||||
|                  'sleekxmpp/test', | ||||
|                  'sleekxmpp/roster', | ||||
|                  'sleekxmpp/util', | ||||
|                  'sleekxmpp/util/sasl', | ||||
|                  'sleekxmpp/xmlstream', | ||||
|                  'sleekxmpp/xmlstream/matcher', | ||||
|                  'sleekxmpp/xmlstream/handler', | ||||
| @@ -56,32 +59,65 @@ packages     = [ 'sleekxmpp', | ||||
|                  'sleekxmpp/plugins/xep_0004/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0009', | ||||
|                  'sleekxmpp/plugins/xep_0009/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0012', | ||||
|                  'sleekxmpp/plugins/xep_0013', | ||||
|                  'sleekxmpp/plugins/xep_0016', | ||||
|                  'sleekxmpp/plugins/xep_0027', | ||||
|                  'sleekxmpp/plugins/xep_0030', | ||||
|                  'sleekxmpp/plugins/xep_0030/stanza', | ||||
|                  'sleekxmpp/plugins/xep_0033', | ||||
|                  'sleekxmpp/plugins/xep_0047', | ||||
|                  '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_0066', | ||||
|                  '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_0107', | ||||
|                  'sleekxmpp/plugins/xep_0108', | ||||
|                  'sleekxmpp/plugins/xep_0115', | ||||
|                  'sleekxmpp/plugins/xep_0118', | ||||
|                  'sleekxmpp/plugins/xep_0128', | ||||
|                  'sleekxmpp/plugins/xep_0131', | ||||
|                  'sleekxmpp/plugins/xep_0153', | ||||
|                  'sleekxmpp/plugins/xep_0172', | ||||
|                  'sleekxmpp/plugins/xep_0184', | ||||
|                  'sleekxmpp/plugins/xep_0186', | ||||
|                  'sleekxmpp/plugins/xep_0191', | ||||
|                  '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/features', | ||||
|                  'sleekxmpp/features/feature_mechanisms', | ||||
|                  'sleekxmpp/features/feature_mechanisms/stanza', | ||||
|                  'sleekxmpp/features/feature_starttls', | ||||
|                  '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( | ||||
| @@ -95,7 +131,7 @@ setup( | ||||
|     license      = 'MIT', | ||||
|     platforms    = [ 'any' ], | ||||
|     packages     = packages, | ||||
|     requires     = [ 'dnspython' ], | ||||
|     requires     = [ 'dnspython', 'pyasn1', 'pyasn1_modules' ], | ||||
|     classifiers  = CLASSIFIERS, | ||||
|     cmdclass     = {'test': TestCommand} | ||||
| ) | ||||
|   | ||||
| @@ -10,6 +10,7 @@ from sleekxmpp.basexmpp import BaseXMPP | ||||
| from sleekxmpp.clientxmpp import ClientXMPP | ||||
| from sleekxmpp.componentxmpp import ComponentXMPP | ||||
| from sleekxmpp.stanza import Message, Presence, Iq | ||||
| from sleekxmpp.jid import JID, InvalidJID | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
|   | ||||
							
								
								
									
										200
									
								
								sleekxmpp/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								sleekxmpp/api.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| from sleekxmpp.xmlstream import JID | ||||
|  | ||||
|  | ||||
| class APIWrapper(object): | ||||
|  | ||||
|     def __init__(self, api, name): | ||||
|         self.api = api | ||||
|         self.name = name | ||||
|         if name not in self.api.settings: | ||||
|             self.api.settings[name] = {} | ||||
|  | ||||
|     def __getattr__(self, attr): | ||||
|         """Curry API management commands with the API name.""" | ||||
|         if attr == 'name': | ||||
|             return self.name | ||||
|         elif attr == 'settings': | ||||
|             return self.api.settings[self.name] | ||||
|         elif attr == 'register': | ||||
|             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 partial | ||||
|         elif attr == 'register_default': | ||||
|             def partial(handler, op, jid=None, node=None): | ||||
|                 return getattr(self.api, attr)(handler, self.name, op) | ||||
|             return partial | ||||
|         elif attr in ('run', 'restore_default', 'unregister'): | ||||
|             def partial(*args, **kwargs): | ||||
|                 return getattr(self.api, attr)(self.name, *args, **kwargs) | ||||
|             return partial | ||||
|         return None | ||||
|  | ||||
|     def __getitem__(self, attr): | ||||
|         def partial(jid=None, node=None, ifrom=None, args=None): | ||||
|             return self.api.run(self.name, attr, jid, node, ifrom, args) | ||||
|         return partial | ||||
|  | ||||
|  | ||||
| class APIRegistry(object): | ||||
|  | ||||
|     def __init__(self, xmpp): | ||||
|         self._handlers = {} | ||||
|         self._handler_defaults = {} | ||||
|         self.xmpp = xmpp | ||||
|         self.settings = {} | ||||
|  | ||||
|     def _setup(self, ctype, op): | ||||
|         """Initialize the API callback dictionaries. | ||||
|  | ||||
|         :param string ctype: The name of the API to initialize. | ||||
|         :param string op: The API operation to initialize. | ||||
|         """ | ||||
|         if ctype not in self.settings: | ||||
|             self.settings[ctype] = {} | ||||
|         if ctype not in self._handler_defaults: | ||||
|             self._handler_defaults[ctype] = {} | ||||
|         if ctype not in self._handlers: | ||||
|             self._handlers[ctype] = {} | ||||
|         if op not in self._handlers[ctype]: | ||||
|             self._handlers[ctype][op] = {'global': None, | ||||
|                                          'jid': {}, | ||||
|                                          'node': {}} | ||||
|  | ||||
|     def wrap(self, ctype): | ||||
|         """Return a wrapper object that targets a specific API.""" | ||||
|         return APIWrapper(self, ctype) | ||||
|  | ||||
|     def purge(self, ctype): | ||||
|         """Remove all information for a given API.""" | ||||
|         del self.settings[ctype] | ||||
|         del self._handler_defaults[ctype] | ||||
|         del self._handlers[ctype] | ||||
|  | ||||
|     def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None): | ||||
|         """Execute an API callback, based on specificity. | ||||
|  | ||||
|         The API callback that is executed is chosen based on the combination | ||||
|         of the provided JID and node: | ||||
|  | ||||
|         JID   | node  | Handler | ||||
|         ============================== | ||||
|         Given | Given | Node handler | ||||
|         Given | None  | JID handler | ||||
|         None  | None  | Global handler | ||||
|  | ||||
|         A node handler is responsible for servicing a single node at a single | ||||
|         JID, while a JID handler may respond for any node at a given JID, and | ||||
|         the global handler will answer to any JID+node combination. | ||||
|  | ||||
|         Handlers should check that the JID ``ifrom`` is authorized to perform | ||||
|         the desired action. | ||||
|  | ||||
|         :param string ctype: The name of the API to use. | ||||
|         :param string op: The API operation to perform. | ||||
|         :param JID jid: Optionally provide specific JID. | ||||
|         :param string node: Optionally provide specific node. | ||||
|         :param JID ifrom: Optionally provide the requesting JID. | ||||
|         :param tuple args: Optional positional arguments to the handler. | ||||
|         """ | ||||
|         self._setup(ctype, op) | ||||
|  | ||||
|         if not jid: | ||||
|             jid = self.xmpp.boundjid | ||||
|         elif jid and not isinstance(jid, JID): | ||||
|             jid = JID(jid) | ||||
|         elif jid == JID(''): | ||||
|             jid = self.xmpp.boundjid | ||||
|  | ||||
|         if node is None: | ||||
|             node = '' | ||||
|  | ||||
|         if self.xmpp.is_component: | ||||
|             if self.settings[ctype].get('component_bare', False): | ||||
|                 jid = jid.bare | ||||
|             else: | ||||
|                 jid = jid.full | ||||
|         else: | ||||
|             if self.settings[ctype].get('client_bare', False): | ||||
|                 jid = jid.bare | ||||
|             else: | ||||
|                 jid = jid.full | ||||
|  | ||||
|         jid = JID(jid) | ||||
|  | ||||
|         handler = self._handlers[ctype][op]['node'].get((jid, node), None) | ||||
|         if handler is None: | ||||
|             handler = self._handlers[ctype][op]['jid'].get(jid, None) | ||||
|         if handler is None: | ||||
|             handler = self._handlers[ctype][op].get('global', None) | ||||
|  | ||||
|         if handler: | ||||
|             try: | ||||
|                 return handler(jid, node, ifrom, args) | ||||
|             except TypeError: | ||||
|                 # To preserve backward compatibility, drop the ifrom | ||||
|                 # parameter for existing handlers that don't understand it. | ||||
|                 return handler(jid, node, args) | ||||
|  | ||||
|     def register(self, handler, ctype, op, jid=None, node=None, default=False): | ||||
|         """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. | ||||
|  | ||||
|         See :meth:`~ApiRegistry.run` for more details. | ||||
|  | ||||
|         :param string ctype: The name of the API to use. | ||||
|         :param string op: The API operation to perform. | ||||
|         :param JID jid: Optionally provide specific JID. | ||||
|         :param string node: Optionally provide specific node. | ||||
|         """ | ||||
|         self._setup(ctype, op) | ||||
|         if jid is None and node is None: | ||||
|             if handler is None: | ||||
|                 handler = self._handler_defaults[op] | ||||
|             self._handlers[ctype][op]['global'] = handler | ||||
|         elif jid is not None and node is None: | ||||
|             self._handlers[ctype][op]['jid'][jid] = handler | ||||
|         else: | ||||
|             self._handlers[ctype][op]['node'][(jid, node)] = handler | ||||
|  | ||||
|         if default: | ||||
|             self.register_default(handler, ctype, op) | ||||
|  | ||||
|     def register_default(self, handler, ctype, op): | ||||
|         """Register a default, global handler for an operation. | ||||
|  | ||||
|         :param func handler: The default, global handler for the operation. | ||||
|         :param string ctype: The name of the API to modify. | ||||
|         :param string op: The API operation to use. | ||||
|         """ | ||||
|         self._setup(ctype, op) | ||||
|         self._handler_defaults[ctype][op] = handler | ||||
|  | ||||
|     def unregister(self, ctype, op, jid=None, node=None): | ||||
|         """Remove an API callback. | ||||
|  | ||||
|         The API callback chosen for removal is based on the | ||||
|         specificity of the provided JID+node combination. | ||||
|  | ||||
|         See :meth:`~ApiRegistry.run` for more details. | ||||
|  | ||||
|         :param string ctype: The name of the API to use. | ||||
|         :param string op: The API operation to perform. | ||||
|         :param JID jid: Optionally provide specific JID. | ||||
|         :param string node: Optionally provide specific node. | ||||
|         """ | ||||
|         self._setup(ctype, op) | ||||
|         self.register(None, ctype, op, jid, node) | ||||
|  | ||||
|     def restore_default(self, ctype, op, jid=None, node=None): | ||||
|         """Reset an API callback to use a default handler. | ||||
|  | ||||
|         :param string ctype: The name of the API to use. | ||||
|         :param string op: The API operation to perform. | ||||
|         :param JID jid: Optionally provide specific JID. | ||||
|         :param string node: Optionally provide specific node. | ||||
|         """ | ||||
|         self.unregister(ctype, op, jid, node) | ||||
|         self.register(self._handler_defaults[ctype][op], ctype, op, jid, node) | ||||
| @@ -15,22 +15,27 @@ | ||||
| from __future__ import with_statement, unicode_literals | ||||
|  | ||||
| import sys | ||||
| import copy | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp import plugins, roster | ||||
| from sleekxmpp import plugins, features, roster | ||||
| from sleekxmpp.api import APIRegistry | ||||
| from sleekxmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
| from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError | ||||
| from sleekxmpp.stanza import Message, Presence, Iq, StreamError | ||||
| from sleekxmpp.stanza.roster import Roster | ||||
| from sleekxmpp.stanza.nick import Nick | ||||
| from sleekxmpp.stanza.htmlim import HTMLIM | ||||
|  | ||||
| from sleekxmpp.xmlstream import XMLStream, JID, tostring | ||||
| from sleekxmpp.xmlstream import XMLStream, JID | ||||
| from sleekxmpp.xmlstream import ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| 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 | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
| @@ -38,8 +43,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): | ||||
| @@ -63,11 +68,25 @@ 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 = {} | ||||
|         self.plugin = PluginManager(self) | ||||
|  | ||||
|         #: Configuration options for whitelisted plugins. | ||||
|         #: If a plugin is registered without any configuration, | ||||
| @@ -82,19 +101,46 @@ 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 | ||||
|         #:   - The node that will be affected | ||||
|         #: | ||||
|         #: API handlers with no JID or node will act as global handlers, | ||||
|         #: while those with a JID and no node will service all nodes | ||||
|         #: for a JID, and handlers with both a JID and node will be | ||||
|         #: used only for that specific combination. The handler that | ||||
|         #: provides the most specificity will be used. | ||||
|         self.api = APIRegistry(self) | ||||
|  | ||||
|         #: Flag indicating that the initial presence broadcast has | ||||
|         #: been sent. Until this happens, some servers may not | ||||
|         #: behave as expected when sending stanzas. | ||||
| @@ -113,11 +159,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', | ||||
| @@ -160,6 +209,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. | ||||
| @@ -187,8 +242,10 @@ class BaseXMPP(XMLStream): | ||||
|         - The scheduler | ||||
|         """ | ||||
|         for name in self.plugin: | ||||
|             if not self.plugin[name].post_inited: | ||||
|             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 | ||||
|         return XMLStream.process(self, *args, **kwargs) | ||||
|  | ||||
|     def register_plugin(self, plugin, pconfig={}, module=None): | ||||
| @@ -201,42 +258,14 @@ class BaseXMPP(XMLStream): | ||||
|         :param module: Optional refence to the module containing the plugin | ||||
|                        class if using custom plugins. | ||||
|         """ | ||||
|         try: | ||||
|             # Import the given module that contains the plugin. | ||||
|             if not module: | ||||
|                 try: | ||||
|                     module = sleekxmpp.plugins | ||||
|                     module = __import__( | ||||
|                             str("%s.%s" % (module.__name__, plugin)), | ||||
|                             globals(), locals(), [str(plugin)]) | ||||
|                 except ImportError: | ||||
|                     module = sleekxmpp.features | ||||
|                     module = __import__( | ||||
|                             str("%s.%s" % (module.__name__, plugin)), | ||||
|                             globals(), locals(), [str(plugin)]) | ||||
|             if isinstance(module, str): | ||||
|                 # We probably want to load a module from outside | ||||
|                 # the sleekxmpp package, so leave out the globals(). | ||||
|                 module = __import__(module, fromlist=[plugin]) | ||||
|  | ||||
|         # Use the global plugin config cache, if applicable | ||||
|         if not pconfig: | ||||
|             pconfig = self.plugin_config.get(plugin, {}) | ||||
|  | ||||
|             # Load the plugin class from the module. | ||||
|             self.plugin[plugin] = getattr(module, plugin)(self, pconfig) | ||||
|  | ||||
|             # Let XEP/RFC implementing plugins have some extra logging info. | ||||
|             spec = '(CUSTOM) %s' | ||||
|             if self.plugin[plugin].xep: | ||||
|                 spec = "(XEP-%s) " % self.plugin[plugin].xep | ||||
|             elif self.plugin[plugin].rfc: | ||||
|                 spec = "(RFC-%s) " % self.plugin[plugin].rfc | ||||
|  | ||||
|             desc = (spec, self.plugin[plugin].description) | ||||
|             log.debug("Loaded Plugin %s %s" % desc) | ||||
|         except: | ||||
|             log.exception("Unable to load plugin: %s", plugin) | ||||
|         if not self.plugin.registered(plugin): | ||||
|             load_plugin(plugin, module) | ||||
|         self.plugin.enable(plugin, pconfig) | ||||
|  | ||||
|     def register_plugins(self): | ||||
|         """Register and initialize all built-in plugins. | ||||
| @@ -253,15 +282,10 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         for plugin in plugin_list: | ||||
|             if plugin in plugins.__all__: | ||||
|                 self.register_plugin(plugin, | ||||
|                                      self.plugin_config.get(plugin, {})) | ||||
|                 self.register_plugin(plugin) | ||||
|             else: | ||||
|                 raise NameError("Plugin %s not in plugins.__all__." % plugin) | ||||
|  | ||||
|         # Resolve plugin inter-dependencies. | ||||
|         for plugin in self.plugin: | ||||
|             self.plugin[plugin].post_init() | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         """Return a plugin given its name, if it has been registered.""" | ||||
|         if key in self.plugin: | ||||
| @@ -276,7 +300,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.""" | ||||
| @@ -284,7 +310,9 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|     def Presence(self, *args, **kwargs): | ||||
|         """Create a Presence stanza associated with this stream.""" | ||||
|         return Presence(self, *args, **kwargs) | ||||
|         pres = Presence(self, *args, **kwargs) | ||||
|         pres['lang'] = self.default_lang | ||||
|         return pres | ||||
|  | ||||
|     def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None): | ||||
|         """Create a new Iq stanza with a given Id and from JID. | ||||
| @@ -533,18 +561,8 @@ class BaseXMPP(XMLStream): | ||||
|         :param pfrom: The sender of the presence. | ||||
|         :param pnick: Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         # Python2.6 chokes on Unicode strings for dict keys. | ||||
|         args = {str('pto'): pto, | ||||
|                 str('ptype'): ptype, | ||||
|                 str('pshow'): pshow, | ||||
|                 str('pstatus'): pstatus, | ||||
|                 str('ppriority'): ppriority, | ||||
|                 str('pnick'): pnick} | ||||
|  | ||||
|         if self.is_component: | ||||
|             self.roster[pfrom].send_presence(**args) | ||||
|         else: | ||||
|             self.client_roster.send_presence(**args) | ||||
|         self.make_presence(pshow, pstatus, ppriority, pto, | ||||
|                            ptype, pfrom, pnick).send() | ||||
|  | ||||
|     def send_presence_subscription(self, pto, pfrom=None, | ||||
|                                    ptype='subscribe', pnick=None): | ||||
| @@ -558,14 +576,10 @@ class BaseXMPP(XMLStream): | ||||
|         :param ptype: The type of presence, such as ``'subscribe'``. | ||||
|         :param pnick: Optional nickname of the presence's sender. | ||||
|         """ | ||||
|         presence = self.makePresence(ptype=ptype, | ||||
|         self.make_presence(ptype=ptype, | ||||
|                            pfrom=pfrom, | ||||
|                                      pto=self.getjidbare(pto)) | ||||
|         if pnick: | ||||
|             nick = ET.Element('{http://jabber.org/protocol/nick}nick') | ||||
|             nick.text = pnick | ||||
|             presence.append(nick) | ||||
|         presence.send() | ||||
|                            pto=JID(pto).bare, | ||||
|                            pnick=pnick).send() | ||||
|  | ||||
|     @property | ||||
|     def jid(self): | ||||
| @@ -597,7 +611,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 | ||||
| @@ -651,7 +665,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: | ||||
| @@ -662,28 +676,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 | ||||
| @@ -695,10 +742,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: | ||||
| @@ -706,37 +755,31 @@ 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. | ||||
|  | ||||
|         Update the roster with presence information. | ||||
|         """ | ||||
|         self.event("presence_%s" % presence['type'], presence) | ||||
|         if not self.is_component and not presence['to'].bare: | ||||
|             presence['to'] = self.boundjid | ||||
|  | ||||
|         self.event('presence', presence) | ||||
|         self.event('presence_%s' % presence['type'], presence) | ||||
|  | ||||
|         # Check for changes in subscription state. | ||||
|         if presence['type'] in ('subscribe', 'subscribed', | ||||
| @@ -763,6 +806,11 @@ class BaseXMPP(XMLStream): | ||||
|             iq = exception.iq | ||||
|             log.error('Request timed out: %s', iq) | ||||
|             log.warning('You should catch IqTimeout exceptions') | ||||
|         elif isinstance(exception, SyntaxError): | ||||
|             # Hide stream parsing errors that occur when the | ||||
|             # stream is disconnected (they've been handled, we | ||||
|             # don't need to make a mess in the logs). | ||||
|             pass | ||||
|         else: | ||||
|             log.exception(exception) | ||||
|  | ||||
|   | ||||
| @@ -15,22 +15,13 @@ | ||||
| from __future__ import absolute_import, unicode_literals | ||||
|  | ||||
| import logging | ||||
| import base64 | ||||
| import sys | ||||
| import hashlib | ||||
| import random | ||||
| import threading | ||||
|  | ||||
| import sleekxmpp | ||||
| from sleekxmpp import plugins | ||||
| from sleekxmpp import stanza | ||||
| from sleekxmpp import features | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.basexmpp import BaseXMPP | ||||
| from sleekxmpp.stanza import * | ||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.xmlstream import XMLStream | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath, MatchXPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
|  | ||||
| # Flag indicating if DNS SRV records are available for use. | ||||
| try: | ||||
| @@ -69,34 +60,42 @@ class ClientXMPP(BaseXMPP): | ||||
|     :param escape_quotes: **Deprecated.** | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, ssl=False, plugin_config={}, | ||||
|                  plugin_whitelist=[], escape_quotes=True, sasl_mech=None): | ||||
|     def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], | ||||
|                  escape_quotes=True, sasl_mech=None, lang='en'): | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||
|  | ||||
|         self.set_jid(jid) | ||||
|         self.password = password | ||||
|         self.escape_quotes = escape_quotes | ||||
|         self.plugin_config = plugin_config | ||||
|         self.plugin_whitelist = plugin_whitelist | ||||
|         self.default_port = 5222 | ||||
|         self.default_lang = lang | ||||
|  | ||||
|         self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % ( | ||||
|         self.credentials = {} | ||||
|  | ||||
|         self.password = password | ||||
|  | ||||
|         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() | ||||
|         self._stream_feature_handlers = {} | ||||
|         self._stream_feature_order = [] | ||||
|  | ||||
|         self.dns_service = 'xmpp-client' | ||||
|  | ||||
|         #TODO: Use stream state here | ||||
|         self.authenticated = False | ||||
|         self.sessionstarted = False | ||||
|         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.register_stanza(StreamFeatures) | ||||
|  | ||||
| @@ -106,18 +105,26 @@ class ClientXMPP(BaseXMPP): | ||||
|                          self._handle_stream_features)) | ||||
|         self.register_handler( | ||||
|                 Callback('Roster Update', | ||||
|                          MatchXPath('{%s}iq/{%s}query' % ( | ||||
|                              self.default_ns, | ||||
|                              'jabber:iq:roster')), | ||||
|                          StanzaPath('iq@type=set/roster'), | ||||
|                          self._handle_roster)) | ||||
|  | ||||
|         # Setup default stream features | ||||
|         self.register_plugin('feature_starttls') | ||||
|         self.register_plugin('feature_bind') | ||||
|         self.register_plugin('feature_session') | ||||
|         self.register_plugin('feature_rosterver') | ||||
|         self.register_plugin('feature_preapproval') | ||||
|         self.register_plugin('feature_mechanisms', | ||||
|                 pconfig={'use_mech': sasl_mech} if sasl_mech else None) | ||||
|  | ||||
|     @property | ||||
|     def password(self): | ||||
|         return self.credentials.get('password', '') | ||||
|  | ||||
|     @password.setter | ||||
|     def password(self, value): | ||||
|         self.credentials['password'] = value | ||||
|  | ||||
|     def connect(self, address=tuple(), reattempt=True, | ||||
|                 use_tls=True, use_ssl=False): | ||||
|         """Connect to the XMPP server. | ||||
| @@ -135,41 +142,22 @@ class ClientXMPP(BaseXMPP): | ||||
|                         should be used. Defaults to ``False``. | ||||
|         """ | ||||
|         self.session_started_event.clear() | ||||
|         if not address: | ||||
|  | ||||
|         # If an address was provided, disable using DNS SRV lookup; | ||||
|         # otherwise, use the domain from the client JID with the standard | ||||
|         # XMPP client port and allow SRV lookup. | ||||
|         if address: | ||||
|             self.dns_service = None | ||||
|         else: | ||||
|             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) | ||||
|  | ||||
|     def get_dns_records(self, domain, port=None): | ||||
|         """Get the DNS records for a domain, including SRV records. | ||||
|  | ||||
|         :param domain: The domain in question. | ||||
|         :param port: If the results don't include a port, use this one. | ||||
|         """ | ||||
|         if port is None: | ||||
|             port = self.default_port | ||||
|         if DNSPYTHON: | ||||
|             try: | ||||
|                 record = "_xmpp-client._tcp.%s" % domain | ||||
|                 answers = [] | ||||
|                 for answer in dns.resolver.query(record, dns.rdatatype.SRV): | ||||
|                     address = (answer.target.to_text()[:-1], answer.port) | ||||
|                     answers.append((address, answer.priority, answer.weight)) | ||||
|             except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): | ||||
|                 log.warning("No SRV records for %s", domain) | ||||
|                 answers = super(ClientXMPP, self).get_dns_records(domain, port) | ||||
|             except dns.exception.Timeout: | ||||
|                 log.warning("DNS resolution timed out " + \ | ||||
|                             "for SRV record of %s", domain) | ||||
|                 answers = super(ClientXMPP, self).get_dns_records(domain, port) | ||||
|             return answers | ||||
|         else: | ||||
|             log.warning("dnspython is not installed -- " + \ | ||||
|                         "relying on OS A record resolution") | ||||
|             return [((domain, port), 0, 0)] | ||||
|  | ||||
|     def register_feature(self, name, handler, restart=False, order=5000): | ||||
|         """Register a stream feature handler. | ||||
|  | ||||
| @@ -185,8 +173,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. | ||||
| @@ -207,6 +200,16 @@ class ClientXMPP(BaseXMPP): | ||||
|                          Will be executed when the roster is received. | ||||
|                          Implies ``block=False``. | ||||
|         """ | ||||
|         current = self.client_roster[jid] | ||||
|  | ||||
|         name = kwargs.get('name', current['name']) | ||||
|         subscription = kwargs.get('subscription', current['subscription']) | ||||
|         groups = kwargs.get('groups', current['groups']) | ||||
|  | ||||
|         block = kwargs.get('block', True) | ||||
|         timeout = kwargs.get('timeout', None) | ||||
|         callback = kwargs.get('callback', None) | ||||
|  | ||||
|         return self.client_roster.update(jid, name, subscription, groups, | ||||
|                                          block, timeout, callback) | ||||
|  | ||||
| @@ -236,12 +239,20 @@ class ClientXMPP(BaseXMPP): | ||||
|         iq = self.Iq() | ||||
|         iq['type'] = 'get' | ||||
|         iq.enable('roster') | ||||
|         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) | ||||
|  | ||||
|         response = iq.send(block, timeout, callback) | ||||
|         self.event('roster_received', response) | ||||
|  | ||||
|         if callback is None: | ||||
|             return self._handle_roster(response, request=True) | ||||
|         if block: | ||||
|             self._handle_roster(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 | ||||
| @@ -261,32 +272,49 @@ 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, request=False): | ||||
|     def _handle_roster(self, iq): | ||||
|         """Update the roster after receiving a roster stanza. | ||||
|  | ||||
|         :param iq: The roster stanza. | ||||
|         :param request: Indicates if this stanza is a response | ||||
|                         to a request for the roster, and not an | ||||
|                         empty acknowledgement from the server. | ||||
|         """ | ||||
|         if iq['type'] == 'set' or (iq['type'] == 'result' and request): | ||||
|             for jid in iq['roster']['items']: | ||||
|                 item = iq['roster']['items'][jid] | ||||
|                 roster = self.roster[iq['to'].bare] | ||||
|         if iq['type'] == 'set': | ||||
|             if iq['from'].bare and iq['from'].bare != self.boundjid.bare: | ||||
|                 raise XMPPError(condition='service-unavailable') | ||||
|  | ||||
|         roster = self.client_roster | ||||
|         if iq['roster']['ver']: | ||||
|             roster.version = iq['roster']['ver'] | ||||
|         items = iq['roster']['items'] | ||||
|  | ||||
|         valid_subscriptions = ('to', 'from', 'both', 'none', 'remove') | ||||
|         for jid, item in items.items(): | ||||
|             if item['subscription'] in valid_subscriptions: | ||||
|                 roster[jid]['name'] = item['name'] | ||||
|                 roster[jid]['groups'] = item['groups'] | ||||
|                 roster[jid]['from'] = item['subscription'] in ['from', 'both'] | ||||
|                 roster[jid]['to'] = item['subscription'] in ['to', 'both'] | ||||
|                 roster[jid]['from'] = item['subscription'] in ('from', 'both') | ||||
|                 roster[jid]['to'] = item['subscription'] in ('to', 'both') | ||||
|                 roster[jid]['pending_out'] = (item['ask'] == 'subscribe') | ||||
|             self.event('roster_received', iq) | ||||
|  | ||||
|                 roster[jid].save(remove=(item['subscription'] == 'remove')) | ||||
|  | ||||
|         self.event("roster_update", iq) | ||||
|         if iq['type'] == 'set': | ||||
|             iq.reply() | ||||
|             iq.enable('roster') | ||||
|             iq.send() | ||||
|         return True | ||||
|             resp = self.Iq(stype='result', | ||||
|                            sto=iq['from'], | ||||
|                            sid=iq['id']) | ||||
|             resp.enable('roster') | ||||
|             resp.send() | ||||
|  | ||||
|     def _handle_session_bind(self, jid): | ||||
|         """Set the client roster to the JID set by the server. | ||||
|  | ||||
|         :param :class:`sleekxmpp.xmlstream.jid.JID` jid: The bound JID as | ||||
|             dictated by the server. The same as :attr:`boundjid`. | ||||
|         """ | ||||
|         self.client_roster = self.roster[jid] | ||||
|  | ||||
|  | ||||
| # To comply with PEP8, method names now use underscores. | ||||
|   | ||||
| @@ -15,17 +15,14 @@ | ||||
| from __future__ import absolute_import | ||||
|  | ||||
| import logging | ||||
| import base64 | ||||
| import sys | ||||
| import hashlib | ||||
|  | ||||
| from sleekxmpp import plugins | ||||
| from sleekxmpp import stanza | ||||
| from sleekxmpp.basexmpp import BaseXMPP | ||||
| from sleekxmpp.xmlstream import XMLStream, RestartStream | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.xmlstream import XMLStream | ||||
| from sleekxmpp.xmlstream import ET | ||||
| from sleekxmpp.xmlstream.matcher import MatchXPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
| @@ -82,7 +79,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|                                self._handle_probe) | ||||
|  | ||||
|     def connect(self, host=None, port=None, use_ssl=False, | ||||
|                       use_tls=True, reattempt=True): | ||||
|                       use_tls=False, reattempt=True): | ||||
|         """Connect to the server. | ||||
|  | ||||
|         Setting ``reattempt`` to ``True`` will cause connection attempts to | ||||
| @@ -104,10 +101,16 @@ class ComponentXMPP(BaseXMPP): | ||||
|             host = self.server_host | ||||
|         if port is None: | ||||
|             port = self.server_port | ||||
|  | ||||
|         self.server_name = self.boundjid.host | ||||
|  | ||||
|         if use_tls: | ||||
|             log.info("XEP-0114 components can not use TLS") | ||||
|  | ||||
|         log.debug("Connecting to %s:%s", host, port) | ||||
|         return XMLStream.connect(self, host=host, port=port, | ||||
|                                        use_ssl=use_ssl, | ||||
|                                        use_tls=use_tls, | ||||
|                                        use_tls=False, | ||||
|                                        reattempt=reattempt) | ||||
|  | ||||
|     def incoming_filter(self, xml): | ||||
| @@ -153,10 +156,10 @@ class ComponentXMPP(BaseXMPP): | ||||
|  | ||||
|         :param xml: The reply handshake stanza. | ||||
|         """ | ||||
|         self.session_bind_event.set() | ||||
|         self.session_started_event.set() | ||||
|         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) | ||||
|   | ||||
| @@ -73,6 +73,7 @@ class IqTimeout(XMPPError): | ||||
|         #: did not arrive before the timeout expired. | ||||
|         self.iq = iq | ||||
|  | ||||
|  | ||||
| class IqError(XMPPError): | ||||
|  | ||||
|     """ | ||||
|   | ||||
| @@ -6,4 +6,11 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| __all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind'] | ||||
| __all__ = [ | ||||
|     'feature_starttls', | ||||
|     'feature_mechanisms', | ||||
|     'feature_bind', | ||||
|     'feature_session', | ||||
|     'feature_rosterver', | ||||
|     'feature_preapproval' | ||||
| ] | ||||
|   | ||||
| @@ -6,5 +6,14 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.features.feature_bind.bind import feature_bind | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.features.feature_bind.bind import FeatureBind | ||||
| from sleekxmpp.features.feature_bind.stanza import Bind | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureBind) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_bind = FeatureBind | ||||
|   | ||||
| @@ -8,25 +8,24 @@ | ||||
|  | ||||
| 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.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.plugins.base import base_plugin | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class feature_bind(base_plugin): | ||||
| class FeatureBind(BasePlugin): | ||||
|  | ||||
|     name = 'feature_bind' | ||||
|     description = 'RFC 6120: Stream Feature: Resource Binding' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.name = 'Bind Resource' | ||||
|         self.rfc = '6120' | ||||
|         self.description = 'Resource Binding Stream Feature' | ||||
|         self.stanza = stanza | ||||
|  | ||||
|         self.xmpp.register_feature('bind', | ||||
|                 self._handle_bind_resource, | ||||
|                 restart=False, | ||||
| @@ -50,8 +49,10 @@ class feature_bind(base_plugin): | ||||
|             iq['bind']['resource'] = self.xmpp.boundjid.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') | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
|  | ||||
|  | ||||
| class Bind(ElementBase): | ||||
|   | ||||
| @@ -6,8 +6,17 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.features.feature_mechanisms.mechanisms import feature_mechanisms | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.features.feature_mechanisms.mechanisms import FeatureMechanisms | ||||
| from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms | ||||
| from sleekxmpp.features.feature_mechanisms.stanza import Auth | ||||
| from sleekxmpp.features.feature_mechanisms.stanza import Success | ||||
| from sleekxmpp.features.feature_mechanisms.stanza import Failure | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureMechanisms) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_mechanisms = FeatureMechanisms | ||||
|   | ||||
| @@ -6,52 +6,56 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import ssl | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp.thirdparty import suelta | ||||
|  | ||||
| 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.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.plugins.base import base_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream.matcher import MatchXPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.features.feature_mechanisms import stanza | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class feature_mechanisms(base_plugin): | ||||
| class FeatureMechanisms(BasePlugin): | ||||
|  | ||||
|     name = 'feature_mechanisms' | ||||
|     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.name = 'SASL Mechanisms' | ||||
|         self.rfc = '6120' | ||||
|         self.description = "SASL Stream Feature" | ||||
|         self.stanza = stanza | ||||
|         if not self.use_mech and not self.xmpp.requested_jid.user: | ||||
|             self.use_mech = 'ANONYMOUS' | ||||
|  | ||||
|         self.use_mech = self.config.get('use_mech', None) | ||||
|         if self.sasl_callback is None: | ||||
|             self.sasl_callback = self._default_credentials | ||||
|  | ||||
|         def tls_active(): | ||||
|             return 'starttls' in self.xmpp.features | ||||
|  | ||||
|         def basic_callback(mech, values): | ||||
|             if 'username' in values: | ||||
|                 values['username'] = self.xmpp.boundjid.user | ||||
|             if 'password' in values: | ||||
|                 values['password'] = self.xmpp.password | ||||
|             mech.fulfill(values) | ||||
|  | ||||
|         sasl_callback = self.config.get('sasl_callback', None) | ||||
|         if sasl_callback is None: | ||||
|             sasl_callback = basic_callback | ||||
|         if self.security_callback is None: | ||||
|             self.security_callback = self._default_security | ||||
|  | ||||
|         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() | ||||
|  | ||||
|         register_stanza_plugin(StreamFeatures, stanza.Mechanisms) | ||||
|  | ||||
| @@ -60,19 +64,18 @@ class feature_mechanisms(base_plugin): | ||||
|         self.xmpp.register_stanza(stanza.Auth) | ||||
|         self.xmpp.register_stanza(stanza.Challenge) | ||||
|         self.xmpp.register_stanza(stanza.Response) | ||||
|         self.xmpp.register_stanza(stanza.Abort) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('SASL Success', | ||||
|                          MatchXPath(stanza.Success.tag_name()), | ||||
|                          self._handle_success, | ||||
|                          instream=True, | ||||
|                          once=True)) | ||||
|                          instream=True)) | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('SASL Failure', | ||||
|                          MatchXPath(stanza.Failure.tag_name()), | ||||
|                          self._handle_fail, | ||||
|                          instream=True, | ||||
|                          once=True)) | ||||
|                          instream=True)) | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('SASL Challenge', | ||||
|                          MatchXPath(stanza.Challenge.tag_name()), | ||||
| @@ -81,7 +84,52 @@ class feature_mechanisms(base_plugin): | ||||
|         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] = self.xmpp.requested_jid.user | ||||
|             elif value == 'password': | ||||
|                 result[value] = creds['password'] | ||||
|             elif value == 'authzid': | ||||
|                 result[value] = creds.get('authzid', '') | ||||
|             elif value == 'email': | ||||
|                 jid = self.xmpp.requested_jid.bare | ||||
|                 result[value] = creds.get('email', jid) | ||||
|             elif value == 'channel_binding': | ||||
|                 if sys.version_info >= (3, 3): | ||||
|                     result[value] = self.xmpp.socket.get_channel_binding() | ||||
|                 else: | ||||
|                     result[value] = None | ||||
|             elif value == 'host': | ||||
|                 result[value] = self.xmpp.requested_jid.domain | ||||
|             elif value == 'realm': | ||||
|                 result[value] = self.xmpp.requested_jid.domain | ||||
|             elif value == 'service-name': | ||||
|                 result[value] = self.xmpp._service_name | ||||
|             elif value == 'service': | ||||
|                 result[value] = '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): | ||||
|         """ | ||||
| @@ -95,35 +143,100 @@ class feature_mechanisms(base_plugin): | ||||
|             # server has incorrectly offered it again. | ||||
|             return False | ||||
|  | ||||
|         mech_list = features['mechanisms'] | ||||
|         self.mech = self.sasl.choose_mechanism(mech_list) | ||||
|         enforce_limit = False | ||||
|         limited_mechs = self.use_mechs | ||||
|  | ||||
|         if self.mech is not None: | ||||
|             resp = stanza.Auth(self.xmpp) | ||||
|             resp['mechanism'] = self.mech.name | ||||
|             resp['value'] = self.mech.process() | ||||
|             resp.send(now=True) | ||||
|         else: | ||||
|         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 | ||||
|         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.attempted_mechs = set() | ||||
|             return 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() | ||||
|         except StringPrepError: | ||||
|             log.exception("A credential value did not pass SASLprep.") | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             resp.send(now=True) | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def _handle_challenge(self, stanza): | ||||
|         """SASL challenge received. Process and send response.""" | ||||
|         resp = self.stanza.Response(self.xmpp) | ||||
|         try: | ||||
|             resp['value'] = self.mech.process(stanza['value']) | ||||
|         except sasl.SASLCancelled: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLFailed: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             resp.send(now=True) | ||||
|  | ||||
|     def _handle_success(self, stanza): | ||||
|         """SASL authentication succeeded. Restart the stream.""" | ||||
|         try: | ||||
|             final = self.mech.process(stanza['value']) | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         else: | ||||
|             self.attempted_mechs = set() | ||||
|             self.xmpp.authenticated = True | ||||
|             self.xmpp.features.add('mechanisms') | ||||
|             self.xmpp.event('auth_success', stanza, direct=True) | ||||
|             raise RestartStream() | ||||
|  | ||||
|     def _handle_fail(self, stanza): | ||||
|         """SASL authentication failed. Disconnect and shutdown.""" | ||||
|         self.attempted_mechs.add(self.mech.name) | ||||
|         log.info("Authentication failed: %s", stanza['condition']) | ||||
|         self.xmpp.event("failed_auth", stanza, direct=True) | ||||
|         self.xmpp.disconnect() | ||||
|         self._send_auth() | ||||
|         return True | ||||
|   | ||||
| @@ -13,3 +13,4 @@ from sleekxmpp.features.feature_mechanisms.stanza.success import Success | ||||
| from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure | ||||
| from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge | ||||
| from sleekxmpp.features.feature_mechanisms.stanza.response import Response | ||||
| from sleekxmpp.features.feature_mechanisms.stanza.abort import Abort | ||||
|   | ||||
							
								
								
									
										24
									
								
								sleekxmpp/features/feature_mechanisms/stanza/abort.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								sleekxmpp/features/feature_mechanisms/stanza/abort.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2011  Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
| class Abort(StanzaBase): | ||||
|  | ||||
|     """ | ||||
|     """ | ||||
|  | ||||
|     name = 'abort' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set() | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     def setup(self, xml): | ||||
|         StanzaBase.setup(self, xml) | ||||
|         self.xml.tag = self.tag_name() | ||||
| @@ -8,11 +8,8 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
| class Auth(StanzaBase): | ||||
| @@ -25,15 +22,28 @@ class Auth(StanzaBase): | ||||
|     interfaces = set(('mechanism', 'value')) | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     #: Some SASL mechs require sending values as is, | ||||
|     #: without converting base64. | ||||
|     plain_mechs = set(['X-MESSENGER-OAUTH2']) | ||||
|  | ||||
|     def setup(self, xml): | ||||
|         StanzaBase.setup(self, xml) | ||||
|         self.xml.tag = self.tag_name() | ||||
|  | ||||
|     def get_value(self): | ||||
|         if not self['mechanism'] in self.plain_mechs: | ||||
|             return base64.b64decode(bytes(self.xml.text)) | ||||
|         else: | ||||
|             return self.xml.text | ||||
|  | ||||
|     def set_value(self, values): | ||||
|         if not self['mechanism'] in self.plain_mechs: | ||||
|             if values: | ||||
|                 self.xml.text = bytes(base64.b64encode(values)).decode('utf-8') | ||||
|             else: | ||||
|                 self.xml.text = '=' | ||||
|         else: | ||||
|             self.xml.text = bytes(values).decode('utf-8') | ||||
|  | ||||
|     def del_value(self): | ||||
|         self.xml.text = '' | ||||
|   | ||||
| @@ -8,11 +8,8 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
| class Challenge(StanzaBase): | ||||
| @@ -33,7 +30,10 @@ class Challenge(StanzaBase): | ||||
|         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 = '' | ||||
|   | ||||
| @@ -6,9 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.xmlstream import StanzaBase, ET | ||||
|  | ||||
|  | ||||
| class Failure(StanzaBase): | ||||
| @@ -49,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: | ||||
| @@ -70,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: | ||||
|   | ||||
| @@ -6,9 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.xmlstream import ElementBase, ET | ||||
|  | ||||
|  | ||||
| class Mechanisms(ElementBase): | ||||
|   | ||||
| @@ -8,11 +8,8 @@ | ||||
|  | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.thirdparty.suelta.util import bytes | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
|  | ||||
| class Response(StanzaBase): | ||||
| @@ -33,7 +30,10 @@ class Response(StanzaBase): | ||||
|         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 = '' | ||||
|   | ||||
| @@ -6,10 +6,10 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| import base64 | ||||
|  | ||||
| from sleekxmpp.util import bytes | ||||
| from sleekxmpp.xmlstream import StanzaBase | ||||
|  | ||||
| class Success(StanzaBase): | ||||
|  | ||||
| @@ -18,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 Iq, 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' | ||||
							
								
								
									
										19
									
								
								sleekxmpp/features/feature_rosterver/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								sleekxmpp/features/feature_rosterver/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| """ | ||||
|     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_rosterver.rosterver import FeatureRosterVer | ||||
| from sleekxmpp.features.feature_rosterver.stanza import RosterVer | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureRosterVer) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_rosterver = FeatureRosterVer | ||||
							
								
								
									
										42
									
								
								sleekxmpp/features/feature_rosterver/rosterver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/features/feature_rosterver/rosterver.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 Iq, StreamFeatures | ||||
| from sleekxmpp.features.feature_rosterver import stanza | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.plugins.base import BasePlugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class FeatureRosterVer(BasePlugin): | ||||
|  | ||||
|     name = 'feature_rosterver' | ||||
|     description = 'RFC 6121: Stream Feature: Roster Versioning' | ||||
|     dependences = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.register_feature('rosterver', | ||||
|                 self._handle_rosterver, | ||||
|                 restart=False, | ||||
|                 order=9000) | ||||
|  | ||||
|         register_stanza_plugin(StreamFeatures, stanza.RosterVer) | ||||
|  | ||||
|     def _handle_rosterver(self, features): | ||||
|         """Enable using roster versioning. | ||||
|  | ||||
|         Arguments: | ||||
|             features -- The stream features stanza. | ||||
|         """ | ||||
|         log.debug("Enabling roster versioning.") | ||||
|         self.xmpp.features.add('rosterver') | ||||
							
								
								
									
										17
									
								
								sleekxmpp/features/feature_rosterver/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sleekxmpp/features/feature_rosterver/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 RosterVer(ElementBase): | ||||
|  | ||||
|     name = 'ver' | ||||
|     namespace = 'urn:xmpp:features:rosterver' | ||||
|     interfaces = set() | ||||
|     plugin_attrib = 'rosterver' | ||||
| @@ -6,5 +6,14 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.features.feature_session.session import feature_session | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.features.feature_session.session import FeatureSession | ||||
| from sleekxmpp.features.feature_session.stanza import Session | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureSession) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_session = FeatureSession | ||||
|   | ||||
| @@ -10,9 +10,7 @@ import logging | ||||
|  | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.plugins.base import base_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
|  | ||||
| from sleekxmpp.features.feature_session import stanza | ||||
|  | ||||
| @@ -20,14 +18,14 @@ from sleekxmpp.features.feature_session import stanza | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class feature_session(base_plugin): | ||||
| class FeatureSession(BasePlugin): | ||||
|  | ||||
|     name = 'feature_session' | ||||
|     description = 'RFC 3920: Stream Feature: Start Session' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.name = 'Start Session' | ||||
|         self.rfc = '3920' | ||||
|         self.description = 'Start Session Stream Feature' | ||||
|         self.stanza = stanza | ||||
|  | ||||
|         self.xmpp.register_feature('session', | ||||
|                 self._handle_start_session, | ||||
|                 restart=False, | ||||
| @@ -46,7 +44,7 @@ class feature_session(base_plugin): | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq.enable('session') | ||||
|         response = iq.send(now=True) | ||||
|         iq.send(now=True) | ||||
|  | ||||
|         self.xmpp.features.add('session') | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza import Iq, StreamFeatures | ||||
| from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
|  | ||||
|  | ||||
| class Session(ElementBase): | ||||
|   | ||||
| @@ -6,5 +6,14 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.features.feature_starttls.starttls import feature_starttls | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.features.feature_starttls.starttls import FeatureSTARTTLS | ||||
| from sleekxmpp.features.feature_starttls.stanza import * | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureSTARTTLS) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_starttls = FeatureSTARTTLS | ||||
|   | ||||
| @@ -6,9 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import StanzaBase, ElementBase | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
|  | ||||
|  | ||||
| class STARTTLS(ElementBase): | ||||
|   | ||||
| @@ -10,23 +10,23 @@ import logging | ||||
|  | ||||
| from sleekxmpp.stanza import StreamFeatures | ||||
| from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.matcher import * | ||||
| from sleekxmpp.xmlstream.handler import * | ||||
| from sleekxmpp.plugins.base import base_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.xmlstream.matcher import MatchXPath | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.features.feature_starttls import stanza | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class feature_starttls(base_plugin): | ||||
| class FeatureSTARTTLS(BasePlugin): | ||||
|  | ||||
|     name = 'feature_starttls' | ||||
|     description = 'RFC 6120: Stream Feature: STARTTLS' | ||||
|     dependencies = set() | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.name = "STARTTLS" | ||||
|         self.rfc = '6120' | ||||
|         self.description = "STARTTLS Stream Feature" | ||||
|         self.stanza = stanza | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('STARTTLS Proceed', | ||||
|                         MatchXPath(stanza.Proceed.tag_name()), | ||||
| @@ -54,13 +54,9 @@ class feature_starttls(base_plugin): | ||||
|             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.""" | ||||
|   | ||||
							
								
								
									
										582
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								sleekxmpp/jid.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,582 @@ | ||||
| # -*- 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 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.iteritems(): | ||||
|                     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: | ||||
|             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 illegar 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 | ||||
|  | ||||
|     # 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 | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __setattr__(self, name, value): | ||||
|         """Update the given JID component. | ||||
|  | ||||
|         :param name: one of: ``user``, ``username``, ``local``, | ||||
|                              ``node``, ``server``, ``domain``, ``host``, | ||||
|                              ``resource``, ``full``, ``jid``, or ``bare``. | ||||
|         :param value: The new string value of the JID component. | ||||
|         """ | ||||
|         if name == '_jid': | ||||
|             super(JID, self).__setattr__('_jid', value) | ||||
|         elif name == 'resource': | ||||
|             self._jid = JID(self, resource=value)._jid | ||||
|         elif name in ('user', 'username', 'local', 'node'): | ||||
|             self._jid = JID(self, local=value)._jid | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             self._jid = JID(self, domain=value)._jid | ||||
|         elif name in ('full', 'jid'): | ||||
|             self._jid = JID(value)._jid | ||||
|         elif name == 'bare': | ||||
|             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) | ||||
| @@ -5,9 +5,75 @@ | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
| __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', | ||||
|            'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', | ||||
|            'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', | ||||
|            'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] | ||||
|  | ||||
| # Don't automatically load xep_0078 | ||||
| from sleekxmpp.plugins.base import PluginManager, PluginNotFound, BasePlugin | ||||
| 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_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_0049',  # Private XML Storage | ||||
|     'xep_0050',  # Ad-hoc Commands | ||||
|     'xep_0054',  # vcard-temp | ||||
|     'xep_0059',  # Result Set Management | ||||
|     'xep_0060',  # Pubsub (Client) | ||||
|     'xep_0066',  # Out of Band Data | ||||
|     'xep_0077',  # In-Band Registration | ||||
| #   'xep_0078',  # Non-SASL auth. Don't automatically load | ||||
|     '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_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_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 | ||||
| ] | ||||
|   | ||||
| @@ -1,91 +1,360 @@ | ||||
| """ | ||||
|     SleekXMPP: The Sleek XMPP Library | ||||
|     Copyright (C) 2010 Nathanael C. Fritz | ||||
|     This file is part of SleekXMPP. | ||||
| # -*- encoding: utf-8 -*- | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|     sleekxmpp.plugins.base | ||||
|     ~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|     This module provides XMPP functionality that | ||||
|     is specific to client connections. | ||||
|  | ||||
|     Part of SleekXMPP: The Sleek XMPP Library | ||||
|  | ||||
|     :copyright: (c) 2012 Nathanael C. Fritz | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| import sys | ||||
| import copy | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| class base_plugin(object): | ||||
|  | ||||
| if sys.version_info >= (3, 0): | ||||
|     unicode = str | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| #: Associate short string names of plugins with implementations. The | ||||
| #: plugin names are based on the spec used by the plugin, such as | ||||
| #: `'xep_0030'` for a plugin that implements XEP-0030. | ||||
| PLUGIN_REGISTRY = {} | ||||
|  | ||||
| #: In order to do cascading plugin disabling, reverse dependencies | ||||
| #: must be tracked. | ||||
| PLUGIN_DEPENDENTS = {} | ||||
|  | ||||
| #: Only allow one thread to manipulate the plugin registry at a time. | ||||
| REGISTRY_LOCK = threading.RLock() | ||||
|  | ||||
|  | ||||
| class PluginNotFound(Exception): | ||||
|     """Raised if an unknown plugin is accessed.""" | ||||
|  | ||||
|  | ||||
| def register_plugin(impl, name=None): | ||||
|     """Add a new plugin implementation to the registry. | ||||
|  | ||||
|     :param class impl: The plugin class. | ||||
|  | ||||
|     The implementation class must provide a :attr:`~BasePlugin.name` | ||||
|     value that will be used as a short name for enabling and disabling | ||||
|     the plugin. The name should be based on the specification used by | ||||
|     the plugin. For example, a plugin implementing XEP-0030 would be | ||||
|     named `'xep_0030'`. | ||||
|     """ | ||||
|     The base_plugin class serves as a base for user created plugins | ||||
|     that provide support for existing or experimental XEPS. | ||||
|     if name is None: | ||||
|         name = impl.name | ||||
|     with REGISTRY_LOCK: | ||||
|         PLUGIN_REGISTRY[name] = impl | ||||
|         if name not in PLUGIN_DEPENDENTS: | ||||
|             PLUGIN_DEPENDENTS[name] = set() | ||||
|         for dep in impl.dependencies: | ||||
|             if dep not in PLUGIN_DEPENDENTS: | ||||
|                 PLUGIN_DEPENDENTS[dep] = set() | ||||
|             PLUGIN_DEPENDENTS[dep].add(name) | ||||
|  | ||||
|     Each plugin has a dictionary for configuration options, as well | ||||
|     as a name and description. | ||||
|  | ||||
|     The lifecycle of a plugin is: | ||||
|         1. The plugin is instantiated during registration. | ||||
|         2. Once the XML stream begins processing, the method | ||||
|            plugin_init() is called (if the plugin is configured | ||||
|            as enabled with {'enable': True}). | ||||
|         3. After all plugins have been initialized, the | ||||
|            method post_init() is called. | ||||
| def load_plugin(name, module=None): | ||||
|     """Find and import a plugin module so that it can be registered. | ||||
|  | ||||
|     Recommended event handlers: | ||||
|         session_start -- Plugins which require the use of the current | ||||
|                          bound JID SHOULD wait for the session_start | ||||
|                          event to perform any initialization (or | ||||
|                          resetting). This is a transitive recommendation, | ||||
|                          plugins that use other plugins which use the | ||||
|                          bound JID should also wait for session_start | ||||
|                          before making such calls. | ||||
|         session_end   -- If the plugin keeps any per-session state, | ||||
|                          such as joined MUC rooms, such state SHOULD | ||||
|                          be cleared when the session_end event is raised. | ||||
|     This function is called to import plugins that have selected for | ||||
|     enabling, but no matching registered plugin has been found. | ||||
|  | ||||
|     Attributes: | ||||
|         xep          -- The XEP number the plugin implements, if any. | ||||
|         description  -- A short description of the plugin, typically | ||||
|                         the long name of the implemented XEP. | ||||
|         xmpp         -- The main SleekXMPP instance. | ||||
|         config       -- A dictionary of custom configuration values. | ||||
|                         The value 'enable' is special and controls | ||||
|                         whether or not the plugin is initialized | ||||
|                         after registration. | ||||
|         post_initted -- Executed after all plugins have been initialized | ||||
|                         to handle any cross-plugin interactions, such as | ||||
|                         registering service discovery items. | ||||
|         enable       -- Indicates that the plugin is enabled for use and | ||||
|                         will be initialized after registration. | ||||
|  | ||||
|     Methods: | ||||
|         plugin_init  -- Initialize the plugin state. | ||||
|         post_init    -- Handle any cross-plugin concerns. | ||||
|     :param str name: The name of the plugin. It is expected that | ||||
|                      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 | ||||
|                        for the plugin. | ||||
|     """ | ||||
|     try: | ||||
|         if not module: | ||||
|             try: | ||||
|                 module = 'sleekxmpp.plugins.%s' % name | ||||
|                 __import__(module) | ||||
|                 mod = sys.modules[module] | ||||
|             except ImportError: | ||||
|                 module = 'sleekxmpp.features.%s' % name | ||||
|                 __import__(module) | ||||
|                 mod = sys.modules[module] | ||||
|         elif isinstance(module, (str, unicode)): | ||||
|             __import__(module) | ||||
|             mod = sys.modules[module] | ||||
|         else: | ||||
|             mod = module | ||||
|  | ||||
|         # Add older style plugins to the registry. | ||||
|         if hasattr(mod, name): | ||||
|             plugin = getattr(mod, name) | ||||
|             if hasattr(plugin, 'xep') or hasattr(plugin, 'rfc'): | ||||
|                 plugin.name = name | ||||
|                 # Mark the plugin as an older style plugin so | ||||
|                 # we can work around dependency issues. | ||||
|                 plugin.old_style = True | ||||
|             register_plugin(plugin, name) | ||||
|     except ImportError: | ||||
|         log.exception("Unable to load plugin: %s", name) | ||||
|  | ||||
|  | ||||
| class PluginManager(object): | ||||
|     def __init__(self, xmpp, config=None): | ||||
|         """ | ||||
|         Instantiate a new plugin and store the given configuration. | ||||
|         #: We will track all enabled plugins in a set so that we | ||||
|         #: can enable plugins in batches and pull in dependencies | ||||
|         #: without problems. | ||||
|         self._enabled = set() | ||||
|  | ||||
|         Arguments: | ||||
|             xmpp   -- The main SleekXMPP instance. | ||||
|             config -- A dictionary of configuration values. | ||||
|         #: Maintain references to active plugins. | ||||
|         self._plugins = {} | ||||
|  | ||||
|         self._plugin_lock = threading.RLock() | ||||
|  | ||||
|         #: Globally set default plugin configuration. This will | ||||
|         #: be used for plugins that are auto-enabled through | ||||
|         #: dependency loading. | ||||
|         self.config = config if config else {} | ||||
|  | ||||
|         self.xmpp = xmpp | ||||
|  | ||||
|     def register(self, plugin, enable=True): | ||||
|         """Register a new plugin, and optionally enable it. | ||||
|  | ||||
|         :param class plugin: The implementation class of the plugin | ||||
|                              to register. | ||||
|         :param bool enable: If ``True``, immediately enable the | ||||
|                             plugin after registration. | ||||
|         """ | ||||
|         register_plugin(plugin) | ||||
|         if enable: | ||||
|             self.enable(plugin.name) | ||||
|  | ||||
|     def enable(self, name, config=None, enabled=None): | ||||
|         """Enable a plugin, including any dependencies. | ||||
|  | ||||
|         :param string name: The short name of the plugin. | ||||
|         :param dict config: Optional settings dictionary for | ||||
|                             configuring plugin behaviour. | ||||
|         """ | ||||
|         top_level = False | ||||
|         if enabled is None: | ||||
|             enabled = set() | ||||
|  | ||||
|         with self._plugin_lock: | ||||
|             if name not in self._enabled: | ||||
|                 enabled.add(name) | ||||
|                 self._enabled.add(name) | ||||
|                 if not self.registered(name): | ||||
|                     load_plugin(name) | ||||
|  | ||||
|                 plugin_class = PLUGIN_REGISTRY.get(name, None) | ||||
|                 if not plugin_class: | ||||
|                     raise PluginNotFound(name) | ||||
|  | ||||
|                 if config is None: | ||||
|                     config = self.config.get(name, None) | ||||
|  | ||||
|                 plugin = plugin_class(self.xmpp, config) | ||||
|                 self._plugins[name] = plugin | ||||
|                 for dep in plugin.dependencies: | ||||
|                     self.enable(dep, enabled=enabled) | ||||
|                 plugin._init() | ||||
|  | ||||
|         if top_level: | ||||
|             for name in enabled: | ||||
|                 if hasattr(self.plugins[name], 'old_style'): | ||||
|                     # Older style plugins require post_init() | ||||
|                     # to run just before stream processing begins, | ||||
|                     # so we don't call it here. | ||||
|                     pass | ||||
|                 self.plugins[name].post_init() | ||||
|  | ||||
|     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. | ||||
|         :param dict config: A dictionary mapping plugin names to | ||||
|                             configuration dictionaries, as used by | ||||
|                             :meth:`~PluginManager.enable`. | ||||
|         """ | ||||
|         names = names if names else PLUGIN_REGISTRY.keys() | ||||
|         if config is None: | ||||
|             config = {} | ||||
|         self.xep = None | ||||
|         self.rfc = None | ||||
|         self.description = 'Base Plugin' | ||||
|         for name in names: | ||||
|             self.enable(name, config.get(name, {})) | ||||
|  | ||||
|     def enabled(self, name): | ||||
|         """Check if a plugin has been enabled. | ||||
|  | ||||
|         :param string name: The name of the plugin to check. | ||||
|         :return: boolean | ||||
|         """ | ||||
|         return name in self._enabled | ||||
|  | ||||
|     def registered(self, name): | ||||
|         """Check if a plugin has been registered. | ||||
|  | ||||
|         :param string name: The name of the plugin to check. | ||||
|         :return: boolean | ||||
|         """ | ||||
|         return name in PLUGIN_REGISTRY | ||||
|  | ||||
|     def disable(self, name, _disabled=None): | ||||
|         """Disable a plugin, including any dependent upon it. | ||||
|  | ||||
|         :param string name: The name of the plugin to disable. | ||||
|         :param set _disabled: Private set used to track the | ||||
|                               disabled status of plugins during | ||||
|                               the cascading process. | ||||
|         """ | ||||
|         if _disabled is None: | ||||
|             _disabled = set() | ||||
|         with self._plugin_lock: | ||||
|             if name not in _disabled and name in self._enabled: | ||||
|                 _disabled.add(name) | ||||
|                 plugin = self._plugins.get(name, None) | ||||
|                 if plugin is None: | ||||
|                     raise PluginNotFound(name) | ||||
|                 for dep in PLUGIN_DEPENDENTS[name]: | ||||
|                     self.disable(dep, _disabled) | ||||
|                 plugin._end() | ||||
|                 if name in self._enabled: | ||||
|                     self._enabled.remove(name) | ||||
|                 del self._plugins[name] | ||||
|  | ||||
|     def __keys__(self): | ||||
|         """Return the set of enabled plugins.""" | ||||
|         return self._plugins.keys() | ||||
|  | ||||
|     def __getitem__(self, name): | ||||
|         """ | ||||
|         Allow plugins to be accessed through the manager as if | ||||
|         it were a dictionary. | ||||
|         """ | ||||
|         plugin = self._plugins.get(name, None) | ||||
|         if plugin is None: | ||||
|             raise PluginNotFound(name) | ||||
|         return plugin | ||||
|  | ||||
|     def __iter__(self): | ||||
|         """Return an iterator over the set of enabled plugins.""" | ||||
|         return self._plugins.__iter__() | ||||
|  | ||||
|     def __len__(self): | ||||
|         """Return the number of enabled plugins.""" | ||||
|         return len(self._plugins) | ||||
|  | ||||
|  | ||||
| class BasePlugin(object): | ||||
|  | ||||
|     #: A short name for the plugin based on the implemented specification. | ||||
|     #: For example, a plugin for XEP-0030 would use `'xep_0030'`. | ||||
|     name = '' | ||||
|  | ||||
|     #: A longer name for the plugin, describing its purpose. For example, | ||||
|     #: a plugin for XEP-0030 would use `'Service Discovery'` as its | ||||
|     #: description value. | ||||
|     description = '' | ||||
|  | ||||
|     #: Some plugins may depend on others in order to function properly. | ||||
|     #: Any plugin names included in :attr:`~BasePlugin.dependencies` will | ||||
|     #: 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 | ||||
|         self.config = config | ||||
|         self.post_inited = False | ||||
|         self.enable = config.get('enable', True) | ||||
|         if self.enable: | ||||
|         if self.xmpp: | ||||
|             self.api = self.xmpp.api.wrap(self.name) | ||||
|  | ||||
|         #: A plugin's behaviour may be configurable, in which case those | ||||
|         #: configuration settings will be provided as a dictionary. | ||||
|         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 any stream or | ||||
|         event handlers, or new stanza types. | ||||
|         """ | ||||
|         """Initialize plugin state, such as registering event handlers.""" | ||||
|         pass | ||||
|  | ||||
|     def plugin_end(self): | ||||
|         """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. | ||||
|         """ | ||||
|         Perform any cross-plugin interactions, such as registering | ||||
|         service discovery identities or items. | ||||
|         """ | ||||
|         self.post_inited = True | ||||
|         pass | ||||
|  | ||||
|  | ||||
| base_plugin = BasePlugin | ||||
|   | ||||
| @@ -6,6 +6,17 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0004.stanza import Form | ||||
| from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption | ||||
| from sleekxmpp.plugins.xep_0004.dataforms import xep_0004 | ||||
| from sleekxmpp.plugins.xep_0004.dataforms import XEP_0004 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0004) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0004 = XEP_0004 | ||||
| xep_0004.makeForm = xep_0004.make_form | ||||
| xep_0004.buildForm = xep_0004.build_form | ||||
|   | ||||
| @@ -6,30 +6,28 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import copy | ||||
|  | ||||
| from sleekxmpp.thirdparty import OrderedDict | ||||
|  | ||||
| from sleekxmpp import Message | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET | ||||
| from sleekxmpp.xmlstream import register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.plugins.base import base_plugin | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0004 import stanza | ||||
| from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption | ||||
|  | ||||
|  | ||||
| class xep_0004(base_plugin): | ||||
| class XEP_0004(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0004: Data Forms | ||||
|     """ | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xep = '0004' | ||||
|         self.description = 'Data Forms' | ||||
|         self.stanza = stanza | ||||
|     name = 'xep_0004' | ||||
|     description = 'XEP-0004: Data Forms' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.register_handler( | ||||
|             Callback('Data Form', | ||||
|                  StanzaPath('message/form'), | ||||
|                  self.handle_form)) | ||||
| @@ -38,6 +36,13 @@ class xep_0004(base_plugin): | ||||
|         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=''): | ||||
|         f = Form() | ||||
|         f['type'] = ftype | ||||
| @@ -45,16 +50,8 @@ class xep_0004(base_plugin): | ||||
|         f['instructions'] = instructions | ||||
|         return f | ||||
|  | ||||
|     def post_init(self): | ||||
|         base_plugin.post_init(self) | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') | ||||
|  | ||||
|     def handle_form(self, message): | ||||
|         self.xmpp.event("message_xform", message) | ||||
|  | ||||
|     def build_form(self, xml): | ||||
|         return Form(xml=xml) | ||||
|  | ||||
|  | ||||
| xep_0004.makeForm = xep_0004.make_form | ||||
| xep_0004.buildForm = xep_0004.build_form | ||||
|   | ||||
| @@ -79,19 +79,21 @@ class FormField(ElementBase): | ||||
|         reqXML = self.xml.find('{%s}required' % self.namespace) | ||||
|         return reqXML is not None | ||||
|  | ||||
|     def get_value(self): | ||||
|     def get_value(self, convert=True): | ||||
|         valsXML = self.xml.findall('{%s}value' % self.namespace) | ||||
|         if len(valsXML) == 0: | ||||
|             return None | ||||
|         elif self._type == 'boolean': | ||||
|             if convert: | ||||
|                 return valsXML[0].text in self.true_values | ||||
|             return valsXML[0].text | ||||
|         elif self._type in self.multi_value_types or len(valsXML) > 1: | ||||
|             values = [] | ||||
|             for valXML in valsXML: | ||||
|                 if valXML.text is None: | ||||
|                     valXML.text = '' | ||||
|                 values.append(valXML.text) | ||||
|             if self._type == 'text-multi': | ||||
|             if self._type == 'text-multi' and convert: | ||||
|                 values = "\n".join(values) | ||||
|             return values | ||||
|         else: | ||||
| @@ -136,6 +138,8 @@ class FormField(ElementBase): | ||||
|                 valXML.text = '0' | ||||
|                 self.xml.append(valXML) | ||||
|         elif self._type in self.multi_value_types or self._type in ('', None): | ||||
|             if isinstance(value, bool): | ||||
|                 value = [value] | ||||
|             if not isinstance(value, list): | ||||
|                 value = value.replace('\r', '') | ||||
|                 value = value.split('\n') | ||||
|   | ||||
| @@ -201,6 +201,7 @@ class Form(ElementBase): | ||||
|         del self['instructions'] | ||||
|         if instructions in [None, '']: | ||||
|             return | ||||
|         if not isinstance(instructions, list): | ||||
|             instructions = instructions.split('\n') | ||||
|         for instruction in instructions: | ||||
|             inst = ET.Element('{%s}instructions' % self.namespace) | ||||
|   | ||||
| @@ -6,6 +6,15 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0009 import stanza | ||||
| from sleekxmpp.plugins.xep_0009.rpc import xep_0009 | ||||
| from sleekxmpp.plugins.xep_0009.rpc import XEP_0009 | ||||
| from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0009) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0009 = XEP_0009 | ||||
|   | ||||
| @@ -6,10 +6,14 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from xml.etree import cElementTree as ET | ||||
| from sleekxmpp.xmlstream import ET | ||||
| import base64 | ||||
| import logging | ||||
| import time | ||||
| import sys | ||||
|  | ||||
| if sys.version_info > (3, 0): | ||||
|     unicode = str | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -54,7 +58,7 @@ def _py2xml(*args): | ||||
|             boolean = ET.Element("{%s}boolean" % _namespace) | ||||
|             boolean.text = str(int(x)) | ||||
|             val.append(boolean) | ||||
|         elif type(x) is str: | ||||
|         elif type(x) in (str, unicode): | ||||
|             string = ET.Element("{%s}string" % _namespace) | ||||
|             string.text = x | ||||
|             val.append(string) | ||||
| @@ -152,7 +156,7 @@ class rpctime(object): | ||||
|  | ||||
|     def __init__(self,data=None): | ||||
|         #assume string data is in iso format YYYYMMDDTHH:MM:SS | ||||
|         if type(data) is str: | ||||
|         if type(data) in (str, unicode): | ||||
|             self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S") | ||||
|         elif type(data) is time.struct_time: | ||||
|             self.timestamp = data | ||||
|   | ||||
| @@ -6,28 +6,28 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins import base | ||||
| from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse | ||||
| from sleekxmpp.stanza.iq import Iq | ||||
| from sleekxmpp.xmlstream.handler.callback import Callback | ||||
| from sleekxmpp.xmlstream.matcher.xpath import MatchXPath | ||||
| from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin | ||||
| from xml.etree import cElementTree as ET | ||||
| import logging | ||||
|  | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.xmlstream import ET, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import MatchXPath | ||||
| from sleekxmpp.plugins import BasePlugin | ||||
| from sleekxmpp.plugins.xep_0009 import stanza | ||||
| from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0009(BasePlugin): | ||||
|  | ||||
| class xep_0009(base.base_plugin): | ||||
|     name = 'xep_0009' | ||||
|     description = 'XEP-0009: Jabber-RPC' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xep = '0009' | ||||
|         self.description = 'Jabber-RPC' | ||||
|         #self.stanza = sleekxmpp.plugins.xep_0009.stanza | ||||
|  | ||||
|         register_stanza_plugin(Iq, RPCQuery) | ||||
|         register_stanza_plugin(RPCQuery, MethodCall) | ||||
|         register_stanza_plugin(RPCQuery, MethodResponse) | ||||
| @@ -51,10 +51,8 @@ class xep_0009(base.base_plugin): | ||||
|         self.xmpp.add_event_handler('error', self._handle_error) | ||||
|         #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('automation','rpc') | ||||
|         self.xmpp['xep_0030'].add_feature('jabber:iq:rpc') | ||||
|         self.xmpp['xep_0030'].add_identity('automation','rpc') | ||||
|  | ||||
|     def make_iq_method_call(self, pto, pmethod, params): | ||||
|         iq = self.xmpp.makeIqSet() | ||||
| @@ -218,4 +216,3 @@ class xep_0009(base.base_plugin): | ||||
|     def _extract_method(self, stanza): | ||||
|         xml = ET.fromstring("%s" % stanza) | ||||
|         return xml.find("./methodCall/methodName").text | ||||
|  | ||||
|   | ||||
| @@ -1,115 +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 datetime import datetime | ||||
| import logging | ||||
|  | ||||
| from . import base | ||||
| from .. stanza.iq import Iq | ||||
| from .. xmlstream.handler.callback import Callback | ||||
| from .. xmlstream.matcher.xpath import MatchXPath | ||||
| from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class LastActivity(ElementBase): | ||||
|     name = 'query' | ||||
|     namespace = 'jabber:iq:last' | ||||
|     plugin_attrib = 'last_activity' | ||||
|     interfaces = set(('seconds', 'status')) | ||||
|  | ||||
|     def get_seconds(self): | ||||
|         return int(self._get_attr('seconds')) | ||||
|  | ||||
|     def set_seconds(self, value): | ||||
|         self._set_attr('seconds', str(value)) | ||||
|  | ||||
|     def get_status(self): | ||||
|         return self.xml.text | ||||
|  | ||||
|     def set_status(self, value): | ||||
|         self.xml.text = str(value) | ||||
|  | ||||
|     def del_status(self): | ||||
|         self.xml.text = '' | ||||
|  | ||||
| class xep_0012(base.base_plugin): | ||||
|     """ | ||||
|     XEP-0012 Last Activity | ||||
|     """ | ||||
|     def plugin_init(self): | ||||
|         self.description = "Last Activity" | ||||
|         self.xep = "0012" | ||||
|  | ||||
|         self.xmpp.registerHandler( | ||||
|             Callback('Last Activity', | ||||
|                  MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, | ||||
|                                   LastActivity.namespace)), | ||||
|                  self.handle_last_activity_query)) | ||||
|         register_stanza_plugin(Iq, LastActivity) | ||||
|  | ||||
|         self.xmpp.add_event_handler('last_activity_request', self.handle_last_activity) | ||||
|  | ||||
|  | ||||
|     def post_init(self): | ||||
|         base.base_plugin.post_init(self) | ||||
|         if self.xmpp.is_component: | ||||
|             # We are a component, so we track the uptime | ||||
|             self.xmpp.add_event_handler("session_start", self._reset_uptime) | ||||
|             self._start_datetime = datetime.now() | ||||
|         self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last') | ||||
|  | ||||
|     def _reset_uptime(self, event): | ||||
|         self._start_datetime = datetime.now() | ||||
|  | ||||
|     def handle_last_activity_query(self, iq): | ||||
|         if iq['type'] == 'get': | ||||
|             log.debug("Last activity requested by %s", iq['from']) | ||||
|             self.xmpp.event('last_activity_request', iq) | ||||
|         elif iq['type'] == 'result': | ||||
|             log.debug("Last activity result from %s", iq['from']) | ||||
|             self.xmpp.event('last_activity', iq) | ||||
|  | ||||
|     def handle_last_activity(self, iq): | ||||
|         jid = iq['from'] | ||||
|  | ||||
|         if self.xmpp.is_component: | ||||
|             # Send the uptime | ||||
|             result = LastActivity() | ||||
|             td = (datetime.now() - self._start_datetime) | ||||
|             result['seconds'] = td.seconds + td.days * 24 * 3600 | ||||
|             reply = iq.reply().setPayload(result.xml).send() | ||||
|         else: | ||||
|             barejid = JID(jid).bare | ||||
|             if barejid in self.xmpp.roster and ( self.xmpp.roster[barejid]['subscription'] in ('from', 'both') or | ||||
|                                                  barejid == self.xmpp.boundjid.bare ): | ||||
|                 # We don't know how to calculate it | ||||
|                 iq.reply().error().setPayload(iq['last_activity'].xml) | ||||
|                 iq['error']['code'] = '503' | ||||
|                 iq['error']['type'] = 'cancel' | ||||
|                 iq['error']['condition'] = 'service-unavailable' | ||||
|                 iq.send() | ||||
|             else: | ||||
|                 iq.reply().error().setPayload(iq['last_activity'].xml) | ||||
|                 iq['error']['code'] = '403' | ||||
|                 iq['error']['type'] = 'auth' | ||||
|                 iq['error']['condition'] = 'forbidden' | ||||
|                 iq.send() | ||||
|  | ||||
|     def get_last_activity(self, jid): | ||||
|         """Query the LastActivity of jid and return it in seconds""" | ||||
|         iq = self.xmpp.makeIqGet() | ||||
|         query = LastActivity() | ||||
|         iq.append(query.xml) | ||||
|         iq.attrib['to'] = jid | ||||
|         iq.attrib['from'] = self.xmpp.boundjid.full | ||||
|         id = iq.get('id') | ||||
|         result = iq.send() | ||||
|         return result['last_activity']['seconds'] | ||||
							
								
								
									
										19
									
								
								sleekxmpp/plugins/xep_0012/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								sleekxmpp/plugins/xep_0012/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| """ | ||||
|     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_0012.stanza import LastActivity | ||||
| from sleekxmpp.plugins.xep_0012.last_activity import XEP_0012 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0012) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0004 = XEP_0012 | ||||
							
								
								
									
										157
									
								
								sleekxmpp/plugins/xep_0012/last_activity.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								sleekxmpp/plugins/xep_0012/last_activity.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| """ | ||||
|     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. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from datetime import datetime, timedelta | ||||
|  | ||||
| from sleekxmpp.plugins import BasePlugin, register_plugin | ||||
| from sleekxmpp import Iq | ||||
| from sleekxmpp.exceptions import XMPPError | ||||
| from sleekxmpp.xmlstream import JID, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.plugins.xep_0012 import stanza, LastActivity | ||||
|  | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0012(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0012 Last Activity | ||||
|     """ | ||||
|  | ||||
|     name = 'xep_0012' | ||||
|     description = 'XEP-0012: Last Activity' | ||||
|     dependencies = set(['xep_0030']) | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Iq, LastActivity) | ||||
|  | ||||
|         self._last_activities = {} | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|             Callback('Last Activity', | ||||
|                  StanzaPath('iq@type=get/last_activity'), | ||||
|                  self._handle_get_last_activity)) | ||||
|  | ||||
|         self.api.register(self._default_get_last_activity, | ||||
|                 'get_last_activity', | ||||
|                 default=True) | ||||
|         self.api.register(self._default_set_last_activity, | ||||
|                 'set_last_activity', | ||||
|                 default=True) | ||||
|         self.api.register(self._default_del_last_activity, | ||||
|                 '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) | ||||
|  | ||||
|     def end_idle(self, jid=None): | ||||
|         self.del_last_activity(jid) | ||||
|  | ||||
|     def start_uptime(self, status=None): | ||||
|         self.set_last_activity(jid, 0, status) | ||||
|  | ||||
|     def set_last_activity(self, jid=None, seconds=None, status=None): | ||||
|         self.api['set_last_activity'](jid, args={ | ||||
|             'seconds': seconds, | ||||
|             'status': status}) | ||||
|  | ||||
|     def del_last_activity(self, jid): | ||||
|         self.api['del_last_activity'](jid) | ||||
|  | ||||
|     def get_last_activity(self, jid, local=False, ifrom=None, block=True, | ||||
|                           timeout=None, callback=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 | ||||
|  | ||||
|         if local or jid in (None, ''): | ||||
|             log.debug("Looking up local last activity data for %s", jid) | ||||
|             return self.api['get_last_activity'](jid, None, ifrom, None) | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['from'] = ifrom | ||||
|         iq['to'] = jid | ||||
|         iq['type'] = 'get' | ||||
|         iq.enable('last_activity') | ||||
|         return iq.send(timeout=timeout, | ||||
|                        block=block, | ||||
|                        callback=callback) | ||||
|  | ||||
|     def _handle_get_last_activity(self, iq): | ||||
|         log.debug("Received last activity query from " + \ | ||||
|                   "<%s> to <%s>.", iq['from'], iq['to']) | ||||
|         reply = self.api['get_last_activity'](iq['to'], None, iq['from'], iq) | ||||
|         reply.send() | ||||
|  | ||||
|     # ================================================================= | ||||
|     # Default in-memory implementations for storing last activity data. | ||||
|     # ================================================================= | ||||
|  | ||||
|     def _default_set_last_activity(self, jid, node, ifrom, data): | ||||
|         seconds = data.get('seconds', None) | ||||
|         if seconds is None: | ||||
|             seconds = 0 | ||||
|  | ||||
|         status = data.get('status', None) | ||||
|         if status is None: | ||||
|             status = '' | ||||
|  | ||||
|         self._last_activities[jid] = { | ||||
|             'seconds': datetime.now() - timedelta(seconds=seconds), | ||||
|             'status': status} | ||||
|  | ||||
|     def _default_del_last_activity(self, jid, node, ifrom, data): | ||||
|         if jid in self._last_activities: | ||||
|             del self._last_activities[jid] | ||||
|  | ||||
|     def _default_get_last_activity(self, jid, node, ifrom, iq): | ||||
|         if not isinstance(iq, Iq): | ||||
|             reply = self.xmpp.Iq() | ||||
|         else: | ||||
|             iq.reply() | ||||
|             reply = iq | ||||
|  | ||||
|         if jid not in self._last_activities: | ||||
|             raise XMPPError('service-unavailable') | ||||
|  | ||||
|         bare = JID(jid).bare | ||||
|  | ||||
|         if bare != self.xmpp.boundjid.bare: | ||||
|             if bare in self.xmpp.roster[jid]: | ||||
|                 sub = self.xmpp.roster[jid][bare]['subscription'] | ||||
|                 if sub not in ('from', 'both'): | ||||
|                     raise XMPPError('forbidden') | ||||
|  | ||||
|         td = datetime.now() - self._last_activities[jid]['seconds'] | ||||
|         seconds = td.seconds + td.days * 24 * 3600 | ||||
|         status = self._last_activities[jid]['status'] | ||||
|  | ||||
|         reply['last_activity']['seconds'] = seconds | ||||
|         reply['last_activity']['status'] = status | ||||
|  | ||||
|         return reply | ||||
							
								
								
									
										32
									
								
								sleekxmpp/plugins/xep_0012/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								sleekxmpp/plugins/xep_0012/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| """ | ||||
|     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 ElementBase | ||||
|  | ||||
|  | ||||
| class LastActivity(ElementBase): | ||||
|  | ||||
|     name = 'query' | ||||
|     namespace = 'jabber:iq:last' | ||||
|     plugin_attrib = 'last_activity' | ||||
|     interfaces = set(('seconds', 'status')) | ||||
|  | ||||
|     def get_seconds(self): | ||||
|         return int(self._get_attr('seconds')) | ||||
|  | ||||
|     def set_seconds(self, value): | ||||
|         self._set_attr('seconds', str(value)) | ||||
|  | ||||
|     def get_status(self): | ||||
|         return self.xml.text | ||||
|  | ||||
|     def set_status(self, value): | ||||
|         self.xml.text = str(value) | ||||
|  | ||||
|     def del_status(self): | ||||
|         self.xml.text = '' | ||||
							
								
								
									
										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) | ||||
							
								
								
									
										15
									
								
								sleekxmpp/plugins/xep_0027/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								sleekxmpp/plugins/xep_0027/__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 permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0027.stanza import Signed, Encrypted | ||||
| from sleekxmpp.plugins.xep_0027.gpg import XEP_0027 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0027) | ||||
							
								
								
									
										170
									
								
								sleekxmpp/plugins/xep_0027/gpg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								sleekxmpp/plugins/xep_0027/gpg.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| """ | ||||
|     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.thirdparty import GPG | ||||
|  | ||||
| from sleekxmpp.stanza import Presence, Message | ||||
| from sleekxmpp.plugins.base import BasePlugin, register_plugin | ||||
| from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
| from sleekxmpp.xmlstream.handler import Callback | ||||
| from sleekxmpp.xmlstream.matcher import StanzaPath | ||||
| from sleekxmpp.plugins.xep_0027 import stanza, Signed, Encrypted | ||||
|  | ||||
|  | ||||
| def _extract_data(data, kind): | ||||
|     stripped = [] | ||||
|     begin_headers = False | ||||
|     begin_data = False | ||||
|     for line in data.split('\n'): | ||||
|         if not begin_headers and 'BEGIN PGP %s' % kind in line: | ||||
|             begin_headers = True | ||||
|             continue | ||||
|         if begin_headers and line.stripped() == '': | ||||
|             begin_data = True | ||||
|             continue | ||||
|         if 'END PGP %s' % kind in line: | ||||
|             return '\n'.join(stripped) | ||||
|         if begin_data: | ||||
|             stripped.append(line) | ||||
|     return '' | ||||
|  | ||||
|  | ||||
| class XEP_0027(BasePlugin): | ||||
|  | ||||
|     name = 'xep_0027' | ||||
|     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 = GPG(gnupghome=self.gpg_home, | ||||
|                        gpgbinary=self.gpg_binary, | ||||
|                        use_agent=self.use_agent, | ||||
|                        keyring=self.keyring) | ||||
|  | ||||
|         self.xmpp.add_filter('out', self._sign_presence) | ||||
|  | ||||
|         self._keyids = {} | ||||
|  | ||||
|         self.api.register(self._set_keyid, 'set_keyid', default=True) | ||||
|         self.api.register(self._get_keyid, 'get_keyid', default=True) | ||||
|         self.api.register(self._del_keyid, 'del_keyid', default=True) | ||||
|         self.api.register(self._get_keyids, 'get_keyids', default=True) | ||||
|  | ||||
|         register_stanza_plugin(Presence, Signed) | ||||
|         register_stanza_plugin(Message, Encrypted) | ||||
|  | ||||
|         self.xmpp.add_event_handler('unverified_signed_presence', | ||||
|                 self._handle_unverified_signed_presence, | ||||
|                 threaded=True) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Signed Presence', | ||||
|                     StanzaPath('presence/signed'), | ||||
|                     self._handle_signed_presence)) | ||||
|  | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('Encrypted Message', | ||||
|                     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: | ||||
|                 stanza['signed'] = stanza['status'] | ||||
|         return stanza | ||||
|  | ||||
|     def sign(self, data, jid=None): | ||||
|         keyid = self.get_keyid(jid) | ||||
|         if keyid: | ||||
|             signed = self.gpg.sign(data, keyid=keyid) | ||||
|             return _extract_data(signed.data, 'SIGNATURE') | ||||
|  | ||||
|     def encrypt(self, data, jid=None): | ||||
|         keyid = self.get_keyid(jid) | ||||
|         if keyid: | ||||
|             enc = self.gpg.encrypt(data, keyid) | ||||
|             return _extract_data(enc.data, 'MESSAGE') | ||||
|  | ||||
|     def decrypt(self, data, jid=None): | ||||
|         template = '-----BEGIN PGP MESSAGE-----\n' + \ | ||||
|                    '\n' + \ | ||||
|                    '%s\n' + \ | ||||
|                    '-----END PGP MESSAGE-----\n' | ||||
|         dec = self.gpg.decrypt(template % data) | ||||
|         return dec.data | ||||
|  | ||||
|     def verify(self, data, sig, jid=None): | ||||
|         template = '-----BEGIN PGP SIGNED MESSAGE-----\n' + \ | ||||
|                    'Hash: SHA1\n' + \ | ||||
|                    '\n' + \ | ||||
|                    '%s\n' + \ | ||||
|                    '-----BEGIN PGP SIGNATURE-----\n' + \ | ||||
|                    '\n' + \ | ||||
|                    '%s\n' + \ | ||||
|                    '-----END PGP SIGNATURE-----\n' | ||||
|         v = self.gpg.verify(template % (data, sig)) | ||||
|         return v | ||||
|  | ||||
|     def set_keyid(self, jid=None, keyid=None): | ||||
|         self.api['set_keyid'](jid, args=keyid) | ||||
|  | ||||
|     def get_keyid(self, jid=None): | ||||
|         return self.api['get_keyid'](jid) | ||||
|  | ||||
|     def del_keyid(self, jid=None): | ||||
|         self.api['del_keyid'](jid) | ||||
|  | ||||
|     def get_keyids(self): | ||||
|         return self.api['get_keyids']() | ||||
|  | ||||
|     def _handle_signed_presence(self, pres): | ||||
|         self.xmpp.event('unverified_signed_presence', pres) | ||||
|  | ||||
|     def _handle_unverified_signed_presence(self, pres): | ||||
|         verified = self.verify(pres['status'], pres['signed']) | ||||
|         if verified.key_id: | ||||
|             if not self.get_keyid(pres['from']): | ||||
|                 known_keyids = [e['keyid'] for e in self.gpg.list_keys()] | ||||
|                 if verified.key_id not in known_keyids: | ||||
|                     self.gpg.recv_keys(self.key_server, verified.key_id) | ||||
|                 self.set_keyid(jid=pres['from'], keyid=verified.key_id) | ||||
|             self.xmpp.event('signed_presence', pres) | ||||
|  | ||||
|     def _handle_encrypted_message(self, msg): | ||||
|         self.xmpp.event('encrypted_message', msg) | ||||
|  | ||||
|     # ================================================================= | ||||
|  | ||||
|     def _set_keyid(self, jid, node, ifrom, keyid): | ||||
|         self._keyids[jid] = keyid | ||||
|  | ||||
|     def _get_keyid(self, jid, node, ifrom, keyid): | ||||
|         return self._keyids.get(jid, None) | ||||
|  | ||||
|     def _del_keyid(self, jid, node, ifrom, keyid): | ||||
|         if jid in self._keyids: | ||||
|             del self._keyids[jid] | ||||
|  | ||||
|     def _get_keyids(self, jid, node, ifrom, data): | ||||
|         return self._keyids | ||||
							
								
								
									
										53
									
								
								sleekxmpp/plugins/xep_0027/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								sleekxmpp/plugins/xep_0027/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 permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.xmlstream import ElementBase | ||||
|  | ||||
|  | ||||
| class Signed(ElementBase): | ||||
|     name = 'x' | ||||
|     namespace = 'jabber:x:signed' | ||||
|     plugin_attrib = 'signed' | ||||
|     interfaces = set(['signed']) | ||||
|     is_extension = True | ||||
|  | ||||
|     def set_signed(self, value): | ||||
|         parent = self.parent() | ||||
|         xmpp = parent.stream | ||||
|         data = xmpp['xep_0027'].sign(value, parent['from']) | ||||
|         if data: | ||||
|             self.xml.text = data | ||||
|         else: | ||||
|             del parent['signed'] | ||||
|  | ||||
|     def get_signed(self): | ||||
|         return self.xml.text | ||||
|  | ||||
|  | ||||
| class Encrypted(ElementBase): | ||||
|     name = 'x' | ||||
|     namespace = 'jabber:x:encrypted' | ||||
|     plugin_attrib = 'encrypted' | ||||
|     interfaces = set(['encrypted']) | ||||
|     is_extension = True | ||||
|  | ||||
|     def set_encrypted(self, value): | ||||
|         parent = self.parent() | ||||
|         xmpp = parent.stream | ||||
|         data = xmpp['xep_0027'].encrypt(value, parent['to'].bare) | ||||
|         if data: | ||||
|             self.xml.text = data | ||||
|         else: | ||||
|             del parent['encrypted'] | ||||
|  | ||||
|     def get_encrypted(self): | ||||
|         parent = self.parent() | ||||
|         xmpp = parent.stream | ||||
|         if self.xml.text: | ||||
|             return xmpp['xep_0027'].decrypt(self.xml.text, parent['to']) | ||||
|         return None | ||||
| @@ -6,7 +6,18 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from sleekxmpp.plugins.base import register_plugin | ||||
|  | ||||
| from sleekxmpp.plugins.xep_0030 import stanza | ||||
| from sleekxmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems | ||||
| from sleekxmpp.plugins.xep_0030.static import StaticDisco | ||||
| from sleekxmpp.plugins.xep_0030.disco import xep_0030 | ||||
| from sleekxmpp.plugins.xep_0030.disco import XEP_0030 | ||||
|  | ||||
|  | ||||
| register_plugin(XEP_0030) | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| xep_0030 = XEP_0030 | ||||
| XEP_0030.getInfo = XEP_0030.get_info | ||||
| XEP_0030.getItems = XEP_0030.get_items | ||||
| XEP_0030.make_static = XEP_0030.restore_defaults | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user