Compare commits
	
		
			552 Commits
		
	
	
		
			exp_idle_c
			...
			slix-1.5.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0dd32be7f5 | ||
|   | bf69698af1 | ||
|   | aa732b3c94 | ||
|   | d076cef023 | ||
|   | f884b67b8b | ||
|   | 0d3116dbdf | ||
|   | f1ab9ab964 | ||
|   | e520ab1f5e | ||
|   | 3dcb96d9d8 | ||
|   | 0a7a4c3abe | ||
|   | a4bbc404ed | ||
|   | c3fbc6cb80 | ||
|   | 355d789061 | ||
|   | 47ed67c04e | ||
|   | 34567f450a | ||
|   | 9126bd8392 | ||
|   | 02202f7cd8 | ||
|   | 2add94f5b0 | ||
|   | 5fc757f200 | ||
|   | 98108d0445 | ||
|   | 76f4fb49d6 | ||
|   | 5be46a5e68 | ||
|   | ab9040c30e | ||
|   | a16e2a0f6c | ||
|   | 842aa3be8f | ||
|   | 6c28b49e7f | ||
|   | 621255027d | ||
|   | efe316dc8c | ||
|   | e9a87a0b77 | ||
|   | 85c9967b9c | ||
|   | deb6d4f176 | ||
|   | 7218bb4499 | ||
|   | d85efec7a2 | ||
|   | 115c234527 | ||
|   | a0f5cb6e09 | ||
|   | 110bbf8afc | ||
|   | d97efa0bd8 | ||
|   | 672f1b28f6 | ||
|   | 27d3ae958b | ||
|   | a32794ec35 | ||
|   | aa11ba463e | ||
|   | a83c00e933 | ||
|   | 31f6ef6814 | ||
|   | 9b3874b5df | ||
|   | 0139fb291e | ||
|   | e58988484a | ||
|   | 5d5e5cda19 | ||
|   | 11f707987d | ||
|   | db13794e0f | ||
|   | 37bc1bb9b3 | ||
|   | 9be30e5291 | ||
|   | 9fe20a4056 | ||
|   | 3253d34c0a | ||
|   | fef575ee1a | ||
|   | 540ff89427 | ||
|   | dd8ac8fc87 | ||
|   | 2249d878d1 | ||
|   | 89fa9dc1dd | ||
|   | d7729e8683 | ||
|   | d618f55dea | ||
|   | b0e688eb35 | ||
|   | 0e7176483b | ||
|   | f35569a2c1 | ||
|   | bec6f7c8f3 | ||
|   | 027ce2434d | ||
|   | d57fbb57a2 | ||
|   | 85cd7a9166 | ||
|   | d50d996c68 | ||
|   | 371ad20ca7 | ||
|   | 5f49df6b56 | ||
|   | b50bfb2f34 | ||
|   | b29bb30eb7 | ||
|   | 4435c81d77 | ||
|   | 2638ba2744 | ||
|   | dbc9758311 | ||
|   | 47968963b1 | ||
|   | 4e8800f954 | ||
|   | 40053518aa | ||
|   | 1ee0f72ead | ||
|   | 4bb81228ae | ||
|   | 60a7a5b8df | ||
|   | 946674f424 | ||
|   | 412a9169bd | ||
|   | 72b355de8c | ||
|   | af246dcfe1 | ||
|   | 9612e518fb | ||
|   | fde8264191 | ||
|   | 1cdc656208 | ||
|   | 0042108a67 | ||
|   | 704161a285 | ||
|   | 6b1b58a339 | ||
|   | 4f96e5fa75 | ||
|   | bcb90a653e | ||
|   | 7e435b703d | ||
|   | 2dda6b80d4 | ||
|   | 5629e44710 | ||
|   | 6a06881d8b | ||
|   | 2b666eb1de | ||
|   | 400e7a3903 | ||
|   | fbab3ad214 | ||
|   | 628b357b06 | ||
|   | 88260cc240 | ||
|   | e9f2f503b8 | ||
|   | 696a72247b | ||
|   | 05d76e4b1d | ||
|   | d52d4fbbbe | ||
|   | e53c0fcb30 | ||
|   | 97d68c5196 | ||
|   | b42fafabb4 | ||
|   | 3a44ec8f15 | ||
|   | 93f385562f | ||
|   | 9cab02438b | ||
|   | 74ed50e626 | ||
|   | 9d378c611c | ||
|   | d85d8f4479 | ||
|   | fb75f7cda9 | ||
|   | 41419a2161 | ||
|   | 7cd73b594e | ||
|   | 15c6b775ff | ||
|   | 4b482477e2 | ||
|   | f7e4caadfe | ||
|   | 5f25b0b6a0 | ||
|   | d228bc42ea | ||
|   | ecdc44a601 | ||
|   | 33370e42f1 | ||
|   | 4699861925 | ||
|   | 2d228bdb56 | ||
|   | 570e653ac2 | ||
|   | 282a481059 | ||
|   | f386db380b | ||
|   | 7b87d98fff | ||
|   | 8779d40602 | ||
|   | f0b21c42d5 | ||
|   | e241d4e3c7 | ||
|   | bd22a41a78 | ||
|   | a29a29227a | ||
|   | d4d542b741 | ||
|   | dc4936a6d3 | ||
|   | 897610d819 | ||
|   | d33366badd | ||
|   | 809c500002 | ||
|   | dda4e18b81 | ||
|   | 8c09d932c8 | ||
|   | 31f5e84671 | ||
|   | ad0dc33df9 | ||
|   | 7c3b3827b4 | ||
|   | 9f6fa65139 | ||
|   | 35fa33e3c2 | ||
|   | 86a2f280d2 | ||
|   | 490f15b8fc | ||
|   | 62661ee04f | ||
|   | 37d1f2a6b0 | ||
|   | 20107ad516 | ||
|   | 7738a01311 | ||
|   | a9abed6151 | ||
|   | 0f690d4005 | ||
|   | 59d4420739 | ||
|   | a88f317bbf | ||
|   | 2fc2a88970 | ||
|   | c55e9279ac | ||
|   | 3502480384 | ||
|   | caae713dd6 | ||
|   | df0198abfe | ||
|   | c20f4bf5fa | ||
|   | 9740e93aeb | ||
|   | e7872aaa29 | ||
|   | 037706552c | ||
|   | b881c6729b | ||
|   | 66909aafb3 | ||
|   | cdfb5d56fc | ||
|   | d146ce9fb6 | ||
|   | cb59d60034 | ||
|   | 1d9fe3553e | ||
|   | fe66c022ad | ||
|   | 92ea131721 | ||
|   | dd7f67d10d | ||
|   | c1562b76b2 | ||
|   | 32839f5252 | ||
|   | 80b7cf6ff8 | ||
|   | 128cc2eeb4 | ||
|   | 037912ee89 | ||
|   | 769bc6d3bf | ||
|   | 084d6cb5d9 | ||
|   | 5184713356 | ||
|   | 2f1225bad3 | ||
|   | 841f5a5a5b | ||
|   | 0c6de5e972 | ||
|   | 81dc61c55c | ||
|   | bd63b1ce70 | ||
|   | 29faf114a7 | ||
|   | 94ea8151d4 | ||
|   | 66500ef5fb | ||
|   | 979396bb1e | ||
|   | e177726387 | ||
|   | 20e88fda50 | ||
|   | f252be9b6d | ||
|   | ee98159586 | ||
|   | c6443af29a | ||
|   | d73f56a7af | ||
|   | 7c7f4308c5 | ||
|   | eab8c265f4 | ||
|   | 80b9cd43b1 | ||
|   | af1f9e08ad | ||
|   | e3fd0af9c8 | ||
|   | 27e23672c1 | ||
|   | b38e229359 | ||
|   | 9a563f1425 | ||
|   | 8b6f5953a7 | ||
|   | 2d2a80c73d | ||
|   | 4dfdd5d8e3 | ||
|   | 1994ed3025 | ||
|   | aaa45846d3 | ||
|   | d7ffcb54eb | ||
|   | c33749e57a | ||
|   | e4107d8b4d | ||
|   | da5cb72d3a | ||
|   | c372bd5168 | ||
|   | cabf623131 | ||
|   | ffc240d5b6 | ||
|   | cc4522d9cd | ||
|   | 5bf69dca76 | ||
|   | 59dad12820 | ||
|   | 007c836296 | ||
|   | 3721bf9f6b | ||
|   | 802949eba8 | ||
|   | 24f35e433f | ||
|   | 22664ee7b8 | ||
|   | 6476cfcde5 | ||
|   | 5bb347e884 | ||
|   | eb1251b919 | ||
|   | 820144c40c | ||
|   | 6034df0a78 | ||
|   | df4012e66d | ||
|   | c372f3071a | ||
|   | 829c8b27b6 | ||
|   | fb3ac78bf9 | ||
|   | ffd9436e5c | ||
|   | bbb1344d79 | ||
|   | 457785b286 | ||
|   | 4847f834bd | ||
|   | 53191ff1cf | ||
|   | ffdb6ffd69 | ||
|   | 7560db856b | ||
|   | 63d245ac48 | ||
|   | 7ddd37be29 | ||
|   | a4d3a4a25e | ||
|   | 58bd07628b | ||
|   | 3569038493 | ||
|   | 20c4ff823a | ||
|   | 8a7448a5a1 | ||
|   | d23d8f901e | ||
|   | 391f12eeab | ||
|   | d008988843 | ||
|   | dcacc7d7d5 | ||
|   | c4285961df | ||
|   | 1038f656eb | ||
|   | 8b06aa1146 | ||
|   | 3c7236fe73 | ||
|   | 36824379c3 | ||
|   | a0a37c19ff | ||
|   | 1b5fe57a5e | ||
|   | 5da31db0c7 | ||
|   | f8cea760b6 | ||
|   | 5ef01ecdd1 | ||
|   | 62aafe0ee7 | ||
|   | cf3f36ac52 | ||
|   | b88d2ecd77 | ||
|   | e691850a2b | ||
|   | d4bff8dee6 | ||
|   | 187c350805 | ||
|   | 96d1c26f90 | ||
|   | 46a90749f8 | ||
|   | 0c63a4bbda | ||
|   | e4696e0471 | ||
|   | 8217dc5239 | ||
|   | 2586abc0d3 | ||
|   | 28f84ab3d9 | ||
|   | 813b45aded | ||
|   | 3a9b45e4f2 | ||
|   | b8e091233e | ||
|   | 0edeefd977 | ||
|   | 6ba53cf1ff | ||
|   | d7758eb7f4 | ||
|   | 125336aeee | ||
|   | 7cd1cf32ae | ||
|   | d099e353a4 | ||
|   | 1e4a301c6e | ||
|   | f53b12d227 | ||
|   | e2562dcccf | ||
|   | 7b69ae3738 | ||
|   | ab6df235d7 | ||
|   | 52cd8f4b22 | ||
|   | e28318c271 | ||
|   | 39ee833c29 | ||
|   | 9019e2bc71 | ||
|   | 9208bf5bf1 | ||
|   | f0f1698e46 | ||
|   | eccd7f1c98 | ||
|   | 2587d82af8 | ||
|   | 7ea121b115 | ||
|   | bb81fbbdfc | ||
|   | 1a00a08b7d | ||
|   | 90ea2a3411 | ||
|   | 8fc6814b6d | ||
|   | ffced0ed9a | ||
|   | e7248d9af9 | ||
|   | 6b1a04f59d | ||
|   | 4905407092 | ||
|   | bd6ec10939 | ||
|   | e15e6735f1 | ||
|   | 67afd6a462 | ||
|   | 2e2b97c53b | ||
|   | a35df7fe1f | ||
|   | fbc8562779 | ||
|   | b549db959a | ||
|   | d5188ac68a | ||
|   | ada9444bf8 | ||
|   | acc52fd935 | ||
|   | 1100ff1feb | ||
|   | c17fc3a869 | ||
|   | 4dba697075 | ||
|   | e42d651d7e | ||
|   | 4305eddb4f | ||
|   | c2dc44cfd1 | ||
|   | 5fc14de32e | ||
|   | d245558fd5 | ||
|   | 9d45370e8a | ||
|   | cc1cc61d36 | ||
|   | c6740a4908 | ||
|   | 55114bcffe | ||
|   | 4fa5dedc47 | ||
|   | 5525ef2285 | ||
|   | a7ac969215 | ||
|   | 329cb5a9f8 | ||
|   | d9b47b33f5 | ||
|   | 3582ac9941 | ||
|   | 2a127a57a7 | ||
|   | 7059400020 | ||
|   | 0b14ef82d4 | ||
|   | 83953af53d | ||
|   | 110cf25c6d | ||
|   | f2bf6072ec | ||
|   | 5f9abe2e0e | ||
|   | ea65b672e7 | ||
|   | 93c705fb31 | ||
|   | 0724f623bb | ||
|   | 82e549c0e9 | ||
|   | 1aa15792b4 | ||
|   | ffb2b6bc04 | ||
|   | 27f98bf22c | ||
|   | 3978078710 | ||
|   | 00a0698720 | ||
|   | 4a24f58be2 | ||
|   | da14ce16ec | ||
|   | 18e5abb9dd | ||
|   | 1a75b76916 | ||
|   | 53b56899a0 | ||
|   | 804b23d390 | ||
|   | 04eaf52b1d | ||
|   | dc7fef1064 | ||
|   | 488c433555 | ||
|   | 9c5dd024b1 | ||
|   | 6e61adf3db | ||
|   | 041bd63864 | ||
|   | a366482551 | ||
|   | a721084f6e | ||
|   | 1b4187fa56 | ||
|   | cf7a60705e | ||
|   | 349b05b9b7 | ||
|   | 9fbacf377a | ||
|   | 2da9e35cbc | ||
|   | 8adc8fa2ba | ||
|   | 9efa909dfc | ||
|   | 7f21fdbe26 | ||
|   | f9c7fa92ea | ||
|   | e75a160d52 | ||
|   | 170bd51387 | ||
|   | abcec1e2d3 | ||
|   | eeab646bfa | ||
|   | 2c69144189 | ||
|   | f54ebec654 | ||
|   | 2ce931cb7a | ||
|   | 84eddd2ed2 | ||
|   | 2042e1a4d5 | ||
|   | be14f0cc52 | ||
|   | edd9199be8 | ||
|   | bb094cc649 | ||
|   | dbaa6ed952 | ||
|   | 8c94d894ab | ||
|   | ffc7eac4dc | ||
|   | 555fd6d926 | ||
|   | c024ac8f0b | ||
|   | f00177c0cf | ||
|   | d0ad25745a | ||
|   | 55be23a6da | ||
|   | 75ba283572 | ||
|   | f7164d35d2 | ||
|   | 4afbb0322b | ||
|   | 7bce1ecc8a | ||
|   | bbce16d526 | ||
|   | c29fc39ef1 | ||
|   | 8335c08782 | ||
|   | 224d7ae133 | ||
|   | 04bff00171 | ||
|   | f3e31baf04 | ||
|   | 9b25a7cf77 | ||
|   | 7a908ac07b | ||
|   | 92901637ec | ||
|   | 3590b663ed | ||
|   | a33bde9cc3 | ||
|   | ac50fdccfc | ||
|   | a0c6bf15e9 | ||
|   | a2852eb249 | ||
|   | f1e6d6b0a9 | ||
|   | 116a33ba51 | ||
|   | a8ac115310 | ||
|   | 1345b7c1d0 | ||
|   | d60a652259 | ||
|   | 61a7cecb31 | ||
|   | 192b7e0349 | ||
|   | 80b60fc048 | ||
|   | b8d7b9520c | ||
|   | 0305ce66b7 | ||
|   | 474405ab90 | ||
|   | 4415d3be1a | ||
|   | 058c530787 | ||
|   | 766d0dfd40 | ||
|   | ac31913a65 | ||
|   | d34ddf33db | ||
|   | eb4e09b0ca | ||
|   | ce085bf4f4 | ||
|   | 990113f8e7 | ||
|   | aa022204ee | ||
|   | c1f23b566b | ||
|   | 45f7cb8bda | ||
|   | bdb1f66ac9 | ||
|   | 842157a6cc | ||
|   | a63cc01482 | ||
|   | 1bbb6f3ff9 | ||
|   | 93894247a4 | ||
|   | 16bb5e2537 | ||
|   | d19a6e05b2 | ||
|   | 86e85f9835 | ||
|   | cc145d20b0 | ||
|   | 881d9040c4 | ||
|   | 1e77ea0944 | ||
|   | 140f0885b2 | ||
|   | 83f71a6610 | ||
|   | 271343a32d | ||
|   | 48857b0030 | ||
|   | 1fe7f5f4e6 | ||
|   | d5b1904ebb | ||
|   | b6b0e82dec | ||
|   | 632b7b4afe | ||
|   | 81b7b2c190 | ||
|   | 460de7d301 | ||
|   | 69022c6db7 | ||
|   | 0ef3fa2703 | ||
|   | 8da269de88 | ||
|   | 93ce318259 | ||
|   | 997928de91 | ||
|   | 83d00a5913 | ||
|   | bf5d7c83af | ||
|   | c66a4d4097 | ||
|   | e112e86475 | ||
|   | e034b31d6b | ||
|   | 18a4978456 | ||
|   | 17464b10a4 | ||
|   | 6fb3ecd414 | ||
|   | c214e4f037 | ||
|   | 2ee05d9616 | ||
|   | f795ac02e3 | ||
|   | 6e8235544c | ||
|   | 6e35948276 | ||
|   | 4da870fd19 | ||
|   | cd7ff685fb | ||
|   | 1e4944d47e | ||
|   | e68135f59f | ||
|   | 6408c5a747 | ||
|   | 115fe954ac | ||
|   | 3d243f7da5 | ||
|   | ea5615f236 | ||
|   | 69da1c1d7c | ||
|   | e85fa4203e | ||
|   | 506ca69917 | ||
|   | 8ac0ecdf40 | ||
|   | dbd8115557 | ||
|   | 74b4ea20bf | ||
|   | 11fbaa4241 | ||
|   | 8fd0d7c993 | ||
|   | 1450d36377 | ||
|   | 06358d0665 | ||
|   | 2b3b86e281 | ||
|   | 92e4bc752a | ||
|   | ffb2e05f21 | ||
|   | 1e2665df19 | ||
|   | 4d063e287e | ||
|   | 44f02fb3ab | ||
|   | f6b3a0c6cf | ||
|   | 8b36e918e8 | ||
|   | 9044807121 | ||
|   | 24264d3a07 | ||
|   | 8bc70264ef | ||
|   | 957c635fb7 | ||
|   | 4027927c6e | ||
|   | c16b862200 | ||
|   | a96f608469 | ||
|   | e1f25604ec | ||
|   | 0fe057b5c3 | ||
|   | be76dda21d | ||
|   | ecd124dd06 | ||
|   | 4a8951c4ee | ||
|   | 8afba7de85 | ||
|   | 1ce42d3a2f | ||
|   | 2f4d811db4 | ||
|   | 61127f521d | ||
|   | 62eefdbd6a | ||
|   | 225e07eb64 | ||
|   | 063e73c0d2 | ||
|   | d261318e1a | ||
|   | d33cc00fe9 | ||
|   | 27582f6fd2 | ||
|   | e328ff4833 | ||
|   | 403462fdb8 | ||
|   | f22d8e67b4 | ||
|   | 35f33f1614 | ||
|   | c9f8ddff65 | ||
|   | f5ae98aaf1 | ||
|   | 073e85381a | ||
|   | afc939708f | ||
|   | aabec8b993 | ||
|   | e5e2fbb16b | ||
|   | 3dd379cdf1 | ||
|   | a20582aba4 | ||
|   | 09cdbf1b76 | ||
|   | ca306e7cec | ||
|   | 1bf34f7fe6 | ||
|   | 4144d60017 | ||
|   | 7265682a4d | ||
|   | 08c62a6bf1 | ||
|   | d61f1cd035 | ||
|   | 1063feb33b | ||
|   | 79f3c1ac8f | ||
|   | a5c03b763a | ||
|   | 3670d82f1c | ||
|   | e94a73553d | ||
|   | 577fd71472 | ||
|   | ef1c4368d0 | ||
|   | 48def71d0c | ||
|   | c8c20fff71 | ||
|   | 75a18b5ffe | ||
|   | ea3d39b50e | 
							
								
								
									
										13
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.github/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| ################ Please use Gitlab instead of Github ################################### | ||||
|  | ||||
| Hello, thank you for contributing to slixmpp! | ||||
|  | ||||
| You’re about to open a pull request on github. However this github repository is not the official place for contributions on slixmpp. | ||||
|  | ||||
| Please open your merge request on https://lab.louiz.org/poezio/slixmpp/ | ||||
|  | ||||
| You should be able to log in there with your github credentials, clone the slixmpp repository in your namespace, push your existing pull request into a new branch, and then open a merge request with one click, within 3 minutes. | ||||
|  | ||||
| This will help us review your contribution, avoid spreading things everywhere and it will even run the tests automatically with your changes. | ||||
|  | ||||
| Thank you. | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -12,3 +12,4 @@ slixmpp.egg-info/ | ||||
| *~ | ||||
| .baboon/ | ||||
| .DS_STORE | ||||
| .idea/ | ||||
|   | ||||
							
								
								
									
										21
									
								
								.gitlab-ci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.gitlab-ci.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| stages: | ||||
|   - test | ||||
|   - trigger | ||||
|  | ||||
| test: | ||||
|   stage: test | ||||
|   tags: | ||||
|     - docker | ||||
|   image: ubuntu:latest | ||||
|   script: | ||||
|     - apt update | ||||
|     - apt install -y python3 cython3 gpg | ||||
|     - ./run_tests.py | ||||
|  | ||||
| trigger_poezio: | ||||
|   stage: trigger | ||||
|   tags: | ||||
|     - docker | ||||
|   image: appropriate/curl:latest | ||||
|   script: | ||||
|     - curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline | ||||
							
								
								
									
										7
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| language: python | ||||
| python: | ||||
|   - "3.7" | ||||
|   - "3.8-dev" | ||||
| install: | ||||
|   - "pip install ." | ||||
| script: testall.py | ||||
							
								
								
									
										14
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| Contributing to the Slixmpp project | ||||
| =================================== | ||||
|  | ||||
| To contribute, the preferred way is to commit your changes on some | ||||
| publicly-available git repository (on a fork `on github | ||||
| <https://github.com/poezio/slixmpp>`_ or on your own repository) and to | ||||
| notify the developers with either: | ||||
|  - a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_ | ||||
|  - a pull request on github | ||||
|  - a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_ | ||||
|  | ||||
| Even though Slixmpp’s github repository is just a read-only mirror, we can | ||||
| still be notified of the pull requests and fetch your mirror manually to | ||||
| integrate your changes. | ||||
							
								
								
									
										6
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,5 +1,7 @@ | ||||
| Pre-requisites: | ||||
| - Python 3.1 or 2.6 | ||||
| - Python 3.7+ | ||||
| - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) | ||||
| - GnuPG, for testing | ||||
|  | ||||
| Install: | ||||
| > python3 setup.py install | ||||
| @@ -9,4 +11,4 @@ Root install: | ||||
|  | ||||
| To test: | ||||
| > cd examples | ||||
| > python echo_client.py -v -j [USER@example.com] -p [PASSWORD] | ||||
| > python3 echo_client.py -d -j [USER@example.com] -p [PASSWORD] | ||||
|   | ||||
							
								
								
									
										25
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||||
| OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | ||||
| ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  | ||||
| socksipy: A Python SOCKS client module. | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| Copyright 2006 Dan-Haim. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without modification, | ||||
| are permitted provided that the following conditions are met: | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
|    list of conditions and the following disclaimer. | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|    this list of conditions and the following disclaimer in the documentation | ||||
|    and/or other materials provided with the distribution. | ||||
| 3. Neither the name of Dan Haim nor the names of his contributors may be used | ||||
|    to endorse or promote products derived from this software without specific | ||||
|    prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED | ||||
| WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||||
| MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | ||||
| EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA | ||||
| OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||
| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT | ||||
| OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| include README.rst | ||||
| include LICENSE | ||||
| include testall.py | ||||
| include run_tests.py | ||||
| include slixmpp/stringprep.pyx | ||||
| recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png | ||||
| recursive-include examples *.py | ||||
| recursive-include tests *.py | ||||
|   | ||||
							
								
								
									
										34
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								README.rst
									
									
									
									
									
								
							| @@ -1,13 +1,21 @@ | ||||
| Slixmpp | ||||
| ######### | ||||
|  | ||||
| Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of | ||||
| Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of | ||||
| SleekXMPP. | ||||
|  | ||||
| Slixmpp's goals is to only rewrite the core of the library (the low level | ||||
| socket handling, the timers, the events dispatching) in order to remove all | ||||
| threads. | ||||
|  | ||||
| Building | ||||
| -------- | ||||
|  | ||||
| Slixmpp can make use of cython to improve performance on critical modules. | ||||
| To do that, **cython3** is necessary along with **libidn** headers. | ||||
| Otherwise, no compilation is needed. Building is done by running setup.py:: | ||||
|  | ||||
|     python3 setup.py build_ext --inplace | ||||
|  | ||||
| Documentation and Testing | ||||
| ------------------------- | ||||
| @@ -21,14 +29,14 @@ be in ``docs/_build/html``:: | ||||
|  | ||||
| To run the test suite for Slixmpp:: | ||||
|  | ||||
|     python testall.py | ||||
|     python run_tests.py | ||||
|  | ||||
|  | ||||
| The Slixmpp Boilerplate | ||||
| ------------------------- | ||||
| Projects using Slixmpp tend to follow a basic pattern for setting up client/component | ||||
| connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp | ||||
| based project. See the documetation or examples directory for more detailed archetypes for | ||||
| based project. See the documentation or examples directory for more detailed archetypes for | ||||
| Slixmpp projects:: | ||||
|  | ||||
|     import logging | ||||
| @@ -88,17 +96,27 @@ Slixmpp projects:: | ||||
|  | ||||
|         xmpp = EchoBot('somejid@example.com', 'use_getpass') | ||||
|         xmpp.connect() | ||||
|         xmpp.process(block=True) | ||||
|         xmpp.process(forever=True) | ||||
|  | ||||
|  | ||||
| Slixmpp Credits | ||||
| --------------- | ||||
|  | ||||
| **Maintainer of the slixmpp fork:** Florent Le Coz | ||||
|     `louiz@louiz.org <xmpp:louiz@louiz.org?message>`_, | ||||
| **Maintainers:** | ||||
|     - Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_), | ||||
|     - Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_), | ||||
|  | ||||
| Credits | ||||
| ------- | ||||
| **Contributors:** | ||||
|     - Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_) | ||||
|     - Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_) | ||||
|     - Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_) | ||||
|     - Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_) | ||||
|     - Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_) | ||||
|     - Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_) | ||||
|     - Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_) | ||||
|  | ||||
| Credits (SleekXMPP) | ||||
| ------------------- | ||||
|  | ||||
| **Main Author:** Nathan Fritz | ||||
|     `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, | ||||
|   | ||||
							
								
								
									
										21
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							| @@ -408,24 +408,3 @@ div.viewcode-block:target { | ||||
|     margin: -1px -12px; | ||||
|     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; | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							| @@ -65,6 +65,5 @@ | ||||
|       <div class="bottomnav"> | ||||
|       {{ nav() }} | ||||
|       </div> | ||||
|       <a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a> | ||||
| {% endblock %} | ||||
|  | ||||
|   | ||||
							
								
								
									
										8
									
								
								docs/api/stanza/iq.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/iq.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| IQ Stanza | ||||
| ========= | ||||
|  | ||||
| .. module:: slixmpp.stanza | ||||
|  | ||||
| .. autoclass:: Iq | ||||
|     :members: | ||||
|  | ||||
							
								
								
									
										7
									
								
								docs/api/stanza/message.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/api/stanza/message.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| Message Stanza | ||||
| ============== | ||||
|  | ||||
| .. module:: slixmpp.stanza | ||||
|  | ||||
| .. autoclass:: Message | ||||
|     :members: | ||||
							
								
								
									
										8
									
								
								docs/api/stanza/presence.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/presence.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| Presence Stanza | ||||
| =============== | ||||
|  | ||||
| .. module:: slixmpp.stanza | ||||
|  | ||||
| .. autoclass:: Presence | ||||
|     :members: | ||||
|  | ||||
							
								
								
									
										8
									
								
								docs/api/stanza/rootstanza.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/rootstanza.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| Root Stanza | ||||
| =========== | ||||
|  | ||||
| .. module:: slixmpp.stanza.rootstanza | ||||
|  | ||||
| .. autoclass:: RootStanza | ||||
|     :members: | ||||
|  | ||||
| @@ -1,12 +0,0 @@ | ||||
| .. module:: slixmpp.xmlstream.filesocket | ||||
|  | ||||
| .. _filesocket: | ||||
|  | ||||
| Python 2.6 File Socket Shims | ||||
| ============================ | ||||
|  | ||||
| .. autoclass:: FileSocket | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: Socket26 | ||||
|     :members: | ||||
| @@ -10,15 +10,19 @@ The Basic Handler | ||||
|  | ||||
| Callback | ||||
| -------- | ||||
| .. module:: slixmpp.xmlstream.handler.callback | ||||
| .. module:: slixmpp.xmlstream.handler | ||||
|  | ||||
| .. autoclass:: Callback | ||||
|     :members: | ||||
|  | ||||
| CoroutineCallback | ||||
| ----------------- | ||||
|  | ||||
| .. autoclass:: CoroutineCallback | ||||
|     :members: | ||||
|  | ||||
| Waiter | ||||
| ------ | ||||
| .. module:: slixmpp.xmlstream.handler.waiter | ||||
|  | ||||
| .. autoclass:: Waiter | ||||
|     :members: | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| Jabber IDs (JID) | ||||
| ================= | ||||
|  | ||||
| .. module:: slixmpp.xmlstream.jid | ||||
| .. module:: slixmpp.jid | ||||
|  | ||||
| .. autoclass:: JID | ||||
|     :members: | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| ========= | ||||
| Scheduler | ||||
| ========= | ||||
|  | ||||
| .. module:: slixmpp.xmlstream.scheduler | ||||
|  | ||||
| .. autoclass:: Task | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: Scheduler | ||||
|     :members: | ||||
| @@ -61,8 +61,8 @@ interacting with a given :term:`stanza` a :term:`stanza object`. | ||||
| To make dealing with more complicated and nested :term:`stanzas <stanza>` | ||||
| or XML chunks easier, :term:`stanza objects <stanza object>` can be | ||||
| composed in two ways: as iterable child objects or as plugins. Iterable | ||||
| child stanzas, or :term:`substanzas`, are accessible through a special | ||||
| ``'substanzas'`` interface. This option is useful for stanzas which | ||||
| child stanzas, or :term:`substanzas <substanza>`, are accessible through a | ||||
| special ``'substanzas'`` interface. This option is useful for stanzas which | ||||
| may contain more than one of the same kind of element. When there is | ||||
| only one child element, the plugin method is more useful. For plugins, | ||||
| a parent stanza object delegates one of its XML child elements to the | ||||
|   | ||||
| @@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace | ||||
| prefixes:: | ||||
|  | ||||
|     >>> from slixmpp.xmlstream.tostring import tostring | ||||
|     >>> from xml.etree import cElementTree as ET | ||||
|     >>> from xml.etree import ElementTree as ET | ||||
|     >>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>') | ||||
|     >>> ET.tostring(xml) | ||||
|     '<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>' | ||||
| @@ -28,7 +28,7 @@ namespace because that is already declared by the stream header. But, if | ||||
| you create a :class:`~slixmpp.stanza.message.Message` instance and dump | ||||
| it to the terminal, the ``jabber:client`` namespace will appear. | ||||
|  | ||||
| .. autofunction:: tostring | ||||
| .. autofunction:: slixmpp.xmlstream.tostring | ||||
|  | ||||
| Escaping Special Characters | ||||
| --------------------------- | ||||
| @@ -43,4 +43,5 @@ In the future, the use of CDATA sections may be allowed to reduce the | ||||
| size of escaped text or for when other XMPP processing agents do not | ||||
| undertand these entities. | ||||
|  | ||||
| .. autofunction:: xml_escape | ||||
| .. | ||||
|     autofunction:: xml_escape | ||||
|   | ||||
| @@ -24,21 +24,20 @@ patterns is received; these callbacks are also referred to as :term:`stream | ||||
| handlers <stream handler>`. The class also provides a basic eventing system | ||||
| which can be triggered either manually or on a timed schedule. | ||||
|  | ||||
| The Main Threads | ||||
| ~~~~~~~~~~~~~~~~ | ||||
| :class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances run using at | ||||
| least three background threads: the send thread, the read thread, and the | ||||
| scheduler thread. The send thread is in charge of monitoring the send queue | ||||
| and writing text to the outgoing XML stream. The read thread pulls text off | ||||
| of the incoming XML stream and stores the results in an event queue. The | ||||
| scheduler thread is used to emit events after a given period of time. | ||||
| The event loop | ||||
| ~~~~~~~~~~~~~~ | ||||
| :class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the | ||||
| :class:`asyncio.BaseProtocol` class, and therefore do not have to handle | ||||
| reads and writes directly, but receive data through | ||||
| :meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write | ||||
| data in the socket transport. | ||||
|  | ||||
| Additionally, the main event processing loop may be executed in its | ||||
| own thread if Slixmpp is being used in the background for another | ||||
| application. | ||||
| Upon receiving data, :term:`stream handlers <stream handler>` are run | ||||
| immediately, except if they are coroutines, in which case they are | ||||
| scheduled using :meth:`asyncio.async`. | ||||
|  | ||||
| Short-lived threads may also be spawned as requested for threaded | ||||
| :term:`event handlers <event handler>`. | ||||
| :term:`Event handlers <event handler>` (which are called inside | ||||
| :term:`stream handlers <stream handler>`) work the same way. | ||||
|  | ||||
| How XML Text is Turned into Action | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| @@ -53,7 +52,7 @@ when this bit of XML is received (with an assumed namespace of | ||||
|     </message> | ||||
|  | ||||
|  | ||||
| 1. **Convert XML strings into objects.** | ||||
| #. **Convert XML strings into objects.** | ||||
|  | ||||
|    Incoming text is parsed and converted into XML objects (using | ||||
|    ElementTree) which are then wrapped into what are referred to as | ||||
| @@ -66,65 +65,43 @@ when this bit of XML is received (with an assumed namespace of | ||||
|    ``{jabber:client}message`` is associated with the class | ||||
|    :class:`~slixmpp.stanza.Message`. | ||||
|  | ||||
| 2. **Match stanza objects to callbacks.** | ||||
| #. **Match stanza objects to callbacks.** | ||||
|  | ||||
|    These objects are then compared against the stored patterns associated | ||||
|    with the registered callback handlers. For each match, a copy of the | ||||
|    :term:`stanza object` is paired with a reference to the handler and | ||||
|    placed into the event queue. | ||||
|    with the registered callback handlers. | ||||
|  | ||||
|    Our :class:`~slixmpp.stanza.Message` object is thus paired with the message stanza handler | ||||
|    :meth:`BaseXMPP._handle_message` to create the tuple:: | ||||
|    Each handler matching our :term:`stanza object` is then added to a list. | ||||
|  | ||||
|        ('stanza', stanza_obj, handler) | ||||
| #. **Processing callbacks** | ||||
|  | ||||
| 3. **Process the event queue.** | ||||
|    Every handler in the list is then called with the :term:`stanza object` | ||||
|    as a parameter; if the handler is a | ||||
|    :class:`~slixmpp.xmlstream.handler.CoroutineCallback` | ||||
|    then it will be scheduled in the event loop using :meth:`asyncio.async` | ||||
|    instead of run. | ||||
|  | ||||
|    The event queue is the heart of Slixmpp. Nearly every action that | ||||
|    takes place is first inserted into this queue, whether that be received | ||||
|    stanzas, custom events, or scheduled events. | ||||
|  | ||||
|    When the stanza is pulled out of the event queue with an associated | ||||
|    callback, the callback function is executed with the stanza as its only | ||||
|    parameter. | ||||
|  | ||||
|    .. warning:: | ||||
|        The callback, aka :term:`stream handler`, is executed in the main event | ||||
|        processing thread. If the handler blocks, event processing will also | ||||
|        block. | ||||
|  | ||||
| 4. **Raise Custom Events** | ||||
| #. **Raise Custom Events** | ||||
|  | ||||
|    Since a :term:`stream handler` shouldn't block, if extensive processing | ||||
|    for a stanza is required (such as needing to send and receive an | ||||
|    :class:`~slixmpp.stanza.Iq` stanza), then custom events must be used. | ||||
|    These events are not explicitly tied to the incoming XML stream and may | ||||
|    be raised at any time. Importantly, these events may be handled in their | ||||
|    own thread. | ||||
|    be raised at any time. | ||||
|  | ||||
|    When the event is raised, a copy of the stanza is created for each | ||||
|    handler registered for the event. In contrast to :term:`stream handlers | ||||
|    <stream handler>`, these functions are referred to as :term:`event | ||||
|    handlers <event handler>`. Each stanza/handler pair is then put into the | ||||
|    event queue. | ||||
|    In contrast to :term:`stream handlers <stream handler>`, these functions | ||||
|    are referred to as :term:`event handlers <event handler>`. | ||||
|  | ||||
|    The code for :meth:`BaseXMPP._handle_message` follows this pattern, and | ||||
|    raises a ``'message'`` event:: | ||||
|    raises a ``'message'`` event | ||||
|  | ||||
|        self.event('message', msg) | ||||
|    .. code-block:: python | ||||
|  | ||||
|    The event call then places the message object back into the event queue | ||||
|    paired with an :term:`event handler`:: | ||||
|         self.event('message', msg) | ||||
|  | ||||
|        ('event', 'message', msg_copy1, custom_event_handler_1) | ||||
|        ('event', 'message', msg_copy2, custom_evetn_handler_2) | ||||
| #. **Process Custom Events** | ||||
|  | ||||
| 5. **Process Custom Events** | ||||
|  | ||||
|    The stanza and :term:`event handler` are then pulled from the event | ||||
|    queue, and the handler is executed, passing the stanza as its only | ||||
|    argument. If the handler was registered as threaded, then a new thread | ||||
|    will be spawned for it. | ||||
|    The :term:`event handlers <event handler>` are then executed, passing | ||||
|    the stanza as the only argument. | ||||
|  | ||||
|    .. note:: | ||||
|        Events may be raised without needing :term:`stanza objects <stanza object>`. | ||||
| @@ -135,9 +112,9 @@ when this bit of XML is received (with an assumed namespace of | ||||
|    Finally, after a long trek, our message is handed off to the user's | ||||
|    custom handler in order to do awesome stuff:: | ||||
|  | ||||
|        msg.reply() | ||||
|        msg['body'] = "Hey! This is awesome!" | ||||
|        msg.send() | ||||
|        reply = msg.reply() | ||||
|        reply['body'] = "Hey! This is awesome!" | ||||
|        reply.send() | ||||
|  | ||||
|  | ||||
| .. index:: BaseXMPP, XMLStream | ||||
|   | ||||
							
								
								
									
										17
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								docs/conf.py
									
									
									
									
									
								
							| @@ -12,12 +12,17 @@ | ||||
| # serve to show the default. | ||||
|  | ||||
| import sys, os | ||||
| import datetime | ||||
|  | ||||
| # If extensions (or modules to document with autodoc) are in another directory, | ||||
| # add these directories to sys.path here. If the directory is relative to the | ||||
| # documentation root, use os.path.abspath to make it absolute, like shown here. | ||||
| sys.path.insert(0, os.path.abspath('..')) | ||||
|  | ||||
| # get version automagically from source tree | ||||
| from slixmpp.version import __version__ as version | ||||
| release = ".".join(version.split(".")[0:2]) | ||||
|  | ||||
| # -- General configuration ----------------------------------------------------- | ||||
|  | ||||
| # If your documentation needs a minimal Sphinx version, state it here. | ||||
| @@ -41,16 +46,18 @@ master_doc = 'index' | ||||
|  | ||||
| # General information about the project. | ||||
| project = u'Slixmpp' | ||||
| copyright = u'2011, Nathan Fritz, Lance Stout' | ||||
| year = datetime.datetime.now().year | ||||
| copyright = u'{}, Nathan Fritz, Lance Stout'.format(year) | ||||
|  | ||||
| # The version info for the project you're documenting, acts as replacement for | ||||
| # |version| and |release|, also used in various other places throughout the | ||||
| # built documents. | ||||
| # | ||||
| # auto imported from code! | ||||
| # The short X.Y version. | ||||
| version = '1.0' | ||||
| # version = '1.4' | ||||
| # The full version, including alpha/beta/rc tags. | ||||
| release = '1.0' | ||||
| # release = '1.4.0' | ||||
|  | ||||
| # The language for content autogenerated by Sphinx. Refer to documentation | ||||
| # for a list of supported languages. | ||||
| @@ -105,7 +112,7 @@ html_theme = 'haiku' | ||||
|  | ||||
| # The name for this set of Sphinx documents.  If None, it defaults to | ||||
| # "<project> v<release> documentation". | ||||
| html_title = 'Slixmpp' | ||||
| html_title = 'slixmpp' | ||||
|  | ||||
| # A shorter title for the navigation bar.  Default is the same as html_title. | ||||
| html_short_title = '%s Documentation' % release | ||||
| @@ -219,4 +226,4 @@ man_pages = [ | ||||
|      [u'Nathan Fritz, Lance Stout'], 1) | ||||
| ] | ||||
|  | ||||
| intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')} | ||||
| intersphinx_mapping = {'python': ('http://docs.python.org/3.4', 'python-objects.inv')} | ||||
|   | ||||
| @@ -163,7 +163,7 @@ behaviour: | ||||
|         namespace = 'jabber:iq:register' | ||||
|         name = 'query' | ||||
|         plugin_attrib = 'register' | ||||
|         interfaces = set(('username', 'password', 'registered', 'remove')) | ||||
|         interfaces = {'username', 'password', 'registered', 'remove'} | ||||
|         sub_interfaces = interfaces | ||||
|  | ||||
|         def getRegistered(self): | ||||
| @@ -535,10 +535,10 @@ with some additional registration fields implemented. | ||||
|         namespace = 'jabber:iq:register' | ||||
|         name = 'query' | ||||
|         plugin_attrib = 'register' | ||||
|         interfaces = set(('username', 'password', 'email', 'nick', 'name', | ||||
|                           'first', 'last', 'address', 'city', 'state', 'zip', | ||||
|                           'phone', 'url', 'date', 'misc', 'text', 'key', | ||||
|                           'registered', 'remove', 'instructions')) | ||||
|         interfaces = {'username', 'password', 'email', 'nick', 'name', | ||||
|                       'first', 'last', 'address', 'city', 'state', 'zip', | ||||
|                       'phone', 'url', 'date', 'misc', 'text', 'key', | ||||
|                       'registered', 'remove', 'instructions'} | ||||
|         sub_interfaces = interfaces | ||||
|  | ||||
|         def getRegistered(self): | ||||
| @@ -634,8 +634,9 @@ with some additional registration fields implemented. | ||||
|                 if self.backend.register(iq['from'].bare, iq['register']): | ||||
|                     # Successful registration | ||||
|                     self.xmpp.event('registered_user', iq) | ||||
|                     iq.reply().set_payload(iq['register'].xml) | ||||
|                     iq.send() | ||||
|                     reply = iq.reply() | ||||
|                     reply.set_payload(iq['register'].xml) | ||||
|                     reply.send() | ||||
|                 else: | ||||
|                     # Conflicting registration | ||||
|                     self._sendError(iq, '409', 'cancel', 'conflict', | ||||
| @@ -666,14 +667,16 @@ with some additional registration fields implemented. | ||||
|                     # Add a blank field | ||||
|                     reg.addField(field) | ||||
|  | ||||
|             iq.reply().set_payload(reg.xml) | ||||
|             iq.send() | ||||
|             reply = iq.reply() | ||||
|             reply.set_payload(reg.xml) | ||||
|             reply.send() | ||||
|  | ||||
|         def _sendError(self, iq, code, error_type, name, text=''): | ||||
|             iq.reply().set_payload(iq['register'].xml) | ||||
|             iq.error() | ||||
|             iq['error']['code'] = code | ||||
|             iq['error']['type'] = error_type | ||||
|             iq['error']['condition'] = name | ||||
|             iq['error']['text'] = text | ||||
|             iq.send() | ||||
|             reply = iq.reply() | ||||
|             reply.set_payload(iq['register'].xml) | ||||
|             reply.error() | ||||
|             reply['error']['code'] = code | ||||
|             reply['error']['type'] = error_type | ||||
|             reply['error']['condition'] = name | ||||
|             reply['error']['text'] = text | ||||
|             reply.send() | ||||
|   | ||||
							
								
								
									
										48
									
								
								docs/differences.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								docs/differences.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| .. _differences: | ||||
|  | ||||
| Differences from SleekXMPP | ||||
| ========================== | ||||
|  | ||||
| **Python 3.7+ only** | ||||
|     slixmpp will work on python 3.7 and above. It may work with previous | ||||
|     versions but we provide no guarantees. | ||||
|  | ||||
| **Stanza copies** | ||||
|     The same stanza object is given through all the handlers; a handler that | ||||
|     edits the stanza object should make its own copy. | ||||
|  | ||||
| **Replies** | ||||
|     Because stanzas are not copied anymore, | ||||
|     :meth:`Stanza.reply() <.StanzaBase.reply>` calls | ||||
|     (for :class:`IQs <.Iq>`, :class:`Messages <.Message>`, etc) | ||||
|     now return a new object instead of editing the stanza object | ||||
|     in-place. | ||||
|  | ||||
| **Block and threaded arguments** | ||||
|     All the functions that had a ``threaded=`` or ``block=`` argument | ||||
|     do not have it anymore. Also, :meth:`.Iq.send` **does not block | ||||
|     anymore**. | ||||
|  | ||||
| **Coroutine facilities** | ||||
|     **See** :ref:`using_asyncio` | ||||
|  | ||||
|     If an event handler is a coroutine, it will be called asynchronously | ||||
|     in the event loop instead of inside the event caller. | ||||
|  | ||||
|     A CoroutineCallback class has been added to create coroutine stream | ||||
|     handlers, which will be also handled in the event loop. | ||||
|  | ||||
|     The :class:`~.slixmpp.stanza.Iq` object’s :meth:`~.slixmpp.stanza.Iq.send` | ||||
|     method now **always** return a :class:`~.asyncio.Future` which result will be set | ||||
|     to the IQ reply when it is received, or to ``None`` if the IQ is not of | ||||
|     type ``get`` or ``set``. | ||||
|  | ||||
|     Many plugins (WIP) calls which retrieve information also return the same | ||||
|     future. | ||||
|  | ||||
| **Architectural differences** | ||||
|     slixmpp does not have an event queue anymore, and instead processes | ||||
|     handlers directly after receiving the XML stanza. | ||||
|  | ||||
| .. note:: | ||||
|     If you find something that doesn’t work but should, please report it. | ||||
| @@ -152,6 +152,13 @@ Event Index | ||||
|         Makes the contents of message stanzas available whenever one is received. Be | ||||
|         sure to check the message type in order to handle error messages. | ||||
|  | ||||
|     message_error | ||||
|         - **Data:** :py:class:`~slixmpp.Message` | ||||
|         - **Source:** :py:class:`BaseXMPP <slixmpp.BaseXMPP>` | ||||
|  | ||||
|         Makes the contents of message stanzas available whenever one is received. | ||||
|         Only handler messages with an ``error`` type. | ||||
|  | ||||
|     message_form | ||||
|         - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` | ||||
|         - **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` | ||||
| @@ -252,8 +259,8 @@ Event Index | ||||
|  | ||||
|         Signal that a connection to the XMPP server has been lost and the current | ||||
|         stream session has ended. Currently equivalent to :term:`disconnected`, but | ||||
|         future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ | ||||
|         will distinguish the two events. | ||||
|         implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ | ||||
|         distinguish between the two events. | ||||
|  | ||||
|         Plugins that maintain session-based state should clear themselves when | ||||
|         this event is fired. | ||||
|   | ||||
| @@ -7,19 +7,11 @@ Create and Run a Server Component | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     join the chat room at `slixmpp@muc.poez.io | ||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||
|  | ||||
| If you have not yet installed Slixmpp, do so now by either checking out a version | ||||
| from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install slixmpp  # Or: easy_install slixmpp | ||||
|  | ||||
| with `Git <https://lab.louiz.org/poezio/slixmpp>`_. | ||||
|  | ||||
| Many XMPP applications eventually graduate to requiring to run as a server | ||||
| component in order to meet scalability requirements. To demonstrate how to | ||||
|   | ||||
| @@ -7,19 +7,11 @@ Slixmpp Quickstart - Echo Bot | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     join the chat room at `slixmpp@muc.poez.io | ||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||
|  | ||||
| If you have not yet installed Slixmpp, do so now by either checking out a version | ||||
| from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install slixmpp  # Or: easy_install slixmpp | ||||
|  | ||||
| with `Git <https://lab.louiz.org/poezio/slixmpp>`_. | ||||
|  | ||||
| As a basic starting project, we will create an echo bot which will reply to any | ||||
| messages sent to it. We will also go through adding some basic command line configuration | ||||
| @@ -44,6 +36,7 @@ To get started, here is a brief outline of the structure that the final project | ||||
|     # -*- coding: utf-8 -*- | ||||
|  | ||||
|     import sys | ||||
|     import asyncio | ||||
|     import logging | ||||
|     import getpass | ||||
|     from optparse import OptionParser | ||||
| @@ -59,24 +52,6 @@ To get started, here is a brief outline of the structure that the final project | ||||
|  | ||||
|         '''Finally, we connect the bot and start listening for messages''' | ||||
|  | ||||
| Default Encoding | ||||
| ---------------- | ||||
| XMPP requires support for UTF-8 and so Slixmpp must use UTF-8 as well. In | ||||
| Python3 this is simple because Unicode is the default string type. For Python2.6+ | ||||
| the situation is not as easy because standard strings are simply byte arrays and | ||||
| use ASCII. We can get Python to use UTF-8 as the default encoding by including: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     if sys.version_info < (3, 0): | ||||
|         from slixmpp.util.misc_ops import setdefaultencoding | ||||
|         setdefaultencoding('utf8') | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     Until we are able to ensure that Slixmpp will always use Unicode in Python2.6+, this | ||||
|     may cause issues embedding Slixmpp into other applications which assume ASCII encoding. | ||||
|  | ||||
| Creating the EchoBot Class | ||||
| -------------------------- | ||||
|  | ||||
| @@ -95,7 +70,7 @@ as well. | ||||
|     class EchoBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password): | ||||
|             super(EchoBot, self).__init__(jid, password) | ||||
|             super().__init__(jid, password) | ||||
|  | ||||
| Handling Session Start | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
| @@ -108,7 +83,7 @@ started. To do that, we will register an event handler for the :term:`session_st | ||||
| .. code-block:: python | ||||
|  | ||||
|      def __init__(self, jid, password): | ||||
|         super(EchoBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|  | ||||
| @@ -178,7 +153,7 @@ whenever a messsage is received. | ||||
| .. code-block:: python | ||||
|  | ||||
|      def __init__(self, jid, password): | ||||
|         super(EchoBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|         self.add_event_handler('message', self.message) | ||||
| @@ -313,9 +288,9 @@ the ``EchoBot.__init__`` method instead. | ||||
|         xmpp.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
| Now we're ready to connect and begin echoing messages. If you have the package | ||||
| ``dnspython`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method | ||||
| ``aiodns`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method | ||||
| will perform a DNS query to find the appropriate server to connect to for the | ||||
| given JID. If you do not have ``dnspython``, then Slixmpp will attempt to | ||||
| given JID. If you do not have ``aiodns``, then Slixmpp will attempt to | ||||
| connect to the hostname used by the JID, unless an address tuple is supplied | ||||
| to :meth:`slixmpp.clientxmpp.ClientXMPP`. | ||||
|  | ||||
| @@ -330,22 +305,6 @@ to :meth:`slixmpp.clientxmpp.ClientXMPP`. | ||||
|         else: | ||||
|             print('Unable to connect') | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     For Google Talk users withouth ``dnspython`` installed, the above code | ||||
|     should look like: | ||||
|  | ||||
|     .. code-block:: python | ||||
|  | ||||
|         if __name__ == '__main__': | ||||
|  | ||||
|             # .. option parsing & echo bot configuration | ||||
|  | ||||
|             if xmpp.connect(('talk.google.com', 5222)): | ||||
|                 xmpp.process(block=True) | ||||
|             else: | ||||
|                 print('Unable to connect') | ||||
|  | ||||
| To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process` | ||||
| which will start the event handling, send queue, and XML reader threads. It will also call | ||||
| the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By | ||||
| @@ -370,7 +329,7 @@ The Final Product | ||||
| ----------------- | ||||
|  | ||||
| Here then is what the final result should look like after working through the guide above. The code | ||||
| can also be found in the Slixmpp `examples directory <http://github.com/fritzy/Slixmpp/tree/master/examples>`_. | ||||
| can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_. | ||||
|  | ||||
| .. compound:: | ||||
|  | ||||
|   | ||||
| @@ -1,25 +1,17 @@ | ||||
| .. _mucbot: | ||||
|  | ||||
| ========================= | ||||
| Mulit-User Chat (MUC) Bot | ||||
| Multi-User Chat (MUC) Bot | ||||
| ========================= | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     join the chat room at `slixmpp@muc.poez.io | ||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||
|  | ||||
| If you have not yet installed Slixmpp, do so now by either checking out a version | ||||
| from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip`` | ||||
| or ``easy_install``. | ||||
|  | ||||
| .. code-block:: sh | ||||
|  | ||||
|     pip install slixmpp  # Or: easy_install slixmpp | ||||
|  | ||||
| from `Git <https://lab.louiz.org/poezio/slixmpp>`_. | ||||
|  | ||||
| Now that you've got the basic gist of using Slixmpp by following the | ||||
| echobot example (:ref:`echobot`), we can use one of the bundled plugins | ||||
| @@ -71,13 +63,13 @@ has been established: | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|         self.send_presence() | ||||
|         self.plugin['xep_0045'].joinMUC(self.room, | ||||
|                                         self.nick, | ||||
|                                         wait=True) | ||||
|         self.plugin['xep_0045'].join_muc(self.room, | ||||
|                                          self.nick, | ||||
|                                          wait=True) | ||||
|  | ||||
| Note that as in :ref:`echobot`, we need to include send an initial presence and request | ||||
| the roster. Next, we want to join the group chat, so we call the | ||||
| ``joinMUC`` method of the MUC plugin. | ||||
| ``join_muc`` method of the MUC plugin. | ||||
|  | ||||
| .. note:: | ||||
|  | ||||
|   | ||||
| @@ -7,10 +7,8 @@ Enable HTTP Proxy Support | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     join the chat room at `slixmpp@muc.poez.io | ||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||
|  | ||||
| In some instances, you may wish to route XMPP traffic through | ||||
| an HTTP proxy, probably to get around restrictive firewalls. | ||||
|   | ||||
| @@ -4,10 +4,8 @@ Sign in, Send a Message, and Disconnect | ||||
| .. note:: | ||||
|  | ||||
|     If you have any issues working through this quickstart guide | ||||
|     or the other tutorials here, please either send a message to the | ||||
|     `mailing list <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     or join the chat room at `sleek@conference.jabber.org | ||||
|     <xmpp:sleek@conference.jabber.org?join>`_. | ||||
|     join the chat room at `slixmpp@muc.poez.io | ||||
|     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||
|  | ||||
| A common use case for Slixmpp is to send one-off messages from | ||||
| time to time. For example, one use case could be sending out a notice when | ||||
| @@ -26,7 +24,7 @@ for the JID that will receive our message, and the string content of the message | ||||
|     class SendMsgBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|         def __init__(self, jid, password, recipient, msg): | ||||
|             super(SendMsgBot, self).__init__(jid, password) | ||||
|             super().__init__(jid, password) | ||||
|  | ||||
|             self.recipient = recipient | ||||
|             self.msg = msg | ||||
| @@ -49,11 +47,11 @@ the roster. Next, we want to send our message, and to do that we will use :meth: | ||||
|         self.send_message(mto=self.recipient, mbody=self.msg) | ||||
|  | ||||
| Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`. | ||||
| Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call | ||||
| :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible | ||||
| for the client to disconnect before the send queue is processed and the message is actually | ||||
| sent on the wire. To ensure that our message is processed, we use | ||||
| :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`. | ||||
| Now, sent stanzas are placed in a queue to pass them to the send thread. | ||||
| :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` by default will wait for an | ||||
| acknowledgement from the server for at least `2.0` seconds. This time is configurable with | ||||
| the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect | ||||
| <slixmpp.xmlstream.XMLStream.disconnect>` will not close the connection gracefully. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
| @@ -63,12 +61,12 @@ sent on the wire. To ensure that our message is processed, we use | ||||
|  | ||||
|         self.send_message(mto=self.recipient, mbody=self.msg) | ||||
|  | ||||
|         self.disconnect(wait=True) | ||||
|         self.disconnect() | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     If you happen to be adding stanzas to the send queue faster than the send thread | ||||
|     can process them, then :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>` | ||||
|     can process them, then :meth:`disconnect() <slixmpp.xmlstream.XMLStream.disconnect>` | ||||
|     will block and not disconnect. | ||||
|  | ||||
| Final Product | ||||
|   | ||||
| @@ -9,21 +9,20 @@ Glossary | ||||
|     stream handler | ||||
|         A callback function that accepts stanza objects pulled directly | ||||
|         from the XML stream. A stream handler is encapsulated in a | ||||
|         object that includes a :term:`Matcher` object, and which provides | ||||
|         additional semantics. For example, the ``Waiter`` handler wrapper | ||||
|         blocks thread execution until a matching stanza is received. | ||||
|         object that includes a :class:`Matcher <.MatcherBase>` object, and | ||||
|         which provides additional semantics. For example, the | ||||
|         :class:`.Waiter` handler wrapper blocks thread execution until a | ||||
|         matching stanza is received. | ||||
|  | ||||
|     event handler | ||||
|         A callback function that responds to events raised by | ||||
|         ``XMLStream.event``. An event handler may be marked as | ||||
|         threaded, allowing it to execute outside of the main processing | ||||
|         loop. | ||||
|         :meth:`.XMLStream.event`. | ||||
|  | ||||
|     stanza object | ||||
|         Informally may refer both to classes which extend ``ElementBase`` | ||||
|         or ``StanzaBase``, and to objects of such classes. | ||||
|         Informally may refer both to classes which extend :class:`.ElementBase` | ||||
|         or :class:`.StanzaBase`, and to objects of such classes. | ||||
|  | ||||
|         A stanza object is a wrapper for an XML object which exposes ``dict`` | ||||
|         A stanza object is a wrapper for an XML object which exposes :class:`dict` | ||||
|         like interfaces which may be assigned to, read from, or deleted. | ||||
|  | ||||
|     stanza plugin | ||||
|   | ||||
| @@ -61,7 +61,7 @@ operation using these stanzas without doing any complex operations such as | ||||
| checking an ACL, etc. | ||||
|  | ||||
| You may find it necessary at some point to revert a particular node or JID to | ||||
| using the default, static handlers. To do so, use the method ``make_static()``. | ||||
| using the default, static handlers. To do so, use the method ``restore_defaults()``. | ||||
| You may also elect to only convert a given set of actions instead. | ||||
|  | ||||
| Creating a Node Handler | ||||
| @@ -161,8 +161,8 @@ item itself, and the JID and node that will own the item. | ||||
|     In this case, the owning JID and node are provided with the | ||||
|     parameters ``ijid`` and ``node``. | ||||
|  | ||||
| Peforming Disco Queries | ||||
| ----------------------- | ||||
| Performing Disco Queries | ||||
| ------------------------ | ||||
| The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs | ||||
| and their nodes for disco information. Since these methods are wrappers for | ||||
| sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()`` | ||||
| @@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     info = self['xep_0030'].get_info(jid='foo@example.com', | ||||
|                                      node='bar', | ||||
|                                      ifrom='baz@mycomponent.example.com', | ||||
|                                      block=True, | ||||
|                                      timeout=30) | ||||
|     info = yield from self['xep_0030'].get_info(jid='foo@example.com', | ||||
|                                                 node='bar', | ||||
|                                                 ifrom='baz@mycomponent.example.com', | ||||
|                                                 timeout=30) | ||||
|  | ||||
|     items = self['xep_0030'].get_info(jid='foo@example.com', | ||||
|                                       node='bar', | ||||
|   | ||||
| @@ -3,38 +3,25 @@ Slixmpp | ||||
|  | ||||
| .. sidebar:: Get the Code | ||||
|  | ||||
|     .. code-block:: sh | ||||
|     The latest source code for Slixmpp may be found on the `Git repo | ||||
|     <https://lab.louiz.org/poezio/slixmpp>`_. :: | ||||
|  | ||||
|         pip install slixmpp | ||||
|         git clone https://lab.louiz.org/poezio/slixmpp | ||||
|  | ||||
|     The latest source code for Slixmpp may be found on `Github | ||||
|     <http://github.com/fritzy/Slixmpp>`_. Releases can be found in the | ||||
|     ``master`` branch, while the latest development version is in the | ||||
|     ``develop`` branch. | ||||
|  | ||||
|     **Latest Stable Release** | ||||
|         - `1.0 <http://github.com/fritzy/Slixmpp/zipball/1.0>`_ | ||||
|  | ||||
|     **Develop Releases** | ||||
|         - `Latest Develop Version <http://github.com/fritzy/Slixmpp/zipball/develop>`_ | ||||
|  | ||||
|  | ||||
|     A mailing list and XMPP chat room are available for discussing and getting | ||||
|     help with Slixmpp. | ||||
|  | ||||
|     **Mailing List** | ||||
|         `Slixmpp Discussion on Google Groups <http://groups.google.com/group/slixmpp-discussion>`_ | ||||
|     An XMPP chat room is available for discussing and getting help with slixmpp. | ||||
|  | ||||
|     **Chat** | ||||
|         `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_ | ||||
|         `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_ | ||||
|  | ||||
|     **Reporting bugs** | ||||
|         You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues. | ||||
|  | ||||
| Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+, | ||||
| and is featured in examples in | ||||
| `XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ | ||||
| by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived | ||||
| here from reading the Definitive Guide, please see the notes on updating | ||||
| the examples to the latest version of Slixmpp. | ||||
| .. note:: | ||||
|     slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_ | ||||
|     which goal is to use asyncio instead of threads to handle networking. See | ||||
|     :ref:`differences`. | ||||
|  | ||||
| Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+, | ||||
|  | ||||
| Slixmpp's design goals and philosphy are: | ||||
|  | ||||
| @@ -59,15 +46,16 @@ Slixmpp's design goals and philosphy are: | ||||
|     sensible defaults and appropriate abstractions. XML can be ugly to work | ||||
|     with, but it doesn't have to be that way. | ||||
|  | ||||
|  | ||||
| Here's your first Slixmpp Bot: | ||||
| -------------------------------- | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import asyncio | ||||
|     import logging | ||||
|  | ||||
|     from slixmpp import ClientXMPP | ||||
|     from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
|     class EchoBot(ClientXMPP): | ||||
| @@ -85,27 +73,13 @@ Here's your first Slixmpp Bot: | ||||
|             # Here's how to access plugins once you've registered them: | ||||
|             # self['xep_0030'].add_feature('echo_demo') | ||||
|  | ||||
|             # If you are working with an OpenFire server, you will | ||||
|             # need to use a different SSL version: | ||||
|             # import ssl | ||||
|             # self.ssl_version = ssl.PROTOCOL_SSLv3 | ||||
|  | ||||
|         def session_start(self, event): | ||||
|             self.send_presence() | ||||
|             self.get_roster() | ||||
|  | ||||
|             # Most get_*/set_* methods from plugins use Iq stanzas, which | ||||
|             # can generate IqError and IqTimeout exceptions | ||||
|             # | ||||
|             # try: | ||||
|             #     self.get_roster() | ||||
|             # except IqError as err: | ||||
|             #     logging.error('There was an error getting the roster') | ||||
|             #     logging.error(err.iq['error']['condition']) | ||||
|             #     self.disconnect() | ||||
|             # except IqTimeout: | ||||
|             #     logging.error('Server is taking too long to respond') | ||||
|             #     self.disconnect() | ||||
|             # are sent asynchronously. You can almost always provide a | ||||
|             # callback that will be executed when the reply is received. | ||||
|  | ||||
|         def message(self, msg): | ||||
|             if msg['type'] in ('chat', 'normal'): | ||||
| @@ -121,9 +95,18 @@ Here's your first Slixmpp Bot: | ||||
|  | ||||
|         xmpp = EchoBot('somejid@example.com', 'use_getpass') | ||||
|         xmpp.connect() | ||||
|         xmpp.process(block=True) | ||||
|         xmpp.process() | ||||
|  | ||||
|  | ||||
| To read if you come from SleekXMPP | ||||
| ---------------------------------- | ||||
|  | ||||
| .. toctree:: | ||||
|     :maxdepth: 1 | ||||
|  | ||||
|     differences | ||||
|     using_asyncio | ||||
|  | ||||
|  | ||||
| Getting Started (with Examples) | ||||
| ------------------------------- | ||||
| @@ -145,7 +128,6 @@ Tutorials, FAQs, and How To Guides | ||||
| .. toctree:: | ||||
|     :maxdepth: 1 | ||||
|  | ||||
|     faq | ||||
|     xeps | ||||
|     xmpp_tdg | ||||
|     howto/stanzas | ||||
| @@ -184,9 +166,7 @@ API Reference | ||||
|     api/xmlstream/handler | ||||
|     api/xmlstream/matcher | ||||
|     api/xmlstream/xmlstream | ||||
|     api/xmlstream/scheduler | ||||
|     api/xmlstream/tostring | ||||
|     api/xmlstream/filesocket | ||||
|  | ||||
| Core Stanzas | ||||
| ~~~~~~~~~~~~ | ||||
| @@ -197,8 +177,6 @@ Core Stanzas | ||||
|     api/stanza/message | ||||
|     api/stanza/presence | ||||
|     api/stanza/iq | ||||
|     api/stanza/error | ||||
|     api/stanza/stream_error | ||||
|  | ||||
| Plugins | ||||
| ~~~~~~~ | ||||
| @@ -220,8 +198,14 @@ Additional Info | ||||
| * :ref:`modindex` | ||||
| * :ref:`search` | ||||
|  | ||||
| Credits | ||||
| ------- | ||||
| SleekXMPP Credits | ||||
| ----------------- | ||||
|  | ||||
| .. note:: | ||||
|     Those people made SleekXMPP, so you should not bother them if | ||||
|     you have an issue with slixmpp. But it’s still fair to credit | ||||
|     them for their work. | ||||
|  | ||||
|  | ||||
| **Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_ | ||||
|      `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, | ||||
|   | ||||
							
								
								
									
										148
									
								
								docs/using_asyncio.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								docs/using_asyncio.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| .. _using_asyncio: | ||||
|  | ||||
| ============= | ||||
| Using asyncio | ||||
| ============= | ||||
|  | ||||
| Block on IQ sending | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| :meth:`.Iq.send` now returns a :class:`~.Future` so you can easily block with: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     result = yield from iq.send() | ||||
|  | ||||
| .. warning:: | ||||
|  | ||||
|     If the reply is an IQ with an ``error`` type, this will raise an | ||||
|     :class:`.IqError`, and if it timeouts, it will raise an | ||||
|     :class:`.IqTimeout`. Don't forget to catch it. | ||||
|  | ||||
| You can still use callbacks instead. | ||||
|  | ||||
| XEP plugin integration | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| The same changes from the SleekXMPP API apply, so you can do: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     iq_info = yield from self.xmpp['xep_0030'].get_info(jid) | ||||
|  | ||||
| But the following will only return a Future: | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     iq_info = self.xmpp['xep_0030'].get_info(jid) | ||||
|  | ||||
|  | ||||
| Callbacks, Event Handlers, and Stream Handlers | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| IQ callbacks and :term:`Event Handlers <event handler>` can be coroutine | ||||
| functions; in this case, they will be scheduled in the event loop using | ||||
| :meth:`.asyncio.async` and not ran immediately. | ||||
|  | ||||
| A :class:`.CoroutineCallback` class has been added as well for | ||||
| :term:`Stream Handlers <stream handler>`, which will use | ||||
| :meth:`.asyncio.async` to schedule the callback. | ||||
|  | ||||
| Running the event loop | ||||
| ~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| :meth:`.XMLStream.process` is only a thin wrapper on top of | ||||
| ``loop.run_forever()`` (if ``timeout`` is provided then it will | ||||
| only run for this amount of time, and if ``forever`` is False it will | ||||
| run until disconnection). | ||||
|  | ||||
| Therefore you can handle the event loop in any way you like | ||||
| instead of using ``process()``. | ||||
|  | ||||
|  | ||||
| Examples | ||||
| ~~~~~~~~ | ||||
|  | ||||
| Blocking until the session is established | ||||
| ----------------------------------------- | ||||
|  | ||||
| This code blocks until the XMPP session is fully established, which | ||||
| can be useful to make sure external events aren’t triggering XMPP | ||||
| callbacks while everything is not ready. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import asyncio, slixmpp | ||||
|  | ||||
|     client = slixmpp.ClientXMPP('jid@example', 'password') | ||||
|     client.connected_event = asyncio.Event() | ||||
|     callback = lambda _: client.connected_event.set() | ||||
|     client.add_event_handler('session_start', callback) | ||||
|     client.connect() | ||||
|     loop.run_until_complete(event.wait()) | ||||
|     # do some other stuff before running the event loop, e.g. | ||||
|     # loop.run_until_complete(httpserver.init()) | ||||
|     client.process() | ||||
|  | ||||
|  | ||||
| Use with other asyncio-based libraries | ||||
| -------------------------------------- | ||||
|  | ||||
| This code interfaces with aiohttp to retrieve two pages asynchronously | ||||
| when the session is established, and then send the HTML content inside | ||||
| a simple <message>. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import asyncio, aiohttp, slixmpp | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def get_pythonorg(event): | ||||
|         req = yield from aiohttp.request('get', 'http://www.python.org') | ||||
|         text = yield from req.text | ||||
|         client.send_message(mto='jid2@example', mbody=text) | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def get_asyncioorg(event): | ||||
|         req = yield from aiohttp.request('get', 'http://www.asyncio.org') | ||||
|         text = yield from req.text | ||||
|         client.send_message(mto='jid3@example', mbody=text) | ||||
|  | ||||
|     client = slixmpp.ClientXMPP('jid@example', 'password') | ||||
|     client.add_event_handler('session_start', get_pythonorg) | ||||
|     client.add_event_handler('session_start', get_asyncioorg) | ||||
|     client.connect() | ||||
|     client.process() | ||||
|  | ||||
|  | ||||
| Blocking Iq | ||||
| ----------- | ||||
|  | ||||
| This client checks (via XEP-0092) the software used by every entity it | ||||
| receives a message from. After this, it sends a message to a specific | ||||
| JID indicating its findings. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     import asyncio, slixmpp | ||||
|  | ||||
|     class ExampleClient(slixmpp.ClientXMPP): | ||||
|         def __init__(self, *args, **kwargs): | ||||
|             slixmpp.ClientXMPP.__init__(self, *args, **kwargs) | ||||
|             self.register_plugin('xep_0092') | ||||
|             self.add_event_handler('message', self.on_message) | ||||
|  | ||||
|         @asyncio.coroutine | ||||
|         def on_message(self, event): | ||||
|             # You should probably handle IqError and IqTimeout exceptions here | ||||
|             # but this is an example. | ||||
|             version = yield from self['xep_0092'].get_version(message['from']) | ||||
|             text = "%s sent me a message, he runs %s" % (message['from'], | ||||
|                                                          version['software_version']['name']) | ||||
|             self.send_message(mto='master@example.tld', mbody=text) | ||||
|  | ||||
|     client = ExampleClient('jid@example', 'password') | ||||
|     client.connect() | ||||
|     client.process() | ||||
|  | ||||
|  | ||||
| @@ -11,20 +11,12 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| # This can be used when you are in a test environment and need to make paths right | ||||
| sys.path=['/Users/jocke/Dropbox/06_dev/Slixmpp']+sys.path | ||||
|  | ||||
| import logging | ||||
| import unittest | ||||
| import distutils.core | ||||
| import datetime | ||||
|  | ||||
| from glob import glob | ||||
| from os.path import splitext, basename, join as pjoin | ||||
| from os.path import basename, join as pjoin | ||||
| from argparse import ArgumentParser | ||||
| from urllib import urlopen | ||||
| from getpass import getpass | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.plugins.xep_0323.device import Device | ||||
| @@ -168,9 +160,9 @@ if __name__ == '__main__': | ||||
|  | ||||
|         myDevice = TheDevice(args.nodeid); | ||||
|         # myDevice._add_field(name="Relay", typename="numeric", unit="Bool"); | ||||
|         myDevice._add_field(name="Temperature", typename="numeric", unit="C"); | ||||
|         myDevice._add_field(name="Temperature", typename="numeric", unit="C") | ||||
|         myDevice._set_momentary_timestamp("2013-03-07T16:24:30") | ||||
|         myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}); | ||||
|         myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"}) | ||||
|  | ||||
|         xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10); | ||||
|         xmpp.beClientOrServer(server=True) | ||||
| @@ -186,5 +178,5 @@ if __name__ == '__main__': | ||||
|         logging.debug("ready ending") | ||||
|  | ||||
|     else: | ||||
|        print "noopp didn't happen" | ||||
|        print("noopp didn't happen") | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class CommandBot(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -47,7 +47,7 @@ class CommandBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         # We add the command after session_start has fired | ||||
|         # to ensure that the correct full JID is used. | ||||
| @@ -68,7 +68,7 @@ class CommandBot(slixmpp.ClientXMPP): | ||||
|                        session. Additional, custom data may be saved | ||||
|                        here to persist across handler callbacks. | ||||
|         """ | ||||
|         form = self['xep_0004'].makeForm('form', 'Greeting') | ||||
|         form = self['xep_0004'].make_form('form', 'Greeting') | ||||
|         form['instructions'] = 'Send a custom greeting to a JID' | ||||
|         form.addField(var='greeting', | ||||
|                       ftype='text-single', | ||||
|   | ||||
| @@ -37,7 +37,7 @@ class CommandUserBot(slixmpp.ClientXMPP): | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -51,7 +51,7 @@ class CommandUserBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         # We first create a session dictionary containing: | ||||
|         #   'next'  -- the handler to execute on a successful response | ||||
| @@ -94,7 +94,7 @@ class CommandUserBot(slixmpp.ClientXMPP): | ||||
|         #          label="Your greeting" /> | ||||
|         # </x> | ||||
|  | ||||
|         form = self['xep_0004'].makeForm(ftype='submit') | ||||
|         form = self['xep_0004'].make_form(ftype='submit') | ||||
|         form.addField(var='greeting', | ||||
|                       value=session['greeting']) | ||||
|  | ||||
| @@ -176,4 +176,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class AdminCommands(slixmpp.ClientXMPP): | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -44,7 +44,7 @@ class AdminCommands(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         def command_success(iq, session): | ||||
|             print('Command completed') | ||||
|   | ||||
							
								
								
									
										99
									
								
								examples/confirm_answer.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										99
									
								
								examples/confirm_answer.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2015 Emmanuel Gil Peyrot | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class AnswerConfirm(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic client demonstrating how to confirm or deny an HTTP request. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, trusted): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.add_event_handler("http_confirm", self.confirm) | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, *args): | ||||
|         self.make_presence().send() | ||||
|  | ||||
|     def prompt(self, stanza): | ||||
|         confirm = stanza['confirm'] | ||||
|         print('Received confirm request %s from %s to access %s using ' | ||||
|                  'method %s' % ( | ||||
|                      confirm['id'], stanza['from'], confirm['url'], | ||||
|                      confirm['method']) | ||||
|                 ) | ||||
|         result = input("Do you accept (y/N)? ") | ||||
|         return 'y' == result.lower() | ||||
|  | ||||
|     def confirm(self, stanza): | ||||
|         if self.prompt(stanza): | ||||
|             reply = stanza.reply() | ||||
|         else: | ||||
|             reply = stanza.reply() | ||||
|             reply.enable('error') | ||||
|             reply['error']['type'] = 'auth' | ||||
|             reply['error']['code'] = '401' | ||||
|             reply['error']['condition'] = 'not-authorized' | ||||
|         reply.append(stanza['confirm']) | ||||
|         reply.send() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     parser = ArgumentParser() | ||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.ERROR, | ||||
|                         default=logging.INFO) | ||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.DEBUG, | ||||
|                         default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|  | ||||
|     # Other options. | ||||
|     parser.add_argument("-t", "--trusted", nargs='*', | ||||
|                         help="List of trusted JIDs") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|  | ||||
|     xmpp = AnswerConfirm(args.jid, args.password, args.trusted) | ||||
|     xmpp.register_plugin('xep_0070') | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
							
								
								
									
										124
									
								
								examples/confirm_ask.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										124
									
								
								examples/confirm_ask.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2015 Emmanuel Gil Peyrot | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import sys | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError, IqError | ||||
| from slixmpp import asyncio | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class AskConfirm(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic client asking an entity if they confirm the access to an HTTP URL. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, recipient, id, url, method): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.recipient = recipient | ||||
|         self.id = id | ||||
|         self.url = url | ||||
|         self.method = method | ||||
|  | ||||
|         # Will be used to set the proper exit code. | ||||
|         self.confirmed = asyncio.Future() | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|         self.add_event_handler("message", self.start) | ||||
|         self.add_event_handler("http_confirm_message", self.confirm) | ||||
|  | ||||
|     def confirm(self, message): | ||||
|         print(message) | ||||
|         if message['confirm']['id'] == self.id: | ||||
|             if message['type'] == 'error': | ||||
|                 self.confirmed.set_result(False) | ||||
|             else: | ||||
|                 self.confirmed.set_result(True) | ||||
|  | ||||
|     async def start(self, event): | ||||
|         log.info('Sending confirm request %s to %s who wants to access %s using ' | ||||
|                  'method %s...' % (self.id, self.recipient, self.url, self.method)) | ||||
|         try: | ||||
|             confirmed = await self['xep_0070'].ask_confirm(self.recipient, | ||||
|                                                                 id=self.id, | ||||
|                                                                 url=self.url, | ||||
|                                                                 method=self.method, | ||||
|                                                                 message='Plz say yes or no for {method} {url} ({id}).') | ||||
|             if isinstance(confirmed, slixmpp.Message): | ||||
|                 confirmed = await self.confirmed | ||||
|             else: | ||||
|                 confirmed = True | ||||
|         except IqError: | ||||
|             confirmed = False | ||||
|         if confirmed: | ||||
|             print('Confirmed') | ||||
|         else: | ||||
|             print('Denied') | ||||
|         self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     parser = ArgumentParser() | ||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.ERROR, | ||||
|                         default=logging.INFO) | ||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.DEBUG, | ||||
|                         default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|  | ||||
|     # Other options. | ||||
|     parser.add_argument("-r", "--recipient", required=True, | ||||
|                         help="Recipient JID") | ||||
|     parser.add_argument("-i", "--id", required=True, | ||||
|                         help="id TODO") | ||||
|     parser.add_argument("-u", "--url", required=True, | ||||
|                         help="URL the user tried to access") | ||||
|     parser.add_argument("-m", "--method", required=True, | ||||
|                         help="HTTP method used") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|  | ||||
|     xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id, | ||||
|                       args.url, args.method) | ||||
|     xmpp.register_plugin('xep_0070') | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
|     sys.exit(0 if xmpp.confirmed else 1) | ||||
| @@ -50,7 +50,7 @@ class ActionBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|         register_stanza_plugin(Iq, Action) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -73,7 +73,7 @@ class ActionBot(slixmpp.ClientXMPP): | ||||
|         """ | ||||
|         self.event('custom_action', iq) | ||||
|  | ||||
|     def _handle_action_event(self, iq): | ||||
|     async def _handle_action_event(self, iq): | ||||
|         """ | ||||
|         Respond to the custom action event. | ||||
|         """ | ||||
| @@ -82,17 +82,20 @@ class ActionBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|         if method == 'is_prime' and param == '2': | ||||
|             print("got message: %s" % iq) | ||||
|             iq.reply() | ||||
|             iq['action']['status'] = 'done' | ||||
|             iq.send() | ||||
|             rep = iq.reply() | ||||
|             rep['action']['status'] = 'done' | ||||
|             await rep.send() | ||||
|         elif method == 'bye': | ||||
|             print("got message: %s" % iq) | ||||
|             rep = iq.reply() | ||||
|             rep['action']['status'] = 'done' | ||||
|             await rep.send() | ||||
|             self.disconnect() | ||||
|         else: | ||||
|             print("got message: %s" % iq) | ||||
|             iq.reply() | ||||
|             iq['action']['status'] = 'error' | ||||
|             iq.send() | ||||
|             rep = iq.reply() | ||||
|             rep['action']['status'] = 'error' | ||||
|             await rep.send() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|   | ||||
| @@ -43,7 +43,7 @@ class ActionUserBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|         register_stanza_plugin(Iq, Action) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -57,11 +57,11 @@ class ActionUserBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         self.send_custom_iq() | ||||
|         await self.send_custom_iq() | ||||
|  | ||||
|     def send_custom_iq(self): | ||||
|     async def send_custom_iq(self): | ||||
|         """Create and send two custom actions. | ||||
|  | ||||
|         If the first action was successful, then send | ||||
| @@ -74,14 +74,14 @@ class ActionUserBot(slixmpp.ClientXMPP): | ||||
|         iq['action']['param'] = '2' | ||||
|  | ||||
|         try: | ||||
|             resp = iq.send() | ||||
|             resp = await 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) | ||||
|                 await iq2.send() | ||||
|  | ||||
|                 self.disconnect() | ||||
|         except XMPPError: | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class Action(ElementBase): | ||||
|     #:     del action['status'] | ||||
|     #: | ||||
|     #: to set, get, or remove its values. | ||||
|     interfaces = set(('method', 'param', 'status')) | ||||
|     interfaces = {'method', 'param', 'status'} | ||||
|  | ||||
|     #: By default, values in the `interfaces` set are mapped to | ||||
|     #: attribute values. This can be changed such that an interface | ||||
|   | ||||
| @@ -53,7 +53,7 @@ class Disco(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -69,27 +69,21 @@ class Disco(slixmpp.ClientXMPP): | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.get_roster() | ||||
|         await 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 | ||||
|                 # received. Non-blocking options would be to listen | ||||
|                 # for the disco_info event, or passing a handler | ||||
|                 # function using the callback parameter. | ||||
|                 info = self['xep_0030'].get_info(jid=self.target_jid, | ||||
|                                                  node=self.target_node, | ||||
|                                                  block=True) | ||||
|             elif self.get in self.items_types: | ||||
|                 info = await self['xep_0030'].get_info(jid=self.target_jid, | ||||
|                                                             node=self.target_node) | ||||
|             if self.get in self.items_types: | ||||
|                 # The same applies from above. Listen for the | ||||
|                 # disco_items event or pass a callback function | ||||
|                 # if you need to process a non-blocking request. | ||||
|                 items = self['xep_0030'].get_items(jid=self.target_jid, | ||||
|                                                    node=self.target_node, | ||||
|                                                    block=True) | ||||
|             else: | ||||
|                 items = await self['xep_0030'].get_items(jid=self.target_jid, | ||||
|                                                               node=self.target_node) | ||||
|             if self.get not in self.info_types and self.get not in self.items_types: | ||||
|                 logging.error("Invalid disco request type.") | ||||
|                 return | ||||
|         except IqError as e: | ||||
| @@ -143,7 +137,7 @@ if __name__ == '__main__': | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|     parser.add_argument("query", choices=["all", "info", "items", "identities", "features"]) | ||||
|     parser.add_argument("target-jid") | ||||
|     parser.add_argument("target_jid") | ||||
|     parser.add_argument("node", nargs='?') | ||||
|  | ||||
|     args = parser.parse_args() | ||||
| @@ -162,4 +156,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -11,11 +11,11 @@ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| import threading | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError | ||||
| from slixmpp import asyncio | ||||
|  | ||||
|  | ||||
| FILE_TYPES = { | ||||
| @@ -40,9 +40,14 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|         self.add_event_handler('avatar_metadata_publish', self.on_avatar) | ||||
|  | ||||
|         self.received = set() | ||||
|         self.presences_received = threading.Event() | ||||
|         self.presences_received = asyncio.Event() | ||||
|         self.roster_received = asyncio.Event() | ||||
|  | ||||
|     def start(self, event): | ||||
|     def roster_received_cb(self, event): | ||||
|         self.roster_received.set() | ||||
|         self.presences_received.clear() | ||||
|  | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -56,16 +61,19 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         self.get_roster(callback=self.roster_received_cb) | ||||
|  | ||||
|         print('Waiting for presence updates...\n') | ||||
|         self.presences_received.wait(15) | ||||
|         await self.roster_received.wait() | ||||
|         print('Roster received') | ||||
|         await self.presences_received.wait() | ||||
|         self.disconnect() | ||||
|  | ||||
|     def on_vcard_avatar(self, pres): | ||||
|     async def on_vcard_avatar(self, pres): | ||||
|         print("Received vCard avatar update from %s" % pres['from'].bare) | ||||
|         try: | ||||
|             result = self['xep_0054'].get_vcard(pres['from'], cached=True) | ||||
|             result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True, | ||||
|                                                            timeout=5) | ||||
|         except XMPPError: | ||||
|             print("Error retrieving avatar for %s" % pres['from']) | ||||
|             return | ||||
| @@ -76,16 +84,17 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|                 pres['from'].bare, | ||||
|                 pres['vcard_temp_update']['photo'], | ||||
|                 filetype) | ||||
|         with open(filename, 'w+') as img: | ||||
|         with open(filename, 'wb+') as img: | ||||
|             img.write(avatar['BINVAL']) | ||||
|  | ||||
|     def on_avatar(self, msg): | ||||
|     async def on_avatar(self, msg): | ||||
|         print("Received avatar update from %s" % msg['from']) | ||||
|         metadata = msg['pubsub_event']['items']['item']['avatar_metadata'] | ||||
|         for info in metadata['items']: | ||||
|             if not info['url']: | ||||
|                 try: | ||||
|                     result = self['xep_0084'].retrieve_avatar(msg['from'], info['id']) | ||||
|                     result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'], | ||||
|                                                                          timeout=5) | ||||
|                 except XMPPError: | ||||
|                     print("Error retrieving avatar for %s" % msg['from']) | ||||
|                     return | ||||
| @@ -94,7 +103,7 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|  | ||||
|                 filetype = FILE_TYPES.get(metadata['type'], 'png') | ||||
|                 filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype) | ||||
|                 with open(filename, 'w+') as img: | ||||
|                 with open(filename, 'wb+') as img: | ||||
|                     img.write(avatar['value']) | ||||
|             else: | ||||
|                 # We could retrieve the avatar via HTTP, etc here instead. | ||||
| @@ -105,6 +114,7 @@ class AvatarDownloader(slixmpp.ClientXMPP): | ||||
|         Wait to receive updates from all roster contacts. | ||||
|         """ | ||||
|         self.received.add(pres['from'].bare) | ||||
|         print((len(self.received), len(self.client_roster.keys()))) | ||||
|         if len(self.received) >= len(self.client_roster.keys()): | ||||
|             self.presences_received.set() | ||||
|         else: | ||||
| @@ -149,4 +159,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -55,10 +55,10 @@ class GTalkBot(slixmpp.ClientXMPP): | ||||
|             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) | ||||
|             logging.error(err.message) | ||||
|             self.disconnect() | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -72,7 +72,7 @@ class GTalkBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
							
								
								
									
										97
									
								
								examples/http_over_xmpp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								examples/http_over_xmpp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Implementation of HTTP over XMPP transport | ||||
|     http://xmpp.org/extensions/xep-0332.html | ||||
|     Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com | ||||
|     This file is part of slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp import ClientXMPP | ||||
|  | ||||
| from argparse import ArgumentParser | ||||
| import logging | ||||
| import getpass | ||||
|  | ||||
|  | ||||
| class HTTPOverXMPPClient(ClientXMPP): | ||||
|     def __init__(self, jid, password): | ||||
|         ClientXMPP.__init__(self, jid, password) | ||||
|         self.register_plugin('xep_0332')    # HTTP over XMPP Transport | ||||
|         self.add_event_handler( | ||||
|             'session_start', self.session_start | ||||
|         ) | ||||
|         self.add_event_handler('http_request', self.http_request_received) | ||||
|         self.add_event_handler('http_response', self.http_response_received) | ||||
|  | ||||
|     def http_request_received(self, iq): | ||||
|         pass | ||||
|  | ||||
|     def http_response_received(self, iq): | ||||
|         print('HTTP Response Received : %s' % iq) | ||||
|         print('From    : %s' %  iq['from']) | ||||
|         print('To      : %s' % iq['to']) | ||||
|         print('Type    : %s' % iq['type']) | ||||
|         print('Headers : %s' % iq['resp']['headers']) | ||||
|         print('Code    : %s' % iq['resp']['code']) | ||||
|         print('Message : %s' % iq['resp']['message']) | ||||
|         print('Data    : %s' % iq['resp']['data']) | ||||
|  | ||||
|     def session_start(self, event): | ||||
|         # TODO: Fill in the blanks | ||||
|         self['xep_0332'].send_request( | ||||
|             to='?', method='?', resource='?', headers={} | ||||
|         ) | ||||
|         self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|  | ||||
|     # | ||||
|     # NOTE: To run this example, fill up the blanks in session_start() and | ||||
|     #       use the following command. | ||||
|     # | ||||
|     # ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v] | ||||
|     # | ||||
|  | ||||
|     parser = ArgumentParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     parser.add_argument( | ||||
|         '-v', '--verbose', help='set logging to DEBUG', action='store_const', | ||||
|         dest='loglevel', const=logging.DEBUG, default=logging.ERROR | ||||
|     ) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument('-J', '--jid', dest='jid', help='JID') | ||||
|     parser.add_argument('-P', '--password', dest='password', help='Password') | ||||
|  | ||||
|     # XMPP server ip and port options. | ||||
|     parser.add_argument( | ||||
|         '-i', '--ipaddr', dest='ipaddr', | ||||
|         help='IP Address of the XMPP server', default=None | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '-p', '--port', dest='port', | ||||
|         help='Port of the XMPP server', default=None | ||||
|     ) | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input('Username: ') | ||||
|     if args.password is None: | ||||
|         args.password = getpass.getpass('Password: ') | ||||
|  | ||||
|     xmpp = HTTPOverXMPPClient(args.jid, args.password) | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|  | ||||
							
								
								
									
										96
									
								
								examples/http_upload.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										96
									
								
								examples/http_upload.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2018 Emmanuel Gil Peyrot | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class HttpUpload(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic client asking an entity if they confirm the access to an HTTP URL. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, recipient, filename, domain=None): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.recipient = recipient | ||||
|         self.filename = filename | ||||
|         self.domain = domain | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     async def start(self, event): | ||||
|         log.info('Uploading file %s...', self.filename) | ||||
|         def timeout_callback(arg): | ||||
|             raise TimeoutError("could not send message in time") | ||||
|         url = await self['xep_0363'].upload_file( | ||||
|             self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback) | ||||
|         log.info('Upload success!') | ||||
|  | ||||
|         log.info('Sending file to %s', self.recipient) | ||||
|         html = '<body xmlns="http://www.w3.org/1999/xhtml"><a href="%s">%s</a></body>' % (url, url) | ||||
|         self.send_message(self.recipient, url, mhtml=html) | ||||
|         self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     parser = ArgumentParser() | ||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.ERROR, | ||||
|                         default=logging.INFO) | ||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.DEBUG, | ||||
|                         default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|  | ||||
|     # Other options. | ||||
|     parser.add_argument("-r", "--recipient", required=True, | ||||
|                         help="Recipient JID") | ||||
|     parser.add_argument("-f", "--file", required=True, | ||||
|                         help="File to send") | ||||
|     parser.add_argument("--domain", | ||||
|                         help="Domain to use for HTTP File Upload (leave out for your own server’s)") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|  | ||||
|     xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain) | ||||
|     xmpp.register_plugin('xep_0071') | ||||
|     xmpp.register_plugin('xep_0128') | ||||
|     xmpp.register_plugin('xep_0363') | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
| @@ -22,13 +22,10 @@ class IBBReceiver(slixmpp.ClientXMPP): | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|     def __init__(self, jid, password, filename): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') # Service Discovery | ||||
|         self.register_plugin('xep_0047', { | ||||
|             'auto_accept': True | ||||
|         }) # In-band Bytestreams | ||||
|         self.file = open(filename, 'wb') | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
| @@ -39,6 +36,7 @@ class IBBReceiver(slixmpp.ClientXMPP): | ||||
|  | ||||
|         self.add_event_handler("ibb_stream_start", self.stream_opened) | ||||
|         self.add_event_handler("ibb_stream_data", self.stream_data) | ||||
|         self.add_event_handler("ibb_stream_end", self.stream_closed) | ||||
|  | ||||
|     def start(self, event): | ||||
|         """ | ||||
| @@ -56,29 +54,16 @@ class IBBReceiver(slixmpp.ClientXMPP): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|     def accept_stream(self, iq): | ||||
|         """ | ||||
|         Check that it is ok to accept a stream request. | ||||
|  | ||||
|         Controlling stream acceptance can be done via either: | ||||
|             - setting 'auto_accept' to False in the plugin | ||||
|               configuration. The default is True. | ||||
|             - setting 'accept_stream' to a function which accepts | ||||
|               an Iq stanza as its argument, like this one. | ||||
|  | ||||
|         The accept_stream function will be used if it exists, and the | ||||
|         auto_accept value will be used otherwise. | ||||
|         """ | ||||
|         return True | ||||
|  | ||||
|     def stream_opened(self, stream): | ||||
|         print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) | ||||
|  | ||||
|         # You could run a loop reading from the stream using stream.recv(), | ||||
|         # or use the ibb_stream_data event. | ||||
|     def stream_data(self, stream): | ||||
|         self.file.write(stream.read()) | ||||
|  | ||||
|     def stream_data(self, event): | ||||
|         print(event['data']) | ||||
|     def stream_closed(self, stream): | ||||
|         print('Stream closed: %s from %s' % (stream.sid, stream.peer_jid)) | ||||
|         self.file.close() | ||||
|         self.disconnect() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
| @@ -97,6 +82,8 @@ if __name__ == '__main__': | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|     parser.add_argument("-o", "--out", dest="filename", | ||||
|                         help="file to save to") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
| @@ -108,9 +95,18 @@ if __name__ == '__main__': | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|     if args.filename is None: | ||||
|         args.filename = input("File path: ") | ||||
|  | ||||
|     xmpp = IBBReceiver(args.jid, args.password) | ||||
|     # Setup the IBBReceiver and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = IBBReceiver(args.jid, args.password, args.filename) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0047', { | ||||
|         'auto_accept': True | ||||
|     }) # In-band Bytestreams | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -14,6 +14,7 @@ from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
| class IBBSender(slixmpp.ClientXMPP): | ||||
| @@ -22,11 +23,13 @@ class IBBSender(slixmpp.ClientXMPP): | ||||
|     A basic example of creating and using an in-band bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, receiver, filename): | ||||
|     def __init__(self, jid, password, receiver, filename, use_messages=False): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.receiver = receiver | ||||
|         self.filename = filename | ||||
|  | ||||
|         self.file = open(filename, 'rb') | ||||
|         self.use_messages = use_messages | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
| @@ -35,7 +38,7 @@ class IBBSender(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -51,15 +54,22 @@ class IBBSender(slixmpp.ClientXMPP): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|  | ||||
|         # For the purpose of demonstration, we'll set a very small block | ||||
|         # size. The default block size is 4096. We'll also use a window | ||||
|         # allowing sending multiple blocks at a time; in this case, three | ||||
|         # block transfers may be in progress at any time. | ||||
|         stream = self['xep_0047'].open_stream(self.receiver) | ||||
|         try: | ||||
|             # Open the IBB stream in which to write to. | ||||
|             stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages) | ||||
|  | ||||
|         with open(self.filename) as f: | ||||
|             data = f.read() | ||||
|             stream.sendall(data) | ||||
|             # If you want to send in-memory bytes, use stream.sendall() instead. | ||||
|             await stream.sendfile(self.file, timeout=10) | ||||
|  | ||||
|             # And finally close the stream. | ||||
|             await stream.close(timeout=10) | ||||
|         except (IqError, IqTimeout): | ||||
|             print('File transfer errored') | ||||
|         else: | ||||
|             print('File transfer finished') | ||||
|         finally: | ||||
|             self.file.close() | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
| @@ -80,9 +90,11 @@ if __name__ == '__main__': | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|     parser.add_argument("-r", "--receiver", dest="receiver", | ||||
|                         help="JID to use") | ||||
|                         help="JID of the receiver") | ||||
|     parser.add_argument("-f", "--file", dest="filename", | ||||
|                         help="JID to use") | ||||
|                         help="file to send") | ||||
|     parser.add_argument("-m", "--use-messages", action="store_true", | ||||
|                         help="use messages instead of iqs for file transfer") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
| @@ -99,16 +111,13 @@ if __name__ == '__main__': | ||||
|     if args.filename is None: | ||||
|         args.filename = input("File path: ") | ||||
|  | ||||
|     # Setup the EchoBot and register plugins. Note that while plugins may | ||||
|     # Setup the IBBSender and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename) | ||||
|     xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename, args.use_messages) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0004') # Data Forms | ||||
|     xmpp.register_plugin('xep_0047') # In-band Bytestreams | ||||
|     xmpp.register_plugin('xep_0060') # PubSub | ||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
							
								
								
									
										97
									
								
								examples/mam.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										97
									
								
								examples/mam.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2017 Mathieu Pasquet | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class MAM(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic client fetching mam archive messages | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, remote_jid, start): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|         self.remote_jid = remote_jid | ||||
|         self.start_date = start | ||||
|  | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     async def start(self, *args): | ||||
|         """ | ||||
|         Fetch mam results for the specified JID. | ||||
|         Use RSM to paginate the results. | ||||
|         """ | ||||
|         results = self.plugin['xep_0313'].retrieve(jid=self.remote_jid, iterator=True, rsm={'max': 10}, start=self.start_date) | ||||
|         page = 1 | ||||
|         async for rsm in results: | ||||
|             print('Page %d' % page) | ||||
|             for msg in rsm['mam']['results']: | ||||
|                 forwarded = msg['mam_result']['forwarded'] | ||||
|                 timestamp = forwarded['delay']['stamp'] | ||||
|                 message = forwarded['stanza'] | ||||
|                 print('[%s] %s: %s' % (timestamp, message['from'], message['body'])) | ||||
|             page += 1 | ||||
|         self.disconnect() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     parser = ArgumentParser() | ||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.ERROR, | ||||
|                         default=logging.INFO) | ||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.DEBUG, | ||||
|                         default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|  | ||||
|     # Other options | ||||
|     parser.add_argument("-r", "--remote-jid", dest="remote_jid", | ||||
|                         help="Remote JID") | ||||
|     parser.add_argument("--start", help="Start date", default='2017-09-20T12:00:00Z') | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|     if args.remote_jid is None: | ||||
|         args.remote_jid = input("Remote JID: ") | ||||
|     if args.start is None: | ||||
|         args.start = input("Start time: ") | ||||
|  | ||||
|     xmpp = MAM(args.jid, args.password, args.remote_jid, args.start) | ||||
|     xmpp.register_plugin('xep_0313') | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
							
								
								
									
										120
									
								
								examples/markup.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										120
									
								
								examples/markup.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2010  Nathanael C. Fritz | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.plugins.xep_0394 import stanza as markup_stanza | ||||
|  | ||||
|  | ||||
| class EchoBot(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A simple Slixmpp bot that will echo messages it | ||||
|     receives, along with a short thank you message. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         slixmpp.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) | ||||
|  | ||||
|     async 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() | ||||
|         await 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. | ||||
|         """ | ||||
|         body = msg['body'] | ||||
|         new_body = self['xep_0394'].to_plain_text(body, msg['markup']) | ||||
|         xhtml = self['xep_0394'].to_xhtml_im(body, msg['markup']) | ||||
|         print('Plain text:', new_body) | ||||
|         print('XHTML-IM:', xhtml['body']) | ||||
|         message = msg.reply() | ||||
|         message['body'] = new_body | ||||
|         message['html']['body'] = xhtml['body'] | ||||
|         self.send(message) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     parser = ArgumentParser(description=EchoBot.__doc__) | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", dest="loglevel", | ||||
|                         const=logging.ERROR, default=logging.INFO) | ||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", dest="loglevel", | ||||
|                         const=logging.DEBUG, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|  | ||||
|     # Setup the EchoBot and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = EchoBot(args.jid, args.password) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||
|     xmpp.register_plugin('xep_0394') # Message Markup | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
| @@ -100,8 +100,8 @@ def on_session2(event): | ||||
|         new_xmpp.update_roster(jid, | ||||
|                 name = item['name'], | ||||
|                 groups = item['groups']) | ||||
|         new_xmpp.disconnect() | ||||
|     new_xmpp.disconnect() | ||||
| new_xmpp.add_event_handler('session_start', on_session2) | ||||
|  | ||||
| if new_xmpp.connect(): | ||||
|     new_xmpp.process(block=True) | ||||
| new_xmpp.connect() | ||||
| new_xmpp.process(forever=False) | ||||
|   | ||||
| @@ -52,7 +52,7 @@ class MUCBot(slixmpp.ClientXMPP): | ||||
|                                self.muc_online) | ||||
|  | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -65,13 +65,13 @@ class MUCBot(slixmpp.ClientXMPP): | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self.send_presence() | ||||
|         self.plugin['xep_0045'].joinMUC(self.room, | ||||
|                                         self.nick, | ||||
|                                         # If a room password is needed, use: | ||||
|                                         # password=the_room_password, | ||||
|                                         wait=True) | ||||
|         self.plugin['xep_0045'].join_muc(self.room, | ||||
|                                          self.nick, | ||||
|                                          # If a room password is needed, use: | ||||
|                                          # password=the_room_password, | ||||
|                                          wait=True) | ||||
|  | ||||
|     def muc_message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
| import slixmpp | ||||
|  | ||||
| @@ -36,7 +37,7 @@ class PingTest(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -50,11 +51,11 @@ class PingTest(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         try: | ||||
|             rtt = self['xep_0199'].ping(self.pingjid, | ||||
|                                         timeout=10) | ||||
|             rtt = await self['xep_0199'].ping(self.pingjid, | ||||
|                                                    timeout=10) | ||||
|             logging.info("Success! RTT: %s", rtt) | ||||
|         except IqError as e: | ||||
|             logging.info("Error pinging %s: %s", | ||||
| @@ -78,8 +79,7 @@ if __name__ == '__main__': | ||||
|                         action="store_const", dest="loglevel", | ||||
|                         const=logging.DEBUG, default=logging.INFO) | ||||
|     parser.add_argument("-t", "--pingto", help="set jid to ping", | ||||
|                         action="store", type="string", dest="pingjid", | ||||
|                         default=None) | ||||
|                         dest="pingjid", default=None) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
| @@ -109,4 +109,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -6,20 +6,21 @@ from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError | ||||
| from slixmpp.xmlstream import ET, tostring | ||||
|  | ||||
|  | ||||
| class PubsubClient(slixmpp.ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password, server, | ||||
|                        node=None, action='list', data=''): | ||||
|         super(PubsubClient, self).__init__(jid, password) | ||||
|                        node=None, action='nodes', data=''): | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0059') | ||||
|         self.register_plugin('xep_0060') | ||||
|  | ||||
|         self.actions = ['nodes', 'create', 'delete', | ||||
|         self.actions = ['nodes', 'create', 'delete', 'get_configure', | ||||
|                         'publish', 'get', 'retract', | ||||
|                         'purge', 'subscribe', 'unsubscribe'] | ||||
|  | ||||
| @@ -30,81 +31,89 @@ class PubsubClient(slixmpp.ClientXMPP): | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|     async def start(self, event): | ||||
|         await self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|         try: | ||||
|             getattr(self, self.action)() | ||||
|             await getattr(self, self.action)() | ||||
|         except: | ||||
|             logging.error('Could not execute: %s' % self.action) | ||||
|             logging.exception('Could not execute %s:', self.action) | ||||
|         self.disconnect() | ||||
|  | ||||
|     def nodes(self): | ||||
|     async def nodes(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].get_nodes(self.pubsub_server, self.node) | ||||
|             result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node) | ||||
|             for item in result['disco_items']['items']: | ||||
|                 print('  - %s' % str(item)) | ||||
|         except: | ||||
|             logging.error('Could not retrieve node list.') | ||||
|                 logging.info('  - %s', str(item)) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not retrieve node list: %s', error.format()) | ||||
|  | ||||
|     def create(self): | ||||
|     async def create(self): | ||||
|         try: | ||||
|             self['xep_0060'].create_node(self.pubsub_server, self.node) | ||||
|         except: | ||||
|             logging.error('Could not create node: %s' % self.node) | ||||
|             await self['xep_0060'].create_node(self.pubsub_server, self.node) | ||||
|             logging.info('Created node %s', self.node) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not create node %s: %s', self.node, error.format()) | ||||
|  | ||||
|     def delete(self): | ||||
|     async 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) | ||||
|             await self['xep_0060'].delete_node(self.pubsub_server, self.node) | ||||
|             logging.info('Deleted node %s', self.node) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not delete node %s: %s', self.node, error.format()) | ||||
|  | ||||
|     def publish(self): | ||||
|     async def get_configure(self): | ||||
|         try: | ||||
|             configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node) | ||||
|             logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form']) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format()) | ||||
|  | ||||
|     async def publish(self): | ||||
|         payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data) | ||||
|         try: | ||||
|             result = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) | ||||
|             id = result['pubsub']['publish']['item']['id'] | ||||
|             print('Published at item id: %s' % id) | ||||
|         except: | ||||
|             logging.error('Could not publish to: %s' % self.node) | ||||
|             result = await self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) | ||||
|             logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id']) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not publish to %s: %s', self.node, error.format()) | ||||
|  | ||||
|     def get(self): | ||||
|     async def get(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) | ||||
|             result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) | ||||
|             for item in result['pubsub']['items']['substanzas']: | ||||
|                 print('Retrieved item %s: %s' % (item['id'], tostring(item['payload']))) | ||||
|         except: | ||||
|             logging.error('Could not retrieve item %s from node %s' % (self.data, self.node)) | ||||
|                 logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload'])) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format()) | ||||
|  | ||||
|     def retract(self): | ||||
|     async 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)) | ||||
|             await self['xep_0060'].retract(self.pubsub_server, self.node, self.data) | ||||
|             logging.info('Retracted item %s from node %s', self.data, self.node) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format()) | ||||
|  | ||||
|     def purge(self): | ||||
|     async 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) | ||||
|             await self['xep_0060'].purge(self.pubsub_server, self.node) | ||||
|             logging.info('Purged all items from node %s', self.node) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not purge items from node %s: %s', self.node, error.format()) | ||||
|  | ||||
|     def subscribe(self): | ||||
|     async def subscribe(self): | ||||
|         try: | ||||
|             result = self['xep_0060'].subscribe(self.pubsub_server, self.node) | ||||
|             print('Subscribed %s to node %s' % (self.boundjid.bare, self.node)) | ||||
|         except: | ||||
|             logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node)) | ||||
|             iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node) | ||||
|             subscription = iq['pubsub']['subscription'] | ||||
|             logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node']) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format()) | ||||
|  | ||||
|     def unsubscribe(self): | ||||
|     async 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)) | ||||
|             await self['xep_0060'].unsubscribe(self.pubsub_server, self.node) | ||||
|             logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node) | ||||
|         except XMPPError as error: | ||||
|             logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format()) | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -114,19 +123,19 @@ if __name__ == '__main__': | ||||
|     parser = ArgumentParser() | ||||
|     parser.version = '%%prog 0.1' | ||||
|     parser.usage = "Usage: %%prog [options] <jid> " + \ | ||||
|                              'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \ | ||||
|                              'nodes|create|delete|get_configure|purge|subscribe|unsubscribe|publish|retract|get' + \ | ||||
|                              ' [<node> <data>]' | ||||
|  | ||||
|     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.ERROR, | ||||
|                         default=logging.ERROR) | ||||
|                         default=logging.INFO) | ||||
|     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", | ||||
|                         dest="loglevel", | ||||
|                         const=logging.DEBUG, | ||||
|                         default=logging.ERROR) | ||||
|                         default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
| @@ -135,7 +144,7 @@ if __name__ == '__main__': | ||||
|                         help="password to use") | ||||
|  | ||||
|     parser.add_argument("server") | ||||
|     parser.add_argument("action", choice=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"]) | ||||
|     parser.add_argument("action", choices=["nodes", "create", "delete", "get_configure", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"]) | ||||
|     parser.add_argument("node", nargs='?') | ||||
|     parser.add_argument("data", nargs='?') | ||||
|  | ||||
| @@ -159,4 +168,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ from slixmpp.xmlstream.handler import Callback | ||||
| class PubsubEvents(slixmpp.ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(PubsubEvents, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.register_plugin('xep_0030') | ||||
|         self.register_plugin('xep_0059') | ||||
| @@ -38,8 +38,8 @@ class PubsubEvents(slixmpp.ClientXMPP): | ||||
|         # self.add_event_handler('event_prefix_purge', handler) | ||||
|         # self.add_event_handler('event_prefix_delete', handler) | ||||
|  | ||||
|     def start(self, event): | ||||
|         self.get_roster() | ||||
|     async def start(self, event): | ||||
|         await self.get_roster() | ||||
|         self.send_presence() | ||||
|  | ||||
|     def _publish(self, msg): | ||||
|   | ||||
| @@ -47,7 +47,7 @@ class RegisterBot(slixmpp.ClientXMPP): | ||||
|         # for data forms and OOB links that will make that easier. | ||||
|         self.add_event_handler("register", self.register) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -61,12 +61,12 @@ class RegisterBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         # We're only concerned about registering, so nothing more to do here. | ||||
|         self.disconnect() | ||||
|  | ||||
|     def register(self, iq): | ||||
|     async def register(self, iq): | ||||
|         """ | ||||
|         Fill out and submit a registration form. | ||||
|  | ||||
| @@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP): | ||||
|         resp['register']['password'] = self.password | ||||
|  | ||||
|         try: | ||||
|             resp.send() | ||||
|             await resp.send() | ||||
|             logging.info("Account created for %s!" % self.boundjid) | ||||
|         except IqError as e: | ||||
|             logging.error("Could not register account: %s" % | ||||
|   | ||||
| @@ -11,11 +11,11 @@ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| import threading | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
| from slixmpp.xmlstream.asyncio import asyncio | ||||
|  | ||||
|  | ||||
| class RosterBrowser(slixmpp.ClientXMPP): | ||||
| @@ -36,9 +36,9 @@ class RosterBrowser(slixmpp.ClientXMPP): | ||||
|         self.add_event_handler("changed_status", self.wait_for_presences) | ||||
|  | ||||
|         self.received = set() | ||||
|         self.presences_received = threading.Event() | ||||
|         self.presences_received = asyncio.Event() | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -52,16 +52,16 @@ class RosterBrowser(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         try: | ||||
|             self.get_roster() | ||||
|             await self.get_roster() | ||||
|         except IqError as err: | ||||
|             print('Error: %' % err.iq['error']['condition']) | ||||
|             print('Error: %s' % err.iq['error']['condition']) | ||||
|         except IqTimeout: | ||||
|             print('Error: Request timed out') | ||||
|         self.send_presence() | ||||
|  | ||||
|  | ||||
|         print('Waiting for presence updates...\n') | ||||
|         self.presences_received.wait(5) | ||||
|         await asyncio.sleep(10) | ||||
|  | ||||
|         print('Roster for %s' % self.boundjid.bare) | ||||
|         groups = self.client_roster.groups() | ||||
| @@ -134,4 +134,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class Boomerang(Endpoint): | ||||
|  | ||||
|     @remote | ||||
|     def throw(self): | ||||
|         print "Duck!" | ||||
|         print("Duck!") | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										89
									
								
								examples/s5b_transfer/s5b_receiver.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										89
									
								
								examples/s5b_transfer/s5b_receiver.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2015  Emmanuel Gil Peyrot | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
|  | ||||
|  | ||||
| class S5BReceiver(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using a SOCKS5 bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, filename): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.file = open(filename, 'wb') | ||||
|  | ||||
|         self.add_event_handler("socks5_connected", self.stream_opened) | ||||
|         self.add_event_handler("socks5_data", self.stream_data) | ||||
|         self.add_event_handler("socks5_closed", self.stream_closed) | ||||
|  | ||||
|     def stream_opened(self, sid): | ||||
|         logging.info('Stream opened. %s', sid) | ||||
|  | ||||
|     def stream_data(self, data): | ||||
|         self.file.write(data) | ||||
|  | ||||
|     def stream_closed(self, exception): | ||||
|         logging.info('Stream closed. %s', exception) | ||||
|         self.file.close() | ||||
|         self.disconnect() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     parser = ArgumentParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", dest="loglevel", | ||||
|                         const=logging.ERROR, default=logging.INFO) | ||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", dest="loglevel", | ||||
|                         const=logging.DEBUG, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|     parser.add_argument("-o", "--out", dest="filename", | ||||
|                         help="file to save to") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|     if args.filename is None: | ||||
|         args.filename = input("File path: ") | ||||
|  | ||||
|     # Setup the S5BReceiver and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = S5BReceiver(args.jid, args.password, args.filename) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0065', { | ||||
|         'auto_accept': True | ||||
|     }) # SOCKS5 Bytestreams | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
							
								
								
									
										122
									
								
								examples/s5b_transfer/s5b_sender.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										122
									
								
								examples/s5b_transfer/s5b_sender.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2015  Emmanuel Gil Peyrot | ||||
|     This file is part of Slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| from getpass import getpass | ||||
| from argparse import ArgumentParser | ||||
|  | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
|  | ||||
| class S5BSender(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
|     A basic example of creating and using a SOCKS5 bytestream. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, receiver, filename): | ||||
|         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||
|  | ||||
|         self.receiver = receiver | ||||
|  | ||||
|         self.file = open(filename, 'rb') | ||||
|  | ||||
|         # The session_start event will be triggered when | ||||
|         # the bot establishes its connection with the server | ||||
|         # and the XML streams are ready for use. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
|         Typical actions for the session_start event are | ||||
|         requesting the roster and broadcasting an initial | ||||
|         presence stanza. | ||||
|  | ||||
|         Arguments: | ||||
|             event -- An empty dictionary. The session_start | ||||
|                      event does not provide any additional | ||||
|                      data. | ||||
|         """ | ||||
|  | ||||
|         try: | ||||
|             # Open the S5B stream in which to write to. | ||||
|             proxy = await self['xep_0065'].handshake(self.receiver) | ||||
|  | ||||
|             # Send the entire file. | ||||
|             while True: | ||||
|                 data = self.file.read(1048576) | ||||
|                 if not data: | ||||
|                     break | ||||
|                 await proxy.write(data) | ||||
|  | ||||
|             # And finally close the stream. | ||||
|             proxy.transport.write_eof() | ||||
|         except (IqError, IqTimeout): | ||||
|             print('File transfer errored') | ||||
|         else: | ||||
|             print('File transfer finished') | ||||
|         finally: | ||||
|             self.file.close() | ||||
|             self.disconnect() | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     # Setup the command line arguments. | ||||
|     parser = ArgumentParser() | ||||
|  | ||||
|     # Output verbosity options. | ||||
|     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||
|                         action="store_const", dest="loglevel", | ||||
|                         const=logging.ERROR, default=logging.INFO) | ||||
|     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||
|                         action="store_const", dest="loglevel", | ||||
|                         const=logging.DEBUG, default=logging.INFO) | ||||
|  | ||||
|     # JID and password options. | ||||
|     parser.add_argument("-j", "--jid", dest="jid", | ||||
|                         help="JID to use") | ||||
|     parser.add_argument("-p", "--password", dest="password", | ||||
|                         help="password to use") | ||||
|     parser.add_argument("-r", "--receiver", dest="receiver", | ||||
|                         help="JID of the receiver") | ||||
|     parser.add_argument("-f", "--file", dest="filename", | ||||
|                         help="file to send") | ||||
|     parser.add_argument("-m", "--use-messages", action="store_true", | ||||
|                         help="use messages instead of iqs for file transfer") | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     # Setup logging. | ||||
|     logging.basicConfig(level=args.loglevel, | ||||
|                         format='%(levelname)-8s %(message)s') | ||||
|  | ||||
|     if args.jid is None: | ||||
|         args.jid = input("Username: ") | ||||
|     if args.password is None: | ||||
|         args.password = getpass("Password: ") | ||||
|     if args.receiver is None: | ||||
|         args.receiver = input("Receiver: ") | ||||
|     if args.filename is None: | ||||
|         args.filename = input("File path: ") | ||||
|  | ||||
|     # Setup the S5BSender and register plugins. Note that while plugins may | ||||
|     # have interdependencies, the order in which you register them does | ||||
|     # not matter. | ||||
|     xmpp = S5BSender(args.jid, args.password, args.receiver, args.filename) | ||||
|     xmpp.register_plugin('xep_0030') # Service Discovery | ||||
|     xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process(forever=False) | ||||
| @@ -38,7 +38,7 @@ class SendMsgBot(slixmpp.ClientXMPP): | ||||
|         # our roster. | ||||
|         self.add_event_handler("session_start", self.start) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -52,7 +52,7 @@ class SendMsgBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         self.send_message(mto=self.recipient, | ||||
|                           mbody=self.msg, | ||||
| @@ -107,4 +107,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -19,7 +19,6 @@ from argparse import ArgumentParser | ||||
| import slixmpp | ||||
| from slixmpp.exceptions import XMPPError | ||||
|  | ||||
|  | ||||
| class AvatarSetter(slixmpp.ClientXMPP): | ||||
|  | ||||
|     """ | ||||
| @@ -33,7 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP): | ||||
|  | ||||
|         self.filepath = filepath | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -47,11 +46,11 @@ class AvatarSetter(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|         avatar_file = None | ||||
|         try: | ||||
|             avatar_file = open(os.path.expanduser(self.filepath)) | ||||
|             avatar_file = open(os.path.expanduser(self.filepath), 'rb') | ||||
|         except IOError: | ||||
|             print('Could not find file: %s' % self.filepath) | ||||
|             return self.disconnect() | ||||
| @@ -65,32 +64,31 @@ class AvatarSetter(slixmpp.ClientXMPP): | ||||
|         avatar_file.close() | ||||
|  | ||||
|         used_xep84 = False | ||||
|         try: | ||||
|             print('Publish XEP-0084 avatar data') | ||||
|             self['xep_0084'].publish_avatar(avatar) | ||||
|             used_xep84 = True | ||||
|         except XMPPError: | ||||
|             print('Could not publish XEP-0084 avatar') | ||||
|  | ||||
|         try: | ||||
|             print('Update vCard with avatar') | ||||
|             self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) | ||||
|         except XMPPError: | ||||
|         print('Publish XEP-0084 avatar data') | ||||
|         result = await self['xep_0084'].publish_avatar(avatar) | ||||
|         if isinstance(result, XMPPError): | ||||
|             print('Could not publish XEP-0084 avatar') | ||||
|         else: | ||||
|             used_xep84 = True | ||||
|  | ||||
|         print('Update vCard with avatar') | ||||
|         result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) | ||||
|         if isinstance(result, XMPPError): | ||||
|             print('Could not set vCard avatar') | ||||
|  | ||||
|         if used_xep84: | ||||
|             try: | ||||
|                 print('Advertise XEP-0084 avatar metadata') | ||||
|                 self['xep_0084'].publish_avatar_metadata([ | ||||
|                     {'id': avatar_id, | ||||
|                      'type': avatar_type, | ||||
|                      'bytes': avatar_bytes} | ||||
|                     # We could advertise multiple avatars to provide | ||||
|                     # options in image type, source (HTTP vs pubsub), | ||||
|                     # size, etc. | ||||
|                     # {'id': ....} | ||||
|                 ]) | ||||
|             except XMPPError: | ||||
|             print('Advertise XEP-0084 avatar metadata') | ||||
|             result = await self['xep_0084'].publish_avatar_metadata([ | ||||
|                 {'id': avatar_id, | ||||
|                  'type': avatar_type, | ||||
|                  'bytes': avatar_bytes} | ||||
|                 # We could advertise multiple avatars to provide | ||||
|                 # options in image type, source (HTTP vs pubsub), | ||||
|                 # size, etc. | ||||
|                 # {'id': ....} | ||||
|             ]) | ||||
|             if isinstance(result, XMPPError): | ||||
|                 print('Could not publish XEP-0084 metadata') | ||||
|  | ||||
|         print('Wait for presence updates to propagate...') | ||||
| @@ -139,4 +137,4 @@ if __name__ == '__main__': | ||||
|  | ||||
|     # Connect to the XMPP server and start processing XMPP stanzas. | ||||
|     xmpp.connect() | ||||
|     xmpp.process() | ||||
|     xmpp.process(forever=False) | ||||
|   | ||||
| @@ -60,7 +60,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP): | ||||
|         # MUC messages and error messages. | ||||
|         self.add_event_handler("message", self.message) | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         """ | ||||
|         Process the session_start event. | ||||
|  | ||||
| @@ -74,7 +74,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP): | ||||
|                      data. | ||||
|         """ | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|  | ||||
|     def message(self, msg): | ||||
|         """ | ||||
|   | ||||
| @@ -22,7 +22,7 @@ from slixmpp import ClientXMPP | ||||
| class LocationBot(ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(LocationBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         self.add_event_handler('session_start', self.start) | ||||
|         self.add_event_handler('user_location_publish', | ||||
| @@ -38,9 +38,9 @@ class LocationBot(ClientXMPP): | ||||
|  | ||||
|         self.current_tune = None | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self['xep_0115'].update_caps() | ||||
|  | ||||
|         print("Using freegeoip.net to get geolocation.") | ||||
|   | ||||
| @@ -17,7 +17,7 @@ from slixmpp import ClientXMPP | ||||
| class TuneBot(ClientXMPP): | ||||
|  | ||||
|     def __init__(self, jid, password): | ||||
|         super(TuneBot, self).__init__(jid, password) | ||||
|         super().__init__(jid, password) | ||||
|  | ||||
|         # Check for the current song every 5 seconds. | ||||
|         self.schedule('Check Current Tune', 5, self._update_tune, repeat=True) | ||||
| @@ -35,9 +35,9 @@ class TuneBot(ClientXMPP): | ||||
|  | ||||
|         self.current_tune = None | ||||
|  | ||||
|     def start(self, event): | ||||
|     async def start(self, event): | ||||
|         self.send_presence() | ||||
|         self.get_roster() | ||||
|         await self.get_roster() | ||||
|         self['xep_0115'].update_caps() | ||||
|  | ||||
|     def _update_tune(self): | ||||
|   | ||||
							
								
								
									
										49
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								setup.py
									
									
									
									
									
								
							| @@ -7,7 +7,10 @@ | ||||
| # This software is licensed as described in the README.rst and LICENSE | ||||
| # file, which you should have received as part of this distribution. | ||||
|  | ||||
| import os | ||||
| from pathlib import Path | ||||
| from subprocess import call, DEVNULL, check_output, CalledProcessError | ||||
| from tempfile import TemporaryFile | ||||
| try: | ||||
|     from setuptools import setup | ||||
| except ImportError: | ||||
| @@ -17,8 +20,7 @@ from run_tests import TestCommand | ||||
| from slixmpp.version import __version__ | ||||
|  | ||||
| VERSION = __version__ | ||||
| DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, ' | ||||
|                'Google Talk, etc).') | ||||
| DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).') | ||||
| with open('README.rst', encoding='utf8') as readme: | ||||
|     LONG_DESCRIPTION = readme.read() | ||||
|  | ||||
| @@ -26,12 +28,48 @@ CLASSIFIERS = [ | ||||
|     'Intended Audience :: Developers', | ||||
|     'License :: OSI Approved :: MIT License', | ||||
|     'Programming Language :: Python', | ||||
|     'Programming Language :: Python :: 3.4', | ||||
|     'Programming Language :: Python :: 3.7', | ||||
|     'Programming Language :: Python :: 3.8', | ||||
|     'Topic :: Internet :: XMPP', | ||||
|     'Topic :: Software Development :: Libraries :: Python Modules', | ||||
| ] | ||||
|  | ||||
| packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')] | ||||
|  | ||||
| def check_include(library_name, header): | ||||
|     command = [os.environ.get('PKG_CONFIG', 'pkg-config'), '--cflags', library_name] | ||||
|     try: | ||||
|         cflags = check_output(command).decode('utf-8').split() | ||||
|     except FileNotFoundError: | ||||
|         print('pkg-config not found.') | ||||
|         return False | ||||
|     except CalledProcessError: | ||||
|         # pkg-config already prints the missing libraries on stderr. | ||||
|         return False | ||||
|     command = [os.environ.get('CC', 'cc')] + cflags + ['-E', '-'] | ||||
|     with TemporaryFile('w+') as c_file: | ||||
|         c_file.write('#include <%s>' % header) | ||||
|         c_file.seek(0) | ||||
|         try: | ||||
|             return call(command, stdin=c_file, stdout=DEVNULL, stderr=DEVNULL) == 0 | ||||
|         except FileNotFoundError: | ||||
|             print('%s headers not found.' % library_name) | ||||
|             return False | ||||
|  | ||||
| HAS_PYTHON_HEADERS = check_include('python3', 'Python.h') | ||||
| HAS_STRINGPREP_HEADERS = check_include('libidn', 'stringprep.h') | ||||
|  | ||||
| ext_modules = None | ||||
| if HAS_PYTHON_HEADERS and HAS_STRINGPREP_HEADERS: | ||||
|     try: | ||||
|         from Cython.Build import cythonize | ||||
|     except ImportError: | ||||
|         print('Cython not found, falling back to the slow stringprep module.') | ||||
|     else: | ||||
|         ext_modules = cythonize('slixmpp/stringprep.pyx') | ||||
| else: | ||||
|     print('Falling back to the slow stringprep module.') | ||||
|  | ||||
| setup( | ||||
|     name="slixmpp", | ||||
|     version=VERSION, | ||||
| @@ -39,11 +77,12 @@ setup( | ||||
|     long_description=LONG_DESCRIPTION, | ||||
|     author='Florent Le Coz', | ||||
|     author_email='louiz@louiz.org', | ||||
|     url='https://dev.louiz.org/projects/slixmpp', | ||||
|     url='https://lab.louiz.org/poezio/slixmpp', | ||||
|     license='MIT', | ||||
|     platforms=['any'], | ||||
|     packages=packages, | ||||
|     requires=['aiodns', 'pyasn1', 'pyasn1_modules'], | ||||
|     ext_modules=ext_modules, | ||||
|     install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'], | ||||
|     classifiers=CLASSIFIERS, | ||||
|     cmdclass={'test': TestCommand} | ||||
| ) | ||||
|   | ||||
| @@ -9,6 +9,10 @@ | ||||
| import logging | ||||
| logging.getLogger(__name__).addHandler(logging.NullHandler()) | ||||
|  | ||||
| import asyncio | ||||
| # Required for python < 3.7 to use the old ssl implementation | ||||
| # and manage to do starttls as an unintended side effect | ||||
| asyncio.sslproto._is_sslproto_available = lambda: False | ||||
|  | ||||
| from slixmpp.stanza import Message, Presence, Iq | ||||
| from slixmpp.jid import JID, InvalidJID | ||||
| @@ -16,6 +20,7 @@ from slixmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin | ||||
| from slixmpp.xmlstream.handler import * | ||||
| from slixmpp.xmlstream import XMLStream | ||||
| from slixmpp.xmlstream.matcher import * | ||||
| from slixmpp.xmlstream.asyncio import asyncio, future_wrapper | ||||
| from slixmpp.basexmpp import BaseXMPP | ||||
| from slixmpp.clientxmpp import ClientXMPP | ||||
| from slixmpp.componentxmpp import ComponentXMPP | ||||
|   | ||||
| @@ -12,8 +12,8 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
| import threading | ||||
|  | ||||
| from slixmpp import plugins, roster, stanza | ||||
| from slixmpp.api import APIRegistry | ||||
| @@ -21,8 +21,6 @@ from slixmpp.exceptions import IqError, IqTimeout | ||||
|  | ||||
| from slixmpp.stanza import Message, Presence, Iq, StreamError | ||||
| from slixmpp.stanza.roster import Roster | ||||
| from slixmpp.stanza.nick import Nick | ||||
| from slixmpp.stanza.htmlim import HTMLIM | ||||
|  | ||||
| from slixmpp.xmlstream import XMLStream, JID | ||||
| from slixmpp.xmlstream import ET, register_stanza_plugin | ||||
| @@ -46,8 +44,8 @@ class BaseXMPP(XMLStream): | ||||
|                        is used during initialization. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid='', default_ns='jabber:client'): | ||||
|         XMLStream.__init__(self) | ||||
|     def __init__(self, jid='', default_ns='jabber:client', **kwargs): | ||||
|         XMLStream.__init__(self, **kwargs) | ||||
|  | ||||
|         self.default_ns = default_ns | ||||
|         self.stream_ns = 'http://etherx.jabber.org/streams' | ||||
| @@ -57,12 +55,12 @@ class BaseXMPP(XMLStream): | ||||
|         self.stream_id = None | ||||
|  | ||||
|         #: The JabberID (JID) requested for this connection. | ||||
|         self.requested_jid = JID(jid, cache_lock=True) | ||||
|         self.requested_jid = JID(jid) | ||||
|  | ||||
|         #: The JabberID (JID) used by this connection, | ||||
|         #: as set after session binding. This may even be a | ||||
|         #: different bare JID than what was requested. | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|         self.boundjid = JID(jid) | ||||
|  | ||||
|         self._expected_server_name = self.boundjid.host | ||||
|         self._redirect_attempts = 0 | ||||
| @@ -71,7 +69,7 @@ class BaseXMPP(XMLStream): | ||||
|         #: redirections that will be followed before quitting. | ||||
|         self.max_redirects = 5 | ||||
|  | ||||
|         self.session_bind_event = threading.Event() | ||||
|         self.session_bind_event = asyncio.Event() | ||||
|  | ||||
|         #: A dictionary mapping plugin names to plugins. | ||||
|         self.plugin = PluginManager(self) | ||||
| @@ -106,12 +104,15 @@ class BaseXMPP(XMLStream): | ||||
|         #: :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 | ||||
|         self.use_message_ids = True | ||||
|  | ||||
|         #: 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 | ||||
|         self.use_presence_ids = True | ||||
|  | ||||
|         #: XEP-0359 <origin-id/> tag that gets added to <message/> stanzas. | ||||
|         self.use_origin_id = True | ||||
|  | ||||
|         #: The API registry is a way to process callbacks based on | ||||
|         #: JID+node combinations. Each callback in the registry is | ||||
| @@ -143,6 +144,13 @@ class BaseXMPP(XMLStream): | ||||
|                      MatchXPath('{%s}message/{%s}body' % (self.default_ns, | ||||
|                                                           self.default_ns)), | ||||
|                      self._handle_message)) | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('IMError', | ||||
|                      MatchXPath('{%s}message/{%s}error' % (self.default_ns, | ||||
|                                                            self.default_ns)), | ||||
|                      self._handle_message_error)) | ||||
|  | ||||
|         self.register_handler( | ||||
|             Callback('Presence', | ||||
|                      MatchXPath("{%s}presence" % self.default_ns), | ||||
| @@ -188,7 +196,6 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         # Initialize a few default stanza plugins. | ||||
|         register_stanza_plugin(Iq, Roster) | ||||
|         register_stanza_plugin(Message, Nick) | ||||
|  | ||||
|     def start_stream_handler(self, xml): | ||||
|         """Save the stream ID once the streams have been established. | ||||
| @@ -203,9 +210,9 @@ class BaseXMPP(XMLStream): | ||||
|             log.warning('Legacy XMPP 0.9 protocol detected.') | ||||
|             self.event('legacy_protocol') | ||||
|  | ||||
|     def process(self, timeout=None): | ||||
|     def process(self, *, forever=True, timeout=None): | ||||
|         self.init_plugins() | ||||
|         XMLStream.process(self, timeout) | ||||
|         XMLStream.process(self, forever=forever, timeout=timeout) | ||||
|  | ||||
|     def init_plugins(self): | ||||
|         for name in self.plugin: | ||||
| @@ -214,7 +221,7 @@ class BaseXMPP(XMLStream): | ||||
|                     self.plugin[name].post_init() | ||||
|                 self.plugin[name].post_inited = True | ||||
|  | ||||
|     def register_plugin(self, plugin, pconfig={}, module=None): | ||||
|     def register_plugin(self, plugin, pconfig=None, module=None): | ||||
|         """Register and configure  a plugin for use in this stream. | ||||
|  | ||||
|         :param plugin: The name of the plugin class. Plugin names must | ||||
| @@ -631,7 +638,7 @@ class BaseXMPP(XMLStream): | ||||
|     def set_jid(self, jid): | ||||
|         """Rip a JID apart and claim it as our own.""" | ||||
|         log.debug("setting jid to %s", jid) | ||||
|         self.boundjid = JID(jid, cache_lock=True) | ||||
|         self.boundjid = JID(jid) | ||||
|  | ||||
|     def getjidresource(self, fulljid): | ||||
|         if '/' in fulljid: | ||||
| @@ -681,7 +688,6 @@ class BaseXMPP(XMLStream): | ||||
|             self.address = (host, port) | ||||
|             self.default_domain = host | ||||
|             self.dns_records = None | ||||
|             self.reconnect_delay = None | ||||
|             self.reconnect() | ||||
|  | ||||
|     def _handle_message(self, msg): | ||||
| @@ -690,6 +696,12 @@ class BaseXMPP(XMLStream): | ||||
|             msg['to'] = self.boundjid | ||||
|         self.event('message', msg) | ||||
|  | ||||
|     def _handle_message_error(self, msg): | ||||
|         """Process incoming message error stanzas.""" | ||||
|         if not self.is_component and not msg['to'].bare: | ||||
|             msg['to'] = self.boundjid | ||||
|         self.event('message_error', msg) | ||||
|  | ||||
|     def _handle_available(self, pres): | ||||
|         self.roster[pres['to']][pres['from']].handle_available(pres) | ||||
|  | ||||
| @@ -741,6 +753,9 @@ class BaseXMPP(XMLStream): | ||||
|  | ||||
|         Update the roster with presence information. | ||||
|         """ | ||||
|         if self.roster[presence['from']].ignore_updates: | ||||
|             return | ||||
|  | ||||
|         if not self.is_component and not presence['to'].bare: | ||||
|             presence['to'] = self.boundjid | ||||
|  | ||||
|   | ||||
| @@ -12,14 +12,16 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
|  | ||||
| from slixmpp.jid import JID | ||||
| from slixmpp.stanza import StreamFeatures | ||||
| from slixmpp.basexmpp import BaseXMPP | ||||
| from slixmpp.exceptions import XMPPError | ||||
| from slixmpp.xmlstream import XMLStream | ||||
| from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| from slixmpp.xmlstream.handler import Callback, CoroutineCallback | ||||
|  | ||||
| # Flag indicating if DNS SRV records are available for use. | ||||
| try: | ||||
| @@ -50,7 +52,6 @@ class ClientXMPP(BaseXMPP): | ||||
|  | ||||
|     :param jid: The JID of the XMPP user account. | ||||
|     :param password: The password for the XMPP user account. | ||||
|     :param ssl: **Deprecated.** | ||||
|     :param plugin_config: A dictionary of plugin configurations. | ||||
|     :param plugin_whitelist: A list of approved plugins that | ||||
|                     will be loaded when calling | ||||
| @@ -58,9 +59,15 @@ class ClientXMPP(BaseXMPP): | ||||
|     :param escape_quotes: **Deprecated.** | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[], | ||||
|                  escape_quotes=True, sasl_mech=None, lang='en'): | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client') | ||||
|     def __init__(self, jid, password, plugin_config=None, | ||||
|                  plugin_whitelist=None, escape_quotes=True, sasl_mech=None, | ||||
|                  lang='en', **kwargs): | ||||
|         if not plugin_whitelist: | ||||
|             plugin_whitelist = [] | ||||
|         if not plugin_config: | ||||
|             plugin_config = {} | ||||
|  | ||||
|         BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs) | ||||
|  | ||||
|         self.escape_quotes = escape_quotes | ||||
|         self.plugin_config = plugin_config | ||||
| @@ -99,13 +106,24 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.register_stanza(StreamFeatures) | ||||
|  | ||||
|         self.register_handler( | ||||
|                 Callback('Stream Features', | ||||
|                          MatchXPath('{%s}features' % self.stream_ns), | ||||
|                          self._handle_stream_features)) | ||||
|                 CoroutineCallback('Stream Features', | ||||
|                      MatchXPath('{%s}features' % self.stream_ns), | ||||
|                      self._handle_stream_features)) | ||||
|         def roster_push_filter(iq): | ||||
|             from_ = iq['from'] | ||||
|             if from_ and from_ != JID('') and from_ != self.boundjid.bare: | ||||
|                 reply = iq.reply() | ||||
|                 reply['type'] = 'error' | ||||
|                 reply['error']['type'] = 'cancel' | ||||
|                 reply['error']['code'] = 503 | ||||
|                 reply['error']['condition'] = 'service-unavailable' | ||||
|                 reply.send() | ||||
|                 return | ||||
|             self.event('roster_update', iq) | ||||
|         self.register_handler( | ||||
|                 Callback('Roster Update', | ||||
|                          StanzaPath('iq@type=set/roster'), | ||||
|                          lambda iq: self.event('roster_update', iq))) | ||||
|                          roster_push_filter)) | ||||
|  | ||||
|         # Setup default stream features | ||||
|         self.register_plugin('feature_starttls') | ||||
| @@ -135,10 +153,11 @@ class ClientXMPP(BaseXMPP): | ||||
|         will be used. | ||||
|  | ||||
|         :param address: A tuple containing the server's host and port. | ||||
|         :param reattempt: If ``True``, repeat attempting to connect if an | ||||
|                          error occurs. Defaults to ``True``. | ||||
|         :param use_tls: Indicates if TLS should be used for the | ||||
|                         connection. Defaults to ``True``. | ||||
|         :param force_starttls: Indicates that negotiation should be aborted | ||||
|                                if the server does not advertise support for | ||||
|                                STARTTLS. Defaults to ``True``. | ||||
|         :param disable_starttls: Disables TLS for the connection. | ||||
|                                  Defaults to ``False``. | ||||
|         :param use_ssl: Indicates if the older SSL connection method | ||||
|                         should be used. Defaults to ``False``. | ||||
|         """ | ||||
| @@ -236,7 +255,7 @@ class ClientXMPP(BaseXMPP): | ||||
|                 orig_cb(resp) | ||||
|             callback = wrapped | ||||
|  | ||||
|         iq.send(callback, timeout, timeout_callback) | ||||
|         return iq.send(callback, timeout, timeout_callback) | ||||
|  | ||||
|     def _reset_connection_state(self, event=None): | ||||
|         #TODO: Use stream state here | ||||
| @@ -246,7 +265,7 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.bindfail = False | ||||
|         self.features = set() | ||||
|  | ||||
|     def _handle_stream_features(self, features): | ||||
|     async def _handle_stream_features(self, features): | ||||
|         """Process the received stream features. | ||||
|  | ||||
|         :param features: The features stanza. | ||||
| @@ -254,7 +273,11 @@ class ClientXMPP(BaseXMPP): | ||||
|         for order, name in self._stream_feature_order: | ||||
|             if name in features['features']: | ||||
|                 handler, restart = self._stream_feature_handlers[name] | ||||
|                 if handler(features) and restart: | ||||
|                 if asyncio.iscoroutinefunction(handler): | ||||
|                     result = await handler(features) | ||||
|                 else: | ||||
|                     result = handler(features) | ||||
|                 if result and restart: | ||||
|                     # Don't continue if the feature requires | ||||
|                     # restarting the XML stream. | ||||
|                     return True | ||||
|   | ||||
| @@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP): | ||||
|                       Defaults to ``False``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, jid, secret, host=None, port=None, | ||||
|                  plugin_config={}, plugin_whitelist=[], use_jc_ns=False): | ||||
|     def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False): | ||||
|  | ||||
|         if not plugin_whitelist: | ||||
|             plugin_whitelist = [] | ||||
|         if not plugin_config: | ||||
|             plugin_config = {} | ||||
|  | ||||
|         if use_jc_ns: | ||||
|             default_ns = 'jabber:client' | ||||
|         else: | ||||
| @@ -55,7 +60,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|         BaseXMPP.__init__(self, jid, default_ns) | ||||
|  | ||||
|         self.auto_authorize = None | ||||
|         self.stream_header = "<stream:stream %s %s to='%s'>" % ( | ||||
|         self.stream_header = '<stream:stream %s %s to="%s">' % ( | ||||
|                 'xmlns="jabber:component:accept"', | ||||
|                 'xmlns:stream="%s"' % self.stream_ns, | ||||
|                 jid) | ||||
| @@ -68,6 +73,8 @@ class ComponentXMPP(BaseXMPP): | ||||
|         self.plugin_whitelist = plugin_whitelist | ||||
|         self.is_component = True | ||||
|  | ||||
|         self.sessionstarted = False | ||||
|  | ||||
|         self.register_handler( | ||||
|                 Callback('Handshake', | ||||
|                          MatchXPath('{jabber:component:accept}handshake'), | ||||
| @@ -75,12 +82,9 @@ class ComponentXMPP(BaseXMPP): | ||||
|         self.add_event_handler('presence_probe', | ||||
|                                self._handle_probe) | ||||
|  | ||||
|     def connect(self, host=None, port=None, use_ssl=False, | ||||
|                       use_tls=False, reattempt=True): | ||||
|     def connect(self, host=None, port=None, use_ssl=False): | ||||
|         """Connect to the server. | ||||
|  | ||||
|         Setting ``reattempt`` to ``True`` will cause connection attempts to | ||||
|         be made every second until a successful connection is established. | ||||
|  | ||||
|         :param host: The name of the desired server for the connection. | ||||
|                      Defaults to :attr:`server_host`. | ||||
| @@ -88,11 +92,6 @@ class ComponentXMPP(BaseXMPP): | ||||
|                      Defauts to :attr:`server_port`. | ||||
|         :param use_ssl: Flag indicating if SSL should be used by connecting | ||||
|                         directly to a port using SSL. | ||||
|         :param use_tls: Flag indicating if TLS should be used, allowing for | ||||
|                         connecting to a port without using SSL immediately and | ||||
|                         later upgrading the connection. | ||||
|         :param reattempt: Flag indicating if the socket should reconnect | ||||
|                           after disconnections. | ||||
|         """ | ||||
|         if host is None: | ||||
|             host = self.server_host | ||||
| @@ -101,14 +100,9 @@ class ComponentXMPP(BaseXMPP): | ||||
|  | ||||
|         self.server_name = self.boundjid.host | ||||
|  | ||||
|         if use_tls: | ||||
|             log.info("XEP-0114 components can not use TLS") | ||||
|  | ||||
|         log.debug("Connecting to %s:%s", host, port) | ||||
|         return XMLStream.connect(self, host=host, port=port, | ||||
|                                        use_ssl=use_ssl, | ||||
|                                        use_tls=False, | ||||
|                                        reattempt=reattempt) | ||||
|                                        use_ssl=use_ssl) | ||||
|  | ||||
|     def incoming_filter(self, xml): | ||||
|         """ | ||||
| @@ -145,7 +139,7 @@ class ComponentXMPP(BaseXMPP): | ||||
|         :param xml: The reply handshake stanza. | ||||
|         """ | ||||
|         self.session_bind_event.set() | ||||
|         self.session_started_event.set() | ||||
|         self.sessionstarted = True | ||||
|         self.event('session_bind', self.boundjid) | ||||
|         self.event('session_start') | ||||
|  | ||||
|   | ||||
| @@ -56,6 +56,18 @@ class XMPPError(Exception): | ||||
|         self.extension_ns = extension_ns | ||||
|         self.extension_args = extension_args | ||||
|  | ||||
|     def format(self): | ||||
|         """ | ||||
|         Format the error in a simple user-readable string. | ||||
|         """ | ||||
|         text = [self.etype, self.condition] | ||||
|         if self.text: | ||||
|             text.append(self.text) | ||||
|         if self.extension: | ||||
|             text.append(self.extension) | ||||
|         # TODO: handle self.extension_args | ||||
|         return ': '.join(text) | ||||
|  | ||||
|  | ||||
| class IqTimeout(XMPPError): | ||||
|  | ||||
| @@ -65,7 +77,7 @@ class IqTimeout(XMPPError): | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, iq): | ||||
|         super(IqTimeout, self).__init__( | ||||
|         super().__init__( | ||||
|                 condition='remote-server-timeout', | ||||
|                 etype='cancel') | ||||
|  | ||||
| @@ -82,7 +94,7 @@ class IqError(XMPPError): | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, iq): | ||||
|         super(IqError, self).__init__( | ||||
|         super().__init__( | ||||
|                 condition=iq['error']['condition'], | ||||
|                 text=iq['error']['text'], | ||||
|                 etype=iq['error']['type']) | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_bind.stanza import Bind | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureBind) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_bind = FeatureBind | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
|  | ||||
| from slixmpp.jid import JID | ||||
| @@ -34,7 +35,7 @@ class FeatureBind(BasePlugin): | ||||
|         register_stanza_plugin(Iq, stanza.Bind) | ||||
|         register_stanza_plugin(StreamFeatures, stanza.Bind) | ||||
|  | ||||
|     def _handle_bind_resource(self, features): | ||||
|     async def _handle_bind_resource(self, features): | ||||
|         """ | ||||
|         Handle requesting a specific resource. | ||||
|  | ||||
| @@ -49,10 +50,10 @@ class FeatureBind(BasePlugin): | ||||
|         if self.xmpp.requested_jid.resource: | ||||
|             iq['bind']['resource'] = self.xmpp.requested_jid.resource | ||||
|  | ||||
|         iq.send(callback=self._on_bind_response) | ||||
|         await iq.send(callback=self._on_bind_response) | ||||
|  | ||||
|     def _on_bind_response(self, response): | ||||
|         self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True) | ||||
|         self.xmpp.boundjid = JID(response['bind']['jid']) | ||||
|         self.xmpp.bound = True | ||||
|         self.xmpp.event('session_bind', self.xmpp.boundjid) | ||||
|         self.xmpp.session_bind_event.set() | ||||
|   | ||||
| @@ -16,6 +16,6 @@ class Bind(ElementBase): | ||||
|  | ||||
|     name = 'bind' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-bind' | ||||
|     interfaces = set(('resource', 'jid')) | ||||
|     interfaces = {'resource', 'jid'} | ||||
|     sub_interfaces = interfaces | ||||
|     plugin_attrib = 'bind' | ||||
|   | ||||
| @@ -16,7 +16,3 @@ from slixmpp.features.feature_mechanisms.stanza import Failure | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureMechanisms) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_mechanisms = FeatureMechanisms | ||||
|   | ||||
| @@ -49,7 +49,7 @@ class FeatureMechanisms(BasePlugin): | ||||
|         if self.security_callback is None: | ||||
|             self.security_callback = self._default_security | ||||
|  | ||||
|         creds = self.sasl_callback(set(['username']), set()) | ||||
|         creds = self.sasl_callback({'username'}, set()) | ||||
|         if not self.use_mech and not creds['username']: | ||||
|             self.use_mech = 'ANONYMOUS' | ||||
|  | ||||
| @@ -97,12 +97,9 @@ class FeatureMechanisms(BasePlugin): | ||||
|                 jid = self.xmpp.requested_jid.bare | ||||
|                 result[value] = creds.get('email', jid) | ||||
|             elif value == 'channel_binding': | ||||
|                 if hasattr(self.xmpp.socket, 'get_channel_binding'): | ||||
|                 if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)): | ||||
|                     result[value] = self.xmpp.socket.get_channel_binding() | ||||
|                 else: | ||||
|                     log.debug("Channel binding not supported.") | ||||
|                     log.debug("Use Python 3.3+ for channel binding and " + \ | ||||
|                               "SCRAM-SHA-1-PLUS support") | ||||
|                     result[value] = None | ||||
|             elif value == 'host': | ||||
|                 result[value] = creds.get('host', self.xmpp.requested_jid.domain) | ||||
| @@ -122,7 +119,7 @@ class FeatureMechanisms(BasePlugin): | ||||
|             if value == 'encrypted': | ||||
|                 if 'starttls' in self.xmpp.features: | ||||
|                     result[value] = True | ||||
|                 elif isinstance(self.xmpp.socket, ssl.SSLSocket): | ||||
|                 elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)): | ||||
|                     result[value] = True | ||||
|                 else: | ||||
|                     result[value] = False | ||||
| @@ -190,14 +187,14 @@ class FeatureMechanisms(BasePlugin): | ||||
|         except sasl.SASLCancelled: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         except sasl.SASLFailed: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         except sasl.SASLFailed: | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self._send_auth() | ||||
|         else: | ||||
|             resp.send() | ||||
|  | ||||
| @@ -210,13 +207,13 @@ class FeatureMechanisms(BasePlugin): | ||||
|             resp['value'] = self.mech.process(stanza['value']) | ||||
|         except sasl.SASLCancelled: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLFailed: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         except sasl.SASLMutualAuthFailed: | ||||
|             log.error("Mutual authentication failed! " + \ | ||||
|                       "A security breach is possible.") | ||||
|             self.attempted_mechs.add(self.mech.name) | ||||
|             self.xmpp.disconnect() | ||||
|         except sasl.SASLFailed: | ||||
|             self.stanza.Abort(self.xmpp).send() | ||||
|         else: | ||||
|             if resp.get_value() == '': | ||||
|                 resp.del_value() | ||||
|   | ||||
| @@ -19,12 +19,12 @@ class Auth(StanzaBase): | ||||
|  | ||||
|     name = 'auth' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set(('mechanism', 'value')) | ||||
|     interfaces = {'mechanism', 'value'} | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     #: Some SASL mechs require sending values as is, | ||||
|     #: without converting base64. | ||||
|     plain_mechs = set(['X-MESSENGER-OAUTH2']) | ||||
|     plain_mechs = {'X-MESSENGER-OAUTH2'} | ||||
|  | ||||
|     def setup(self, xml): | ||||
|         StanzaBase.setup(self, xml) | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class Challenge(StanzaBase): | ||||
|  | ||||
|     name = 'challenge' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set(('value',)) | ||||
|     interfaces = {'value'} | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     def setup(self, xml): | ||||
|   | ||||
| @@ -16,13 +16,14 @@ class Failure(StanzaBase): | ||||
|  | ||||
|     name = 'failure' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set(('condition', 'text')) | ||||
|     interfaces = {'condition', 'text'} | ||||
|     plugin_attrib = name | ||||
|     sub_interfaces = set(('text',)) | ||||
|     conditions = set(('aborted', 'account-disabled', 'credentials-expired', | ||||
|         'encryption-required', 'incorrect-encoding', 'invalid-authzid', | ||||
|         'invalid-mechanism', 'malformed-request', 'mechansism-too-weak', | ||||
|         'not-authorized', 'temporary-auth-failure')) | ||||
|     sub_interfaces = {'text'} | ||||
|     conditions = {'aborted', 'account-disabled', 'credentials-expired', | ||||
|                   'encryption-required', 'incorrect-encoding', | ||||
|                   'invalid-authzid', 'invalid-mechanism', 'malformed-request', | ||||
|                   'mechansism-too-weak', 'not-authorized', | ||||
|                   'temporary-auth-failure'} | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         """ | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class Mechanisms(ElementBase): | ||||
|  | ||||
|     name = 'mechanisms' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set(('mechanisms', 'required')) | ||||
|     interfaces = {'mechanisms', 'required'} | ||||
|     plugin_attrib = name | ||||
|     is_extension = True | ||||
|  | ||||
| @@ -29,7 +29,7 @@ class Mechanisms(ElementBase): | ||||
|         """ | ||||
|         """ | ||||
|         results = [] | ||||
|         mechs = self.findall('{%s}mechanism' % self.namespace) | ||||
|         mechs = self.xml.findall('{%s}mechanism' % self.namespace) | ||||
|         if mechs: | ||||
|             for mech in mechs: | ||||
|                 results.append(mech.text) | ||||
| @@ -47,7 +47,7 @@ class Mechanisms(ElementBase): | ||||
|     def del_mechanisms(self): | ||||
|         """ | ||||
|         """ | ||||
|         mechs = self.findall('{%s}mechanism' % self.namespace) | ||||
|         mechs = self.xml.findall('{%s}mechanism' % self.namespace) | ||||
|         if mechs: | ||||
|             for mech in mechs: | ||||
|                 self.xml.remove(mech) | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class Response(StanzaBase): | ||||
|  | ||||
|     name = 'response' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set(('value',)) | ||||
|     interfaces = {'value'} | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     def setup(self, xml): | ||||
|   | ||||
| @@ -18,7 +18,7 @@ class Success(StanzaBase): | ||||
|  | ||||
|     name = 'success' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' | ||||
|     interfaces = set(['value']) | ||||
|     interfaces = {'value'} | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     def setup(self, xml): | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_rosterver.stanza import RosterVer | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureRosterVer) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_rosterver = FeatureRosterVer | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_session.stanza import Session | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureSession) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_session = FeatureSession | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| import asyncio | ||||
| import logging | ||||
|  | ||||
| from slixmpp.stanza import Iq, StreamFeatures | ||||
| @@ -34,17 +35,22 @@ class FeatureSession(BasePlugin): | ||||
|         register_stanza_plugin(Iq, stanza.Session) | ||||
|         register_stanza_plugin(StreamFeatures, stanza.Session) | ||||
|  | ||||
|     def _handle_start_session(self, features): | ||||
|     async def _handle_start_session(self, features): | ||||
|         """ | ||||
|         Handle the start of the session. | ||||
|  | ||||
|         Arguments: | ||||
|             feature -- The stream features element. | ||||
|         """ | ||||
|         if features['session']['optional']: | ||||
|             self.xmpp.sessionstarted = True | ||||
|             self.xmpp.event('session_start') | ||||
|             return | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq.enable('session') | ||||
|         iq.send(callback=self._on_start_session_response) | ||||
|         await iq.send(callback=self._on_start_session_response) | ||||
|  | ||||
|     def _on_start_session_response(self, response): | ||||
|         self.xmpp.features.add('session') | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.xmlstream import ElementBase | ||||
| from slixmpp.xmlstream import ElementBase, ET | ||||
|  | ||||
|  | ||||
| class Session(ElementBase): | ||||
| @@ -16,5 +16,19 @@ class Session(ElementBase): | ||||
|  | ||||
|     name = 'session' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-session' | ||||
|     interfaces = set() | ||||
|     interfaces = {'optional'} | ||||
|     plugin_attrib = 'session' | ||||
|  | ||||
|     def get_optional(self): | ||||
|         return self.xml.find('{%s}optional' % self.namespace) is not None | ||||
|  | ||||
|     def set_optional(self, value): | ||||
|         if value: | ||||
|             optional = ET.Element('{%s}optional' % self.namespace) | ||||
|             self.xml.append(optional) | ||||
|         else: | ||||
|             self.del_optional() | ||||
|  | ||||
|     def del_optional(self): | ||||
|         optional = self.xml.find('{%s}optional' % self.namespace) | ||||
|         self.xml.remove(optional) | ||||
|   | ||||
| @@ -13,7 +13,3 @@ from slixmpp.features.feature_starttls.stanza import * | ||||
|  | ||||
|  | ||||
| register_plugin(FeatureSTARTTLS) | ||||
|  | ||||
|  | ||||
| # Retain some backwards compatibility | ||||
| feature_starttls = FeatureSTARTTLS | ||||
|   | ||||
| @@ -16,7 +16,7 @@ class STARTTLS(ElementBase): | ||||
|  | ||||
|     name = 'starttls' | ||||
|     namespace = 'urn:ietf:params:xml:ns:xmpp-tls' | ||||
|     interfaces = set(('required',)) | ||||
|     interfaces = {'required'} | ||||
|     plugin_attrib = name | ||||
|  | ||||
|     def get_required(self): | ||||
|   | ||||
| @@ -12,7 +12,7 @@ from slixmpp.stanza import StreamFeatures | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
| from slixmpp.plugins import BasePlugin | ||||
| from slixmpp.xmlstream.matcher import MatchXPath | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| from slixmpp.xmlstream.handler import CoroutineCallback | ||||
| from slixmpp.features.feature_starttls import stanza | ||||
|  | ||||
|  | ||||
| @@ -28,7 +28,7 @@ class FeatureSTARTTLS(BasePlugin): | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         self.xmpp.register_handler( | ||||
|                 Callback('STARTTLS Proceed', | ||||
|                 CoroutineCallback('STARTTLS Proceed', | ||||
|                         MatchXPath(stanza.Proceed.tag_name()), | ||||
|                         self._handle_starttls_proceed, | ||||
|                         instream=True)) | ||||
| @@ -58,8 +58,8 @@ class FeatureSTARTTLS(BasePlugin): | ||||
|             self.xmpp.send(features['starttls']) | ||||
|             return True | ||||
|  | ||||
|     def _handle_starttls_proceed(self, proceed): | ||||
|     async def _handle_starttls_proceed(self, proceed): | ||||
|         """Restart the XML stream when TLS is accepted.""" | ||||
|         log.debug("Starting TLS") | ||||
|         if self.xmpp.start_tls(): | ||||
|         if await self.xmpp.start_tls(): | ||||
|             self.xmpp.features.add('starttls') | ||||
|   | ||||
							
								
								
									
										533
									
								
								slixmpp/jid.py
									
									
									
									
									
								
							
							
						
						
									
										533
									
								
								slixmpp/jid.py
									
									
									
									
									
								
							| @@ -11,24 +11,16 @@ | ||||
|     :license: MIT, see LICENSE for more details | ||||
| """ | ||||
|  | ||||
| from __future__ import unicode_literals | ||||
|  | ||||
| import re | ||||
| import socket | ||||
| import stringprep | ||||
| import threading | ||||
| import encodings.idna | ||||
|  | ||||
| from copy import deepcopy | ||||
| from functools import lru_cache | ||||
| from typing import Optional | ||||
|  | ||||
| from slixmpp.util import stringprep_profiles | ||||
| from collections import OrderedDict | ||||
| from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError | ||||
|  | ||||
| #: These characters are not allowed to appear in a JID. | ||||
| ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \ | ||||
|                 '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \ | ||||
|                 '\x1a\x1b\x1c\x1d\x1e\x1f' + \ | ||||
|                 ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f' | ||||
| HAVE_INET_PTON = hasattr(socket, 'inet_pton') | ||||
|  | ||||
| #: The basic regex pattern that a JID must match in order to determine | ||||
| #: the local, domain, and resource parts. This regex does NOT do any | ||||
| @@ -38,22 +30,8 @@ JID_PATTERN = re.compile( | ||||
| ) | ||||
|  | ||||
| #: The set of escape sequences for the characters not allowed by nodeprep. | ||||
| JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f', | ||||
|                             '\\3a', '\\3c', '\\3e', '\\40', '\\5c']) | ||||
|  | ||||
| #: A mapping of unallowed characters to their escape sequences. An escape | ||||
| #: sequence for '\' is also included since it must also be escaped in | ||||
| #: certain situations. | ||||
| JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20', | ||||
|                               '"': '\\22', | ||||
|                               '&': '\\26', | ||||
|                               "'": '\\27', | ||||
|                               '/': '\\2f', | ||||
|                               ':': '\\3a', | ||||
|                               '<': '\\3c', | ||||
|                               '>': '\\3e', | ||||
|                               '@': '\\40', | ||||
|                               '\\': '\\5c'} | ||||
| JID_ESCAPE_SEQUENCES = {'\\20', '\\22', '\\26', '\\27', '\\2f', | ||||
|                         '\\3a', '\\3c', '\\3e', '\\40', '\\5c'} | ||||
|  | ||||
| #: The reverse mapping of escape sequences to their original forms. | ||||
| JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', | ||||
| @@ -67,70 +45,9 @@ JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', | ||||
|                                 '\\40': '@', | ||||
|                                 '\\5c': '\\'} | ||||
|  | ||||
| JID_CACHE = OrderedDict() | ||||
| JID_CACHE_LOCK = threading.Lock() | ||||
| JID_CACHE_MAX_SIZE = 1024 | ||||
|  | ||||
| def _cache(key, parts, locked): | ||||
|     JID_CACHE[key] = (parts, locked) | ||||
|     if len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|         with JID_CACHE_LOCK: | ||||
|             while len(JID_CACHE) > JID_CACHE_MAX_SIZE: | ||||
|                 found = None | ||||
|                 for key, item in JID_CACHE.items(): | ||||
|                     if not item[1]: # if not locked | ||||
|                         found = key | ||||
|                         break | ||||
|                 if not found: # more than MAX_SIZE locked | ||||
|                     # warn? | ||||
|                     break | ||||
|                 del JID_CACHE[found] | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The nodeprep profile of stringprep used to validate the local, | ||||
| #: or username, portion of a JID. | ||||
| nodeprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[ | ||||
|         stringprep_profiles.b1_mapping, | ||||
|         stringprep.map_table_b2], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c11, | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9, | ||||
|         lambda c: c in ' \'"&/:<>@'], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
| # pylint: disable=c0103 | ||||
| #: The resourceprep profile of stringprep, which is used to validate | ||||
| #: the resource portion of a JID. | ||||
| resourceprep = stringprep_profiles.create( | ||||
|     nfkc=True, | ||||
|     bidi=True, | ||||
|     mappings=[stringprep_profiles.b1_mapping], | ||||
|     prohibited=[ | ||||
|         stringprep.in_table_c12, | ||||
|         stringprep.in_table_c21, | ||||
|         stringprep.in_table_c22, | ||||
|         stringprep.in_table_c3, | ||||
|         stringprep.in_table_c4, | ||||
|         stringprep.in_table_c5, | ||||
|         stringprep.in_table_c6, | ||||
|         stringprep.in_table_c7, | ||||
|         stringprep.in_table_c8, | ||||
|         stringprep.in_table_c9], | ||||
|     unassigned=[stringprep.in_table_a1]) | ||||
|  | ||||
|  | ||||
| # TODO: Find the best cache size for a standard usage. | ||||
| @lru_cache(maxsize=1024) | ||||
| def _parse_jid(data): | ||||
|     """ | ||||
|     Parse string data into the node, domain, and resource | ||||
| @@ -155,27 +72,29 @@ def _parse_jid(data): | ||||
|     return node, domain, resource | ||||
|  | ||||
|  | ||||
| def _validate_node(node): | ||||
| def _validate_node(node: Optional[str]): | ||||
|     """Validate the local, or username, portion of a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by nodeprep. | ||||
|     """ | ||||
|     if node is None: | ||||
|         return '' | ||||
|  | ||||
|     try: | ||||
|         if node is not None: | ||||
|             node = nodeprep(node) | ||||
|         node = nodeprep(node) | ||||
|     except StringprepError: | ||||
|         raise InvalidJID('Nodeprep failed') | ||||
|  | ||||
|             if not node: | ||||
|                 raise InvalidJID('Localpart must not be 0 bytes') | ||||
|             if len(node) > 1023: | ||||
|                 raise InvalidJID('Localpart must be less than 1024 bytes') | ||||
|             return node | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid local part') | ||||
|     if not node: | ||||
|         raise InvalidJID('Localpart must not be 0 bytes') | ||||
|     if len(node) > 1023: | ||||
|         raise InvalidJID('Localpart must be less than 1024 bytes') | ||||
|     return node | ||||
|  | ||||
|  | ||||
| def _validate_domain(domain): | ||||
| def _validate_domain(domain: str): | ||||
|     """Validate the domain portion of a JID. | ||||
|  | ||||
|     IP literal addresses are left as-is, if valid. Domain names | ||||
| @@ -199,10 +118,10 @@ def _validate_domain(domain): | ||||
|         pass | ||||
|  | ||||
|     # Check if this is an IPv6 address | ||||
|     if not ip_addr and hasattr(socket, 'inet_pton'): | ||||
|     if not ip_addr and HAVE_INET_PTON and domain[0] == '[' and domain[-1] == ']': | ||||
|         try: | ||||
|             socket.inet_pton(socket.AF_INET6, domain.strip('[]')) | ||||
|             domain = '[%s]' % domain.strip('[]') | ||||
|             ip = domain[1:-1] | ||||
|             socket.inet_pton(socket.AF_INET6, ip) | ||||
|             ip_addr = True | ||||
|         except (socket.error, ValueError): | ||||
|             pass | ||||
| @@ -213,31 +132,19 @@ def _validate_domain(domain): | ||||
|         if domain and domain[-1] == '.': | ||||
|             domain = domain[:-1] | ||||
|  | ||||
|         domain_parts = [] | ||||
|         try: | ||||
|             domain = idna(domain) | ||||
|         except StringprepError: | ||||
|             raise InvalidJID('idna validation failed') | ||||
|  | ||||
|         if ':' in domain: | ||||
|             raise InvalidJID('Domain containing a port') | ||||
|         for label in domain.split('.'): | ||||
|             try: | ||||
|                 label = encodings.idna.nameprep(label) | ||||
|                 encodings.idna.ToASCII(label) | ||||
|                 pass_nameprep = True | ||||
|             except UnicodeError: | ||||
|                 pass_nameprep = False | ||||
|  | ||||
|             if not pass_nameprep: | ||||
|                 raise InvalidJID('Could not encode domain as ASCII') | ||||
|  | ||||
|             if label.startswith('xn--'): | ||||
|                 label = encodings.idna.ToUnicode(label) | ||||
|  | ||||
|             for char in label: | ||||
|                 if char in ILLEGAL_CHARS: | ||||
|                     raise InvalidJID('Domain contains illegal characters') | ||||
|  | ||||
|             if not label: | ||||
|                 raise InvalidJID('Domain containing too many dots') | ||||
|             if '-' in (label[0], label[-1]): | ||||
|                 raise InvalidJID('Domain started or ended with -') | ||||
|  | ||||
|             domain_parts.append(label) | ||||
|         domain = '.'.join(domain_parts) | ||||
|  | ||||
|     if not domain: | ||||
|         raise InvalidJID('Domain must not be 0 bytes') | ||||
|     if len(domain) > 1023: | ||||
| @@ -246,52 +153,29 @@ def _validate_domain(domain): | ||||
|     return domain | ||||
|  | ||||
|  | ||||
| def _validate_resource(resource): | ||||
| def _validate_resource(resource: Optional[str]): | ||||
|     """Validate the resource portion of a JID. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|  | ||||
|     :returns: The local portion of a JID, as validated by resourceprep. | ||||
|     """ | ||||
|     if resource is None: | ||||
|         return '' | ||||
|  | ||||
|     try: | ||||
|         if resource is not None: | ||||
|             resource = resourceprep(resource) | ||||
|         resource = resourceprep(resource) | ||||
|     except StringprepError: | ||||
|         raise InvalidJID('Resourceprep failed') | ||||
|  | ||||
|             if not resource: | ||||
|                 raise InvalidJID('Resource must not be 0 bytes') | ||||
|             if len(resource) > 1023: | ||||
|                 raise InvalidJID('Resource must be less than 1024 bytes') | ||||
|             return resource | ||||
|     except stringprep_profiles.StringPrepError: | ||||
|         raise InvalidJID('Invalid resource') | ||||
|     if not resource: | ||||
|         raise InvalidJID('Resource must not be 0 bytes') | ||||
|     if len(resource) > 1023: | ||||
|         raise InvalidJID('Resource must be less than 1024 bytes') | ||||
|     return resource | ||||
|  | ||||
|  | ||||
| def _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): | ||||
| def _unescape_node(node: str): | ||||
|     """Unescape a local portion of a JID. | ||||
|  | ||||
|     .. note:: | ||||
| @@ -313,12 +197,14 @@ def _unescape_node(node): | ||||
|             seq = seq[1:] | ||||
|         else: | ||||
|             unescaped.append(char) | ||||
|     unescaped = ''.join(unescaped) | ||||
|  | ||||
|     return unescaped | ||||
|     return ''.join(unescaped) | ||||
|  | ||||
|  | ||||
| def _format_jid(local=None, domain=None, resource=None): | ||||
| def _format_jid( | ||||
|         local: Optional[str] = None, | ||||
|         domain: Optional[str] = None, | ||||
|         resource: Optional[str] = None, | ||||
|     ): | ||||
|     """Format the given JID components into a full or bare JID. | ||||
|  | ||||
|     :param string local: Optional. The local portion of the JID. | ||||
| @@ -327,16 +213,15 @@ def _format_jid(local=None, domain=None, resource=None): | ||||
|  | ||||
|     :return: A full or bare JID string. | ||||
|     """ | ||||
|     result = [] | ||||
|     if local: | ||||
|         result.append(local) | ||||
|         result.append('@') | ||||
|     if domain: | ||||
|         result.append(domain) | ||||
|     if resource: | ||||
|         result.append('/') | ||||
|         result.append(resource) | ||||
|     return ''.join(result) | ||||
|     if domain is None: | ||||
|         return '' | ||||
|     if local is not None: | ||||
|         result = local + '@' + domain | ||||
|     else: | ||||
|         result = domain | ||||
|     if resource is not None: | ||||
|         result += '/' + resource | ||||
|     return result | ||||
|  | ||||
|  | ||||
| class InvalidJID(ValueError): | ||||
| @@ -349,47 +234,52 @@ class InvalidJID(ValueError): | ||||
|     """ | ||||
|  | ||||
| # pylint: disable=R0903 | ||||
| class UnescapedJID(object): | ||||
| class UnescapedJID: | ||||
|  | ||||
|     """ | ||||
|     .. versionadded:: 1.1.10 | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, local, domain, resource): | ||||
|         self._jid = (local, domain, resource) | ||||
|     __slots__ = ('_node', '_domain', '_resource') | ||||
|  | ||||
|     # pylint: disable=R0911 | ||||
|     def __getattr__(self, name): | ||||
|     def __init__( | ||||
|             self, | ||||
|             node: Optional[str], | ||||
|             domain: Optional[str], | ||||
|             resource: Optional[str], | ||||
|         ): | ||||
|         self._node = node | ||||
|         self._domain = domain | ||||
|         self._resource = resource | ||||
|  | ||||
|     def __getattribute__(self, name: str): | ||||
|         """Retrieve the given JID component. | ||||
|  | ||||
|         :param name: one of: user, server, domain, resource, | ||||
|                      full, or bare. | ||||
|         """ | ||||
|         if name == 'resource': | ||||
|             return self._jid[2] or '' | ||||
|         elif name in ('user', 'username', 'local', 'node'): | ||||
|             return self._jid[0] or '' | ||||
|         elif name in ('server', 'domain', 'host'): | ||||
|             return self._jid[1] or '' | ||||
|         elif name in ('full', 'jid'): | ||||
|             return _format_jid(*self._jid) | ||||
|         elif name == 'bare': | ||||
|             return _format_jid(self._jid[0], self._jid[1]) | ||||
|         elif name == '_jid': | ||||
|             return getattr(super(JID, self), '_jid') | ||||
|         else: | ||||
|             return None | ||||
|             return self._resource or '' | ||||
|         if name in ('user', 'username', 'local', 'node'): | ||||
|             return self._node or '' | ||||
|         if name in ('server', 'domain', 'host'): | ||||
|             return self._domain or '' | ||||
|         if name in ('full', 'jid'): | ||||
|             return _format_jid(self._node, self._domain, self._resource) | ||||
|         if name == 'bare': | ||||
|             return _format_jid(self._node, self._domain) | ||||
|         return object.__getattribute__(self, name) | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|         return _format_jid(self._node, self._domain, self._resource) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|         return _format_jid(self._node, self._domain, self._resource) | ||||
|  | ||||
|  | ||||
| class JID(object): | ||||
| class JID: | ||||
|  | ||||
|     """ | ||||
|     A representation of a Jabber ID, or JID. | ||||
| @@ -401,13 +291,13 @@ class JID(object): | ||||
|     The JID is a full JID otherwise. | ||||
|  | ||||
|     **JID Properties:** | ||||
|         :jid: Alias for ``full``. | ||||
|         :full: The string value of the full JID. | ||||
|         :jid: Alias for ``full``. | ||||
|         :bare: The string value of the bare JID. | ||||
|         :user: The username portion of the JID. | ||||
|         :username: Alias for ``user``. | ||||
|         :local: Alias for ``user``. | ||||
|         :node: Alias for ``user``. | ||||
|         :node: The node portion of the JID. | ||||
|         :user: Alias for ``node``. | ||||
|         :local: Alias for ``node``. | ||||
|         :username: Alias for ``node``. | ||||
|         :domain: The domain name portion of the JID. | ||||
|         :server: Alias for ``domain``. | ||||
|         :host: Alias for ``domain``. | ||||
| @@ -415,67 +305,27 @@ class JID(object): | ||||
|  | ||||
|     :param string jid: | ||||
|         A string of the form ``'[user@]domain[/resource]'``. | ||||
|     :param string local: | ||||
|         Optional. Specify the local, or username, portion | ||||
|         of the JID. If provided, it will override the local | ||||
|         value provided by the `jid` parameter. The given | ||||
|         local value will also be escaped if necessary. | ||||
|     :param string domain: | ||||
|         Optional. Specify the domain of the JID. If | ||||
|         provided, it will override the domain given by | ||||
|         the `jid` parameter. | ||||
|     :param string resource: | ||||
|         Optional. Specify the resource value of the JID. | ||||
|         If provided, it will override the domain given | ||||
|         by the `jid` parameter. | ||||
|  | ||||
|     :raises InvalidJID: | ||||
|     """ | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __init__(self, jid=None, **kwargs): | ||||
|         locked = kwargs.get('cache_lock', False) | ||||
|         in_local = kwargs.get('local', None) | ||||
|         in_domain = kwargs.get('domain', None) | ||||
|         in_resource = kwargs.get('resource', None) | ||||
|         parts = None | ||||
|         if in_local or in_domain or in_resource: | ||||
|             parts = (in_local, in_domain, in_resource) | ||||
|     __slots__ = ('_node', '_domain', '_resource', '_bare', '_full') | ||||
|  | ||||
|         # only check cache if there is a jid string, or parts, not if there | ||||
|         # are both | ||||
|         self._jid = None | ||||
|         key = None | ||||
|         if (jid is not None) and (parts is None): | ||||
|             if isinstance(jid, JID): | ||||
|                 # it's already good to go, and there are no additions | ||||
|                 self._jid = jid._jid | ||||
|                 return | ||||
|             key = jid | ||||
|             self._jid, locked = JID_CACHE.get(jid, (None, locked)) | ||||
|         elif jid is None and parts is not None: | ||||
|             key = parts | ||||
|             self._jid, locked = JID_CACHE.get(parts, (None, locked)) | ||||
|         if not self._jid: | ||||
|             if not jid: | ||||
|                 parsed_jid = (None, None, None) | ||||
|             elif not isinstance(jid, JID): | ||||
|                 parsed_jid = _parse_jid(jid) | ||||
|             else: | ||||
|                 parsed_jid = jid._jid | ||||
|  | ||||
|             local, domain, resource = parsed_jid | ||||
|  | ||||
|             if 'local' in kwargs: | ||||
|                 local = _escape_node(in_local) | ||||
|             if 'domain' in kwargs: | ||||
|                 domain = _validate_domain(in_domain) | ||||
|             if 'resource' in kwargs: | ||||
|                 resource = _validate_resource(in_resource) | ||||
|  | ||||
|             self._jid = (local, domain, resource) | ||||
|             if key: | ||||
|                 _cache(key, self._jid, locked) | ||||
|     def __init__(self, jid: Optional[str] = None): | ||||
|         if not jid: | ||||
|             self._node = '' | ||||
|             self._domain = '' | ||||
|             self._resource = '' | ||||
|             self._bare = '' | ||||
|             self._full = '' | ||||
|             return | ||||
|         elif not isinstance(jid, JID): | ||||
|             self._node, self._domain, self._resource = _parse_jid(jid) | ||||
|         else: | ||||
|             self._node = jid._node | ||||
|             self._domain = jid._domain | ||||
|             self._resource = jid._resource | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     def unescape(self): | ||||
|         """Return an unescaped JID object. | ||||
| @@ -488,151 +338,104 @@ class JID(object): | ||||
|  | ||||
|         .. versionadded:: 1.1.10 | ||||
|         """ | ||||
|         return UnescapedJID(_unescape_node(self._jid[0]), | ||||
|                             self._jid[1], | ||||
|                             self._jid[2]) | ||||
|         return UnescapedJID(_unescape_node(self._node), | ||||
|                             self._domain, | ||||
|                             self._resource) | ||||
|  | ||||
|     def regenerate(self): | ||||
|         """No-op | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|     def _update_bare_full(self): | ||||
|         """Format the given JID into a bare and a full JID. | ||||
|         """ | ||||
|         pass | ||||
|  | ||||
|     def reset(self, data): | ||||
|         """Start fresh from a new JID string. | ||||
|  | ||||
|         :param string data: A string of the form ``'[user@]domain[/resource]'``. | ||||
|  | ||||
|         .. deprecated:: 1.1.10 | ||||
|         """ | ||||
|         self._jid = JID(data)._jid | ||||
|  | ||||
|     @property | ||||
|     def resource(self): | ||||
|         return self._jid[2] or '' | ||||
|  | ||||
|     @property | ||||
|     def user(self): | ||||
|         return self._jid[0] or '' | ||||
|  | ||||
|     @property | ||||
|     def local(self): | ||||
|         return self._jid[0] or '' | ||||
|         self._bare = (self._node + '@' + self._domain | ||||
|                       if self._node | ||||
|                       else self._domain) | ||||
|         self._full = (self._bare + '/' + self._resource | ||||
|                       if self._resource | ||||
|                       else self._bare) | ||||
|  | ||||
|     @property | ||||
|     def node(self): | ||||
|         return self._jid[0] or '' | ||||
|  | ||||
|     @property | ||||
|     def username(self): | ||||
|         return self._jid[0] or '' | ||||
|  | ||||
|     @property | ||||
|     def bare(self): | ||||
|         return _format_jid(self._jid[0], self._jid[1]) | ||||
|  | ||||
|     @property | ||||
|     def server(self): | ||||
|         return self._jid[1] or '' | ||||
|         return self._node | ||||
|  | ||||
|     @property | ||||
|     def domain(self): | ||||
|         return self._jid[1] or '' | ||||
|         return self._domain | ||||
|  | ||||
|     @property | ||||
|     def host(self): | ||||
|         return self._jid[1] or '' | ||||
|  | ||||
|     @property | ||||
|     def full(self): | ||||
|         return _format_jid(*self._jid) | ||||
|  | ||||
|     @property | ||||
|     def jid(self): | ||||
|         return _format_jid(*self._jid) | ||||
|     def resource(self): | ||||
|         return self._resource | ||||
|  | ||||
|     @property | ||||
|     def bare(self): | ||||
|         return _format_jid(self._jid[0], self._jid[1]) | ||||
|         return self._bare | ||||
|  | ||||
|  | ||||
|     @resource.setter | ||||
|     def resource(self, value): | ||||
|         self._jid = JID(self, resource=value)._jid | ||||
|  | ||||
|     @user.setter | ||||
|     def user(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @username.setter | ||||
|     def username(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @local.setter | ||||
|     def local(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|     @property | ||||
|     def full(self): | ||||
|         return self._full | ||||
|  | ||||
|     @node.setter | ||||
|     def node(self, value): | ||||
|         self._jid = JID(self, local=value)._jid | ||||
|  | ||||
|     @server.setter | ||||
|     def server(self, value): | ||||
|         self._jid = JID(self, domain=value)._jid | ||||
|     def node(self, value: str): | ||||
|         self._node = _validate_node(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @domain.setter | ||||
|     def domain(self, value): | ||||
|         self._jid = JID(self, domain=value)._jid | ||||
|  | ||||
|     @host.setter | ||||
|     def host(self, value): | ||||
|         self._jid = JID(self, domain=value)._jid | ||||
|  | ||||
|     @full.setter | ||||
|     def full(self, value): | ||||
|         self._jid = JID(value)._jid | ||||
|  | ||||
|     @jid.setter | ||||
|     def jid(self, value): | ||||
|         self._jid = JID(value)._jid | ||||
|     def domain(self, value: str): | ||||
|         self._domain = _validate_domain(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @bare.setter | ||||
|     def bare(self, value): | ||||
|         parsed = JID(value)._jid | ||||
|         self._jid = (parsed[0], parsed[1], self._jid[2]) | ||||
|     def bare(self, value: str): | ||||
|         node, domain, resource = _parse_jid(value) | ||||
|         assert not resource | ||||
|         self._node = node | ||||
|         self._domain = domain | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @resource.setter | ||||
|     def resource(self, value: str): | ||||
|         self._resource = _validate_resource(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     @full.setter | ||||
|     def full(self, value: str): | ||||
|         self._node, self._domain, self._resource = _parse_jid(value) | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     user = node | ||||
|     local = node | ||||
|     username = node | ||||
|  | ||||
|     server = domain | ||||
|     host = domain | ||||
|  | ||||
|     jid = full | ||||
|  | ||||
|     def __str__(self): | ||||
|         """Use the full JID as the string value.""" | ||||
|         return _format_jid(*self._jid) | ||||
|         return self._full | ||||
|  | ||||
|     def __repr__(self): | ||||
|         """Use the full JID as the representation.""" | ||||
|         return self.__str__() | ||||
|         return self._full | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __eq__(self, other): | ||||
|         """Two JIDs are equal if they have the same full JID value.""" | ||||
|         if isinstance(other, UnescapedJID): | ||||
|             return False | ||||
|         if not isinstance(other, JID): | ||||
|             try: | ||||
|                 other = JID(other) | ||||
|             except InvalidJID: | ||||
|                 return NotImplemented | ||||
|  | ||||
|         other = JID(other) | ||||
|         return self._jid == other._jid | ||||
|         return (self._node == other._node and | ||||
|                 self._domain == other._domain and | ||||
|                 self._resource == other._resource) | ||||
|  | ||||
|     # pylint: disable=W0212 | ||||
|     def __ne__(self, other): | ||||
|         """Two JIDs are considered unequal if they are not equal.""" | ||||
|         return not self == other | ||||
|  | ||||
|     def __hash__(self): | ||||
|         """Hash a JID based on the string version of its full JID.""" | ||||
|         return hash(self.__str__()) | ||||
|  | ||||
|     def __copy__(self): | ||||
|         """Generate a duplicate JID.""" | ||||
|         return JID(self) | ||||
|  | ||||
|     def __deepcopy__(self, memo): | ||||
|         """Generate a duplicate JID.""" | ||||
|         return JID(deepcopy(str(self), memo)) | ||||
|         return hash(self._full) | ||||
|   | ||||
| @@ -47,6 +47,7 @@ __all__ = [ | ||||
|     'xep_0108',  # User Activity | ||||
|     'xep_0115',  # Entity Capabilities | ||||
|     'xep_0118',  # User Tune | ||||
|     'xep_0122',  # Data Forms Validation | ||||
|     'xep_0128',  # Extended Service Discovery | ||||
|     'xep_0131',  # Standard Headers and Internet Metadata | ||||
|     'xep_0133',  # Service Administration | ||||
| @@ -83,4 +84,7 @@ __all__ = [ | ||||
|     'xep_0319',  # Last User Interaction in Presence | ||||
|     'xep_0323',  # IoT Systems Sensor Data | ||||
|     'xep_0325',  # IoT Systems Control | ||||
|     'xep_0332',  # HTTP Over XMPP Transport | ||||
|     'protoxep_reactions',  # https://dino.im/xeps/reactions.html | ||||
|     'protoxep_occupantid',  # https://dino.im/xeps/occupant-id.html | ||||
| ] | ||||
|   | ||||
| @@ -142,7 +142,6 @@ class PluginManager(object): | ||||
|         :param dict config: Optional settings dictionary for | ||||
|                             configuring plugin behaviour. | ||||
|         """ | ||||
|         top_level = False | ||||
|         if enabled is None: | ||||
|             enabled = set() | ||||
|  | ||||
| @@ -166,14 +165,14 @@ class PluginManager(object): | ||||
|                     self.enable(dep, enabled=enabled) | ||||
|                 plugin._init() | ||||
|  | ||||
|         if top_level: | ||||
|             for name in enabled: | ||||
|                 if hasattr(self.plugins[name], 'old_style'): | ||||
|                     # Older style plugins require post_init() | ||||
|                     # to run just before stream processing begins, | ||||
|                     # so we don't call it here. | ||||
|                     pass | ||||
|                 self.plugins[name].post_init() | ||||
|         for name in enabled: | ||||
|             if hasattr(self._plugins[name], 'old_style'): | ||||
|                 # Older style plugins require post_init() | ||||
|                 # to run just before stream processing begins, | ||||
|                 # so we don't call it here. | ||||
|                 pass | ||||
|             else: | ||||
|                 self._plugins[name].post_init() | ||||
|  | ||||
|     def enable_all(self, names=None, config=None): | ||||
|         """Enable all registered plugins. | ||||
| @@ -309,7 +308,7 @@ class BasePlugin(object): | ||||
|         if key in self.default_config: | ||||
|             self.config[key] = value | ||||
|         else: | ||||
|             super(BasePlugin, self).__setattr__(key, value) | ||||
|             super().__setattr__(key, value) | ||||
|  | ||||
|     def _init(self): | ||||
|         """Initialize plugin state, such as registering event handlers. | ||||
|   | ||||
| @@ -21,15 +21,15 @@ class GmailQuery(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'query' | ||||
|     plugin_attrib = 'gmail' | ||||
|     interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) | ||||
|     interfaces = {'newer-than-time', 'newer-than-tid', 'q', 'search'} | ||||
|  | ||||
|     def getSearch(self): | ||||
|     def get_search(self): | ||||
|         return self['q'] | ||||
|  | ||||
|     def setSearch(self, search): | ||||
|     def set_search(self, search): | ||||
|         self['q'] = search | ||||
|  | ||||
|     def delSearch(self): | ||||
|     def del_search(self): | ||||
|         del self['q'] | ||||
|  | ||||
|  | ||||
| @@ -37,20 +37,20 @@ class MailBox(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'mailbox' | ||||
|     plugin_attrib = 'mailbox' | ||||
|     interfaces = set(('result-time', 'total-matched', 'total-estimate', | ||||
|                       'url', 'threads', 'matched', 'estimate')) | ||||
|     interfaces = {'result-time', 'total-matched', 'total-estimate', | ||||
|                   'url', 'threads', 'matched', 'estimate'} | ||||
|  | ||||
|     def getThreads(self): | ||||
|     def get_threads(self): | ||||
|         threads = [] | ||||
|         for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, | ||||
|                                                       MailThread.name)): | ||||
|             threads.append(MailThread(xml=threadXML, parent=None)) | ||||
|         return threads | ||||
|  | ||||
|     def getMatched(self): | ||||
|     def get_matched(self): | ||||
|         return self['total-matched'] | ||||
|  | ||||
|     def getEstimate(self): | ||||
|     def get_estimate(self): | ||||
|         return self['total-estimate'] == '1' | ||||
|  | ||||
|  | ||||
| @@ -58,11 +58,11 @@ class MailThread(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'mail-thread-info' | ||||
|     plugin_attrib = 'thread' | ||||
|     interfaces = set(('tid', 'participation', 'messages', 'date', | ||||
|                       'senders', 'url', 'labels', 'subject', 'snippet')) | ||||
|     sub_interfaces = set(('labels', 'subject', 'snippet')) | ||||
|     interfaces = {'tid', 'participation', 'messages', 'date', | ||||
|                   'senders', 'url', 'labels', 'subject', 'snippet'} | ||||
|     sub_interfaces = {'labels', 'subject', 'snippet'} | ||||
|  | ||||
|     def getSenders(self): | ||||
|     def get_senders(self): | ||||
|         senders = [] | ||||
|         sendersXML = self.xml.find('{%s}senders' % self.namespace) | ||||
|         if sendersXML is not None: | ||||
| @@ -75,12 +75,12 @@ class MailSender(ElementBase): | ||||
|     namespace = 'google:mail:notify' | ||||
|     name = 'sender' | ||||
|     plugin_attrib = 'sender' | ||||
|     interfaces = set(('address', 'name', 'originator', 'unread')) | ||||
|     interfaces = {'address', 'name', 'originator', 'unread'} | ||||
|  | ||||
|     def getOriginator(self): | ||||
|     def get_originator(self): | ||||
|         return self.xml.attrib.get('originator', '0') == '1' | ||||
|  | ||||
|     def getUnread(self): | ||||
|     def get_unread(self): | ||||
|         return self.xml.attrib.get('unread', '0') == '1' | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										47
									
								
								slixmpp/plugins/google/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								slixmpp/plugins/google/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.plugins.base import register_plugin, BasePlugin | ||||
|  | ||||
| from slixmpp.plugins.google.gmail import Gmail | ||||
| from slixmpp.plugins.google.auth import GoogleAuth | ||||
| from slixmpp.plugins.google.settings import GoogleSettings | ||||
| from slixmpp.plugins.google.nosave import GoogleNoSave | ||||
|  | ||||
|  | ||||
| class Google(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     Google: Custom GTalk Features | ||||
|  | ||||
|     Also see: <https://developers.google.com/talk/jep_extensions/extensions> | ||||
|     """ | ||||
|  | ||||
|     name = 'google' | ||||
|     description = 'Google: Custom GTalk Features' | ||||
|     dependencies = set([ | ||||
|         'gmail', | ||||
|         'google_settings', | ||||
|         'google_nosave', | ||||
|         'google_auth' | ||||
|     ]) | ||||
|  | ||||
|     def __getitem__(self, attr): | ||||
|         if attr in ('settings', 'nosave', 'auth'): | ||||
|             return self.xmpp['google_%s' % attr] | ||||
|         elif attr == 'gmail': | ||||
|             return self.xmpp['gmail'] | ||||
|         else: | ||||
|             raise KeyError(attr) | ||||
|  | ||||
|  | ||||
| register_plugin(Gmail) | ||||
| register_plugin(GoogleAuth) | ||||
| register_plugin(GoogleSettings) | ||||
| register_plugin(GoogleNoSave) | ||||
| register_plugin(Google) | ||||
							
								
								
									
										10
									
								
								slixmpp/plugins/google/auth/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								slixmpp/plugins/google/auth/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| """ | ||||
|     Slixmpp: The Slick XMPP Library | ||||
|     Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout | ||||
|     This file is part of slixmpp. | ||||
|  | ||||
|     See the file LICENSE for copying permission. | ||||
| """ | ||||
|  | ||||
| from slixmpp.plugins.google.auth import stanza | ||||
| from slixmpp.plugins.google.auth.auth import GoogleAuth | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user