Compare commits
	
		
			376 Commits
		
	
	
		
			sleek-1.0-
			...
			sleek-1.1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					0e36a01354 | ||
| 
						 | 
					c39ad7dfbb | ||
| 
						 | 
					b92ae706e9 | ||
| 
						 | 
					6997261c6b | ||
| 
						 | 
					6cfb5cb14c | ||
| 
						 | 
					8567d6034f | ||
| 
						 | 
					e06368f8cd | ||
| 
						 | 
					4b37a4706f | ||
| 
						 | 
					7b1564947d | ||
| 
						 | 
					f5652a667b | ||
| 
						 | 
					3b2c865a58 | ||
| 
						 | 
					db0e683d01 | ||
| 
						 | 
					e29a9e0394 | ||
| 
						 | 
					edf65f4f52 | ||
| 
						 | 
					98677fd602 | ||
| 
						 | 
					61a4f76c8d | ||
| 
						 | 
					856a826eea | ||
| 
						 | 
					387ef513d6 | ||
| 
						 | 
					2858dbf57f | ||
| 
						 | 
					350a2b8bbc | ||
| 
						 | 
					c9093c9972 | ||
| 
						 | 
					d1ad31696e | ||
| 
						 | 
					f49311ef9e | ||
| 
						 | 
					678e529efc | ||
| 
						 | 
					6ddb430fef | ||
| 
						 | 
					74d1f88146 | ||
| 
						 | 
					7842c55da3 | ||
| 
						 | 
					f5beac2afa | ||
| 
						 | 
					8a23f28dfa | ||
| 
						 | 
					9c4886e746 | ||
| 
						 | 
					e0bcd5d722 | ||
| 
						 | 
					ba854e7d85 | ||
| 
						 | 
					4ded34ebc9 | ||
| 
						 | 
					e918a86028 | ||
| 
						 | 
					24234bf718 | ||
| 
						 | 
					ec99339140 | ||
| 
						 | 
					03dedfc871 | ||
| 
						 | 
					9e86a7b357 | ||
| 
						 | 
					6a32417957 | ||
| 
						 | 
					97a7be7dfa | ||
| 
						 | 
					fa86f956ef | ||
| 
						 | 
					a9acff5294 | ||
| 
						 | 
					ad5b61de50 | ||
| 
						 | 
					f53b815855 | ||
| 
						 | 
					bf8a9dc20d | ||
| 
						 | 
					08716c35fd | ||
| 
						 | 
					fd81bab906 | ||
| 
						 | 
					1cf55c14b0 | ||
| 
						 | 
					8b47159788 | ||
| 
						 | 
					2eeaf4d80c | ||
| 
						 | 
					4d89d26a1c | ||
| 
						 | 
					0cc14cee4d | ||
| 
						 | 
					a20a9c505d | ||
| 
						 | 
					913738444e | ||
| 
						 | 
					8ee30179ea | ||
| 
						 | 
					cb2469322b | ||
| 
						 | 
					94aa6673ca | ||
| 
						 | 
					4b2b2d16b8 | ||
| 
						 | 
					4cd5d3b3b5 | ||
| 
						 | 
					e48e50c6ff | ||
| 
						 | 
					01189376e2 | ||
| 
						 | 
					60195cf2dc | ||
| 
						 | 
					15ef273141 | ||
| 
						 | 
					eed6da538a | ||
| 
						 | 
					d3e8993e22 | ||
| 
						 | 
					8a8926c5e8 | ||
| 
						 | 
					f9d0ee824b | ||
| 
						 | 
					af099737ab | ||
| 
						 | 
					9ffde5ab37 | ||
| 
						 | 
					272ddf9f01 | ||
| 
						 | 
					259c84e99a | ||
| 
						 | 
					7391288668 | ||
| 
						 | 
					7734aee7ad | ||
| 
						 | 
					9f855b9679 | ||
| 
						 | 
					aedbecd673 | ||
| 
						 | 
					83c5a4cd2f | ||
| 
						 | 
					9c61c2882f | ||
| 
						 | 
					e0dd9c3618 | ||
| 
						 | 
					4921c44d0a | ||
| 
						 | 
					3161f104c7 | ||
| 
						 | 
					898f5f4b51 | ||
| 
						 | 
					3ee3fdca91 | ||
| 
						 | 
					488f7ed886 | ||
| 
						 | 
					51e5aee830 | ||
| 
						 | 
					af13bea2b8 | ||
| 
						 | 
					cdf0b353db | ||
| 
						 | 
					48504ed5e2 | ||
| 
						 | 
					4d4d1e0ee5 | ||
| 
						 | 
					c1d36cad46 | ||
| 
						 | 
					aad2eb31fc | ||
| 
						 | 
					1bd7824f24 | ||
| 
						 | 
					912463ed6a | ||
| 
						 | 
					dda2473d35 | ||
| 
						 | 
					94923ae898 | ||
| 
						 | 
					f1fde07eb9 | ||
| 
						 | 
					a1ddd88208 | ||
| 
						 | 
					ee6a9b981a | ||
| 
						 | 
					9879c7af59 | ||
| 
						 | 
					fa4c52e499 | ||
| 
						 | 
					d5484808a7 | ||
| 
						 | 
					1c83391948 | ||
| 
						 | 
					59d1b8e131 | ||
| 
						 | 
					859822ff05 | ||
| 
						 | 
					3acc7d0914 | ||
| 
						 | 
					b077ef9150 | ||
| 
						 | 
					e2ce5ae222 | ||
| 
						 | 
					73cabcb6ae | ||
| 
						 | 
					fbdf2bed49 | ||
| 
						 | 
					33d01fb694 | ||
| 
						 | 
					ab2e43d052 | ||
| 
						 | 
					0c24fbdb06 | ||
| 
						 | 
					eb25998e72 | ||
| 
						 | 
					eafd2aee93 | ||
| 
						 | 
					a6f3d740a2 | ||
| 
						 | 
					19a6f61b44 | ||
| 
						 | 
					58e0f1e6c3 | ||
| 
						 | 
					96ff2d43c0 | ||
| 
						 | 
					1b00b7e8df | ||
| 
						 | 
					7284ceb90c | ||
| 
						 | 
					24ec448b7f | ||
| 
						 | 
					ed5a2f400d | ||
| 
						 | 
					9596616b42 | ||
| 
						 | 
					8d38fb511b | ||
| 
						 | 
					5a2cbbb731 | ||
| 
						 | 
					32d6f85649 | ||
| 
						 | 
					a2b47e5749 | ||
| 
						 | 
					14d4062f4a | ||
| 
						 | 
					67972c5e84 | ||
| 
						 | 
					3467ac18cc | ||
| 
						 | 
					cabf27424f | ||
| 
						 | 
					162e955bd6 | ||
| 
						 | 
					57d761b8a2 | ||
| 
						 | 
					8b2023225c | ||
| 
						 | 
					f8f2b541db | ||
| 
						 | 
					9d645ad5cd | ||
| 
						 | 
					610d366bdb | ||
| 
						 | 
					64c46562d3 | ||
| 
						 | 
					87d6ade06d | ||
| 
						 | 
					4a009515c1 | ||
| 
						 | 
					6497857495 | ||
| 
						 | 
					5a324c01de | ||
| 
						 | 
					17279de4a3 | ||
| 
						 | 
					34a7a62c35 | ||
| 
						 | 
					2305cc61fd | ||
| 
						 | 
					2f677c98f8 | ||
| 
						 | 
					3fda053606 | ||
| 
						 | 
					6d855ec06c | ||
| 
						 | 
					23cc62fe7c | ||
| 
						 | 
					26ea67d211 | ||
| 
						 | 
					d43cd9fa54 | ||
| 
						 | 
					6f337b5425 | ||
| 
						 | 
					d104a5fe75 | ||
| 
						 | 
					cdd69c6842 | ||
| 
						 | 
					4a3a9067d4 | ||
| 
						 | 
					1aecb2293a | ||
| 
						 | 
					ad8fd91b7a | ||
| 
						 | 
					1f5a3a4445 | ||
| 
						 | 
					be363e0b46 | ||
| 
						 | 
					a104cd6dae | ||
| 
						 | 
					e287282782 | ||
| 
						 | 
					8b06d10415 | ||
| 
						 | 
					1a153487c3 | ||
| 
						 | 
					01b2499915 | ||
| 
						 | 
					9f43d31bf5 | ||
| 
						 | 
					a318beded4 | ||
| 
						 | 
					5f4b528e6b | ||
| 
						 | 
					f759b0ada1 | ||
| 
						 | 
					7d89fa27a8 | ||
| 
						 | 
					10ec92f7c6 | ||
| 
						 | 
					58d2f317a0 | ||
| 
						 | 
					34b094561f | ||
| 
						 | 
					91155444c0 | ||
| 
						 | 
					7f71ac7e0a | ||
| 
						 | 
					e5fc59a4c6 | ||
| 
						 | 
					549a9ab472 | ||
| 
						 | 
					09720dcf42 | ||
| 
						 | 
					ec044affd4 | ||
| 
						 | 
					af39945009 | ||
| 
						 | 
					78a50d0237 | ||
| 
						 | 
					861d279b08 | ||
| 
						 | 
					eb1a32fc90 | ||
| 
						 | 
					4610a6615c | ||
| 
						 | 
					4cb8a8d389 | ||
| 
						 | 
					a71823dc04 | ||
| 
						 | 
					d41ada6b66 | ||
| 
						 | 
					fdfe2cd64f | ||
| 
						 | 
					7b51c6f5cc | ||
| 
						 | 
					be7f07ad12 | ||
| 
						 | 
					830db11b41 | ||
| 
						 | 
					53bcd33e1d | ||
| 
						 | 
					e3d596c9fa | ||
| 
						 | 
					ecd6ad6930 | ||
| 
						 | 
					c36073b40e | ||
| 
						 | 
					afe0d16797 | ||
| 
						 | 
					977fcc0632 | ||
| 
						 | 
					94b57d232d | ||
| 
						 | 
					7cdedb2ec0 | ||
| 
						 | 
					676324805e | ||
| 
						 | 
					7d74a7b027 | ||
| 
						 | 
					9d5eb864d1 | ||
| 
						 | 
					86a482e032 | ||
| 
						 | 
					c43c7be86c | ||
| 
						 | 
					c58462f154 | ||
| 
						 | 
					31d3e3b2b6 | ||
| 
						 | 
					fb2582e53b | ||
| 
						 | 
					d807613117 | ||
| 
						 | 
					6d922d00c3 | ||
| 
						 | 
					61ea84093b | ||
| 
						 | 
					e76d6a481f | ||
| 
						 | 
					c1357717d9 | ||
| 
						 | 
					ca5145c210 | ||
| 
						 | 
					1a272fd276 | ||
| 
						 | 
					952260b423 | ||
| 
						 | 
					caa967105c | ||
| 
						 | 
					d565e4be20 | ||
| 
						 | 
					85dd005abc | ||
| 
						 | 
					021c57205f | ||
| 
						 | 
					261a501afc | ||
| 
						 | 
					9a38a101d2 | ||
| 
						 | 
					4665c5cf1a | ||
| 
						 | 
					bd52a5e6c1 | ||
| 
						 | 
					f98e5a03de | ||
| 
						 | 
					2217c69757 | ||
| 
						 | 
					5a4df56836 | ||
| 
						 | 
					3ab7c8bcc3 | ||
| 
						 | 
					8f25acd0f3 | ||
| 
						 | 
					999f1932cc | ||
| 
						 | 
					69940a8ab9 | ||
| 
						 | 
					13158e3cdf | ||
| 
						 | 
					f06589c913 | ||
| 
						 | 
					2735b680b9 | ||
| 
						 | 
					5f1d4ce433 | ||
| 
						 | 
					25f87607aa | ||
| 
						 | 
					f81fb6af44 | ||
| 
						 | 
					bb0a5186d6 | ||
| 
						 | 
					baad907422 | ||
| 
						 | 
					1022fc0060 | ||
| 
						 | 
					3a22d798f8 | ||
| 
						 | 
					71ea430c62 | ||
| 
						 | 
					0d2125e737 | ||
| 
						 | 
					02f4006153 | ||
| 
						 | 
					b25668b5b7 | ||
| 
						 | 
					bb3080e829 | ||
| 
						 | 
					bd85e95398 | ||
| 
						 | 
					22cc194ed8 | ||
| 
						 | 
					79b71228c1 | ||
| 
						 | 
					fd515d807c | ||
| 
						 | 
					4f4c121d9b | ||
| 
						 | 
					72e1ab47fc | ||
| 
						 | 
					3575084640 | ||
| 
						 | 
					1e01903072 | ||
| 
						 | 
					3672856ab4 | ||
| 
						 | 
					86d8736dcc | ||
| 
						 | 
					2923f56561 | ||
| 
						 | 
					4274f49ada | ||
| 
						 | 
					a4b27ff031 | ||
| 
						 | 
					f49b6fa79f | ||
| 
						 | 
					7b854a190e | ||
| 
						 | 
					947d1ffbb3 | ||
| 
						 | 
					de35848500 | ||
| 
						 | 
					1ae219025a | ||
| 
						 | 
					e8b2dd6698 | ||
| 
						 | 
					c0074f95b1 | ||
| 
						 | 
					a79ce1c35e | ||
| 
						 | 
					1eb69f7075 | ||
| 
						 | 
					a86935a42f | ||
| 
						 | 
					1674bd753e | ||
| 
						 | 
					6b9a55e62d | ||
| 
						 | 
					c578ddeb1a | ||
| 
						 | 
					8ef7188dae | ||
| 
						 | 
					738ec92b8e | ||
| 
						 | 
					be9e26b4a3 | ||
| 
						 | 
					b345c227b2 | ||
| 
						 | 
					c7e95c8dec | ||
| 
						 | 
					3a4e3d3f51 | ||
| 
						 | 
					8fd2efa2fa | ||
| 
						 | 
					97378998a5 | ||
| 
						 | 
					79f1aa0e1b | ||
| 
						 | 
					fb5a6a7d71 | ||
| 
						 | 
					7d1c5f4a2b | ||
| 
						 | 
					6b6995bb0b | ||
| 
						 | 
					27c658922e | ||
| 
						 | 
					1b0fd76b45 | ||
| 
						 | 
					35954cdc90 | ||
| 
						 | 
					46e93bea09 | ||
| 
						 | 
					cbc6a0296b | ||
| 
						 | 
					cc63bef179 | ||
| 
						 | 
					cbcfa156c4 | ||
| 
						 | 
					fa912aeb84 | ||
| 
						 | 
					4a12e1059a | ||
| 
						 | 
					9a5e2ae768 | ||
| 
						 | 
					f9cd051209 | ||
| 
						 | 
					e0545bf0bc | ||
| 
						 | 
					03bc38f7e3 | ||
| 
						 | 
					4e23a4e08e | ||
| 
						 | 
					d817d64c65 | ||
| 
						 | 
					8a29ec67ac | ||
| 
						 | 
					6722b0224a | ||
| 
						 | 
					8eb225bdec | ||
| 
						 | 
					a7df76a275 | ||
| 
						 | 
					efae8f3369 | ||
| 
						 | 
					a11e6c0b77 | ||
| 
						 | 
					1bb0b38868 | ||
| 
						 | 
					8cafa8578f | ||
| 
						 | 
					b74ea47650 | ||
| 
						 | 
					2dc230a68b | ||
| 
						 | 
					4df1641689 | ||
| 
						 | 
					5ef0b96d5c | ||
| 
						 | 
					d979b5f2b9 | ||
| 
						 | 
					1a61bdb302 | ||
| 
						 | 
					e8545dd2bc | ||
| 
						 | 
					2f2ebb37e4 | ||
| 
						 | 
					522f0dac16 | ||
| 
						 | 
					cd5ae944ec | ||
| 
						 | 
					42a86fe0d4 | ||
| 
						 | 
					e928b9c434 | ||
| 
						 | 
					fb55d9e9d1 | ||
| 
						 | 
					74e7e5a291 | ||
| 
						 | 
					6c58b8cc4b | ||
| 
						 | 
					2b3d11a7a5 | ||
| 
						 | 
					9950208d06 | ||
| 
						 | 
					a67e16d1b7 | ||
| 
						 | 
					c98a22e065 | ||
| 
						 | 
					8f9d1bcfe0 | ||
| 
						 | 
					a7a2fd1d5b | ||
| 
						 | 
					d496417deb | ||
| 
						 | 
					f6e30edbc4 | ||
| 
						 | 
					45ed68006f | ||
| 
						 | 
					dcb0d8b00e | ||
| 
						 | 
					116bb6e1b9 | ||
| 
						 | 
					9c6dde5d22 | ||
| 
						 | 
					cb635dcd5a | ||
| 
						 | 
					eff3330e75 | ||
| 
						 | 
					fc8a13df5a | ||
| 
						 | 
					85e9042db6 | ||
| 
						 | 
					62e6d6fb4c | ||
| 
						 | 
					16c72e8efd | ||
| 
						 | 
					efe1b9f5a9 | ||
| 
						 | 
					65dbddb6b6 | ||
| 
						 | 
					2a67a31120 | ||
| 
						 | 
					a720c3348b | ||
| 
						 | 
					79ac60b6e8 | ||
| 
						 | 
					e01c2d222a | ||
| 
						 | 
					8922e2050a | ||
| 
						 | 
					a85891c611 | ||
| 
						 | 
					2586fdffda | ||
| 
						 | 
					c9dc9ec11e | ||
| 
						 | 
					b9332142c9 | ||
| 
						 | 
					b7b53362e1 | ||
| 
						 | 
					68cf66a5fe | ||
| 
						 | 
					4eb7eeb40f | ||
| 
						 | 
					a1d64fa215 | ||
| 
						 | 
					5f44c0e678 | ||
| 
						 | 
					b87c4d786d | ||
| 
						 | 
					329b0df3f6 | ||
| 
						 | 
					6906c15e8e | ||
| 
						 | 
					ff5421cefc | ||
| 
						 | 
					4498e992a2 | ||
| 
						 | 
					2d610dfdc8 | ||
| 
						 | 
					2b0a05ee32 | ||
| 
						 | 
					bc2d0ee9a8 | ||
| 
						 | 
					862a2a1440 | ||
| 
						 | 
					fba60ffff1 | ||
| 
						 | 
					d1a945a305 | ||
| 
						 | 
					685b9ab102 | ||
| 
						 | 
					24f27c0fe3 | ||
| 
						 | 
					3019c82d8a | ||
| 
						 | 
					f9d0b55ca3 | ||
| 
						 | 
					b54cc97e4c | ||
| 
						 | 
					e3b9d5abbf | ||
| 
						 | 
					2332970cf2 | ||
| 
						 | 
					48af3d3322 | ||
| 
						 | 
					429c94d6a9 | ||
| 
						 | 
					deb52ad350 | ||
| 
						 | 
					6f3cc77bb5 | ||
| 
						 | 
					1baf139ca4 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -6,3 +6,4 @@ docs/_build/
 | 
			
		||||
*.swp
 | 
			
		||||
.tox/
 | 
			
		||||
.coverage
 | 
			
		||||
sleekxmpp.egg-info/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								LICENSE
									
									
									
									
									
								
							@@ -138,3 +138,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
python-gnupg: A Python wrapper for the GNU Privacy Guard
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
Copyright (c) 2008-2012 by Vinay Sajip.
 | 
			
		||||
All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 | 
			
		||||
    * Redistributions of source code must retain the above copyright notice,
 | 
			
		||||
      this list of conditions and the following disclaimer.
 | 
			
		||||
    * Redistributions in binary form must reproduce the above copyright notice,
 | 
			
		||||
      this list of conditions and the following disclaimer in the documentation
 | 
			
		||||
      and/or other materials provided with the distribution.
 | 
			
		||||
    * The name(s) of the copyright holder(s) may not be used to endorse or
 | 
			
		||||
      promote products derived from this software without specific prior
 | 
			
		||||
      written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) "AS IS" AND ANY EXPRESS OR
 | 
			
		||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 | 
			
		||||
EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
 | 
			
		||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 | 
			
		||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 | 
			
		||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 | 
			
		||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 | 
			
		||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								README.rst
									
									
									
									
									
								
							@@ -44,20 +44,15 @@ The latest source code for SleekXMPP may be found on `Github
 | 
			
		||||
``master`` branch, while the latest development version is in the
 | 
			
		||||
``develop`` branch.
 | 
			
		||||
 | 
			
		||||
**Stable Releases**
 | 
			
		||||
    - `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_  
 | 
			
		||||
    - `1.0 RC2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC2>`_  
 | 
			
		||||
    - `1.0 RC1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC1>`_  
 | 
			
		||||
    - `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_  
 | 
			
		||||
    - `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_
 | 
			
		||||
    - `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_
 | 
			
		||||
    - `1.0 Beta3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta3>`_
 | 
			
		||||
    - `1.0 Beta2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta2>`_
 | 
			
		||||
    - `1.0 Beta1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta1>`_
 | 
			
		||||
**Latest Release**
 | 
			
		||||
    - `1.1.4 <http://github.com/fritzy/SleekXMPP/zipball/1.1.4>`_
 | 
			
		||||
 | 
			
		||||
**Develop Releases**
 | 
			
		||||
    - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
 | 
			
		||||
 | 
			
		||||
**Older Stable Releases**
 | 
			
		||||
    - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_
 | 
			
		||||
 | 
			
		||||
Installing DNSPython
 | 
			
		||||
---------------------
 | 
			
		||||
If you are using Python3 and wish to use dnspython, you will have to checkout and
 | 
			
		||||
@@ -115,8 +110,12 @@ SleekXMPP projects::
 | 
			
		||||
            self.add_event_handler("session_start", self.session_start)
 | 
			
		||||
            self.add_event_handler("message", self.message)
 | 
			
		||||
 | 
			
		||||
            self.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
            self.register_plugin('xep_0199') # XMPP Ping
 | 
			
		||||
            # If you wanted more functionality, here's how to register plugins:
 | 
			
		||||
            # self.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
            # self.register_plugin('xep_0199') # XMPP Ping
 | 
			
		||||
 | 
			
		||||
            # Here's how to access plugins once you've registered them:
 | 
			
		||||
            # self['xep_0030'].add_feature('echo_demo')
 | 
			
		||||
 | 
			
		||||
            # If you are working with an OpenFire server, you will
 | 
			
		||||
            # need to use a different SSL version:
 | 
			
		||||
@@ -125,18 +124,20 @@ SleekXMPP projects::
 | 
			
		||||
 | 
			
		||||
        def session_start(self, event):
 | 
			
		||||
            self.send_presence()
 | 
			
		||||
            self.get_roster()
 | 
			
		||||
 | 
			
		||||
            # Most get_*/set_* methods from plugins use Iq stanzas, which
 | 
			
		||||
            # can generate IqError and IqTimeout exceptions
 | 
			
		||||
            try:
 | 
			
		||||
                self.get_roster()
 | 
			
		||||
            except IqError as err:
 | 
			
		||||
                logging.error('There was an error getting the roster')
 | 
			
		||||
                logging.error(err.iq['error']['condition'])
 | 
			
		||||
                self.disconnect()
 | 
			
		||||
            except IqTimeout:
 | 
			
		||||
                logging.error('Server is taking too long to respond')
 | 
			
		||||
                self.disconnect()
 | 
			
		||||
            #
 | 
			
		||||
            # try:
 | 
			
		||||
            #     self.get_roster()
 | 
			
		||||
            # except IqError as err:
 | 
			
		||||
            #     logging.error('There was an error getting the roster')
 | 
			
		||||
            #     logging.error(err.iq['error']['condition'])
 | 
			
		||||
            #     self.disconnect()
 | 
			
		||||
            # except IqTimeout:
 | 
			
		||||
            #     logging.error('Server is taking too long to respond')
 | 
			
		||||
            #     self.disconnect()
 | 
			
		||||
 | 
			
		||||
        def message(self, msg):
 | 
			
		||||
            if msg['type'] in ('chat', 'normal'):
 | 
			
		||||
@@ -151,7 +152,7 @@ SleekXMPP projects::
 | 
			
		||||
                            format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
        xmpp = EchoBot('somejid@example.com', 'use_getpass')
 | 
			
		||||
        xmpp.connect():
 | 
			
		||||
        xmpp.connect()
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -162,8 +163,8 @@ Credits
 | 
			
		||||
    `@fritzy <http://twitter.com/fritzy>`_
 | 
			
		||||
 | 
			
		||||
    Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
 | 
			
		||||
    <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP
 | 
			
		||||
    Council.
 | 
			
		||||
    <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of 
 | 
			
		||||
    the XMPP Council.
 | 
			
		||||
 | 
			
		||||
**Co-Author:** Lance Stout
 | 
			
		||||
    `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, 
 | 
			
		||||
 
 | 
			
		||||
@@ -1,171 +0,0 @@
 | 
			
		||||
import logging
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
import sys
 | 
			
		||||
import unittest
 | 
			
		||||
import sleekxmpp.plugins.xep_0004
 | 
			
		||||
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler.waiter import Waiter
 | 
			
		||||
try:
 | 
			
		||||
	import configparser
 | 
			
		||||
except ImportError:
 | 
			
		||||
	import ConfigParser as configparser
 | 
			
		||||
try:
 | 
			
		||||
	import queue
 | 
			
		||||
except ImportError:
 | 
			
		||||
	import Queue as queue
 | 
			
		||||
 | 
			
		||||
class TestClient(sleekxmpp.ClientXMPP):
 | 
			
		||||
	def __init__(self, jid, password):
 | 
			
		||||
		sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
		self.add_event_handler("session_start", self.start)
 | 
			
		||||
		#self.add_event_handler("message", self.message)
 | 
			
		||||
		self.waitforstart = queue.Queue()
 | 
			
		||||
	
 | 
			
		||||
	def start(self, event):
 | 
			
		||||
		self.getRoster()
 | 
			
		||||
		self.sendPresence()
 | 
			
		||||
		self.waitforstart.put(True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPubsubServer(unittest.TestCase):
 | 
			
		||||
	statev = {}
 | 
			
		||||
 | 
			
		||||
	def __init__(self, *args, **kwargs):
 | 
			
		||||
		unittest.TestCase.__init__(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
	def setUp(self):
 | 
			
		||||
		pass
 | 
			
		||||
 | 
			
		||||
	def test001getdefaultconfig(self):
 | 
			
		||||
		"""Get the default node config"""
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2')
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3')
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4')
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5')
 | 
			
		||||
		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost)
 | 
			
		||||
		self.statev['defaultconfig'] = result
 | 
			
		||||
		self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form))
 | 
			
		||||
	
 | 
			
		||||
	def test002createdefaultnode(self):
 | 
			
		||||
		"""Create a node without config"""
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1'))
 | 
			
		||||
 | 
			
		||||
	def test003deletenode(self):
 | 
			
		||||
		"""Delete recently created node"""
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1'))
 | 
			
		||||
	
 | 
			
		||||
	def test004createnode(self):
 | 
			
		||||
		"""Create a node with a config"""
 | 
			
		||||
		self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open')
 | 
			
		||||
		self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True)
 | 
			
		||||
		self.statev['defaultconfig'].field['pubsub#persist_items'].setValue(True)
 | 
			
		||||
		self.statev['defaultconfig'].field['pubsub#presence_based_delivery'].setValue(True)
 | 
			
		||||
		p = self.xmpp2.Presence()
 | 
			
		||||
		p['to'] = self.pshost
 | 
			
		||||
		p.send()
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig'], ntype='job'))
 | 
			
		||||
	
 | 
			
		||||
	def test005reconfigure(self):
 | 
			
		||||
		"""Retrieving node config and reconfiguring"""
 | 
			
		||||
		nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2')
 | 
			
		||||
		self.failUnless(nconfig, "No configuration returned")
 | 
			
		||||
		#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues()))
 | 
			
		||||
		self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match")
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig))
 | 
			
		||||
 | 
			
		||||
	def test006subscribetonode(self):
 | 
			
		||||
		"""Subscribe to node from account 2"""
 | 
			
		||||
		self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2"))
 | 
			
		||||
	
 | 
			
		||||
	def test007publishitem(self):
 | 
			
		||||
		"""Publishing item"""
 | 
			
		||||
		item = ET.Element('{http://netflint.net/protocol/test}test')
 | 
			
		||||
		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items'))
 | 
			
		||||
		self.xmpp2.registerHandler(w)
 | 
			
		||||
		#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),))
 | 
			
		||||
		result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test1', item)
 | 
			
		||||
		msg = w.wait(5) # got to get a result in 5 seconds
 | 
			
		||||
		self.failUnless(msg != False, "Account #2 did not get message event")
 | 
			
		||||
		#result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),))
 | 
			
		||||
		result = self.xmpp1['jobs'].createJob(self.pshost, "testnode2", 'test2', item)
 | 
			
		||||
		w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items'))
 | 
			
		||||
		self.xmpp2.registerHandler(w)
 | 
			
		||||
		self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test1')
 | 
			
		||||
		msg = w.wait(5) # got to get a result in 5 seconds
 | 
			
		||||
		self.xmpp2['jobs'].claimJob(self.pshost, 'testnode2', 'test2')
 | 
			
		||||
		self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test1')
 | 
			
		||||
		self.xmpp2['jobs'].finishJob(self.pshost, 'testnode2', 'test2')
 | 
			
		||||
		print result
 | 
			
		||||
		#need to add check for update
 | 
			
		||||
 | 
			
		||||
	def test900cleanup(self):
 | 
			
		||||
		"Cleaning up"
 | 
			
		||||
		#self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.")
 | 
			
		||||
		time.sleep(10)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
	#parse command line arguements
 | 
			
		||||
	optp = OptionParser()
 | 
			
		||||
	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO)
 | 
			
		||||
	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
 | 
			
		||||
	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use")
 | 
			
		||||
	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use")
 | 
			
		||||
	opts,args = optp.parse_args()
 | 
			
		||||
	
 | 
			
		||||
	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
	#load xml config
 | 
			
		||||
	logging.info("Loading config file: %s" % opts.configfile)
 | 
			
		||||
	config = configparser.RawConfigParser()
 | 
			
		||||
	config.read(opts.configfile)
 | 
			
		||||
	
 | 
			
		||||
	#init
 | 
			
		||||
	logging.info("Account 1 is %s" % config.get('account1', 'jid'))
 | 
			
		||||
	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
 | 
			
		||||
	logging.info("Account 2 is %s" % config.get('account2', 'jid'))
 | 
			
		||||
	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
 | 
			
		||||
	
 | 
			
		||||
	xmpp1.registerPlugin('xep_0004')
 | 
			
		||||
	xmpp1.registerPlugin('xep_0030')
 | 
			
		||||
	xmpp1.registerPlugin('xep_0060')
 | 
			
		||||
	xmpp1.registerPlugin('xep_0199')
 | 
			
		||||
	xmpp1.registerPlugin('jobs')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0004')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0030')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0060')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0199')
 | 
			
		||||
	xmpp2.registerPlugin('jobs')
 | 
			
		||||
 | 
			
		||||
	if not config.get('account1', 'server'):
 | 
			
		||||
		# we don't know the server, but the lib can probably figure it out
 | 
			
		||||
		xmpp1.connect() 
 | 
			
		||||
	else:
 | 
			
		||||
		xmpp1.connect((config.get('account1', 'server'), 5222))
 | 
			
		||||
	xmpp1.process(threaded=True)
 | 
			
		||||
	
 | 
			
		||||
	#init
 | 
			
		||||
	if not config.get('account2', 'server'):
 | 
			
		||||
		# we don't know the server, but the lib can probably figure it out
 | 
			
		||||
		xmpp2.connect() 
 | 
			
		||||
	else:
 | 
			
		||||
		xmpp2.connect((config.get('account2', 'server'), 5222))
 | 
			
		||||
	xmpp2.process(threaded=True)
 | 
			
		||||
 | 
			
		||||
	TestPubsubServer.xmpp1 = xmpp1
 | 
			
		||||
	TestPubsubServer.xmpp2 = xmpp2
 | 
			
		||||
	TestPubsubServer.pshost = config.get('settings', 'pubsub')
 | 
			
		||||
	xmpp1.waitforstart.get(True)
 | 
			
		||||
	xmpp2.waitforstart.get(True)
 | 
			
		||||
	testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer)
 | 
			
		||||
 | 
			
		||||
	alltests_suite = unittest.TestSuite([testsuite])
 | 
			
		||||
	result = unittest.TextTestRunner(verbosity=2).run(alltests_suite)
 | 
			
		||||
	xmpp1.disconnect()
 | 
			
		||||
	xmpp2.disconnect()
 | 
			
		||||
@@ -1,233 +0,0 @@
 | 
			
		||||
import logging
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
import sys
 | 
			
		||||
import unittest
 | 
			
		||||
import sleekxmpp.plugins.xep_0004
 | 
			
		||||
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler.waiter import Waiter
 | 
			
		||||
try:
 | 
			
		||||
	import configparser
 | 
			
		||||
except ImportError:
 | 
			
		||||
	import ConfigParser as configparser
 | 
			
		||||
try:
 | 
			
		||||
	import queue
 | 
			
		||||
except ImportError:
 | 
			
		||||
	import Queue as queue
 | 
			
		||||
 | 
			
		||||
class TestClient(sleekxmpp.ClientXMPP):
 | 
			
		||||
	def __init__(self, jid, password):
 | 
			
		||||
		sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
		self.add_event_handler("session_start", self.start)
 | 
			
		||||
		#self.add_event_handler("message", self.message)
 | 
			
		||||
		self.waitforstart = queue.Queue()
 | 
			
		||||
	
 | 
			
		||||
	def start(self, event):
 | 
			
		||||
		self.getRoster()
 | 
			
		||||
		self.sendPresence()
 | 
			
		||||
		self.waitforstart.put(True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPubsubServer(unittest.TestCase):
 | 
			
		||||
	statev = {}
 | 
			
		||||
 | 
			
		||||
	def __init__(self, *args, **kwargs):
 | 
			
		||||
		unittest.TestCase.__init__(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
	def setUp(self):
 | 
			
		||||
		pass
 | 
			
		||||
 | 
			
		||||
	def test001getdefaultconfig(self):
 | 
			
		||||
		"""Get the default node config"""
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2')
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3')
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4')
 | 
			
		||||
		self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode5')
 | 
			
		||||
		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost)
 | 
			
		||||
		self.statev['defaultconfig'] = result
 | 
			
		||||
		self.failUnless(isinstance(result, sleekxmpp.plugins.xep_0004.Form))
 | 
			
		||||
	
 | 
			
		||||
	def test002createdefaultnode(self):
 | 
			
		||||
		"""Create a node without config"""
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode1'))
 | 
			
		||||
 | 
			
		||||
	def test003deletenode(self):
 | 
			
		||||
		"""Delete recently created node"""
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode1'))
 | 
			
		||||
	
 | 
			
		||||
	def test004createnode(self):
 | 
			
		||||
		"""Create a node with a config"""
 | 
			
		||||
		self.statev['defaultconfig'].field['pubsub#access_model'].setValue('open')
 | 
			
		||||
		self.statev['defaultconfig'].field['pubsub#notify_retract'].setValue(True)
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode2', self.statev['defaultconfig']))
 | 
			
		||||
	
 | 
			
		||||
	def test005reconfigure(self):
 | 
			
		||||
		"""Retrieving node config and reconfiguring"""
 | 
			
		||||
		nconfig = self.xmpp1['xep_0060'].getNodeConfig(self.pshost, 'testnode2')
 | 
			
		||||
		self.failUnless(nconfig, "No configuration returned")
 | 
			
		||||
		#print("\n%s ==\n %s" % (nconfig.getValues(), self.statev['defaultconfig'].getValues()))
 | 
			
		||||
		self.failUnless(nconfig.getValues() == self.statev['defaultconfig'].getValues(), "Configuration does not match")
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].setNodeConfig(self.pshost, 'testnode2', nconfig))
 | 
			
		||||
 | 
			
		||||
	def test006subscribetonode(self):
 | 
			
		||||
		"""Subscribe to node from account 2"""
 | 
			
		||||
		self.failUnless(self.xmpp2['xep_0060'].subscribe(self.pshost, "testnode2"))
 | 
			
		||||
	
 | 
			
		||||
	def test007publishitem(self):
 | 
			
		||||
		"""Publishing item"""
 | 
			
		||||
		item = ET.Element('{http://netflint.net/protocol/test}test')
 | 
			
		||||
		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items'))
 | 
			
		||||
		self.xmpp2.registerHandler(w)
 | 
			
		||||
		result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),))
 | 
			
		||||
		msg = w.wait(5) # got to get a result in 5 seconds
 | 
			
		||||
		self.failUnless(msg != False, "Account #2 did not get message event")
 | 
			
		||||
		self.failUnless(result)
 | 
			
		||||
		#need to add check for update
 | 
			
		||||
	
 | 
			
		||||
	def test008updateitem(self):
 | 
			
		||||
		"""Updating item"""
 | 
			
		||||
		item = ET.Element('{http://netflint.net/protocol/test}test', {'someattr': 'hi there'})
 | 
			
		||||
		w = Waiter('wait publish', StanzaPath('message/pubsub_event/items'))
 | 
			
		||||
		self.xmpp2.registerHandler(w)
 | 
			
		||||
		result = self.xmpp1['xep_0060'].setItem(self.pshost, "testnode2", (('test1', item),))
 | 
			
		||||
		msg = w.wait(5) # got to get a result in 5 seconds
 | 
			
		||||
		self.failUnless(msg != False, "Account #2 did not get message event")
 | 
			
		||||
		self.failUnless(result)
 | 
			
		||||
		#need to add check for update
 | 
			
		||||
	
 | 
			
		||||
	def test009deleteitem(self):
 | 
			
		||||
		"""Deleting item"""
 | 
			
		||||
		w = Waiter('wait retract', StanzaPath('message/pubsub_event/items@node=testnode2'))
 | 
			
		||||
		self.xmpp2.registerHandler(w)
 | 
			
		||||
		result = self.xmpp1['xep_0060'].deleteItem(self.pshost, "testnode2", "test1")
 | 
			
		||||
		self.failUnless(result, "Got error when deleting item.")
 | 
			
		||||
		msg = w.wait(1)
 | 
			
		||||
		self.failUnless(msg != False, "Did not get retract notice.")
 | 
			
		||||
	
 | 
			
		||||
	def test010unsubscribenode(self):
 | 
			
		||||
		"Unsubscribing Account #2"
 | 
			
		||||
		self.failUnless(self.xmpp2['xep_0060'].unsubscribe(self.pshost, "testnode2"), "Got error response when unsubscribing.")
 | 
			
		||||
	
 | 
			
		||||
	def test011createcollectionnode(self):
 | 
			
		||||
		"Create a collection node w/ Account #2"
 | 
			
		||||
		self.failUnless(self.xmpp2['xep_0060'].create_node(self.pshost, "testnode3", self.statev['defaultconfig'], True), "Could not create collection node")
 | 
			
		||||
	
 | 
			
		||||
	def test012subscribecollection(self):
 | 
			
		||||
		"Subscribe Account #1 to collection"
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode3"))
 | 
			
		||||
	
 | 
			
		||||
	def test013assignnodetocollection(self):
 | 
			
		||||
		"Assign node to collection"
 | 
			
		||||
		self.failUnless(self.xmpp2['xep_0060'].addNodeToCollection(self.pshost, 'testnode2', 'testnode3'))
 | 
			
		||||
	
 | 
			
		||||
	def test014publishcollection(self):
 | 
			
		||||
		"""Publishing item to collection child"""
 | 
			
		||||
		item = ET.Element('{http://netflint.net/protocol/test}test')
 | 
			
		||||
		w = Waiter('wait publish2', StanzaPath('message/pubsub_event/items@node=testnode2'))
 | 
			
		||||
		self.xmpp1.registerHandler(w)
 | 
			
		||||
		result = self.xmpp2['xep_0060'].setItem(self.pshost, "testnode2", (('test2', item),))
 | 
			
		||||
		msg = w.wait(5) # got to get a result in 5 seconds
 | 
			
		||||
		self.failUnless(msg != False, "Account #1 did not get message event: perhaps node was advertised incorrectly?")
 | 
			
		||||
		self.failUnless(result)
 | 
			
		||||
 | 
			
		||||
#	def test016speedtest(self):
 | 
			
		||||
#		"Uncached speed test"
 | 
			
		||||
#		import time
 | 
			
		||||
#		start = time.time()
 | 
			
		||||
#		for y in range(0, 50000, 1000):
 | 
			
		||||
#			start2 = time.time()
 | 
			
		||||
#			for x in range(y, y+1000):
 | 
			
		||||
#				self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode4", subscribee="testuser%s@whatever" % x))
 | 
			
		||||
#			print time.time() - start2
 | 
			
		||||
#		seconds = time.time() - start
 | 
			
		||||
#		print "--", seconds
 | 
			
		||||
#		print "---------"
 | 
			
		||||
#		time.sleep(15)
 | 
			
		||||
#		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode4'), "Could not delete non-cached test node")
 | 
			
		||||
	
 | 
			
		||||
#	def test015speedtest(self):
 | 
			
		||||
#		"cached speed test"
 | 
			
		||||
#		result = self.xmpp1['xep_0060'].getNodeConfig(self.pshost)
 | 
			
		||||
#		self.statev['defaultconfig'] = result
 | 
			
		||||
#		self.statev['defaultconfig'].field['pubsub#node_type'].setValue("leaf")
 | 
			
		||||
#		self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(True)
 | 
			
		||||
#		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode4', self.statev['defaultconfig']))
 | 
			
		||||
#		self.statev['defaultconfig'].field['sleek#saveonchange'].setValue(False)
 | 
			
		||||
#		self.failUnless(self.xmpp1['xep_0060'].create_node(self.pshost, 'testnode5', self.statev['defaultconfig']))
 | 
			
		||||
#		start = time.time()
 | 
			
		||||
#		for y in range(0, 50000, 1000):
 | 
			
		||||
#			start2 = time.time()
 | 
			
		||||
#			for x in range(y, y+1000):
 | 
			
		||||
#				self.failUnless(self.xmpp1['xep_0060'].subscribe(self.pshost, "testnode5", subscribee="testuser%s@whatever" % x))
 | 
			
		||||
#			print time.time() - start2
 | 
			
		||||
#		seconds = time.time() - start
 | 
			
		||||
#		print "--", seconds
 | 
			
		||||
 | 
			
		||||
	def test900cleanup(self):
 | 
			
		||||
		"Cleaning up"
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode2'), "Could not delete test node.")
 | 
			
		||||
		self.failUnless(self.xmpp1['xep_0060'].deleteNode(self.pshost, 'testnode3'), "Could not delete collection node")
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
	#parse command line arguements
 | 
			
		||||
	optp = OptionParser()
 | 
			
		||||
	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO)
 | 
			
		||||
	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
 | 
			
		||||
	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use")
 | 
			
		||||
	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use")
 | 
			
		||||
	opts,args = optp.parse_args()
 | 
			
		||||
	
 | 
			
		||||
	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
	#load xml config
 | 
			
		||||
	logging.info("Loading config file: %s" % opts.configfile)
 | 
			
		||||
	config = configparser.RawConfigParser()
 | 
			
		||||
	config.read(opts.configfile)
 | 
			
		||||
	
 | 
			
		||||
	#init
 | 
			
		||||
	logging.info("Account 1 is %s" % config.get('account1', 'jid'))
 | 
			
		||||
	xmpp1 = TestClient(config.get('account1','jid'), config.get('account1','pass'))
 | 
			
		||||
	logging.info("Account 2 is %s" % config.get('account2', 'jid'))
 | 
			
		||||
	xmpp2 = TestClient(config.get('account2','jid'), config.get('account2','pass'))
 | 
			
		||||
	
 | 
			
		||||
	xmpp1.registerPlugin('xep_0004')
 | 
			
		||||
	xmpp1.registerPlugin('xep_0030')
 | 
			
		||||
	xmpp1.registerPlugin('xep_0060')
 | 
			
		||||
	xmpp1.registerPlugin('xep_0199')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0004')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0030')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0060')
 | 
			
		||||
	xmpp2.registerPlugin('xep_0199')
 | 
			
		||||
 | 
			
		||||
	if not config.get('account1', 'server'):
 | 
			
		||||
		# we don't know the server, but the lib can probably figure it out
 | 
			
		||||
		xmpp1.connect() 
 | 
			
		||||
	else:
 | 
			
		||||
		xmpp1.connect((config.get('account1', 'server'), 5222))
 | 
			
		||||
	xmpp1.process(threaded=True)
 | 
			
		||||
	
 | 
			
		||||
	#init
 | 
			
		||||
	if not config.get('account2', 'server'):
 | 
			
		||||
		# we don't know the server, but the lib can probably figure it out
 | 
			
		||||
		xmpp2.connect() 
 | 
			
		||||
	else:
 | 
			
		||||
		xmpp2.connect((config.get('account2', 'server'), 5222))
 | 
			
		||||
	xmpp2.process(threaded=True)
 | 
			
		||||
 | 
			
		||||
	TestPubsubServer.xmpp1 = xmpp1
 | 
			
		||||
	TestPubsubServer.xmpp2 = xmpp2
 | 
			
		||||
	TestPubsubServer.pshost = config.get('settings', 'pubsub')
 | 
			
		||||
	xmpp1.waitforstart.get(True)
 | 
			
		||||
	xmpp2.waitforstart.get(True)
 | 
			
		||||
	testsuite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubServer)
 | 
			
		||||
 | 
			
		||||
	alltests_suite = unittest.TestSuite([testsuite])
 | 
			
		||||
	result = unittest.TextTestRunner(verbosity=2).run(alltests_suite)
 | 
			
		||||
	xmpp1.disconnect()
 | 
			
		||||
	xmpp2.disconnect()
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
[settings]
 | 
			
		||||
enabled=true
 | 
			
		||||
pubsub=pubsub.recon
 | 
			
		||||
 | 
			
		||||
[account1]
 | 
			
		||||
jid=fritzy@recon
 | 
			
		||||
pass=testing123
 | 
			
		||||
server=
 | 
			
		||||
 | 
			
		||||
[account2]
 | 
			
		||||
jid=fritzy2@recon
 | 
			
		||||
pass=testing123
 | 
			
		||||
server=
 | 
			
		||||
@@ -1,350 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
    
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import os
 | 
			
		||||
import time
 | 
			
		||||
import sys
 | 
			
		||||
import Queue
 | 
			
		||||
import thread
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class testps(sleekxmpp.ClientXMPP):
 | 
			
		||||
	def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], nodenum=0, pshost=None):
 | 
			
		||||
		sleekxmpp.ClientXMPP.__init__(self, jid, password, ssl, plugin_config, plugin_whitelist)
 | 
			
		||||
		self.registerPlugin('xep_0004')
 | 
			
		||||
		self.registerPlugin('xep_0030')
 | 
			
		||||
		self.registerPlugin('xep_0060')
 | 
			
		||||
		self.registerPlugin('xep_0092')
 | 
			
		||||
		self.add_handler("<message xmlns='jabber:client'><event xmlns='http://jabber.org/protocol/pubsub#event' /></message>", self.pubsubEventHandler, name='Pubsub Event', threaded=True)
 | 
			
		||||
		self.add_event_handler("session_start", self.start, threaded=True)
 | 
			
		||||
		self.add_handler("<iq type='error' />", self.handleError, name='Iq Error')
 | 
			
		||||
		self.events = Queue.Queue()
 | 
			
		||||
		self.default_config = None
 | 
			
		||||
		self.ps = self.plugin['xep_0060']
 | 
			
		||||
		self.node = "pstestnode_%s"
 | 
			
		||||
		self.pshost = pshost
 | 
			
		||||
		if pshost is None:
 | 
			
		||||
			self.pshost = self.boundjid.host
 | 
			
		||||
		self.nodenum = int(nodenum)
 | 
			
		||||
		self.leafnode = self.nodenum + 1
 | 
			
		||||
		self.collectnode = self.nodenum + 2
 | 
			
		||||
		self.lasterror = ''
 | 
			
		||||
		self.sprintchars = 0
 | 
			
		||||
		self.defaultconfig = None
 | 
			
		||||
		self.tests = ['test_defaultConfig', 'test_createDefaultNode', 'test_getNodes', 'test_deleteNode', 'test_createWithConfig', 'test_reconfigureNode', 'test_subscribeToNode', 'test_addItem', 'test_updateItem', 'test_deleteItem', 'test_unsubscribeNode', 'test_createCollection', 'test_subscribeCollection', 'test_addNodeCollection', 'test_deleteNodeCollection', 'test_addCollectionNode', 'test_deleteCollectionNode', 'test_unsubscribeNodeCollection', 'test_deleteCollection']
 | 
			
		||||
		self.passed = 0
 | 
			
		||||
		self.width = 120
 | 
			
		||||
	
 | 
			
		||||
	def start(self, event):
 | 
			
		||||
		#TODO: make this configurable
 | 
			
		||||
		self.getRoster()
 | 
			
		||||
		self.sendPresence(ppriority=20)
 | 
			
		||||
		thread.start_new(self.test_all, tuple())
 | 
			
		||||
	
 | 
			
		||||
	def sprint(self, msg, end=False, color=False):
 | 
			
		||||
		length = len(msg)
 | 
			
		||||
		if color:
 | 
			
		||||
			if color == "red":
 | 
			
		||||
				color = "1;31"
 | 
			
		||||
			elif color == "green":
 | 
			
		||||
				color = "0;32"
 | 
			
		||||
			msg = "%s%s%s" % ("\033[%sm" % color, msg, "\033[0m")
 | 
			
		||||
		if not end:
 | 
			
		||||
			sys.stdout.write(msg)
 | 
			
		||||
			self.sprintchars += length
 | 
			
		||||
		else:
 | 
			
		||||
			self.sprint("%s%s" % ("." * (self.width - self.sprintchars - length), msg))
 | 
			
		||||
			print('')
 | 
			
		||||
			self.sprintchars = 0
 | 
			
		||||
		sys.stdout.flush()
 | 
			
		||||
 | 
			
		||||
	def pubsubEventHandler(self, xml):
 | 
			
		||||
		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}item'):
 | 
			
		||||
			self.events.put(item.get('id', '__unknown__'))
 | 
			
		||||
		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}retract'):
 | 
			
		||||
			self.events.put(item.get('id', '__unknown__'))
 | 
			
		||||
		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}disassociate'):
 | 
			
		||||
			self.events.put(item.get('node', '__unknown__'))
 | 
			
		||||
		for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}associate'):
 | 
			
		||||
			self.events.put(item.get('node', '__unknown__'))
 | 
			
		||||
	
 | 
			
		||||
	def handleError(self, xml):
 | 
			
		||||
		error = xml.find('{jabber:client}error')
 | 
			
		||||
		self.lasterror =  error.getchildren()[0].tag.split('}')[-1]
 | 
			
		||||
		
 | 
			
		||||
	def test_all(self):
 | 
			
		||||
		print("Running Publish-Subscribe Tests")
 | 
			
		||||
		version = self.plugin['xep_0092'].getVersion(self.pshost)
 | 
			
		||||
		if version:
 | 
			
		||||
			print("%s %s on %s" % (version.get('name', 'Unknown Server'), version.get('version', 'v?'), version.get('os', 'Unknown OS')))
 | 
			
		||||
		print("=" * self.width)
 | 
			
		||||
		for test in self.tests:
 | 
			
		||||
			testfunc = getattr(self, test)
 | 
			
		||||
			self.sprint("%s" % testfunc.__doc__)
 | 
			
		||||
			if testfunc():
 | 
			
		||||
				self.sprint("Passed", True, "green")
 | 
			
		||||
				self.passed += 1
 | 
			
		||||
			else:
 | 
			
		||||
				if not self.lasterror:
 | 
			
		||||
					self.lasterror = 'No response'
 | 
			
		||||
				self.sprint("Failed (%s)" % self.lasterror, True, "red")
 | 
			
		||||
				self.lasterror = ''
 | 
			
		||||
		print("=" * self.width)
 | 
			
		||||
		self.sprint("Cleaning up...")
 | 
			
		||||
		#self.ps.deleteNode(self.pshost, self.node % self.nodenum)
 | 
			
		||||
		self.ps.deleteNode(self.pshost, self.node % self.leafnode)
 | 
			
		||||
		#self.ps.deleteNode(self.pshost, self.node % self.collectnode)
 | 
			
		||||
		self.sprint("Done", True, "green")
 | 
			
		||||
		self.disconnect()
 | 
			
		||||
		self.sprint("%s" % self.passed, False, "green")
 | 
			
		||||
		self.sprint("/%s Passed -- " % len(self.tests))
 | 
			
		||||
		if len(self.tests) - self.passed:
 | 
			
		||||
			self.sprint("%s" % (len(self.tests) - self.passed), False, "red")
 | 
			
		||||
		else:
 | 
			
		||||
			self.sprint("%s" % (len(self.tests) - self.passed), False, "green")
 | 
			
		||||
		self.sprint(" Failed Tests")
 | 
			
		||||
		print
 | 
			
		||||
		#print "%s/%s Passed -- %s Failed Tests" % (self.passed, len(self.tests), len(self.tests) - self.passed)
 | 
			
		||||
	
 | 
			
		||||
	def test_defaultConfig(self):
 | 
			
		||||
		"Retreiving default configuration"
 | 
			
		||||
		result = self.ps.getNodeConfig(self.pshost)
 | 
			
		||||
		if result is False or result is None:
 | 
			
		||||
			return False
 | 
			
		||||
		else:
 | 
			
		||||
			self.defaultconfig = result
 | 
			
		||||
			try:
 | 
			
		||||
				self.defaultconfig.field['pubsub#access_model'].setValue('open')
 | 
			
		||||
			except KeyError:
 | 
			
		||||
				pass
 | 
			
		||||
			try:
 | 
			
		||||
				self.defaultconfig.field['pubsub#notify_retract'].setValue(True)
 | 
			
		||||
			except KeyError:
 | 
			
		||||
				pass
 | 
			
		||||
			return True
 | 
			
		||||
	
 | 
			
		||||
	def test_createDefaultNode(self):
 | 
			
		||||
		"Creating default node"
 | 
			
		||||
		return self.ps.create_node(self.pshost, self.node % self.nodenum)
 | 
			
		||||
	
 | 
			
		||||
	def test_getNodes(self):
 | 
			
		||||
		"Getting list of nodes"
 | 
			
		||||
		self.ps.getNodes(self.pshost)
 | 
			
		||||
		self.ps.getItems(self.pshost, 'blog')
 | 
			
		||||
		return True
 | 
			
		||||
	
 | 
			
		||||
	def test_deleteNode(self):
 | 
			
		||||
		"Deleting node"
 | 
			
		||||
		return self.ps.deleteNode(self.pshost, self.node % self.nodenum)
 | 
			
		||||
	
 | 
			
		||||
	def test_createWithConfig(self):
 | 
			
		||||
		"Creating node with config"
 | 
			
		||||
		if self.defaultconfig is None:
 | 
			
		||||
			self.lasterror = "No Avail Config"
 | 
			
		||||
			return False
 | 
			
		||||
		return self.ps.create_node(self.pshost, self.node % self.leafnode, self.defaultconfig)
 | 
			
		||||
	
 | 
			
		||||
	def test_reconfigureNode(self):
 | 
			
		||||
		"Retrieving node config and reconfiguring"
 | 
			
		||||
		nconfig = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
 | 
			
		||||
		if nconfig == False:
 | 
			
		||||
			return False
 | 
			
		||||
		return self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, nconfig)
 | 
			
		||||
		
 | 
			
		||||
	def test_subscribeToNode(self):
 | 
			
		||||
		"Subscribing to node"
 | 
			
		||||
		return self.ps.subscribe(self.pshost, self.node % self.leafnode)
 | 
			
		||||
	
 | 
			
		||||
	def test_addItem(self):
 | 
			
		||||
		"Adding item, waiting for notification"
 | 
			
		||||
		item = ET.Element('test')
 | 
			
		||||
		result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),))
 | 
			
		||||
		if result == False:
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			event = self.events.get(True, 10)
 | 
			
		||||
		except Queue.Empty:
 | 
			
		||||
			return False
 | 
			
		||||
		if event == 'test_node1':
 | 
			
		||||
			return True
 | 
			
		||||
		return False
 | 
			
		||||
	
 | 
			
		||||
	def test_updateItem(self):
 | 
			
		||||
		"Updating item, waiting for notification"
 | 
			
		||||
		item = ET.Element('test')
 | 
			
		||||
		item.attrib['crap'] = 'yup, right here'
 | 
			
		||||
		result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),))
 | 
			
		||||
		if result == False:
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			event = self.events.get(True, 10)
 | 
			
		||||
		except Queue.Empty:
 | 
			
		||||
			return False
 | 
			
		||||
		if event == 'test_node1':
 | 
			
		||||
			return True
 | 
			
		||||
		return False
 | 
			
		||||
 | 
			
		||||
	def test_deleteItem(self):
 | 
			
		||||
		"Deleting item, waiting for notification"
 | 
			
		||||
		result = self.ps.deleteItem(self.pshost, self.node % self.leafnode, 'test_node1')
 | 
			
		||||
		if result == False:
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			event = self.events.get(True, 10)
 | 
			
		||||
		except Queue.Empty:
 | 
			
		||||
			self.lasterror = "No Notification"
 | 
			
		||||
			return False
 | 
			
		||||
		if event == 'test_node1':
 | 
			
		||||
			return True
 | 
			
		||||
		return False
 | 
			
		||||
	
 | 
			
		||||
	def test_unsubscribeNode(self):
 | 
			
		||||
		"Unsubscribing from node"
 | 
			
		||||
		return self.ps.unsubscribe(self.pshost, self.node % self.leafnode)
 | 
			
		||||
 | 
			
		||||
	def test_createCollection(self):
 | 
			
		||||
		"Creating collection node"
 | 
			
		||||
		return self.ps.create_node(self.pshost, self.node % self.collectnode, self.defaultconfig, True)
 | 
			
		||||
	
 | 
			
		||||
	def test_subscribeCollection(self):
 | 
			
		||||
		"Subscribing to collection node"
 | 
			
		||||
		return self.ps.subscribe(self.pshost, self.node % self.collectnode)
 | 
			
		||||
	
 | 
			
		||||
	def test_addNodeCollection(self):
 | 
			
		||||
		"Assigning node to collection, waiting for notification"
 | 
			
		||||
		config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
 | 
			
		||||
		if not config or config is None:
 | 
			
		||||
			self.lasterror = "Config Error"
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			config.field['pubsub#collection'].setValue(self.node % self.collectnode)
 | 
			
		||||
		except KeyError:
 | 
			
		||||
			self.sprint("...Missing Field...", False, "red")
 | 
			
		||||
			config.addField('pubsub#collection', value=self.node % self.collectnode)
 | 
			
		||||
		if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config):
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			event = self.events.get(True, 10)
 | 
			
		||||
		except Queue.Empty:
 | 
			
		||||
			self.lasterror = "No Notification"
 | 
			
		||||
			return False
 | 
			
		||||
		if event == self.node % self.leafnode:
 | 
			
		||||
			return True
 | 
			
		||||
		return False
 | 
			
		||||
	
 | 
			
		||||
	def test_deleteNodeCollection(self):
 | 
			
		||||
		"Removing node assignment to collection, waiting for notification"
 | 
			
		||||
		config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
 | 
			
		||||
		if not config or config is None:
 | 
			
		||||
			self.lasterror = "Config Error"
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			config.field['pubsub#collection'].delValue(self.node % self.collectnode)
 | 
			
		||||
		except KeyError:
 | 
			
		||||
			self.sprint("...Missing Field...", False, "red")
 | 
			
		||||
			config.addField('pubsub#collection', value='')
 | 
			
		||||
		if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config):
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			event = self.events.get(True, 10)
 | 
			
		||||
		except Queue.Empty:
 | 
			
		||||
			self.lasterror = "No Notification"
 | 
			
		||||
			return False
 | 
			
		||||
		if event == self.node % self.leafnode:
 | 
			
		||||
			return True
 | 
			
		||||
		return False
 | 
			
		||||
 | 
			
		||||
	def test_addCollectionNode(self):
 | 
			
		||||
		"Assigning node from collection, waiting for notification"
 | 
			
		||||
		config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode)
 | 
			
		||||
		if not config or config is None:
 | 
			
		||||
			self.lasterror = "Config Error"
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			config.field['pubsub#children'].setValue(self.node % self.leafnode)
 | 
			
		||||
		except KeyError:
 | 
			
		||||
			self.sprint("...Missing Field...", False, "red")
 | 
			
		||||
			config.addField('pubsub#children', value=self.node % self.leafnode)
 | 
			
		||||
		if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config):
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			event = self.events.get(True, 10)
 | 
			
		||||
		except Queue.Empty:
 | 
			
		||||
			self.lasterror = "No Notification"
 | 
			
		||||
			return False
 | 
			
		||||
		if event == self.node % self.leafnode:
 | 
			
		||||
			return True
 | 
			
		||||
		return False
 | 
			
		||||
 | 
			
		||||
	def test_deleteCollectionNode(self):
 | 
			
		||||
		"Removing node from collection, waiting for notification"
 | 
			
		||||
		config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode)
 | 
			
		||||
		if not config or config is None:
 | 
			
		||||
			self.lasterror = "Config Error"
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			config.field['pubsub#children'].delValue(self.node % self.leafnode)
 | 
			
		||||
		except KeyError:
 | 
			
		||||
			self.sprint("...Missing Field...", False, "red")
 | 
			
		||||
			config.addField('pubsub#children', value='')
 | 
			
		||||
		if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config):
 | 
			
		||||
			return False
 | 
			
		||||
		try:
 | 
			
		||||
			event = self.events.get(True, 10)
 | 
			
		||||
		except Queue.Empty:
 | 
			
		||||
			self.lasterror = "No Notification"
 | 
			
		||||
			return False
 | 
			
		||||
		if event == self.node % self.leafnode:
 | 
			
		||||
			return True
 | 
			
		||||
		return False
 | 
			
		||||
	
 | 
			
		||||
	def test_unsubscribeNodeCollection(self):
 | 
			
		||||
		"Unsubscribing from collection"
 | 
			
		||||
		return self.ps.unsubscribe(self.pshost, self.node % self.collectnode)
 | 
			
		||||
	
 | 
			
		||||
	def test_deleteCollection(self):
 | 
			
		||||
		"Deleting collection"
 | 
			
		||||
		return self.ps.deleteNode(self.pshost, self.node % self.collectnode)
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
	#parse command line arguements
 | 
			
		||||
	optp = OptionParser()
 | 
			
		||||
	optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
	optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
	optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO)
 | 
			
		||||
	optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
 | 
			
		||||
	optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use")
 | 
			
		||||
	optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use")
 | 
			
		||||
	opts,args = optp.parse_args()
 | 
			
		||||
	
 | 
			
		||||
	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
	#load xml config
 | 
			
		||||
	logging.info("Loading config file: %s" % opts.configfile)
 | 
			
		||||
	config = ET.parse(os.path.expanduser(opts.configfile)).find('auth')
 | 
			
		||||
	
 | 
			
		||||
	#init
 | 
			
		||||
	logging.info("Logging in as %s" % config.attrib['jid'])
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	plugin_config = {}
 | 
			
		||||
	plugin_config['xep_0092'] = {'name': 'SleekXMPP Example', 'version': '0.1-dev'}
 | 
			
		||||
	plugin_config['xep_0199'] = {'keepalive': True, 'timeout': 30, 'frequency': 300}
 | 
			
		||||
	
 | 
			
		||||
	con = testps(config.attrib['jid'], config.attrib['pass'], plugin_config=plugin_config, plugin_whitelist=[], nodenum=opts.nodenum, pshost=opts.pubsub)
 | 
			
		||||
	if not config.get('server', None):
 | 
			
		||||
		# we don't know the server, but the lib can probably figure it out
 | 
			
		||||
		con.connect() 
 | 
			
		||||
	else:
 | 
			
		||||
		con.connect((config.attrib['server'], 5222))
 | 
			
		||||
	con.process(threaded=False)
 | 
			
		||||
	print("")
 | 
			
		||||
							
								
								
									
										1
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
_build/*
 | 
			
		||||
							
								
								
									
										35
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
								
							@@ -59,9 +59,10 @@ body {
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    padding: 0px;
 | 
			
		||||
    font-family: "Helvetica Neueu", Helvetica, sans-serif;
 | 
			
		||||
    min-width: 59em;
 | 
			
		||||
    min-width: 30em;
 | 
			
		||||
    max-width: 70em;
 | 
			
		||||
    color: #444;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.footer {
 | 
			
		||||
@@ -124,21 +125,25 @@ a.headerlink:hover {
 | 
			
		||||
/* basic text elements */
 | 
			
		||||
 | 
			
		||||
div.content {
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
    margin-left: 40px;
 | 
			
		||||
    margin-right: 40px;
 | 
			
		||||
    margin-bottom: 50px;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
    width: 700px;
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* heading and navigation */
 | 
			
		||||
 | 
			
		||||
div.header {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    margin-top: 125px;
 | 
			
		||||
    height: 85px;
 | 
			
		||||
    padding: 0 40px;
 | 
			
		||||
    font-family: "Yanone Kaffeesatz";
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    width: 750px;
 | 
			
		||||
}
 | 
			
		||||
div.header h1 {
 | 
			
		||||
    font-size: 2.6em;
 | 
			
		||||
@@ -185,12 +190,12 @@ div.topnav {
 | 
			
		||||
    z-index: 0;
 | 
			
		||||
}
 | 
			
		||||
div.topnav p {
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin-left: 40px;
 | 
			
		||||
    margin-right: 40px;
 | 
			
		||||
    margin-bottom: 0px;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    width: 750px;
 | 
			
		||||
}
 | 
			
		||||
div.bottomnav {
 | 
			
		||||
    background: #eeeeee;
 | 
			
		||||
@@ -404,3 +409,23 @@ div.viewcode-block:target {
 | 
			
		||||
    padding: 0 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#from_andyet {
 | 
			
		||||
    -webkit-box-shadow: #CCC 0px 0px 3px;
 | 
			
		||||
    background: rgba(255, 255, 255, 1);
 | 
			
		||||
    bottom: 0px;
 | 
			
		||||
    right: 17px;
 | 
			
		||||
    padding: 3px 10px;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#from_andyet h2 {
 | 
			
		||||
    background-image: url("images/from_&yet.png");
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    height: 29px;
 | 
			
		||||
    line-height: 0;
 | 
			
		||||
    text-indent: -9999em;
 | 
			
		||||
    width: 79px;
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
    margin: 0px;
 | 
			
		||||
    padding: 0px;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.7 KiB  | 
							
								
								
									
										35
									
								
								docs/_templates/defindex.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								docs/_templates/defindex.html
									
									
									
									
										vendored
									
									
								
							@@ -1,35 +0,0 @@
 | 
			
		||||
{#
 | 
			
		||||
    basic/defindex.html
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    Default template for the "index" page.
 | 
			
		||||
 | 
			
		||||
    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
    :license: BSD, see LICENSE for details.
 | 
			
		||||
#}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% set title = _('Overview') %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
  <h1>{{ docstitle|e }}</h1>
 | 
			
		||||
  <p>
 | 
			
		||||
    Welcome! This is
 | 
			
		||||
    {% block description %}the documentation for {{ project|e }}
 | 
			
		||||
    {{ release|e }}{% if last_updated %}, last updated {{ last_updated|e }}{% endif %}{% endblock %}.
 | 
			
		||||
  </p>
 | 
			
		||||
  {% block tables %}
 | 
			
		||||
  <p><strong>{{ _('Indices and tables:') }}</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">{{ _('Complete Table of Contents') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('lists all sections and subsections') }}</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">{{ _('Search Page') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('search this documentation') }}</span></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("modindex") }}">{{ _('Global Module Index') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('quick access to all modules') }}</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">{{ _('General Index') }}</a><br>
 | 
			
		||||
         <span class="linkdescr">{{ _('all functions, classes, terms') }}</span></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
  {% endblock %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										61
									
								
								docs/_templates/indexcontent.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										61
									
								
								docs/_templates/indexcontent.html
									
									
									
									
										vendored
									
									
								
							@@ -1,61 +0,0 @@
 | 
			
		||||
{% extends "defindex.html" %}
 | 
			
		||||
{% block tables %}
 | 
			
		||||
  <p><strong>Parts of the documentation:</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("whatsnew/" + version) }}">What's new in Python {{ version }}?</a><br/>
 | 
			
		||||
         <span class="linkdescr">or <a href="{{ pathto("whatsnew/index") }}">all "What's new" documents</a> since 2.0</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("tutorial/index") }}">Tutorial</a><br/>
 | 
			
		||||
         <span class="linkdescr">start here</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("library/index") }}">Library Reference</a><br/>
 | 
			
		||||
         <span class="linkdescr">keep this under your pillow</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("reference/index") }}">Language Reference</a><br/>
 | 
			
		||||
         <span class="linkdescr">describes syntax and language elements</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("using/index") }}">Python Setup and Usage</a><br/>
 | 
			
		||||
         <span class="linkdescr">how to use Python on different platforms</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("howto/index") }}">Python HOWTOs</a><br/>
 | 
			
		||||
         <span class="linkdescr">in-depth documents on specific topics</span></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("extending/index") }}">Extending and Embedding</a><br/>
 | 
			
		||||
         <span class="linkdescr">tutorial for C/C++ programmers</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("c-api/index") }}">Python/C API</a><br/>
 | 
			
		||||
         <span class="linkdescr">reference for C/C++ programmers</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("install/index") }}">Installing Python Modules</a><br/>
 | 
			
		||||
         <span class="linkdescr">information for installers & sys-admins</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("distutils/index") }}">Distributing Python Modules</a><br/>
 | 
			
		||||
         <span class="linkdescr">sharing modules with others</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("documenting/index") }}">Documenting Python</a><br/>
 | 
			
		||||
         <span class="linkdescr">guide for documentation authors</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("faq/index") }}">FAQs</a><br/>
 | 
			
		||||
         <span class="linkdescr">frequently asked questions (with answers!)</span></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  <p><strong>Indices and tables:</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("py-modindex") }}">Global Module Index</a><br/>
 | 
			
		||||
         <span class="linkdescr">quick access to all modules</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">General Index</a><br/>
 | 
			
		||||
         <span class="linkdescr">all functions, classes, terms</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("glossary") }}">Glossary</a><br/>
 | 
			
		||||
         <span class="linkdescr">the most important terms explained</span></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">Search page</a><br/>
 | 
			
		||||
         <span class="linkdescr">search this documentation</span></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br/>
 | 
			
		||||
         <span class="linkdescr">lists all sections and subsections</span></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  <p><strong>Meta information:</strong></p>
 | 
			
		||||
  <table class="contentstable" align="center"><tr>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("bugs") }}">Reporting bugs</a></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("about") }}">About the documentation</a></p>
 | 
			
		||||
    </td><td width="50%">
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("license") }}">History and License of Python</a></p>
 | 
			
		||||
      <p class="biglink"><a class="biglink" href="{{ pathto("copyright") }}">Copyright</a></p>
 | 
			
		||||
    </td></tr>
 | 
			
		||||
  </table>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										9
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
 | 
			
		||||
    Sphinx layout template for the haiku theme.
 | 
			
		||||
 | 
			
		||||
    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
			
		||||
    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
 | 
			
		||||
    :license: BSD, see LICENSE for details.
 | 
			
		||||
#}
 | 
			
		||||
{% extends "basic/layout.html" %}
 | 
			
		||||
@@ -44,9 +44,8 @@
 | 
			
		||||
        {%- if logo -%}
 | 
			
		||||
          <img class="rightlogo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
 | 
			
		||||
        {%- endif -%}
 | 
			
		||||
        <h1 class="heading">
 | 
			
		||||
            <a href="{{ pathto('index') }}"><span>{{ project|e }}</span></a>
 | 
			
		||||
        </h1>
 | 
			
		||||
        <h1 class="heading"><a href="{{ pathto('index') }}">
 | 
			
		||||
          <span>{{ title|striptags }}</span></a></h1>
 | 
			
		||||
        <h2 class="heading"><span>{{ shorttitle|e }}</span></h2>
 | 
			
		||||
        {%- endif %}
 | 
			
		||||
        {%- endblock %}
 | 
			
		||||
@@ -66,4 +65,6 @@
 | 
			
		||||
      <div class="bottomnav">
 | 
			
		||||
      {{ nav() }}
 | 
			
		||||
      </div>
 | 
			
		||||
      <a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
========
 | 
			
		||||
basexmpp
 | 
			
		||||
BaseXMPP
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.basexmpp
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,8 @@
 | 
			
		||||
==========
 | 
			
		||||
clientxmpp
 | 
			
		||||
ClientXMPP
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.clientxmpp
 | 
			
		||||
 | 
			
		||||
.. autoclass:: ClientXMPP
 | 
			
		||||
    
 | 
			
		||||
    .. automethod:: connect
 | 
			
		||||
 | 
			
		||||
    .. automethod:: register_feature
 | 
			
		||||
 | 
			
		||||
    .. automethod:: get_roster
 | 
			
		||||
 | 
			
		||||
    .. automethod:: update_roster
 | 
			
		||||
 | 
			
		||||
    .. automethod:: del_roster_item
 | 
			
		||||
    :members:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								docs/api/componentxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/componentxmpp.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
=============
 | 
			
		||||
ComponentXMPP
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.componentxmpp
 | 
			
		||||
 | 
			
		||||
.. autoclass:: ComponentXMPP
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										14
									
								
								docs/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								docs/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
Exceptions
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.exceptions
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
.. autoexception:: XMPPError
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
.. autoexception:: IqError
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
.. autoexception:: IqTimeout
 | 
			
		||||
    :members:
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
=========
 | 
			
		||||
xmlstream
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.xmlstream
 | 
			
		||||
 | 
			
		||||
.. autoclass:: XMLStream
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										12
									
								
								docs/api/xmlstream/filesocket.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docs/api/xmlstream/filesocket.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.filesocket
 | 
			
		||||
 | 
			
		||||
.. _filesocket:
 | 
			
		||||
 | 
			
		||||
Python 2.6 File Socket Shims
 | 
			
		||||
============================
 | 
			
		||||
 | 
			
		||||
.. autoclass:: FileSocket
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
.. autoclass:: Socket26
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										24
									
								
								docs/api/xmlstream/handler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								docs/api/xmlstream/handler.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
Stanza Handlers
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
The Basic Handler
 | 
			
		||||
-----------------
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.handler.base
 | 
			
		||||
 | 
			
		||||
.. autoclass:: BaseHandler
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
Callback
 | 
			
		||||
--------
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.handler.callback
 | 
			
		||||
 | 
			
		||||
.. autoclass:: Callback
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Waiter
 | 
			
		||||
------
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.handler.waiter
 | 
			
		||||
 | 
			
		||||
.. autoclass:: Waiter
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										7
									
								
								docs/api/xmlstream/jid.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/api/xmlstream/jid.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
Jabber IDs (JID)
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.jid
 | 
			
		||||
 | 
			
		||||
.. autoclass:: JID
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										41
									
								
								docs/api/xmlstream/matcher.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								docs/api/xmlstream/matcher.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
Stanza Matchers
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
The Basic Matcher
 | 
			
		||||
-----------------
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.matcher.base
 | 
			
		||||
 | 
			
		||||
.. autoclass:: MatcherBase
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ID Matching
 | 
			
		||||
-----------
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.matcher.id
 | 
			
		||||
 | 
			
		||||
.. autoclass:: MatcherId
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Stanza Path Matching
 | 
			
		||||
--------------------
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.matcher.stanzapath
 | 
			
		||||
 | 
			
		||||
.. autoclass:: StanzaPath
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XPath
 | 
			
		||||
-----
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.matcher.xpath
 | 
			
		||||
 | 
			
		||||
.. autoclass:: MatchXPath
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
XMLMask
 | 
			
		||||
-------
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.matcher.xmlmask
 | 
			
		||||
 | 
			
		||||
.. autoclass:: MatchXMLMask
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										11
									
								
								docs/api/xmlstream/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								docs/api/xmlstream/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
=========
 | 
			
		||||
Scheduler
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.scheduler
 | 
			
		||||
 | 
			
		||||
.. autoclass:: Task
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
.. autoclass:: Scheduler
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										123
									
								
								docs/api/xmlstream/stanzabase.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								docs/api/xmlstream/stanzabase.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
.. _stanzabase:
 | 
			
		||||
 | 
			
		||||
==============
 | 
			
		||||
Stanza Objects
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.stanzabase
 | 
			
		||||
 | 
			
		||||
The :mod:`~sleekmxpp.xmlstream.stanzabase` module provides a wrapper for the
 | 
			
		||||
standard :mod:`~xml.etree.ElementTree` module that makes working with XML
 | 
			
		||||
less painful. Instead of having to manually move up and down an element
 | 
			
		||||
tree and insert subelements and attributes, you can interact with an object
 | 
			
		||||
that behaves like a normal dictionary or JSON object, which silently maps
 | 
			
		||||
keys to XML attributes and elements behind the scenes.
 | 
			
		||||
 | 
			
		||||
Overview
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
The usefulness of this layer grows as the XML you have to work with
 | 
			
		||||
becomes nested. The base unit here, :class:`ElementBase`, can map to a
 | 
			
		||||
single XML element, or several depending on how advanced of a mapping
 | 
			
		||||
is desired from interface keys to XML structures. For example, a single
 | 
			
		||||
:class:`ElementBase` derived class could easily describe:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <message to="user@example.com" from="friend@example.com">
 | 
			
		||||
      <body>Hi!</body>
 | 
			
		||||
      <x:extra>
 | 
			
		||||
        <x:item>Custom item 1</x:item>
 | 
			
		||||
        <x:item>Custom item 2</x:item>
 | 
			
		||||
        <x:item>Custom item 3</x:item>
 | 
			
		||||
      </x:extra>
 | 
			
		||||
    </message>
 | 
			
		||||
 | 
			
		||||
If that chunk of XML were put in the :class:`ElementBase` instance
 | 
			
		||||
``msg``, we could extract the data from the XML using::
 | 
			
		||||
 | 
			
		||||
    >>> msg['extra']
 | 
			
		||||
    ['Custom item 1', 'Custom item 2', 'Custom item 3']
 | 
			
		||||
 | 
			
		||||
Provided we set up the handler for the ``'extra'`` interface to load the
 | 
			
		||||
``<x:item>`` element content into a list.
 | 
			
		||||
 | 
			
		||||
The key concept is that given an XML structure that will be repeatedly
 | 
			
		||||
used, we can define a set of :term:`interfaces` which when we read from,
 | 
			
		||||
write to, or delete, will automatically manipulate the underlying XML
 | 
			
		||||
as needed. In addition, some of these interfaces may in turn reference
 | 
			
		||||
child objects which expose interfaces for particularly complex child
 | 
			
		||||
elements of the original XML chunk.
 | 
			
		||||
 | 
			
		||||
.. seealso::
 | 
			
		||||
    :ref:`create-stanza-interfaces`.
 | 
			
		||||
 | 
			
		||||
Because the :mod:`~sleekxmpp.xmlstream.stanzabase` module was developed
 | 
			
		||||
as part of an `XMPP <http://xmpp.org>`_ library, these chunks of XML are
 | 
			
		||||
referred to as :term:`stanzas <stanza>`, and in SleekXMPP we refer to a
 | 
			
		||||
subclass of :class:`ElementBase` which defines the interfaces needed for
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
plugin stanza object. Here is an example:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <iq type="result">
 | 
			
		||||
      <query xmlns="http://jabber.org/protocol/disco#info">
 | 
			
		||||
        <identity category="client" type="bot" name="SleekXMPP Bot" />
 | 
			
		||||
      </query>
 | 
			
		||||
    </iq>
 | 
			
		||||
 | 
			
		||||
We can can arrange this stanza into two objects: an outer, wrapper object for
 | 
			
		||||
dealing with the ``<iq />`` element and its attributes, and a plugin object to
 | 
			
		||||
control the ``<query />`` payload element. If we give the plugin object the
 | 
			
		||||
name ``'disco_info'`` (using its :attr:`ElementBase.plugin_attrib` value), then
 | 
			
		||||
we can access the plugin as so::
 | 
			
		||||
 | 
			
		||||
    >>> iq['disco_info']
 | 
			
		||||
    '<query xmlns="http://jabber.org/protocol/disco#info">
 | 
			
		||||
      <identity category="client" type="bot" name="SleekXMPP Bot" />
 | 
			
		||||
    </query>'
 | 
			
		||||
 | 
			
		||||
We can then drill down through the plugin object's interfaces as desired::
 | 
			
		||||
 | 
			
		||||
    >>> iq['disco_info']['identities']
 | 
			
		||||
    [('client', 'bot', 'SleekXMPP Bot')]
 | 
			
		||||
 | 
			
		||||
Plugins may also add new interfaces to the parent stanza object as if they
 | 
			
		||||
had been defined by the parent directly, and can also override the behaviour
 | 
			
		||||
of an interface defined by the parent.
 | 
			
		||||
 | 
			
		||||
.. seealso::
 | 
			
		||||
 | 
			
		||||
    - :ref:`create-stanza-plugins`
 | 
			
		||||
    - :ref:`create-extension-plugins`
 | 
			
		||||
    - :ref:`override-parent-interfaces`
 | 
			
		||||
     
 | 
			
		||||
 | 
			
		||||
Registering Stanza Plugins
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
.. autofunction:: register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
ElementBase
 | 
			
		||||
-----------
 | 
			
		||||
 | 
			
		||||
.. autoclass:: ElementBase
 | 
			
		||||
    :members:
 | 
			
		||||
    :private-members:
 | 
			
		||||
    :special-members:
 | 
			
		||||
 | 
			
		||||
StanzaBase
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
.. autoclass:: StanzaBase
 | 
			
		||||
    :members:
 | 
			
		||||
							
								
								
									
										46
									
								
								docs/api/xmlstream/tostring.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								docs/api/xmlstream/tostring.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.tostring
 | 
			
		||||
 | 
			
		||||
.. _tostring:
 | 
			
		||||
 | 
			
		||||
XML Serialization
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
Since the XML layer of SleekXMPP is based on :mod:`~xml.etree.ElementTree`,
 | 
			
		||||
why not just use the built-in :func:`~xml.etree.ElementTree.tostring`
 | 
			
		||||
method? The answer is that using that method produces ugly results when
 | 
			
		||||
using namespaces. The :func:`tostring()` method used here intelligently
 | 
			
		||||
hides namespaces when able and does not introduce excessive namespace
 | 
			
		||||
prefixes::
 | 
			
		||||
 | 
			
		||||
    >>> from sleekxmpp.xmlstream.tostring import tostring
 | 
			
		||||
    >>> from xml.etree import cElementTree as ET
 | 
			
		||||
    >>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
 | 
			
		||||
    >>> ET.tostring(xml)
 | 
			
		||||
    '<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'
 | 
			
		||||
    >>> tostring(xml)
 | 
			
		||||
    '<foo xmlns="bar"><baz /></foo>'
 | 
			
		||||
 | 
			
		||||
As a side effect of this namespace hiding, using :func:`tostring()` may
 | 
			
		||||
produce unexpected results depending on how the :func:`tostring()` method
 | 
			
		||||
is invoked. For example, when sending XML on the wire, the main XMPP
 | 
			
		||||
stanzas with their namespace of ``jabber:client`` will not include the
 | 
			
		||||
namespace because that is already declared by the stream header. But, if
 | 
			
		||||
you create a :class:`~sleekxmpp.stanza.message.Message` instance and dump
 | 
			
		||||
it to the terminal, the ``jabber:client`` namespace will appear.
 | 
			
		||||
 | 
			
		||||
.. autofunction:: tostring
 | 
			
		||||
 | 
			
		||||
Escaping Special Characters
 | 
			
		||||
---------------------------
 | 
			
		||||
 | 
			
		||||
In order to prevent errors when sending arbitrary text as the textual
 | 
			
		||||
content of an XML element, certain characters must be escaped. These
 | 
			
		||||
are: ``&``, ``<``, ``>``, ``"``, and ``'``. The default escaping
 | 
			
		||||
mechanism is to replace those characters with their equivalent escape
 | 
			
		||||
entities: ``&``, ``<``, ``>``, ``'``, and ``"``.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
							
								
								
									
										10
									
								
								docs/api/xmlstream/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docs/api/xmlstream/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
==========
 | 
			
		||||
XML Stream
 | 
			
		||||
==========
 | 
			
		||||
 | 
			
		||||
.. module:: sleekxmpp.xmlstream.xmlstream
 | 
			
		||||
 | 
			
		||||
.. autoexception:: RestartStream
 | 
			
		||||
 | 
			
		||||
.. autoclass:: XMLStream
 | 
			
		||||
    :members:
 | 
			
		||||
@@ -17,21 +17,21 @@ of the tedium of creating/manipulating XML.
 | 
			
		||||
 | 
			
		||||
The Foundation: XMLStream
 | 
			
		||||
-------------------------
 | 
			
		||||
``XMLStream`` is a mostly XMPP-agnostic class whose purpose is to read
 | 
			
		||||
and write from a bi-directional XML stream. It also allows for callback
 | 
			
		||||
functions to execute when XML matching given 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.
 | 
			
		||||
:class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` is a mostly XMPP-agnostic
 | 
			
		||||
class whose purpose is to read and write from a bi-directional XML stream.
 | 
			
		||||
It also allows for callback functions to execute when XML matching given
 | 
			
		||||
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
 | 
			
		||||
~~~~~~~~~~~~~~~~
 | 
			
		||||
``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.
 | 
			
		||||
:class:`~sleekxmpp.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.
 | 
			
		||||
 | 
			
		||||
Additionally, the main event processing loop may be executed in its
 | 
			
		||||
own thread if SleekXMPP is being used in the background for another
 | 
			
		||||
@@ -61,9 +61,10 @@ when this bit of XML is received (with an assumed namespace of
 | 
			
		||||
   new object is determined using a map of namespaced element names to
 | 
			
		||||
   classes.
 | 
			
		||||
 | 
			
		||||
   Our incoming XML is thus turned into a ``Message`` :term:`stanza object`
 | 
			
		||||
   because the namespaced element name ``{jabber:client}message`` is
 | 
			
		||||
   associated with the class ``sleekxmpp.stanza.Message``.
 | 
			
		||||
   Our incoming XML is thus turned into a :class:`~sleekxmpp.stanza.Message`
 | 
			
		||||
   :term:`stanza object` because the namespaced element name
 | 
			
		||||
   ``{jabber:client}message`` is associated with the class
 | 
			
		||||
   :class:`~sleekxmpp.stanza.Message`.
 | 
			
		||||
 | 
			
		||||
2. **Match stanza objects to callbacks.**
 | 
			
		||||
 | 
			
		||||
@@ -72,8 +73,8 @@ when this bit of XML is received (with an assumed namespace of
 | 
			
		||||
   :term:`stanza object` is paired with a reference to the handler and
 | 
			
		||||
   placed into the event queue.
 | 
			
		||||
 | 
			
		||||
   Our ``Message`` object is thus paired with the message stanza handler
 | 
			
		||||
   ``BaseXMPP._handle_message`` to create the tuple::
 | 
			
		||||
   Our :class:`~sleekxmpp.stanza.Message` object is thus paired with the message stanza handler
 | 
			
		||||
   :meth:`BaseXMPP._handle_message` to create the tuple::
 | 
			
		||||
 | 
			
		||||
       ('stanza', stanza_obj, handler)
 | 
			
		||||
 | 
			
		||||
@@ -88,7 +89,7 @@ when this bit of XML is received (with an assumed namespace of
 | 
			
		||||
   parameter.
 | 
			
		||||
 | 
			
		||||
   .. warning:: 
 | 
			
		||||
       The callback, aka :term:`stream handler`, is executed in the main
 | 
			
		||||
       The callback, aka :term:`stream handler`, is executed in the main event
 | 
			
		||||
       processing thread. If the handler blocks, event processing will also
 | 
			
		||||
       block.
 | 
			
		||||
 | 
			
		||||
@@ -96,20 +97,22 @@ when this bit of XML is received (with an assumed namespace of
 | 
			
		||||
 | 
			
		||||
   Since a :term:`stream handler` shouldn't block, if extensive processing
 | 
			
		||||
   for a stanza is required (such as needing to send and receive an
 | 
			
		||||
   ``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.
 | 
			
		||||
   :class:`~sleekxmpp.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.
 | 
			
		||||
 | 
			
		||||
   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.
 | 
			
		||||
   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.
 | 
			
		||||
 | 
			
		||||
   .. note::
 | 
			
		||||
       It is possible to skip the event queue and process an event immediately
 | 
			
		||||
       by using ``direct=True`` when raising the event.
 | 
			
		||||
 | 
			
		||||
   The code for ``BaseXMPP._handle_message`` follows this pattern, and
 | 
			
		||||
   The code for :meth:`BaseXMPP._handle_message` follows this pattern, and
 | 
			
		||||
   raises a ``'message'`` event::
 | 
			
		||||
 | 
			
		||||
       self.event('message', msg)
 | 
			
		||||
@@ -145,125 +148,30 @@ when this bit of XML is received (with an assumed namespace of
 | 
			
		||||
 | 
			
		||||
Raising XMPP Awareness: BaseXMPP
 | 
			
		||||
--------------------------------
 | 
			
		||||
While ``XMLStream`` attempts to shy away from anything too XMPP specific,
 | 
			
		||||
``BaseXMPP``'s sole purpose is to provide foundational support for sending
 | 
			
		||||
and receiving XMPP stanzas. This support includes registering the basic
 | 
			
		||||
message, presence, and iq stanzas, methods for creating and sending
 | 
			
		||||
stanzas, and default handlers for incoming messages and keeping track of
 | 
			
		||||
presence notifications.
 | 
			
		||||
While :class:`~sleekxmpp.xmlstream.xmlstream.XMLStream` attempts to shy away
 | 
			
		||||
from anything too XMPP specific, :class:`~sleekxmpp.basexmpp.BaseXMPP`'s
 | 
			
		||||
sole purpose is to provide foundational support for sending and receiving
 | 
			
		||||
XMPP stanzas. This support includes registering the basic message,
 | 
			
		||||
presence, and iq stanzas, methods for creating and sending stanzas, and
 | 
			
		||||
default handlers for incoming messages and keeping track of presence
 | 
			
		||||
notifications.
 | 
			
		||||
 | 
			
		||||
The plugin system for adding new XEP support is also maintained by
 | 
			
		||||
``BaseXMPP``.
 | 
			
		||||
:class:`~sleekxmpp.basexmpp.BaseXMPP`.
 | 
			
		||||
 | 
			
		||||
.. index:: ClientXMPP, BaseXMPP
 | 
			
		||||
 | 
			
		||||
ClientXMPP
 | 
			
		||||
----------
 | 
			
		||||
``ClientXMPP`` extends ``BaseXMPP`` with additional logic for connecting to
 | 
			
		||||
an XMPP server by performing DNS lookups. It also adds support for stream
 | 
			
		||||
:class:`~sleekxmpp.clientxmpp.ClientXMPP` extends
 | 
			
		||||
:class:`~sleekxmpp.clientxmpp.BaseXMPP` with additional logic for connecting
 | 
			
		||||
to an XMPP server by performing DNS lookups. It also adds support for stream
 | 
			
		||||
features such as STARTTLS and SASL.
 | 
			
		||||
 | 
			
		||||
.. index:: ComponentXMPP, BaseXMPP
 | 
			
		||||
 | 
			
		||||
ComponentXMPP
 | 
			
		||||
-------------
 | 
			
		||||
``ComponentXMPP`` is only a thin layer on top of ``BaseXMPP`` that
 | 
			
		||||
implements the component handshake protocol.
 | 
			
		||||
 | 
			
		||||
.. index::
 | 
			
		||||
    double: object; stanza
 | 
			
		||||
 | 
			
		||||
Stanza Objects: A Brief Look
 | 
			
		||||
----------------------------
 | 
			
		||||
.. seealso::
 | 
			
		||||
    See :ref:`api-stanza-objects` for a more detailed overview.
 | 
			
		||||
 | 
			
		||||
Almost worthy of their own standalone library, :term:`stanza objects <stanza object>`
 | 
			
		||||
are wrappers for XML objects which expose dictionary like interfaces
 | 
			
		||||
for manipulating their XML content. For example, consider the XML:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <message />
 | 
			
		||||
 | 
			
		||||
A very plain element to start with, but we can create a :term:`stanza object`
 | 
			
		||||
using ``sleekxmpp.stanza.Message`` as so::
 | 
			
		||||
 | 
			
		||||
    msg = Message(xml=ET.fromstring("<message />"))
 | 
			
		||||
 | 
			
		||||
The ``Message`` stanza class defines interfaces such as ``'body'`` and
 | 
			
		||||
``'to'``, so we can assign values to those interfaces to include new XML
 | 
			
		||||
content::
 | 
			
		||||
 | 
			
		||||
    msg['body'] = "Following so far?"
 | 
			
		||||
    msg['to'] = 'user@example.com'
 | 
			
		||||
 | 
			
		||||
Dumping the XML content of ``msg`` (using ``msg.xml``), we find:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <message to="user@example.com">
 | 
			
		||||
      <body>Following so far?</body>
 | 
			
		||||
    </message>
 | 
			
		||||
 | 
			
		||||
The process is similar for reading from interfaces and deleting interface
 | 
			
		||||
contents. A :term:`stanza object` behaves very similarly to a regular
 | 
			
		||||
``dict`` object: you may assign to keys, read from keys, and ``del`` keys.
 | 
			
		||||
 | 
			
		||||
Stanza interfaces come with built-in behaviours such as adding/removing
 | 
			
		||||
attribute and sub element values. However, a lot of the time more custom
 | 
			
		||||
logic is needed. This can be provided by defining methods of the form
 | 
			
		||||
``get_*``, ``set_*``, and ``del_*`` for any interface which requires custom
 | 
			
		||||
behaviour.
 | 
			
		||||
 | 
			
		||||
Stanza Plugins
 | 
			
		||||
~~~~~~~~~~~~~~
 | 
			
		||||
Since it is generally possible to embed one XML element inside another,
 | 
			
		||||
:term:`stanza objects <stanza object>` may be nested. Nested 
 | 
			
		||||
:term:`stanza objects <stanza object>` are referred to as :term:`stanza plugins <stanza plugin>`
 | 
			
		||||
or :term:`substanzas <substanza>`.
 | 
			
		||||
 | 
			
		||||
A :term:`stanza plugin` exposes its own interfaces by adding a new
 | 
			
		||||
interface to its parent stanza. To demonstrate, consider these two stanza
 | 
			
		||||
class definitions using ``sleekxmpp.xmlstream.ElementBase``:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    class Parent(ElementBase):
 | 
			
		||||
        name = "the-parent-xml-element-name"
 | 
			
		||||
        namespace = "the-parent-namespace"
 | 
			
		||||
        interfaces = set(('foo', 'bar'))
 | 
			
		||||
 | 
			
		||||
    class Child(ElementBase):
 | 
			
		||||
        name = "the-child-xml-element-name"
 | 
			
		||||
        namespace = "the-child-namespace"
 | 
			
		||||
        plugin_attrib = 'child'
 | 
			
		||||
        interfaces = set(('baz',))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
If we register the ``Child`` stanza as a plugin of the ``Parent`` stanza as
 | 
			
		||||
so, using ``sleekxmpp.xmlstream.register_stanza_plugin``::
 | 
			
		||||
 | 
			
		||||
    register_stanza_plugin(Parent, Child)
 | 
			
		||||
 | 
			
		||||
Then we can access content in the child stanza through the parent.
 | 
			
		||||
Note that the interface used to access the child stanza is the same as
 | 
			
		||||
``Child.plugin_attrib``::
 | 
			
		||||
 | 
			
		||||
    parent = Parent()
 | 
			
		||||
    parent['foo'] = 'a'
 | 
			
		||||
    parent['child']['baz'] = 'b'
 | 
			
		||||
 | 
			
		||||
The above code would produce:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <the-parent-xml-element xmlns="the-parent-namespace" foo="a">
 | 
			
		||||
      <the-child-xml-element xmlsn="the-child-namespace" baz="b" />
 | 
			
		||||
    </the-parent-xml-element>
 | 
			
		||||
 | 
			
		||||
It is also possible to allow a :term:`substanza` to appear multiple times
 | 
			
		||||
by using ``iterable=True`` in the ``register_stanza_plugin`` call. All
 | 
			
		||||
iterable :term:`substanzas <substanza>` can be accessed using a standard
 | 
			
		||||
``substanzas`` interface.
 | 
			
		||||
:class:`~sleekxmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of
 | 
			
		||||
:class:`~sleekxmpp.basexmpp.BaseXMPP` that implements the component handshake
 | 
			
		||||
protocol.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								docs/conf.py
									
									
									
									
									
								
							@@ -16,7 +16,7 @@ import sys, os
 | 
			
		||||
# 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('.'))
 | 
			
		||||
sys.path.insert(0, os.path.abspath('..'))
 | 
			
		||||
 | 
			
		||||
# -- General configuration -----------------------------------------------------
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +25,7 @@ import sys, os
 | 
			
		||||
 | 
			
		||||
# Add any Sphinx extension module names here, as strings. They can be extensions
 | 
			
		||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 | 
			
		||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
 | 
			
		||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx']
 | 
			
		||||
 | 
			
		||||
# Add any paths that contain templates here, relative to this directory.
 | 
			
		||||
templates_path = ['_templates']
 | 
			
		||||
@@ -50,7 +50,7 @@ copyright = u'2011, Nathan Fritz, Lance Stout'
 | 
			
		||||
# The short X.Y version.
 | 
			
		||||
version = '1.0'
 | 
			
		||||
# The full version, including alpha/beta/rc tags.
 | 
			
		||||
release = '1.0RC1'
 | 
			
		||||
release = '1.0'
 | 
			
		||||
 | 
			
		||||
# The language for content autogenerated by Sphinx. Refer to documentation
 | 
			
		||||
# for a list of supported languages.
 | 
			
		||||
@@ -81,7 +81,7 @@ exclude_patterns = ['_build']
 | 
			
		||||
#show_authors = False
 | 
			
		||||
 | 
			
		||||
# The name of the Pygments (syntax highlighting) style to use.
 | 
			
		||||
pygments_style = 'default'
 | 
			
		||||
pygments_style = 'tango'
 | 
			
		||||
 | 
			
		||||
# A list of ignored prefixes for module index sorting.
 | 
			
		||||
#modindex_common_prefix = []
 | 
			
		||||
@@ -218,3 +218,5 @@ man_pages = [
 | 
			
		||||
    ('index', 'sleekxmpp', u'SleekXMPP Documentation',
 | 
			
		||||
     [u'Nathan Fritz, Lance Stout'], 1)
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
.. _create-plugin:
 | 
			
		||||
 | 
			
		||||
Creating a SleekXMPP Plugin
 | 
			
		||||
===========================
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,75 @@
 | 
			
		||||
.. _echocomponent:
 | 
			
		||||
 | 
			
		||||
=================================
 | 
			
		||||
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/sleekxmpp-discussion>`_
 | 
			
		||||
    or join the chat room at `sleek@conference.jabber.org
 | 
			
		||||
    <xmpp:sleek@conference.jabber.org?join>`_.
 | 
			
		||||
 | 
			
		||||
If you have not yet installed SleekXMPP, do so now by either checking out a version
 | 
			
		||||
from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip``
 | 
			
		||||
or ``easy_install``.
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
    pip install sleekxmpp  # Or: easy_install sleekxmpp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Many XMPP applications eventually graduate to requiring to run as a server 
 | 
			
		||||
component in order to meet scalability requirements. To demonstrate how to 
 | 
			
		||||
turn an XMPP client bot into a component, we'll turn the echobot example
 | 
			
		||||
(:ref:`echobot`) into a component version.
 | 
			
		||||
 | 
			
		||||
The first difference is that we will add an additional import statement:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    from sleekxmpp.componentxmpp import ComponentXMPP
 | 
			
		||||
 | 
			
		||||
Likewise, we will change the bot's class definition to match:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    class EchoComponent(ComponentXMPP):
 | 
			
		||||
 | 
			
		||||
        def __init__(self, jid, secret, server, port):
 | 
			
		||||
            ComponentXMPP.__init__(self, jid, secret, server, port)
 | 
			
		||||
 | 
			
		||||
A component instance requires two extra parameters compared to a client
 | 
			
		||||
instance: ``server`` and ``port``. These specifiy the name and port of
 | 
			
		||||
the XMPP server that will be accepting the component. For example, for
 | 
			
		||||
a MUC component, the following could be used:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    muc = ComponentXMPP('muc.sleekxmpp.com', '******', 'sleekxmpp.com', 5555)
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    The ``server`` value is **NOT** derived from the provided JID for the
 | 
			
		||||
    component, unlike with client connections.
 | 
			
		||||
 | 
			
		||||
One difference with the component version is that we do not have
 | 
			
		||||
to handle the :term:`session_start` event if we don't wish to deal
 | 
			
		||||
with presence.
 | 
			
		||||
 | 
			
		||||
The other, main difference with components is that the
 | 
			
		||||
``'from'`` value for every stanza must be explicitly set, since
 | 
			
		||||
components may send stanzas from multiple JIDs. To do so,
 | 
			
		||||
the :meth:`~sleekxmpp.basexmpp.BaseXMPP.send_message()` and
 | 
			
		||||
:meth:`~sleekxmpp.basexmpp.BaseXMPP.send_presence()` accept the parameters
 | 
			
		||||
``mfrom`` and ``pfrom``, respectively. For any method that uses
 | 
			
		||||
:class:`~sleekxmpp.stanza.iq.Iq` stanzas, ``ifrom`` may be used.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Final Product
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
.. include:: ../../examples/echo_component.py
 | 
			
		||||
    :literal:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,182 @@
 | 
			
		||||
Send/Receive IQ Stanzas
 | 
			
		||||
=======================
 | 
			
		||||
 | 
			
		||||
Unlike :class:`~sleekxmpp.stanza.message.Message` and
 | 
			
		||||
:class:`~sleekxmpp.stanza.presence.Presence` stanzas which only use
 | 
			
		||||
text data for basic usage, :class:`~sleekxmpp.stanza.iq.Iq` stanzas
 | 
			
		||||
require using XML payloads, and generally entail creating a new
 | 
			
		||||
SleekXMPP plugin to provide the necessary convenience methods to
 | 
			
		||||
make working with them easier.
 | 
			
		||||
 | 
			
		||||
Basic Use
 | 
			
		||||
---------
 | 
			
		||||
 | 
			
		||||
XMPP's use of :class:`~sleekxmpp.stanza.iq.Iq` stanzas is built around
 | 
			
		||||
namespaced ``<query />`` elements. For clients, just sending the
 | 
			
		||||
empty ``<query />`` element will suffice for retrieving information. For
 | 
			
		||||
example, a very basic implementation of service discovery would just
 | 
			
		||||
need to be able to send:
 | 
			
		||||
 | 
			
		||||
.. code-block:: xml
 | 
			
		||||
 | 
			
		||||
    <iq to="user@example.com" type="get" id="1">
 | 
			
		||||
      <query xmlns="http://jabber.org/protocol/disco#info" />
 | 
			
		||||
    </iq>
 | 
			
		||||
 | 
			
		||||
Creating Iq Stanzas
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
SleekXMPP provides built-in support for creating basic :class:`~sleekxmpp.stanza.iq.Iq`
 | 
			
		||||
stanzas this way. The relevant methods are:
 | 
			
		||||
 | 
			
		||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq`
 | 
			
		||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_get`
 | 
			
		||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_set`
 | 
			
		||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_result`
 | 
			
		||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_error`
 | 
			
		||||
* :meth:`~sleekxmpp.basexmpp.BaseXMPP.make_iq_query`
 | 
			
		||||
 | 
			
		||||
These methods all follow the same pattern: create or modify an existing 
 | 
			
		||||
:class:`~sleekxmpp.stanza.iq.Iq` stanza, set the ``'type'`` value based
 | 
			
		||||
on the method name, and finally add a ``<query />`` element with the given
 | 
			
		||||
namespace. For example, to produce the query above, you would use:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    self.make_iq_get(queryxmlns='http://jabber.org/protocol/disco#info',
 | 
			
		||||
                     ito='user@example.com')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Sending Iq Stanzas
 | 
			
		||||
~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Once an :class:`~sleekxmpp.stanza.iq.Iq` stanza is created, sending it
 | 
			
		||||
over the wire is done using its :meth:`~sleekxmpp.stanza.iq.Iq.send()`
 | 
			
		||||
method, like any other stanza object. However, there are a few extra
 | 
			
		||||
options to control how to wait for the query's response.
 | 
			
		||||
 | 
			
		||||
These options are:
 | 
			
		||||
 | 
			
		||||
* ``block``: The default behaviour is that :meth:`~sleekxmpp.stanza.iq.Iq.send()`
 | 
			
		||||
  will block until a response is received and the response stanza will be the
 | 
			
		||||
  return value. Setting ``block`` to ``False`` will cause the call to return
 | 
			
		||||
  immediately. In which case, you will need to arrange some way to capture
 | 
			
		||||
  the response stanza if you need it.
 | 
			
		||||
 | 
			
		||||
* ``timeout``: When using the blocking behaviour, the call will eventually
 | 
			
		||||
  timeout with an error. The default timeout is 30 seconds, but this may
 | 
			
		||||
  be overidden two ways. To change the timeout globally, set:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
        self.response_timeout = 10
 | 
			
		||||
 | 
			
		||||
  To change the timeout for a single call, the ``timeout`` parameter works:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
        
 | 
			
		||||
        iq.send(timeout=60)
 | 
			
		||||
 | 
			
		||||
* ``callback``: When not using a blocking call, using the ``callback``
 | 
			
		||||
  argument is a simple way to register a handler that will execute
 | 
			
		||||
  whenever a response is finally received. Using this method, there
 | 
			
		||||
  is no timeout limit. In case you need to remove the callback, the
 | 
			
		||||
  name of the newly created callback is returned.
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
       cb_name = iq.send(callback=self.a_callback) 
 | 
			
		||||
 | 
			
		||||
       # ... later if we need to cancel
 | 
			
		||||
       self.remove_handler(cb_name)
 | 
			
		||||
 | 
			
		||||
Properly working with :class:`~sleekxmpp.stanza.iq.Iq` stanzas requires
 | 
			
		||||
handling the intended, normal flow, error responses, and timed out
 | 
			
		||||
requests. To make this easier, two exceptions may be thrown by
 | 
			
		||||
:meth:`~sleekxmpp.stanza.iq.Iq.send()`: :exc:`~sleekxmpp.exceptions.IqError`
 | 
			
		||||
and :exc:`~sleekxmpp.exceptions.IqTimeout`. These exceptions only
 | 
			
		||||
apply to the default, blocking calls.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        resp = iq.send()
 | 
			
		||||
        # ... do stuff with expected Iq result
 | 
			
		||||
    except IqError as e:
 | 
			
		||||
        err_resp = e.iq
 | 
			
		||||
        # ... handle error case
 | 
			
		||||
    except IqTimeout:
 | 
			
		||||
        # ... no response received in time
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
If you do not care to distinguish between errors and timeouts, then you
 | 
			
		||||
can combine both cases with a generic :exc:`~sleekxmpp.exceptions.XMPPError`
 | 
			
		||||
exception:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        resp = iq.send()
 | 
			
		||||
    except XMPPError:
 | 
			
		||||
        # ... Don't care about the response
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
Advanced Use
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
Going beyond the basics provided by SleekXMPP requires building at least a
 | 
			
		||||
rudimentary SleekXMPP plugin to create a :term:`stanza object` for
 | 
			
		||||
interfacting with the :class:`~sleekxmpp.stanza.iq.Iq` payload.
 | 
			
		||||
 | 
			
		||||
.. seealso::
 | 
			
		||||
 | 
			
		||||
    * :ref:`create-plugin`
 | 
			
		||||
    * :ref:`work-with-stanzas`
 | 
			
		||||
    * :ref:`using-handlers-matchers`
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
The typical way to respond to :class:`~sleekxmpp.stanza.iq.Iq` requests is
 | 
			
		||||
to register stream handlers. As an example, suppose we create a stanza class
 | 
			
		||||
named ``CustomXEP`` which uses the XML element ``<query xmlns="custom-xep" />``,
 | 
			
		||||
and has a :attr:`~sleekxmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value
 | 
			
		||||
of ``custom_xep``.
 | 
			
		||||
 | 
			
		||||
There are two types of incoming :class:`~sleekxmpp.stanza.iq.Iq` requests:
 | 
			
		||||
``get`` and ``set``. You can register a handler that will accept both and then
 | 
			
		||||
filter by type as needed, as so:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    self.register_handler(Callback(
 | 
			
		||||
        'CustomXEP Handler',
 | 
			
		||||
        StanzaPath('iq/custom_xep'),
 | 
			
		||||
        self._handle_custom_iq))
 | 
			
		||||
 | 
			
		||||
    # ...
 | 
			
		||||
 | 
			
		||||
    def _handle_custom_iq(self, iq):
 | 
			
		||||
        if iq['type'] == 'get':
 | 
			
		||||
            # ...
 | 
			
		||||
            pass
 | 
			
		||||
        elif iq['type'] == 'set':
 | 
			
		||||
            # ...
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            # ... This will capture error responses too
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
If you want to filter out query types beforehand, you can adjust the matching
 | 
			
		||||
filter by using ``@type=get`` or ``@type=set`` if you are using the recommended
 | 
			
		||||
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath` matcher.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    self.register_handler(Callback(
 | 
			
		||||
        'CustomXEP Handler',
 | 
			
		||||
        StanzaPath('iq@type=get/custom_xep'),
 | 
			
		||||
        self._handle_custom_iq_get))
 | 
			
		||||
 | 
			
		||||
    # ...
 | 
			
		||||
 | 
			
		||||
    def _handle_custom_iq_get(self, iq):
 | 
			
		||||
        assert(iq['type'] == 'get')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,208 @@
 | 
			
		||||
.. _mucbot:
 | 
			
		||||
 | 
			
		||||
=========================
 | 
			
		||||
Mulit-User Chat (MUC) Bot
 | 
			
		||||
=========================
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    If you have any issues working through this quickstart guide
 | 
			
		||||
    or the other tutorials here, please either send a message to the
 | 
			
		||||
    `mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
 | 
			
		||||
    or join the chat room at `sleek@conference.jabber.org
 | 
			
		||||
    <xmpp:sleek@conference.jabber.org?join>`_.
 | 
			
		||||
 | 
			
		||||
If you have not yet installed SleekXMPP, do so now by either checking out a version
 | 
			
		||||
from `Github <http://github.com/fritzy/SleekXMPP>`_, or installing it using ``pip``
 | 
			
		||||
or ``easy_install``.
 | 
			
		||||
 | 
			
		||||
.. code-block:: sh
 | 
			
		||||
 | 
			
		||||
    pip install sleekxmpp  # Or: easy_install sleekxmpp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Now that you've got the basic gist of using SleekXMPP by following the
 | 
			
		||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
 | 
			
		||||
to create a very popular XMPP starter project: a `Multi-User Chat`_
 | 
			
		||||
(MUC) bot. Our bot will login to an XMPP server, join an MUC chat room
 | 
			
		||||
and "lurk" indefinitely, responding with a generic message to anyone
 | 
			
		||||
that mentions its nickname. It will also greet members as they join the
 | 
			
		||||
chat room.
 | 
			
		||||
 | 
			
		||||
.. _`multi-user chat`: http://xmpp.org/extensions/xep-0045.html
 | 
			
		||||
 | 
			
		||||
Joining The Room
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
As usual, our code will be based on the pattern explained in :ref:`echobot`.
 | 
			
		||||
To start, we create an ``MUCBot`` class based on
 | 
			
		||||
:class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` and which accepts
 | 
			
		||||
parameters for the JID of the MUC room to join, and the nick that the
 | 
			
		||||
bot will use inside the chat room.  We also register an
 | 
			
		||||
:term:`event handler` for the :term:`session_start` event.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    import sleekxmpp
 | 
			
		||||
 | 
			
		||||
    class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        def __init__(self, jid, password, room, nick):
 | 
			
		||||
            sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
            self.room = room
 | 
			
		||||
            self.nick = nick
 | 
			
		||||
 | 
			
		||||
            self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
After initialization, we also need to register the MUC (XEP-0045) plugin
 | 
			
		||||
so that we can make use of the group chat plugin's methods and events.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    xmpp.register_plugin('xep_0045')
 | 
			
		||||
 | 
			
		||||
Finally, we can make our bot join the chat room once an XMPP session
 | 
			
		||||
has been established:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.plugin['xep_0045'].joinMUC(self.room,
 | 
			
		||||
                                        self.nick,
 | 
			
		||||
                                        wait=True)
 | 
			
		||||
 | 
			
		||||
Note that as in :ref:`echobot`, we need to include send an initial presence and request
 | 
			
		||||
the roster. Next, we want to join the group chat, so we call the
 | 
			
		||||
``joinMUC`` method of the MUC plugin.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    The :attr:`plugin <sleekxmpp.basexmpp.BaseXMPP.plugin>` attribute is
 | 
			
		||||
    dictionary that maps to instances of plugins that we have previously
 | 
			
		||||
    registered, by their names.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Adding Functionality
 | 
			
		||||
--------------------
 | 
			
		||||
 | 
			
		||||
Currently, our bot just sits dormantly inside the chat room, but we
 | 
			
		||||
would like it to respond to two distinct events by issuing a generic
 | 
			
		||||
message in each case to the chat room. In particular, when a member
 | 
			
		||||
mentions the bot's nickname inside the chat room, and when a member
 | 
			
		||||
joins the chat room.
 | 
			
		||||
 | 
			
		||||
Responding to Mentions
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Whenever a user mentions our bot's nickname in chat, our bot will
 | 
			
		||||
respond with a generic message resembling *"I heard that, user."* We do
 | 
			
		||||
this by examining all of the messages sent inside the chat and looking
 | 
			
		||||
for the ones which contain the nickname string.
 | 
			
		||||
 | 
			
		||||
First, we register an event handler for the :term:`groupchat_message`
 | 
			
		||||
event inside the bot's ``__init__`` function.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    We do not register a handler for the :term:`message` event in this
 | 
			
		||||
    bot, but if we did, the group chat message would have been sent to
 | 
			
		||||
    both handlers.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, room, nick):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        self.room = room
 | 
			
		||||
        self.nick = nick
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
        self.add_event_handler("groupchat_message", self.muc_message)
 | 
			
		||||
 | 
			
		||||
Then, we can send our generic message whenever the bot's nickname gets
 | 
			
		||||
mentioned.
 | 
			
		||||
 | 
			
		||||
.. warning::
 | 
			
		||||
 | 
			
		||||
    Always check that a message is not from yourself,
 | 
			
		||||
    otherwise you will create an infinite loop responding
 | 
			
		||||
    to your own messages.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def muc_message(self, msg):
 | 
			
		||||
        if msg['mucnick'] != self.nick and self.nick in msg['body']:
 | 
			
		||||
            self.send_message(mto=msg['from'].bare,
 | 
			
		||||
                              mbody="I heard that, %s." % msg['mucnick'],
 | 
			
		||||
                              mtype='groupchat')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Greeting Members
 | 
			
		||||
~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
Now we want to greet member whenever they join the group chat. To
 | 
			
		||||
do this we will use the dynamic ``muc::room@server::got_online`` [1]_
 | 
			
		||||
event so it's a good idea to register an event handler for it.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    The groupchat_presence event is triggered whenever a
 | 
			
		||||
    presence stanza is received from any chat room, including
 | 
			
		||||
    any presences you send yourself. To limit event handling
 | 
			
		||||
    to a single room, use the events ``muc::room@server::presence``,
 | 
			
		||||
    ``muc::room@server::got_online``, or ``muc::room@server::got_offline``.
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, room, nick):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        self.room = room
 | 
			
		||||
        self.nick = nick
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
        self.add_event_handler("groupchat_message", self.muc_message)
 | 
			
		||||
        self.add_event_handler("muc::%s::got_online" % self.room,
 | 
			
		||||
                               self.muc_online)
 | 
			
		||||
 | 
			
		||||
Now all that's left to do is to greet them:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    def muc_online(self, presence):
 | 
			
		||||
        if presence['muc']['nick'] != self.nick:
 | 
			
		||||
            self.send_message(mto=presence['from'].bare,
 | 
			
		||||
                              mbody="Hello, %s %s" % (presence['muc']['role'],
 | 
			
		||||
                                                      presence['muc']['nick']),
 | 
			
		||||
                              mtype='groupchat')
 | 
			
		||||
 | 
			
		||||
.. [1] this is similar to the :term:`got_online` event and is sent by
 | 
			
		||||
       the xep_0045 plugin whenever a member joins the referenced
 | 
			
		||||
       MUC chat room.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Final Product
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
.. compound::
 | 
			
		||||
 | 
			
		||||
    The final step is to create a small runner script for initialising our ``MUCBot`` class and adding some
 | 
			
		||||
    basic configuration options. By following the basic boilerplate pattern in :ref:`echobot`, we arrive
 | 
			
		||||
    at the code below. To experiment with this example, you can use:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: sh
 | 
			
		||||
 | 
			
		||||
            python muc.py -d -j jid@example.com -r room@muc.example.net -n lurkbot
 | 
			
		||||
 | 
			
		||||
    which will prompt for the password, log in, and join the group chat. To test, open
 | 
			
		||||
    your regular IM client and join the same group chat that you sent the bot to. You
 | 
			
		||||
    will see ``lurkbot`` as one of the members in the group chat, and that it greeted
 | 
			
		||||
    you upon entry. Send a message with the string "lurkbot" inside the body text, and you
 | 
			
		||||
    will also see that it responds with our pre-programmed customized message.
 | 
			
		||||
 | 
			
		||||
.. include:: ../../examples/muc.py
 | 
			
		||||
    :literal:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,42 @@
 | 
			
		||||
.. _proxy:
 | 
			
		||||
 | 
			
		||||
=========================
 | 
			
		||||
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/sleekxmpp-discussion>`_
 | 
			
		||||
    or join the chat room at `sleek@conference.jabber.org
 | 
			
		||||
    <xmpp:sleek@conference.jabber.org?join>`_.
 | 
			
		||||
 | 
			
		||||
In some instances, you may wish to route XMPP traffic through
 | 
			
		||||
an HTTP proxy, probably to get around restrictive firewalls.
 | 
			
		||||
SleekXMPP provides support for basic HTTP proxying with DIGEST
 | 
			
		||||
authentication.
 | 
			
		||||
 | 
			
		||||
Enabling proxy support is done in two steps. The first is to instruct SleekXMPP
 | 
			
		||||
to use a proxy, and the second is to configure the proxy details:
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    xmpp = ClientXMPP(...)
 | 
			
		||||
    xmpp.use_proxy = True
 | 
			
		||||
    xmpp.proxy_config = {
 | 
			
		||||
        'host': 'proxy.example.com',
 | 
			
		||||
        'port': 5555,
 | 
			
		||||
        'username': 'example_user',
 | 
			
		||||
        'password': '******'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
The ``'username'`` and ``'password'`` fields are optional if the proxy does not
 | 
			
		||||
require authentication.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The Final Product
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
.. include:: ../../examples/proxy_echo_client.py
 | 
			
		||||
    :literal:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,4 @@
 | 
			
		||||
.. _using-handlers-matchers:
 | 
			
		||||
 | 
			
		||||
Using Stream Handlers and Matchers
 | 
			
		||||
==================================
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								docs/howto/stanzas.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docs/howto/stanzas.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
.. _work-with-stanzas:
 | 
			
		||||
 | 
			
		||||
How to Work with Stanza Objects
 | 
			
		||||
===============================
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _create-stanza-interfaces:
 | 
			
		||||
 | 
			
		||||
Defining Stanza Interfaces
 | 
			
		||||
--------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _create-stanza-plugins:
 | 
			
		||||
 | 
			
		||||
Creating Stanza Plugins
 | 
			
		||||
-----------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _create-extension-plugins:
 | 
			
		||||
 | 
			
		||||
Creating a Stanza Extension
 | 
			
		||||
---------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _override-parent-interfaces:
 | 
			
		||||
 | 
			
		||||
Overriding a Parent Stanza
 | 
			
		||||
--------------------------
 | 
			
		||||
							
								
								
									
										131
									
								
								docs/index.rst
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								docs/index.rst
									
									
									
									
									
								
							@@ -12,13 +12,8 @@ SleekXMPP
 | 
			
		||||
    ``master`` branch, while the latest development version is in the
 | 
			
		||||
    ``develop`` branch.
 | 
			
		||||
 | 
			
		||||
    **Stable Releases**
 | 
			
		||||
        - `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_  
 | 
			
		||||
        - `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_
 | 
			
		||||
        - `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_
 | 
			
		||||
        - `1.0 Beta3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta3>`_
 | 
			
		||||
        - `1.0 Beta2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta2>`_
 | 
			
		||||
        - `1.0 Beta1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta1>`_
 | 
			
		||||
    **Latest Stable Release**
 | 
			
		||||
        - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_
 | 
			
		||||
 | 
			
		||||
    **Develop Releases**
 | 
			
		||||
        - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
 | 
			
		||||
@@ -64,6 +59,72 @@ SleekXMPP's design goals and philosphy are:
 | 
			
		||||
    sensible defaults and appropriate abstractions. XML can be ugly to work
 | 
			
		||||
    with, but it doesn't have to be that way.
 | 
			
		||||
 | 
			
		||||
Here's your first SleekXMPP Bot:
 | 
			
		||||
--------------------------------
 | 
			
		||||
 | 
			
		||||
.. code-block:: python
 | 
			
		||||
 | 
			
		||||
    import logging
 | 
			
		||||
 | 
			
		||||
    from sleekxmpp import ClientXMPP
 | 
			
		||||
    from sleekxmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    class EchoBot(ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        def __init__(self, jid, password):
 | 
			
		||||
            ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
            self.add_event_handler("session_start", self.session_start)
 | 
			
		||||
            self.add_event_handler("message", self.message)
 | 
			
		||||
 | 
			
		||||
            # If you wanted more functionality, here's how to register plugins:
 | 
			
		||||
            # self.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
            # self.register_plugin('xep_0199') # XMPP Ping
 | 
			
		||||
 | 
			
		||||
            # Here's how to access plugins once you've registered them:
 | 
			
		||||
            # self['xep_0030'].add_feature('echo_demo')
 | 
			
		||||
 | 
			
		||||
            # If you are working with an OpenFire server, you will
 | 
			
		||||
            # need to use a different SSL version:
 | 
			
		||||
            # import ssl
 | 
			
		||||
            # self.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
        def session_start(self, event):
 | 
			
		||||
            self.send_presence()
 | 
			
		||||
            self.get_roster()
 | 
			
		||||
 | 
			
		||||
            # Most get_*/set_* methods from plugins use Iq stanzas, which
 | 
			
		||||
            # can generate IqError and IqTimeout exceptions
 | 
			
		||||
            #
 | 
			
		||||
            # try:
 | 
			
		||||
            #     self.get_roster()
 | 
			
		||||
            # except IqError as err:
 | 
			
		||||
            #     logging.error('There was an error getting the roster')
 | 
			
		||||
            #     logging.error(err.iq['error']['condition'])
 | 
			
		||||
            #     self.disconnect()
 | 
			
		||||
            # except IqTimeout:
 | 
			
		||||
            #     logging.error('Server is taking too long to respond')
 | 
			
		||||
            #     self.disconnect()
 | 
			
		||||
 | 
			
		||||
        def message(self, msg):
 | 
			
		||||
            if msg['type'] in ('chat', 'normal'):
 | 
			
		||||
                msg.reply("Thanks for sending\n%(body)s" % msg).send()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if __name__ == '__main__':
 | 
			
		||||
        # Ideally use optparse or argparse to get JID, 
 | 
			
		||||
        # password, and log level.
 | 
			
		||||
 | 
			
		||||
        logging.basicConfig(level=logging.DEBUG,
 | 
			
		||||
                            format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
        xmpp = EchoBot('somejid@example.com', 'use_getpass')
 | 
			
		||||
        xmpp.connect()
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Getting Started (with Examples)
 | 
			
		||||
-------------------------------
 | 
			
		||||
.. toctree::
 | 
			
		||||
@@ -84,8 +145,10 @@ Tutorials, FAQs, and How To Guides
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 1
 | 
			
		||||
   
 | 
			
		||||
    faq
 | 
			
		||||
    xeps
 | 
			
		||||
    xmpp_tdg
 | 
			
		||||
    howto/stanzas
 | 
			
		||||
    create_plugin
 | 
			
		||||
    features
 | 
			
		||||
    sasl
 | 
			
		||||
@@ -113,8 +176,35 @@ API Reference
 | 
			
		||||
    
 | 
			
		||||
    event_index
 | 
			
		||||
    api/clientxmpp
 | 
			
		||||
    api/componentxmpp
 | 
			
		||||
    api/basexmpp
 | 
			
		||||
    api/xmlstream
 | 
			
		||||
    api/exceptions
 | 
			
		||||
    api/xmlstream/jid
 | 
			
		||||
    api/xmlstream/stanzabase
 | 
			
		||||
    api/xmlstream/handler
 | 
			
		||||
    api/xmlstream/matcher
 | 
			
		||||
    api/xmlstream/xmlstream
 | 
			
		||||
    api/xmlstream/scheduler
 | 
			
		||||
    api/xmlstream/tostring
 | 
			
		||||
    api/xmlstream/filesocket
 | 
			
		||||
 | 
			
		||||
Core Stanzas
 | 
			
		||||
~~~~~~~~~~~~
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 2
 | 
			
		||||
 | 
			
		||||
    api/stanza/rootstanza
 | 
			
		||||
    api/stanza/message
 | 
			
		||||
    api/stanza/presence
 | 
			
		||||
    api/stanza/iq
 | 
			
		||||
    api/stanza/error
 | 
			
		||||
    api/stanza/stream_error
 | 
			
		||||
 | 
			
		||||
Plugins
 | 
			
		||||
~~~~~~~
 | 
			
		||||
.. toctree::
 | 
			
		||||
    :maxdepth: 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Additional Info
 | 
			
		||||
---------------
 | 
			
		||||
@@ -132,17 +222,24 @@ Additional Info
 | 
			
		||||
 | 
			
		||||
Credits
 | 
			
		||||
-------
 | 
			
		||||
**Main Author:** Nathan Fritz
 | 
			
		||||
    `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, 
 | 
			
		||||
    `@fritzy <http://twitter.com/fritzy>`_
 | 
			
		||||
 | 
			
		||||
    Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
 | 
			
		||||
    <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a member of the XMPP
 | 
			
		||||
    Council.
 | 
			
		||||
**Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_
 | 
			
		||||
     `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
 | 
			
		||||
     `@fritzy <http://twitter.com/fritzy>`_
 | 
			
		||||
 | 
			
		||||
**Co-Author:** Lance Stout
 | 
			
		||||
    `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, 
 | 
			
		||||
    `@lancestout <http://twitter.com/lancestout>`_
 | 
			
		||||
     Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
 | 
			
		||||
     <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of the XMPP
 | 
			
		||||
     Council.
 | 
			
		||||
 | 
			
		||||
**Co-Author:** `Lance Stout <http://andyet.net/team/lance>`_
 | 
			
		||||
     `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,
 | 
			
		||||
     `@lancestout <http://twitter.com/lancestout>`_
 | 
			
		||||
 | 
			
		||||
Both Fritzy and Lance work for `&yet <http://andyet.net>`_, which specializes in
 | 
			
		||||
realtime web and XMPP applications.
 | 
			
		||||
 | 
			
		||||
    - `contact@andyet.net <mailto:contact@andyet.net>`_
 | 
			
		||||
    - `XMPP Consulting <http://xmppconsulting.com>`_
 | 
			
		||||
 | 
			
		||||
**Contributors:**
 | 
			
		||||
    - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/python-objects.inv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/python-objects.inv
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1,2 +1,50 @@
 | 
			
		||||
Supported XEPS
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
======= ============================= ================
 | 
			
		||||
XEP     Description                   Notes
 | 
			
		||||
======= ============================= ================
 | 
			
		||||
`0004`_ Data forms                   
 | 
			
		||||
`0009`_ Jabber RPC                   
 | 
			
		||||
`0012`_ Last Activity             
 | 
			
		||||
`0030`_ Service Discovery
 | 
			
		||||
`0033`_ Extended Stanza Addressing
 | 
			
		||||
`0045`_ Multi-User Chat (MUC)         Client-side only
 | 
			
		||||
`0050`_ Ad-hoc Commands
 | 
			
		||||
`0059`_ Result Set Management
 | 
			
		||||
`0060`_ Publish/Subscribe (PubSub)    Client-side only
 | 
			
		||||
`0066`_ Out-of-band Data
 | 
			
		||||
`0078`_ Non-SASL Authentication
 | 
			
		||||
`0082`_ XMPP Date and Time Profiles
 | 
			
		||||
`0085`_ Chat-State Notifications
 | 
			
		||||
`0086`_ Error Condition Mappings
 | 
			
		||||
`0092`_ Software Version
 | 
			
		||||
`0128`_ Service Discovery Extensions
 | 
			
		||||
`0202`_ Entity Time
 | 
			
		||||
`0203`_ Delayed Delivery
 | 
			
		||||
`0224`_ Attention
 | 
			
		||||
`0249`_ Direct MUC Invitations
 | 
			
		||||
======= ============================= ================
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _0004: http://xmpp.org/extensions/xep-0004.html
 | 
			
		||||
.. _0009: http://xmpp.org/extensions/xep-0009.html
 | 
			
		||||
.. _0012: http://xmpp.org/extensions/xep-0012.html
 | 
			
		||||
.. _0030: http://xmpp.org/extensions/xep-0030.html
 | 
			
		||||
.. _0033: http://xmpp.org/extensions/xep-0033.html
 | 
			
		||||
.. _0045: http://xmpp.org/extensions/xep-0045.html
 | 
			
		||||
.. _0050: http://xmpp.org/extensions/xep-0050.html
 | 
			
		||||
.. _0059: http://xmpp.org/extensions/xep-0059.html
 | 
			
		||||
.. _0060: http://xmpp.org/extensions/xep-0060.html
 | 
			
		||||
.. _0066: http://xmpp.org/extensions/xep-0066.html
 | 
			
		||||
.. _0078: http://xmpp.org/extensions/xep-0078.html
 | 
			
		||||
.. _0082: http://xmpp.org/extensions/xep-0082.html
 | 
			
		||||
.. _0085: http://xmpp.org/extensions/xep-0085.html
 | 
			
		||||
.. _0086: http://xmpp.org/extensions/xep-0086.html
 | 
			
		||||
.. _0092: http://xmpp.org/extensions/xep-0092.html
 | 
			
		||||
.. _0128: http://xmpp.org/extensions/xep-0128.html
 | 
			
		||||
.. _0199: http://xmpp.org/extensions/xep-0199.html
 | 
			
		||||
.. _0202: http://xmpp.org/extensions/xep-0202.html
 | 
			
		||||
.. _0203: http://xmpp.org/extensions/xep-0203.html
 | 
			
		||||
.. _0224: http://xmpp.org/extensions/xep-0224.html
 | 
			
		||||
.. _0249: http://xmpp.org/extensions/xep-0249.html
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
@@ -41,7 +40,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +49,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -72,7 +71,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    def _handle_command(self, iq, session):
 | 
			
		||||
        """
 | 
			
		||||
        Respond to the intial request for a command.
 | 
			
		||||
        Respond to the initial request for a command.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq      -- The iq stanza containing the command request.
 | 
			
		||||
@@ -192,14 +191,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
@@ -44,7 +43,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
        self.add_event_handler("message", self.message)
 | 
			
		||||
@@ -54,7 +53,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -198,14 +197,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										173
									
								
								examples/custom_stanzas/custom_stanza_provider.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										173
									
								
								examples/custom_stanzas/custom_stanza_provider.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import ClientXMPP, Iq
 | 
			
		||||
from sleekxmpp.exceptions import IqError, IqTimeout, XMPPError
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from stanza import Action
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple SleekXMPP bot that receives a custom stanza
 | 
			
		||||
    from another client.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
        self.registerHandler(
 | 
			
		||||
          Callback('Some custom iq',
 | 
			
		||||
            StanzaPath('iq@type=set/action'),
 | 
			
		||||
            self._handle_action))
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('custom_action', 
 | 
			
		||||
                self._handle_action_event, 
 | 
			
		||||
                threaded=True)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, Action)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
    def _handle_action(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Raise an event for the stanza so that it can be processed in its
 | 
			
		||||
        own thread without blocking the main stanza processing loop.
 | 
			
		||||
        """
 | 
			
		||||
        self.event('custom_action', iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_action_event(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Respond to the custom action event.
 | 
			
		||||
 | 
			
		||||
        Since one of the actions is to disconnect, this
 | 
			
		||||
        event handler needs to be run in threaded mode, by
 | 
			
		||||
        using `threaded=True` in the `add_event_handler` call.
 | 
			
		||||
        """
 | 
			
		||||
        method = iq['action']['method']
 | 
			
		||||
        param = iq['action']['param']
 | 
			
		||||
 | 
			
		||||
        if method == 'is_prime' and param == '2':
 | 
			
		||||
            print("got message: %s" % iq)
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            iq['action']['status'] = 'done'
 | 
			
		||||
            iq.send()
 | 
			
		||||
        elif method == 'bye':
 | 
			
		||||
            print("got message: %s" % iq)
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
        else:
 | 
			
		||||
            print("got message: %s" % iq)
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            iq['action']['status'] = 'error'
 | 
			
		||||
            iq.send()
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the CommandBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = ActionBot(opts.jid, opts.password)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0050') # Adhoc Commands
 | 
			
		||||
    xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency':15})
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										175
									
								
								examples/custom_stanzas/custom_stanza_user.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										175
									
								
								examples/custom_stanzas/custom_stanza_user.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.exceptions import XMPPError
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
from stanza import Action
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ActionUserBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple SleekXMPP bot that sends a custom action stanza
 | 
			
		||||
    to another client.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, other):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        self.action_provider = other
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start, threaded=True)
 | 
			
		||||
        self.add_event_handler("message", self.message)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, Action)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        self.send_custom_iq()
 | 
			
		||||
 | 
			
		||||
    def send_custom_iq(self):
 | 
			
		||||
        """Create and send two custom actions.
 | 
			
		||||
 | 
			
		||||
        If the first action was successful, then send
 | 
			
		||||
        a shutdown command and then disconnect.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()
 | 
			
		||||
        iq['to'] = self.action_provider
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['action']['method'] = 'is_prime'
 | 
			
		||||
        iq['action']['param'] = '2'
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            resp = iq.send()
 | 
			
		||||
            if resp['action']['status'] == 'done':
 | 
			
		||||
                #sending bye
 | 
			
		||||
                iq2 = self.Iq()
 | 
			
		||||
                iq2['to'] = self.action_provider
 | 
			
		||||
                iq2['type'] = 'set'
 | 
			
		||||
                iq2['action']['method'] = 'bye'
 | 
			
		||||
                iq2.send(block=False)
 | 
			
		||||
            
 | 
			
		||||
                # The wait=True delays the disconnect until the queue
 | 
			
		||||
                # of stanzas to be sent becomes empty.
 | 
			
		||||
                self.disconnect(wait=True)
 | 
			
		||||
        except XMPPError:
 | 
			
		||||
            print('There was an error sending the custom action.')
 | 
			
		||||
 | 
			
		||||
    def message(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming message stanzas.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            msg -- The received message stanza.
 | 
			
		||||
        """
 | 
			
		||||
        logging.info(msg['body'])
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
    optp.add_option("-o", "--other", dest="other",
 | 
			
		||||
                    help="JID providing custom stanza")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
    if opts.other is None:
 | 
			
		||||
        opts.other = raw_input("JID Providing custom stanza: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the CommandBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = ActionUserBot(opts.jid, opts.password, opts.other)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0050') # Adhoc Commands
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										56
									
								
								examples/custom_stanzas/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								examples/custom_stanzas/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
class Action(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A stanza class for XML content of the form:
 | 
			
		||||
 | 
			
		||||
    <action xmlns="sleekxmpp:custom:actions">
 | 
			
		||||
      <method>X</method>
 | 
			
		||||
      <param>X</param>
 | 
			
		||||
      <status>X</status>
 | 
			
		||||
    </action>
 | 
			
		||||
    """
 | 
			
		||||
   
 | 
			
		||||
    #: The `name` field refers to the basic XML tag name of the
 | 
			
		||||
    #: stanza. Here, the tag name will be 'action'.
 | 
			
		||||
    name = 'action'
 | 
			
		||||
 | 
			
		||||
    #: The namespace of the main XML tag.
 | 
			
		||||
    namespace = 'sleekxmpp:custom:actions'
 | 
			
		||||
 | 
			
		||||
    #: The `plugin_attrib` value is the name that can be used
 | 
			
		||||
    #: with a parent stanza to access this stanza. For example
 | 
			
		||||
    #: from an Iq stanza object, accessing:
 | 
			
		||||
    #: 
 | 
			
		||||
    #:     iq['action']
 | 
			
		||||
    #: 
 | 
			
		||||
    #: would reference an Action object, and will even create
 | 
			
		||||
    #: an Action object and append it to the Iq stanza if
 | 
			
		||||
    #: one doesn't already exist.
 | 
			
		||||
    plugin_attrib = 'action'
 | 
			
		||||
 | 
			
		||||
    #: Stanza objects expose dictionary-like interfaces for
 | 
			
		||||
    #: accessing and manipulating substanzas and other values.
 | 
			
		||||
    #: The set of interfaces defined here are the names of
 | 
			
		||||
    #: these dictionary-like interfaces provided by this stanza
 | 
			
		||||
    #: type. For example, an Action stanza object can use:
 | 
			
		||||
    #:
 | 
			
		||||
    #:     action['method'] = 'foo'
 | 
			
		||||
    #:     print(action['param'])
 | 
			
		||||
    #:     del action['status']
 | 
			
		||||
    #:
 | 
			
		||||
    #: to set, get, or remove its values.
 | 
			
		||||
    interfaces = set(('method', 'param', 'status'))
 | 
			
		||||
 | 
			
		||||
    #: By default, values in the `interfaces` set are mapped to
 | 
			
		||||
    #: attribute values. This can be changed such that an interface
 | 
			
		||||
    #: maps to a subelement's text value by adding interfaces to
 | 
			
		||||
    #: the sub_interfaces set. For example, here all interfaces
 | 
			
		||||
    #: are marked as sub_interfaces, and so the XML produced will
 | 
			
		||||
    #: look like:
 | 
			
		||||
    #: 
 | 
			
		||||
    #:     <action xmlns="sleekxmpp:custom:actions">
 | 
			
		||||
    #:       <method>foo</method>
 | 
			
		||||
    #:     </action>
 | 
			
		||||
    sub_interfaces = interfaces
 | 
			
		||||
@@ -10,7 +10,6 @@
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
@@ -61,16 +60,16 @@ class Disco(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
        self.add_event_handler("session_start", self.start, threaded=True)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        In this case, we send disco#info and disco#items
 | 
			
		||||
@@ -188,13 +187,13 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
@@ -41,7 +40,7 @@ class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
@@ -55,7 +54,7 @@ class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -132,14 +131,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
@@ -116,7 +116,7 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
@@ -44,7 +44,7 @@ class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +68,7 @@ class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -76,8 +76,8 @@ class MUCBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.getRoster()
 | 
			
		||||
        self.sendPresence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.plugin['xep_0045'].joinMUC(self.room,
 | 
			
		||||
                                        self.nick,
 | 
			
		||||
                                        # If a room password is needed, use:
 | 
			
		||||
@@ -161,9 +161,14 @@ if __name__ == '__main__':
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if None in [opts.jid, opts.password, opts.room, opts.nick]:
 | 
			
		||||
        optp.print_help()
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
    if opts.room is None:
 | 
			
		||||
        opts.room = raw_input("MUC room: ")
 | 
			
		||||
    if opts.nick is None:
 | 
			
		||||
        opts.nick = raw_input("MUC nickname: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the MUCBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
@@ -175,14 +180,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
@@ -44,16 +43,16 @@ class PingTest(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
        self.add_event_handler("session_start", self.start, threaded=True)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -72,7 +71,7 @@ class PingTest(sleekxmpp.ClientXMPP):
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            sys.exit(1)
 | 
			
		||||
        else:
 | 
			
		||||
            logging.info("Success! RTT: %s" % str(result))
 | 
			
		||||
            logging.info("Success! RTT: %s", str(result))
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -129,14 +128,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
@@ -41,7 +40,7 @@ class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
@@ -55,7 +54,7 @@ class EchoBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -156,14 +155,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										198
									
								
								examples/pubsub_client.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								examples/pubsub_client.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,198 @@
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.xmlstream import ET, tostring
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PubsubClient(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, server, 
 | 
			
		||||
                       node=None, action='list', data=''):
 | 
			
		||||
        super(PubsubClient, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
        self.register_plugin('xep_0030')
 | 
			
		||||
        self.register_plugin('xep_0059')
 | 
			
		||||
        self.register_plugin('xep_0060')
 | 
			
		||||
 | 
			
		||||
        self.actions = ['nodes', 'create', 'delete', 
 | 
			
		||||
                        'publish', 'get', 'retract',
 | 
			
		||||
                        'purge', 'subscribe', 'unsubscribe']
 | 
			
		||||
 | 
			
		||||
        self.action = action
 | 
			
		||||
        self.node = node
 | 
			
		||||
        self.data = data
 | 
			
		||||
        self.pubsub_server = server
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('session_start', self.start, threaded=True)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            getattr(self, self.action)()
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not execute: %s' % self.action)
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
    def nodes(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = self['xep_0060'].get_nodes(self.pubsub_server, self.node)
 | 
			
		||||
            for item in result['disco_items']['items']:
 | 
			
		||||
                print('  - %s' % str(item))
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not retrieve node list.')
 | 
			
		||||
 | 
			
		||||
    def create(self):
 | 
			
		||||
        try:
 | 
			
		||||
            self['xep_0060'].create_node(self.pubsub_server, self.node)
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not create node: %s' % self.node)
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        try:
 | 
			
		||||
            self['xep_0060'].delete_node(self.pubsub_server, self.node)
 | 
			
		||||
            print('Deleted node: %s' % self.node)
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not delete node: %s' % self.node)
 | 
			
		||||
 | 
			
		||||
    def publish(self):
 | 
			
		||||
        payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
 | 
			
		||||
        try:
 | 
			
		||||
            result = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
 | 
			
		||||
            id = result['pubsub']['publish']['item']['id']
 | 
			
		||||
            print('Published at item id: %s' % id)
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not publish to: %s' % self.node)
 | 
			
		||||
 | 
			
		||||
    def get(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
 | 
			
		||||
            for item in result['pubsub']['items']['substanzas']:
 | 
			
		||||
                print('Retrieved item %s: %s' % (item['id'], tostring(item['payload'])))
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not retrieve item %s from node %s' % (self.data, self.node))
 | 
			
		||||
 | 
			
		||||
    def retract(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
 | 
			
		||||
            print('Retracted item %s from node %s' % (self.data, self.node))
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not retract item %s from node %s' % (self.data, self.node))
 | 
			
		||||
 | 
			
		||||
    def purge(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = self['xep_0060'].purge(self.pubsub_server, self.node)
 | 
			
		||||
            print('Purged all items from node %s' % self.node)
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not purge items from node %s' % self.node)
 | 
			
		||||
 | 
			
		||||
    def subscribe(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = self['xep_0060'].subscribe(self.pubsub_server, self.node)
 | 
			
		||||
            print('Subscribed %s to node %s' % (self.boundjid.bare, self.node))
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node))
 | 
			
		||||
 | 
			
		||||
    def unsubscribe(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
 | 
			
		||||
            print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node))
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
    optp.version = '%%prog 0.1'
 | 
			
		||||
    optp.usage = "Usage: %%prog [options] <jid> " + \
 | 
			
		||||
                             'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \
 | 
			
		||||
                             ' [<node> <data>]'
 | 
			
		||||
 | 
			
		||||
    optp.add_option('-q','--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
    optp.add_option('-d','--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
    optp.add_option('-v','--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const',
 | 
			
		||||
                    dest='loglevel',
 | 
			
		||||
                    const=5,
 | 
			
		||||
                    default=logging.ERROR)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
    opts,args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if len(args) < 2:
 | 
			
		||||
        optp.print_help()
 | 
			
		||||
        exit()
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    if len(args) == 2:
 | 
			
		||||
        args = (args[0], args[1], '', '', '')
 | 
			
		||||
    elif len(args) == 3:
 | 
			
		||||
        args = (args[0], args[1], args[2], '', '')
 | 
			
		||||
    elif len(args) == 4:
 | 
			
		||||
        args = (args[0], args[1], args[2], args[3], '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Setup the Pubsub client
 | 
			
		||||
    xmpp = PubsubClient(opts.jid, opts.password,
 | 
			
		||||
                        server=args[0],
 | 
			
		||||
                        node=args[2],
 | 
			
		||||
                        action=args[1],
 | 
			
		||||
                        data=args[3])
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										151
									
								
								examples/pubsub_events.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								examples/pubsub_events.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.xmlstream import ET, tostring
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PubsubEvents(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        super(PubsubEvents, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
        self.register_plugin('xep_0030')
 | 
			
		||||
        self.register_plugin('xep_0059')
 | 
			
		||||
        self.register_plugin('xep_0060')
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('session_start', self.start)
 | 
			
		||||
 | 
			
		||||
        # Some services may require configuration to allow
 | 
			
		||||
        # sending delete, configuration, or subscription  events.
 | 
			
		||||
        self.add_event_handler('pubsub_publish', self._publish)
 | 
			
		||||
        self.add_event_handler('pubsub_retract', self._retract)
 | 
			
		||||
        self.add_event_handler('pubsub_purge', self._purge)
 | 
			
		||||
        self.add_event_handler('pubsub_delete', self._delete)
 | 
			
		||||
        self.add_event_handler('pubsub_config', self._config)
 | 
			
		||||
        self.add_event_handler('pubsub_subscription', self._subscription)
 | 
			
		||||
 | 
			
		||||
        # Want to use nicer, more specific pubsub event names?
 | 
			
		||||
        # self['xep_0060'].map_node_event('node_name', 'event_prefix')
 | 
			
		||||
        # self.add_event_handler('event_prefix_publish', handler)
 | 
			
		||||
        # self.add_event_handler('event_prefix_retract', handler)
 | 
			
		||||
        # self.add_event_handler('event_prefix_purge', handler)
 | 
			
		||||
        # self.add_event_handler('event_prefix_delete', handler)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
 | 
			
		||||
    def _publish(self, msg):
 | 
			
		||||
        """Handle receiving a publish item event."""
 | 
			
		||||
        print('Published item %s to %s:' % (
 | 
			
		||||
            msg['pubsub_event']['items']['item']['id'],
 | 
			
		||||
            msg['pubsub_event']['items']['node']))
 | 
			
		||||
        data = msg['pubsub_event']['items']['item']['payload']
 | 
			
		||||
        if data is not None:
 | 
			
		||||
            print(tostring(data))
 | 
			
		||||
        else:
 | 
			
		||||
            print('No item content')
 | 
			
		||||
 | 
			
		||||
    def _retract(self, msg):
 | 
			
		||||
        """Handle receiving a retract item event."""
 | 
			
		||||
        print('Retracted item %s from %s' % (
 | 
			
		||||
            msg['pubsub_event']['items']['retract']['id'],
 | 
			
		||||
            msg['pubsub_event']['items']['node']))
 | 
			
		||||
 | 
			
		||||
    def _purge(self, msg):
 | 
			
		||||
        """Handle receiving a node purge event."""
 | 
			
		||||
        print('Purged all items from %s' % (
 | 
			
		||||
            msg['pubsub_event']['purge']['node']))
 | 
			
		||||
 | 
			
		||||
    def _delete(self, msg):
 | 
			
		||||
        """Handle receiving a node deletion event."""
 | 
			
		||||
        print('Deleted node %s' % (
 | 
			
		||||
           msg['pubsub_event']['delete']['node']))
 | 
			
		||||
 
 | 
			
		||||
    def _config(self, msg):
 | 
			
		||||
        """Handle receiving a node configuration event."""
 | 
			
		||||
        print('Configured node %s:' % (
 | 
			
		||||
            msg['pubsub_event']['configuration']['node']))
 | 
			
		||||
        print(msg['pubsub_event']['configuration']['form'])
 | 
			
		||||
 | 
			
		||||
    def _subscription(self, msg):
 | 
			
		||||
        """Handle receiving a node subscription event."""
 | 
			
		||||
        print('Subscription change for node %s:' % (
 | 
			
		||||
            msg['pubsub_event']['subscription']['node']))
 | 
			
		||||
        print(msg['pubsub_event']['subscription'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    logging.info("Run this in conjunction with the pubsub_client.py " + \
 | 
			
		||||
                 "example to watch events happen as you give commands.")
 | 
			
		||||
 | 
			
		||||
    # Setup the PubsubEvents listener
 | 
			
		||||
    xmpp = PubsubEvents(opts.jid, opts.password)
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										175
									
								
								examples/register_account.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								examples/register_account.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RegisterBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A basic bot that will attempt to register an account
 | 
			
		||||
    with an XMPP server.
 | 
			
		||||
 | 
			
		||||
    NOTE: This follows the very basic registration workflow
 | 
			
		||||
          from XEP-0077. More advanced server registration
 | 
			
		||||
          workflows will need to check for data forms, etc.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start, threaded=True)
 | 
			
		||||
 | 
			
		||||
        # The register event provides an Iq result stanza with
 | 
			
		||||
        # a registration form from the server. This may include
 | 
			
		||||
        # the basic registration fields, a data form, an 
 | 
			
		||||
        # out-of-band URL, or any combination. For more advanced
 | 
			
		||||
        # cases, you will need to examine the fields provided
 | 
			
		||||
        # and respond accordingly. SleekXMPP provides plugins
 | 
			
		||||
        # for data forms and OOB links that will make that easier.
 | 
			
		||||
        self.add_event_handler("register", self.register, threaded=True)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        # We're only concerned about registering, so nothing more to do here.
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
    def register(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Fill out and submit a registration form.
 | 
			
		||||
 | 
			
		||||
        The form may be composed of basic registration fields, a data form,
 | 
			
		||||
        an out-of-band link, or any combination thereof. Data forms and OOB
 | 
			
		||||
        links can be checked for as so:
 | 
			
		||||
 | 
			
		||||
        if iq.match('iq/register/form'):
 | 
			
		||||
            # do stuff with data form
 | 
			
		||||
            # iq['register']['form']['fields']
 | 
			
		||||
        if iq.match('iq/register/oob'):
 | 
			
		||||
            # do stuff with OOB URL
 | 
			
		||||
            # iq['register']['oob']['url']
 | 
			
		||||
 | 
			
		||||
        To get the list of basic registration fields, you can use:
 | 
			
		||||
            iq['register']['fields']
 | 
			
		||||
        """
 | 
			
		||||
        resp = self.Iq()
 | 
			
		||||
        resp['type'] = 'set'
 | 
			
		||||
        resp['register']['username'] = self.boundjid.user
 | 
			
		||||
        resp['register']['password'] = self.password
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            resp.send(now=True)
 | 
			
		||||
            logging.info("Account created for %s!" % self.boundjid)
 | 
			
		||||
        except IqError as e:
 | 
			
		||||
            logging.error("Could not register account: %s" % 
 | 
			
		||||
                    e.iq['error']['text'])
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            logging.error("No response from server.")
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the RegisterBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = RegisterBot(opts.jid, opts.password)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data forms
 | 
			
		||||
    xmpp.register_plugin('xep_0066') # Out-of-band Data
 | 
			
		||||
    xmpp.register_plugin('xep_0077') # In-band Registration
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
@@ -10,7 +10,6 @@
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
import threading
 | 
			
		||||
@@ -43,7 +42,7 @@ class RosterBrowser(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster. We need threaded=True so that the
 | 
			
		||||
        # session_start handler doesn't block event processing
 | 
			
		||||
        # while we wait for presence stanzas to arrive.
 | 
			
		||||
@@ -58,7 +57,7 @@ class RosterBrowser(sleekxmpp.ClientXMPP):
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -160,14 +159,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ class Thermostat(Endpoint):
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'thermostat'
 | 
			
		||||
    
 | 
			
		||||
    def __init(self, initial_temperature):
 | 
			
		||||
    def __init__(self, initial_temperature):
 | 
			
		||||
        self._temperature = initial_temperature
 | 
			
		||||
        self._event = threading.Event()        
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ class Thermostat(Endpoint):
 | 
			
		||||
    def FQN(self):
 | 
			
		||||
        return 'thermostat'
 | 
			
		||||
    
 | 
			
		||||
    def __init(self, initial_temperature):
 | 
			
		||||
    def __init__(self, initial_temperature):
 | 
			
		||||
        self._temperature = initial_temperature
 | 
			
		||||
        self._event = threading.Event()        
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
@@ -46,16 +45,16 @@ class SendMsgBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
        # 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 intialize
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
        self.add_event_handler("session_start", self.start, threaded=True)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an intial
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
@@ -131,14 +130,14 @@ if __name__ == '__main__':
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the pydns library installed, you will need
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(threaded=False)
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										247
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								examples/thirdparty_auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,247 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from httplib import HTTPSConnection
 | 
			
		||||
    from urllib import urlencode
 | 
			
		||||
except ImportError:
 | 
			
		||||
    from urllib.parse import urlencode
 | 
			
		||||
    from http.client import HTTPSConnection
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp.xmlstream import JID
 | 
			
		||||
 | 
			
		||||
# Python versions before 3.0 do not use UTF-8 encoding
 | 
			
		||||
# by default. To ensure that Unicode is handled properly
 | 
			
		||||
# throughout SleekXMPP, we will set the default encoding
 | 
			
		||||
# ourselves to UTF-8.
 | 
			
		||||
if sys.version_info < (3, 0):
 | 
			
		||||
    reload(sys)
 | 
			
		||||
    sys.setdefaultencoding('utf8')
 | 
			
		||||
else:
 | 
			
		||||
    raw_input = input
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ThirdPartyAuthBot(sleekxmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple SleekXMPP bot that will echo messages it
 | 
			
		||||
    receives, along with a short thank you message.
 | 
			
		||||
 | 
			
		||||
    This version uses a thirdpary service for authentication,
 | 
			
		||||
    such as Facebook or Google.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        # The X-GOOGLE-TOKEN mech is ranked lower than PLAIN
 | 
			
		||||
        # due to Google only allowing a single SASL attempt per
 | 
			
		||||
        # connection. So PLAIN will be used for TLS connections,
 | 
			
		||||
        # and X-GOOGLE-TOKEN for non-TLS connections. To use
 | 
			
		||||
        # X-GOOGLE-TOKEN with a TLS connection, explicitly select
 | 
			
		||||
        # it using:
 | 
			
		||||
        #
 | 
			
		||||
        # sleekxmpp.ClientXMPP.__init__(self, jid, password,
 | 
			
		||||
        #                               sasl_mech="X-GOOGLE-TOKEN")
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
        # The message event is triggered whenever a message
 | 
			
		||||
        # stanza is received. Be aware that that includes
 | 
			
		||||
        # MUC messages and error messages.
 | 
			
		||||
        self.add_event_handler("message", self.message)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
    def message(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming message stanzas. Be aware that this also
 | 
			
		||||
        includes MUC messages and error messages. It is usually
 | 
			
		||||
        a good idea to check the messages's type before processing
 | 
			
		||||
        or sending replies.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            msg -- The received message stanza. See the documentation
 | 
			
		||||
                   for stanza objects and the Message stanza to see
 | 
			
		||||
                   how it may be used.
 | 
			
		||||
        """
 | 
			
		||||
        if msg['type'] in ('chat', 'normal'):
 | 
			
		||||
            msg.reply("Thanks for sending\n%(body)s" % msg).send()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    access_token = None
 | 
			
		||||
 | 
			
		||||
    # Since documentation on how to work with Google tokens
 | 
			
		||||
    # can be difficult to find, we'll demo a basic version
 | 
			
		||||
    # here. Note that responses could refer to a Captcha
 | 
			
		||||
    # URL that would require a browser.
 | 
			
		||||
 | 
			
		||||
    # Using Facebook or MSN's custom authentication requires
 | 
			
		||||
    # a browser, but the process is the same once a token
 | 
			
		||||
    # has been retrieved.
 | 
			
		||||
 | 
			
		||||
    # Request an access token from Google:
 | 
			
		||||
    try:
 | 
			
		||||
        conn = HTTPSConnection('www.google.com')
 | 
			
		||||
    except:
 | 
			
		||||
        print('Could not connect to Google')
 | 
			
		||||
        sys.exit()
 | 
			
		||||
 | 
			
		||||
    params = urlencode({
 | 
			
		||||
        'accountType': 'GOOGLE',
 | 
			
		||||
        'service': 'mail',
 | 
			
		||||
        'Email': JID(opts.jid).bare,
 | 
			
		||||
        'Passwd': opts.password
 | 
			
		||||
    })
 | 
			
		||||
    headers = {
 | 
			
		||||
        'Content-Type': 'application/x-www-form-urlencoded' 
 | 
			
		||||
    }
 | 
			
		||||
    try:
 | 
			
		||||
        conn.request('POST', '/accounts/ClientLogin', params, headers)
 | 
			
		||||
        resp = conn.getresponse().read()
 | 
			
		||||
        data = {}
 | 
			
		||||
        for line in resp.split():
 | 
			
		||||
            k, v = line.split(b'=', 1)
 | 
			
		||||
            data[k] = v
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print('Could not retrieve login data')
 | 
			
		||||
        sys.exit()
 | 
			
		||||
 | 
			
		||||
    if b'SID' not in data:
 | 
			
		||||
        print('Required data not found')
 | 
			
		||||
        sys.exit()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    params = urlencode({
 | 
			
		||||
        'SID': data[b'SID'],
 | 
			
		||||
        'LSID': data[b'LSID'],
 | 
			
		||||
        'service': 'mail'
 | 
			
		||||
    })
 | 
			
		||||
    try:
 | 
			
		||||
        conn.request('POST', '/accounts/IssueAuthToken', params, headers)
 | 
			
		||||
        resp = conn.getresponse()
 | 
			
		||||
        data = resp.read().split()
 | 
			
		||||
    except:
 | 
			
		||||
        print('Could not retrieve auth data')
 | 
			
		||||
        sys.exit()
 | 
			
		||||
 | 
			
		||||
    if not data:
 | 
			
		||||
        print('Could not retrieve token')
 | 
			
		||||
        sys.exit()
 | 
			
		||||
 | 
			
		||||
    access_token = data[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Setup the ThirdPartyAuthBot and register plugins. Note that while plugins
 | 
			
		||||
    # may have interdependencies, the order in which you register them does not
 | 
			
		||||
    # matter.
 | 
			
		||||
 | 
			
		||||
    # If using MSN, the JID should be "user@messenger.live.com", which will
 | 
			
		||||
    # be overridden on session bind.
 | 
			
		||||
 | 
			
		||||
    # We're using an access token instead of a password, so we'll use `''` as
 | 
			
		||||
    # a password argument filler.
 | 
			
		||||
 | 
			
		||||
    xmpp = ThirdPartyAuthBot(opts.jid, '')  
 | 
			
		||||
    xmpp.credentials['access_token'] = access_token
 | 
			
		||||
 | 
			
		||||
    # The credentials dictionary is used to provide additional authentication
 | 
			
		||||
    # information beyond just a password.
 | 
			
		||||
    
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0004') # Data Forms
 | 
			
		||||
    xmpp.register_plugin('xep_0060') # PubSub
 | 
			
		||||
 | 
			
		||||
    # MSN will kill connections that have been inactive for even
 | 
			
		||||
    # short periods of time. So use pings to keep the session alive;
 | 
			
		||||
    # whitespace keepalives do not work.
 | 
			
		||||
    xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency': 60})
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    # Google only allows one SASL attempt per connection, so in order to 
 | 
			
		||||
    # enable the X-GOOGLE-TOKEN mechanism, we'll disable TLS.
 | 
			
		||||
    if xmpp.connect(use_tls=False):
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										125
									
								
								examples/user_location.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								examples/user_location.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import json
 | 
			
		||||
except ImportError:
 | 
			
		||||
    import simplejson as json
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import requests
 | 
			
		||||
except ImportError:
 | 
			
		||||
    print('This demo requires the requests package for using HTTP.')
 | 
			
		||||
    sys.exit()
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import ClientXMPP
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocationBot(ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        super(LocationBot, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('session_start', self.start, threaded=True)
 | 
			
		||||
        self.add_event_handler('user_location_publish', 
 | 
			
		||||
                               self.user_location_publish)
 | 
			
		||||
 | 
			
		||||
        self.register_plugin('xep_0004')
 | 
			
		||||
        self.register_plugin('xep_0030')
 | 
			
		||||
        self.register_plugin('xep_0060')
 | 
			
		||||
        self.register_plugin('xep_0115')
 | 
			
		||||
        self.register_plugin('xep_0128')
 | 
			
		||||
        self.register_plugin('xep_0163')
 | 
			
		||||
        self.register_plugin('xep_0080')
 | 
			
		||||
 | 
			
		||||
        self.current_tune = None
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self['xep_0115'].update_caps()
 | 
			
		||||
 | 
			
		||||
        print("Using freegeoip.net to get geolocation.")
 | 
			
		||||
        r = requests.get('http://freegeoip.net/json/')
 | 
			
		||||
        try:
 | 
			
		||||
            data = json.loads(r.text)
 | 
			
		||||
        except:
 | 
			
		||||
            print("Could not retrieve user location.")
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self['xep_0080'].publish_location(
 | 
			
		||||
                lat=data['latitude'],
 | 
			
		||||
                lon=data['longitude'],
 | 
			
		||||
                locality=data['city'],
 | 
			
		||||
                region=data['region_name'],
 | 
			
		||||
                country=data['country_name'],
 | 
			
		||||
                countrycode=data['country_code'],
 | 
			
		||||
                postalcode=data['zipcode'])
 | 
			
		||||
 | 
			
		||||
    def user_location_publish(self, msg):
 | 
			
		||||
        geo = msg['pubsub_event']['items']['item']['geoloc']
 | 
			
		||||
        print("%s is at:" % msg['from'])
 | 
			
		||||
        for key, val in geo.values.items():
 | 
			
		||||
            if val:
 | 
			
		||||
                print("  %s: %s" % (key, val))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    xmpp = LocationBot(opts.jid, opts.password)
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										137
									
								
								examples/user_tune.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								examples/user_tune.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import getpass
 | 
			
		||||
from optparse import OptionParser
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from appscript import *
 | 
			
		||||
except ImportError:
 | 
			
		||||
    print('This demo requires the appscript package to interact with iTunes.')
 | 
			
		||||
    sys.exit()
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import ClientXMPP
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TuneBot(ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        super(TuneBot, self).__init__(jid, password)
 | 
			
		||||
 | 
			
		||||
        # Check for the current song every 5 seconds.
 | 
			
		||||
        self.schedule('Check Current Tune', 5, self._update_tune, repeat=True)
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('session_start', self.start)
 | 
			
		||||
        self.add_event_handler('user_tune_publish', self.user_tune_publish)
 | 
			
		||||
 | 
			
		||||
        self.register_plugin('xep_0004')
 | 
			
		||||
        self.register_plugin('xep_0030')
 | 
			
		||||
        self.register_plugin('xep_0060')
 | 
			
		||||
        self.register_plugin('xep_0115')
 | 
			
		||||
        self.register_plugin('xep_0118')
 | 
			
		||||
        self.register_plugin('xep_0128')
 | 
			
		||||
        self.register_plugin('xep_0163')
 | 
			
		||||
 | 
			
		||||
        self.current_tune = None
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self['xep_0115'].update_caps()
 | 
			
		||||
 | 
			
		||||
    def _update_tune(self):
 | 
			
		||||
        itunes_count = app('System Events').processes[its.name == 'iTunes'].count()
 | 
			
		||||
        if itunes_count > 0:
 | 
			
		||||
            iTunes = app('iTunes')
 | 
			
		||||
            if iTunes.player_state.get() == k.playing:
 | 
			
		||||
                track = iTunes.current_track.get()
 | 
			
		||||
                length = track.time.get()
 | 
			
		||||
                if ':' in length:
 | 
			
		||||
                    minutes, secs = map(int, length.split(':'))
 | 
			
		||||
                    secs += minutes * 60
 | 
			
		||||
                else:
 | 
			
		||||
                    secs = int(length)
 | 
			
		||||
 | 
			
		||||
                artist = track.artist.get()
 | 
			
		||||
                title = track.name.get()
 | 
			
		||||
                source = track.album.get()
 | 
			
		||||
                rating = track.rating.get() / 10
 | 
			
		||||
 | 
			
		||||
                tune = (artist, secs, rating, source, title)
 | 
			
		||||
                if tune != self.current_tune:
 | 
			
		||||
                    self.current_tune = tune
 | 
			
		||||
 | 
			
		||||
                    # We have a new song playing, so publish it.
 | 
			
		||||
                    self['xep_0118'].publish_tune(
 | 
			
		||||
                            artist=artist,
 | 
			
		||||
                            length=secs,
 | 
			
		||||
                            title=title,
 | 
			
		||||
                            rating=rating,
 | 
			
		||||
                            source=source)
 | 
			
		||||
            else:
 | 
			
		||||
                # No song is playing, clear the user tune.
 | 
			
		||||
                tune = None
 | 
			
		||||
                if tune != self.current_tune:
 | 
			
		||||
                    self.current_tune = tune
 | 
			
		||||
                    self['xep_0118'].stop()
 | 
			
		||||
 | 
			
		||||
    def user_tune_publish(self, msg):
 | 
			
		||||
        tune = msg['pubsub_event']['items']['item']['tune']
 | 
			
		||||
        print("%s is listening to: %s" % (msg['from'], tune['title']))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    optp = OptionParser()
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
			
		||||
                    action='store_const', dest='loglevel',
 | 
			
		||||
                    const=5, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    optp.add_option("-j", "--jid", dest="jid",
 | 
			
		||||
                    help="JID to use")
 | 
			
		||||
    optp.add_option("-p", "--password", dest="password",
 | 
			
		||||
                    help="password to use")
 | 
			
		||||
 | 
			
		||||
    opts, args = optp.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=opts.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if opts.jid is None:
 | 
			
		||||
        opts.jid = raw_input("Username: ")
 | 
			
		||||
    if opts.password is None:
 | 
			
		||||
        opts.password = getpass.getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    xmpp = TuneBot(opts.jid, opts.password)
 | 
			
		||||
 | 
			
		||||
    # If you are working with an OpenFire server, you may need
 | 
			
		||||
    # to adjust the SSL version used:
 | 
			
		||||
    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
			
		||||
 | 
			
		||||
    # If you want to verify the SSL certificates offered by a server:
 | 
			
		||||
    # xmpp.ca_certs = "path/to/ca/cert"
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    if xmpp.connect():
 | 
			
		||||
        # If you do not have the dnspython library installed, you will need
 | 
			
		||||
        # to manually specify the name of the server if it does not match
 | 
			
		||||
        # the one in the JID. For example, to use Google Talk you would
 | 
			
		||||
        # need to use:
 | 
			
		||||
        #
 | 
			
		||||
        # if xmpp.connect(('talk.google.com', 5222)):
 | 
			
		||||
        #     ...
 | 
			
		||||
        xmpp.process(block=True)
 | 
			
		||||
        print("Done")
 | 
			
		||||
    else:
 | 
			
		||||
        print("Unable to connect.")
 | 
			
		||||
							
								
								
									
										21
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								setup.py
									
									
									
									
									
								
							@@ -8,6 +8,7 @@
 | 
			
		||||
# file, which you should have received as part of this distribution.
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import codecs
 | 
			
		||||
try:
 | 
			
		||||
    from setuptools import setup, Command
 | 
			
		||||
except ImportError:
 | 
			
		||||
@@ -31,7 +32,7 @@ from sleekxmpp.version import __version__
 | 
			
		||||
 | 
			
		||||
VERSION          = __version__
 | 
			
		||||
DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
 | 
			
		||||
with open('README.rst') as readme:
 | 
			
		||||
with codecs.open('README.rst', 'r', encoding='UTF-8') as readme:
 | 
			
		||||
    LONG_DESCRIPTION = ''.join(readme)
 | 
			
		||||
 | 
			
		||||
CLASSIFIERS      = [ 'Intended Audience :: Developers',
 | 
			
		||||
@@ -56,22 +57,37 @@ packages     = [ 'sleekxmpp',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0004/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0009/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0012',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0027',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0030/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0047',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0050',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0054',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0059',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0060',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0060/stanza',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0066',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0077',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0078',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0080',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0085',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0086',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0092',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0107',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0108',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0115',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0118',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0128',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0153',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0172',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0184',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0198',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0199',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0202',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0203',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0224',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0231',
 | 
			
		||||
                 'sleekxmpp/plugins/xep_0249',
 | 
			
		||||
                 'sleekxmpp/features',
 | 
			
		||||
                 'sleekxmpp/features/feature_mechanisms',
 | 
			
		||||
@@ -79,6 +95,7 @@ packages     = [ 'sleekxmpp',
 | 
			
		||||
                 'sleekxmpp/features/feature_starttls',
 | 
			
		||||
                 'sleekxmpp/features/feature_bind',
 | 
			
		||||
                 'sleekxmpp/features/feature_session',
 | 
			
		||||
                 'sleekxmpp/features/feature_rosterver',
 | 
			
		||||
                 'sleekxmpp/thirdparty',
 | 
			
		||||
                 'sleekxmpp/thirdparty/suelta',
 | 
			
		||||
                 'sleekxmpp/thirdparty/suelta/mechanisms',
 | 
			
		||||
@@ -95,7 +112,7 @@ setup(
 | 
			
		||||
    license      = 'MIT',
 | 
			
		||||
    platforms    = [ 'any' ],
 | 
			
		||||
    packages     = packages,
 | 
			
		||||
    requires     = [ 'dnspython' ],
 | 
			
		||||
    requires     = [ 'dnspython', 'pyasn1', 'pyasn1_modules' ],
 | 
			
		||||
    classifiers  = CLASSIFIERS,
 | 
			
		||||
    cmdclass     = {'test': TestCommand}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										198
									
								
								sleekxmpp/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								sleekxmpp/api.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,198 @@
 | 
			
		||||
from sleekxmpp.xmlstream import JID
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIWrapper(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, api, name):
 | 
			
		||||
        self.api = api
 | 
			
		||||
        self.name = name
 | 
			
		||||
        if name not in self.api.settings:
 | 
			
		||||
            self.api.settings[name] = {}
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, attr):
 | 
			
		||||
        """Curry API management commands with the API name."""
 | 
			
		||||
        if attr == 'name':
 | 
			
		||||
            return self.name
 | 
			
		||||
        elif attr == 'settings':
 | 
			
		||||
            return self.api.settings[self.name]
 | 
			
		||||
        elif attr == 'register':
 | 
			
		||||
            def curried_handler(handler, op, jid=None, node=None, default=False):
 | 
			
		||||
                register = getattr(self.api, attr)
 | 
			
		||||
                return register(handler, self.name, op, jid, node, default)
 | 
			
		||||
            return curried_handler
 | 
			
		||||
        elif attr == 'register_default':
 | 
			
		||||
            def curried_handler(handler, op, jid=None, node=None):
 | 
			
		||||
                return getattr(self.api, attr)(handler, self.name, op)
 | 
			
		||||
            return curried_handler
 | 
			
		||||
        elif attr in ('run', 'restore_default', 'unregister'):
 | 
			
		||||
            def curried_handler(*args, **kwargs):
 | 
			
		||||
                return getattr(self.api, attr)(self.name, *args, **kwargs)
 | 
			
		||||
            return curried_handler
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, attr):
 | 
			
		||||
        def curried_handler(jid=None, node=None, ifrom=None, args=None):
 | 
			
		||||
            return self.api.run(self.name, attr, jid, node, ifrom, args)
 | 
			
		||||
        return curried_handler
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIRegistry(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, xmpp):
 | 
			
		||||
        self._handlers = {}
 | 
			
		||||
        self._handler_defaults = {}
 | 
			
		||||
        self.xmpp = xmpp
 | 
			
		||||
        self.settings = {} 
 | 
			
		||||
 | 
			
		||||
    def _setup(self, ctype, op):
 | 
			
		||||
        """Initialize the API callback dictionaries.
 | 
			
		||||
 | 
			
		||||
        :param string ctype: The name of the API to initialize.
 | 
			
		||||
        :param string op: The API operation to initialize.
 | 
			
		||||
        """
 | 
			
		||||
        if ctype not in self.settings:
 | 
			
		||||
            self.settings[ctype] = {}
 | 
			
		||||
        if ctype not in self._handler_defaults:
 | 
			
		||||
            self._handler_defaults[ctype] = {}
 | 
			
		||||
        if ctype not in self._handlers:
 | 
			
		||||
            self._handlers[ctype] = {}
 | 
			
		||||
        if op not in self._handlers[ctype]:
 | 
			
		||||
            self._handlers[ctype][op] = {'global': None,
 | 
			
		||||
                                         'jid': {},
 | 
			
		||||
                                         'node': {}}
 | 
			
		||||
 | 
			
		||||
    def wrap(self, ctype):
 | 
			
		||||
        """Return a wrapper object that targets a specific API."""
 | 
			
		||||
        return APIWrapper(self, ctype)
 | 
			
		||||
 | 
			
		||||
    def purge(self, ctype):
 | 
			
		||||
        """Remove all information for a given API."""
 | 
			
		||||
        del self.settings[ctype]
 | 
			
		||||
        del self._handler_defaults[ctype]
 | 
			
		||||
        del self._handlers[ctype]
 | 
			
		||||
 | 
			
		||||
    def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None):
 | 
			
		||||
        """Execute an API callback, based on specificity.
 | 
			
		||||
 | 
			
		||||
        The API callback that is executed is chosen based on the combination
 | 
			
		||||
        of the provided JID and node:
 | 
			
		||||
 | 
			
		||||
        JID   | node  | Handler
 | 
			
		||||
        ==============================
 | 
			
		||||
        Given | Given | Node handler
 | 
			
		||||
        Given | None  | JID handler
 | 
			
		||||
        None  | None  | Global handler
 | 
			
		||||
 | 
			
		||||
        A node handler is responsible for servicing a single node at a single
 | 
			
		||||
        JID, while a JID handler may respond for any node at a given JID, and
 | 
			
		||||
        the global handler will answer to any JID+node combination.
 | 
			
		||||
 | 
			
		||||
        Handlers should check that the JID ``ifrom`` is authorized to perform
 | 
			
		||||
        the desired action.
 | 
			
		||||
 | 
			
		||||
        :param string ctype: The name of the API to use.
 | 
			
		||||
        :param string op: The API operation to perform.
 | 
			
		||||
        :param JID jid: Optionally provide specific JID.
 | 
			
		||||
        :param string node: Optionally provide specific node.
 | 
			
		||||
        :param JID ifrom: Optionally provide the requesting JID.
 | 
			
		||||
        :param tuple args: Optional positional arguments to the handler.
 | 
			
		||||
        """
 | 
			
		||||
        self._setup(ctype, op)
 | 
			
		||||
 | 
			
		||||
        if jid in (None, ''):
 | 
			
		||||
            jid = self.xmpp.boundjid
 | 
			
		||||
        if jid and not isinstance(jid, JID):
 | 
			
		||||
            jid = JID(jid)
 | 
			
		||||
 | 
			
		||||
        if node is None:
 | 
			
		||||
            node = ''
 | 
			
		||||
 | 
			
		||||
        if self.xmpp.is_component:
 | 
			
		||||
            if self.settings[ctype].get('component_bare', False):
 | 
			
		||||
                jid = jid.bare
 | 
			
		||||
            else:
 | 
			
		||||
                jid = jid.full
 | 
			
		||||
        else:
 | 
			
		||||
            if self.settings[ctype].get('client_bare', True):
 | 
			
		||||
                jid = jid.bare
 | 
			
		||||
            else:
 | 
			
		||||
                jid = jid.full
 | 
			
		||||
 | 
			
		||||
        jid = JID(jid)
 | 
			
		||||
 | 
			
		||||
        handler = self._handlers[ctype][op]['node'].get((jid, node), None)
 | 
			
		||||
        if handler is None:
 | 
			
		||||
            handler = self._handlers[ctype][op]['jid'].get(jid, None)
 | 
			
		||||
        if handler is None:
 | 
			
		||||
            handler = self._handlers[ctype][op].get('global', None)
 | 
			
		||||
 | 
			
		||||
        if handler:
 | 
			
		||||
            try:
 | 
			
		||||
                return handler(jid, node, ifrom, args)
 | 
			
		||||
            except TypeError:
 | 
			
		||||
                # To preserve backward compatibility, drop the ifrom
 | 
			
		||||
                # parameter for existing handlers that don't understand it.
 | 
			
		||||
                return handler(jid, node, args)
 | 
			
		||||
 | 
			
		||||
    def register(self, handler, ctype, op, jid=None, node=None, default=False):
 | 
			
		||||
        """Register an API callback, with JID+node specificity.
 | 
			
		||||
 | 
			
		||||
        The API callback can later be executed based on the
 | 
			
		||||
        specificity of the provided JID+node combination. 
 | 
			
		||||
        
 | 
			
		||||
        See :meth:`~ApiRegistry.run` for more details.
 | 
			
		||||
 | 
			
		||||
        :param string ctype: The name of the API to use.
 | 
			
		||||
        :param string op: The API operation to perform.
 | 
			
		||||
        :param JID jid: Optionally provide specific JID.
 | 
			
		||||
        :param string node: Optionally provide specific node.
 | 
			
		||||
        """
 | 
			
		||||
        self._setup(ctype, op)
 | 
			
		||||
        if jid is None and node is None:
 | 
			
		||||
            if handler is None:
 | 
			
		||||
                handler = self._handler_defaults[op]
 | 
			
		||||
            self._handlers[ctype][op]['global'] = handler
 | 
			
		||||
        elif jid is not None and node is None:
 | 
			
		||||
            self._handlers[ctype][op]['jid'][jid] = handler
 | 
			
		||||
        else:
 | 
			
		||||
            self._handlers[ctype][op]['node'][(jid, node)] = handler
 | 
			
		||||
 | 
			
		||||
        if default:
 | 
			
		||||
            self.register_default(handler, ctype, op)
 | 
			
		||||
 | 
			
		||||
    def register_default(self, handler, ctype, op):
 | 
			
		||||
        """Register a default, global handler for an operation.
 | 
			
		||||
 | 
			
		||||
        :param func handler: The default, global handler for the operation.
 | 
			
		||||
        :param string ctype: The name of the API to modify.
 | 
			
		||||
        :param string op: The API operation to use.
 | 
			
		||||
        """
 | 
			
		||||
        self._setup(ctype, op)
 | 
			
		||||
        self._handler_defaults[ctype][op] = handler
 | 
			
		||||
 | 
			
		||||
    def unregister(self, ctype, op, jid=None, node=None):
 | 
			
		||||
        """Remove an API callback.
 | 
			
		||||
 | 
			
		||||
        The API callback chosen for removal is based on the
 | 
			
		||||
        specificity of the provided JID+node combination.
 | 
			
		||||
 | 
			
		||||
        See :meth:`~ApiRegistry.run` for more details.
 | 
			
		||||
 | 
			
		||||
        :param string ctype: The name of the API to use.
 | 
			
		||||
        :param string op: The API operation to perform.
 | 
			
		||||
        :param JID jid: Optionally provide specific JID.
 | 
			
		||||
        :param string node: Optionally provide specific node.
 | 
			
		||||
        """
 | 
			
		||||
        self._setup(ctype, op)
 | 
			
		||||
        self.register(None, ctype, op, jid, node)
 | 
			
		||||
 | 
			
		||||
    def restore_default(self, ctype, op, jid=None, node=None):
 | 
			
		||||
        """Reset an API callback to use a default handler.
 | 
			
		||||
 | 
			
		||||
        :param string ctype: The name of the API to use.
 | 
			
		||||
        :param string op: The API operation to perform.
 | 
			
		||||
        :param JID jid: Optionally provide specific JID.
 | 
			
		||||
        :param string node: Optionally provide specific node.
 | 
			
		||||
        """
 | 
			
		||||
        self.unregister(ctype, op, jid, node)
 | 
			
		||||
        self.register(self._handler_defaults[ctype][op], ctype, op, jid, node)
 | 
			
		||||
@@ -1,30 +1,39 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
    sleekxmpp.basexmpp
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
    This module provides the common XMPP functionality
 | 
			
		||||
    for both clients and components.
 | 
			
		||||
 | 
			
		||||
    Part of SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
 | 
			
		||||
    :copyright: (c) 2011 Nathanael C. Fritz
 | 
			
		||||
    :license: MIT, see LICENSE for more details
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import with_statement, unicode_literals
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import copy
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import plugins, roster
 | 
			
		||||
from sleekxmpp import plugins, features, roster
 | 
			
		||||
from sleekxmpp.api import APIRegistry
 | 
			
		||||
from sleekxmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Message, Presence, Iq, Error, StreamError
 | 
			
		||||
from sleekxmpp.stanza import Message, Presence, Iq, StreamError
 | 
			
		||||
from sleekxmpp.stanza.roster import Roster
 | 
			
		||||
from sleekxmpp.stanza.nick import Nick
 | 
			
		||||
from sleekxmpp.stanza.htmlim import HTMLIM
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import XMLStream, JID, tostring
 | 
			
		||||
from sleekxmpp.xmlstream import XMLStream, JID
 | 
			
		||||
from sleekxmpp.xmlstream import ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatchXPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features import *
 | 
			
		||||
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
@@ -43,71 +52,76 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    with XMPP. It also provides a plugin mechanism to easily extend
 | 
			
		||||
    and add support for new XMPP features.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
       auto_authorize   -- Manage automatically accepting roster
 | 
			
		||||
                           subscriptions.
 | 
			
		||||
       auto_subscribe   -- Manage automatically requesting mutual
 | 
			
		||||
                           subscriptions.
 | 
			
		||||
       is_component     -- Indicates if this stream is for an XMPP component.
 | 
			
		||||
       jid              -- The XMPP JID for this stream.
 | 
			
		||||
       plugin           -- A dictionary of loaded plugins.
 | 
			
		||||
       plugin_config    -- A dictionary of plugin configurations.
 | 
			
		||||
       plugin_whitelist -- A list of approved plugins.
 | 
			
		||||
       sentpresence     -- Indicates if an initial presence has been sent.
 | 
			
		||||
       roster           -- A dictionary containing subscribed JIDs and
 | 
			
		||||
                           their presence statuses.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
       Iq                      -- Factory for creating an Iq stanzas.
 | 
			
		||||
       Message                 -- Factory for creating Message stanzas.
 | 
			
		||||
       Presence                -- Factory for creating Presence stanzas.
 | 
			
		||||
       get                     -- Return a plugin given its name.
 | 
			
		||||
       make_iq                 -- Create and initialize an Iq stanza.
 | 
			
		||||
       make_iq_error           -- Create an Iq stanza of type 'error'.
 | 
			
		||||
       make_iq_get             -- Create an Iq stanza of type 'get'.
 | 
			
		||||
       make_iq_query           -- Create an Iq stanza with a given query.
 | 
			
		||||
       make_iq_result          -- Create an Iq stanza of type 'result'.
 | 
			
		||||
       make_iq_set             -- Create an Iq stanza of type 'set'.
 | 
			
		||||
       make_message            -- Create and initialize a Message stanza.
 | 
			
		||||
       make_presence           -- Create and initialize a Presence stanza.
 | 
			
		||||
       make_query_roster       -- Create a roster query.
 | 
			
		||||
       process                 -- Overrides XMLStream.process.
 | 
			
		||||
       register_plugin         -- Load and configure a plugin.
 | 
			
		||||
       register_plugins        -- Load and configure multiple plugins.
 | 
			
		||||
       send_message            -- Create and send a Message stanza.
 | 
			
		||||
       send_presence           -- Create and send a Presence stanza.
 | 
			
		||||
       send_presence_subscribe -- Send a subscription request.
 | 
			
		||||
    :param default_ns: Ensure that the correct default XML namespace
 | 
			
		||||
                       is used during initialization.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid='', default_ns='jabber:client'):
 | 
			
		||||
        """
 | 
			
		||||
        Adapt an XML stream for use with XMPP.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            default_ns -- Ensure that the correct default XML namespace
 | 
			
		||||
                          is used during initialization.
 | 
			
		||||
        """
 | 
			
		||||
        XMLStream.__init__(self)
 | 
			
		||||
 | 
			
		||||
        # To comply with PEP8, method names now use underscores.
 | 
			
		||||
        # Deprecated method names are re-mapped for backwards compatibility.
 | 
			
		||||
        self.default_ns = default_ns
 | 
			
		||||
        self.stream_ns = 'http://etherx.jabber.org/streams'
 | 
			
		||||
        self.namespace_map[self.stream_ns] = 'stream'
 | 
			
		||||
 | 
			
		||||
        self.boundjid = JID(jid)
 | 
			
		||||
        #: An identifier for the stream as given by the server.
 | 
			
		||||
        self.stream_id = None
 | 
			
		||||
 | 
			
		||||
        self.plugin = {}
 | 
			
		||||
        #: The JabberID (JID) used by this connection. 
 | 
			
		||||
        self.boundjid = JID(jid)
 | 
			
		||||
        self._expected_server_name = self.boundjid.host
 | 
			
		||||
 | 
			
		||||
        #: A dictionary mapping plugin names to plugins.
 | 
			
		||||
        self.plugin = PluginManager(self)
 | 
			
		||||
 | 
			
		||||
        #: Configuration options for whitelisted plugins.
 | 
			
		||||
        #: If a plugin is registered without any configuration,
 | 
			
		||||
        #: and there is an entry here, it will be used.
 | 
			
		||||
        self.plugin_config = {}
 | 
			
		||||
 | 
			
		||||
        #: A list of plugins that will be loaded if
 | 
			
		||||
        #: :meth:`register_plugins` is called.
 | 
			
		||||
        self.plugin_whitelist = []
 | 
			
		||||
 | 
			
		||||
        #: The main roster object. This roster supports multiple
 | 
			
		||||
        #: owner JIDs, as in the case for components. For clients
 | 
			
		||||
        #: which only have a single JID, see :attr:`client_roster`.
 | 
			
		||||
        self.roster = roster.Roster(self)
 | 
			
		||||
        self.roster.add(self.boundjid.bare)
 | 
			
		||||
 | 
			
		||||
        #: The single roster for the bound JID. This is the
 | 
			
		||||
        #: equivalent of::
 | 
			
		||||
        #:
 | 
			
		||||
        #:     self.roster[self.boundjid.bare]
 | 
			
		||||
        self.client_roster = self.roster[self.boundjid.bare]
 | 
			
		||||
 | 
			
		||||
        #: The distinction between clients and components can be
 | 
			
		||||
        #: important, primarily for choosing how to handle the
 | 
			
		||||
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
 | 
			
		||||
        self.is_component = False
 | 
			
		||||
 | 
			
		||||
        #: The API registry is a way to process callbacks based on
 | 
			
		||||
        #: JID+node combinations. Each callback in the registry is
 | 
			
		||||
        #: marked with:
 | 
			
		||||
        #: 
 | 
			
		||||
        #:   - An API name, e.g. xep_0030
 | 
			
		||||
        #:   - The name of an action, e.g. get_info
 | 
			
		||||
        #:   - The JID that will be affected
 | 
			
		||||
        #:   - The node that will be affected
 | 
			
		||||
        #:
 | 
			
		||||
        #: API handlers with no JID or node will act as global handlers,
 | 
			
		||||
        #: while those with a JID and no node will service all nodes
 | 
			
		||||
        #: for a JID, and handlers with both a JID and node will be
 | 
			
		||||
        #: used only for that specific combination. The handler that
 | 
			
		||||
        #: provides the most specificity will be used.
 | 
			
		||||
        self.api = APIRegistry(self)
 | 
			
		||||
 | 
			
		||||
        #: Flag indicating that the initial presence broadcast has
 | 
			
		||||
        #: been sent. Until this happens, some servers may not
 | 
			
		||||
        #: behave as expected when sending stanzas.
 | 
			
		||||
        self.sentpresence = False
 | 
			
		||||
 | 
			
		||||
        #: A reference to :mod:`sleekxmpp.stanza` to make accessing
 | 
			
		||||
        #: stanza classes easier.
 | 
			
		||||
        self.stanza = sleekxmpp.stanza
 | 
			
		||||
 | 
			
		||||
        self.register_handler(
 | 
			
		||||
@@ -161,103 +175,77 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        register_stanza_plugin(Message, HTMLIM)
 | 
			
		||||
 | 
			
		||||
    def start_stream_handler(self, xml):
 | 
			
		||||
        """
 | 
			
		||||
        Save the stream ID once the streams have been established.
 | 
			
		||||
        """Save the stream ID once the streams have been established.
 | 
			
		||||
 | 
			
		||||
        Overrides XMLStream.start_stream_handler.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The incoming stream's root element.
 | 
			
		||||
        :param xml: The incoming stream's root element.
 | 
			
		||||
        """
 | 
			
		||||
        self.stream_id = xml.get('id', '')
 | 
			
		||||
 | 
			
		||||
    def process(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Overrides XMLStream.process.
 | 
			
		||||
 | 
			
		||||
        Initialize the XML streams and begin processing events.
 | 
			
		||||
        """Initialize plugins and begin processing the XML stream.
 | 
			
		||||
 | 
			
		||||
        The number of threads used for processing stream events is determined
 | 
			
		||||
        by HANDLER_THREADS.
 | 
			
		||||
        by :data:`HANDLER_THREADS`.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            block -- If block=False then event dispatcher will run
 | 
			
		||||
                     in a separate thread, allowing for the stream to be
 | 
			
		||||
                     used in the background for another application.
 | 
			
		||||
                     Otherwise, process(block=True) blocks the current thread.
 | 
			
		||||
                     Defaults to False.
 | 
			
		||||
        :param bool block: If ``False``, then event dispatcher will run
 | 
			
		||||
                    in a separate thread, allowing for the stream to be
 | 
			
		||||
                    used in the background for another application.
 | 
			
		||||
                    Otherwise, ``process(block=True)`` blocks the current
 | 
			
		||||
                    thread. Defaults to ``False``.
 | 
			
		||||
        :param bool threaded: **DEPRECATED**
 | 
			
		||||
                    If ``True``, then event dispatcher will run
 | 
			
		||||
                    in a separate thread, allowing for the stream to be
 | 
			
		||||
                    used in the background for another application.
 | 
			
		||||
                    Defaults to ``True``. This does **not** mean that no
 | 
			
		||||
                    threads are used at all if ``threaded=False``.
 | 
			
		||||
 | 
			
		||||
            **threaded is deprecated and included for API compatibility**
 | 
			
		||||
            threaded -- If threaded=True then event dispatcher will run
 | 
			
		||||
                        in a separate thread, allowing for the stream to be
 | 
			
		||||
                        used in the background for another application.
 | 
			
		||||
                        Defaults to True.
 | 
			
		||||
        Regardless of these threading options, these threads will 
 | 
			
		||||
        always exist:
 | 
			
		||||
 | 
			
		||||
            Event handlers and the send queue will be threaded
 | 
			
		||||
            regardless of these parameters.
 | 
			
		||||
        - The event queue processor
 | 
			
		||||
        - The send queue processor
 | 
			
		||||
        - The scheduler
 | 
			
		||||
        """
 | 
			
		||||
        if 'xep_0115' in self.plugin:
 | 
			
		||||
            name = 'xep_0115'
 | 
			
		||||
            if not hasattr(self.plugin[name], 'post_inited'):
 | 
			
		||||
                if hasattr(self.plugin[name], 'post_init'):
 | 
			
		||||
                    self.plugin[name].post_init()
 | 
			
		||||
                self.plugin[name].post_inited = True
 | 
			
		||||
 | 
			
		||||
        for name in self.plugin:
 | 
			
		||||
            if not self.plugin[name].post_inited:
 | 
			
		||||
                self.plugin[name].post_init()
 | 
			
		||||
            if not hasattr(self.plugin[name], 'post_inited'):
 | 
			
		||||
                if hasattr(self.plugin[name], 'post_init'):
 | 
			
		||||
                    self.plugin[name].post_init()
 | 
			
		||||
                self.plugin[name].post_inited = True
 | 
			
		||||
        return XMLStream.process(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def register_plugin(self, plugin, pconfig={}, module=None):
 | 
			
		||||
        """
 | 
			
		||||
        Register and configure  a plugin for use in this stream.
 | 
			
		||||
        """Register and configure  a plugin for use in this stream.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            plugin  -- The name of the plugin class. Plugin names must
 | 
			
		||||
        :param plugin: The name of the plugin class. Plugin names must
 | 
			
		||||
                       be unique.
 | 
			
		||||
            pconfig -- A dictionary of configuration data for the plugin.
 | 
			
		||||
                       Defaults to an empty dictionary.
 | 
			
		||||
            module  -- Optional refence to the module containing the plugin
 | 
			
		||||
        :param pconfig: A dictionary of configuration data for the plugin.
 | 
			
		||||
                        Defaults to an empty dictionary.
 | 
			
		||||
        :param module: Optional refence to the module containing the plugin
 | 
			
		||||
                       class if using custom plugins.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # Import the given module that contains the plugin.
 | 
			
		||||
            if not module:
 | 
			
		||||
                try:
 | 
			
		||||
                    module = sleekxmpp.plugins
 | 
			
		||||
                    module = __import__(
 | 
			
		||||
                            str("%s.%s" % (module.__name__, plugin)),
 | 
			
		||||
                            globals(), locals(), [str(plugin)])
 | 
			
		||||
                except ImportError:
 | 
			
		||||
                    module = sleekxmpp.features
 | 
			
		||||
                    module = __import__(
 | 
			
		||||
                            str("%s.%s" % (module.__name__, plugin)),
 | 
			
		||||
                            globals(), locals(), [str(plugin)])
 | 
			
		||||
            if isinstance(module, str):
 | 
			
		||||
                # We probably want to load a module from outside
 | 
			
		||||
                # the sleekxmpp package, so leave out the globals().
 | 
			
		||||
                module = __import__(module, fromlist=[plugin])
 | 
			
		||||
 | 
			
		||||
            # Use the global plugin config cache, if applicable
 | 
			
		||||
            if not pconfig:
 | 
			
		||||
                pconfig = self.plugin_config.get(plugin, {})
 | 
			
		||||
        # Use the global plugin config cache, if applicable
 | 
			
		||||
        if not pconfig:
 | 
			
		||||
            pconfig = self.plugin_config.get(plugin, {})
 | 
			
		||||
 | 
			
		||||
            # Load the plugin class from the module.
 | 
			
		||||
            self.plugin[plugin] = getattr(module, plugin)(self, pconfig)
 | 
			
		||||
 | 
			
		||||
            # Let XEP/RFC implementing plugins have some extra logging info.
 | 
			
		||||
            spec = '(CUSTOM) '
 | 
			
		||||
            if self.plugin[plugin].xep:
 | 
			
		||||
                spec = "(XEP-%s) " % self.plugin[plugin].xep
 | 
			
		||||
            elif self.plugin[plugin].rfc:
 | 
			
		||||
                spec = "(RFC-%s) " % self.plugin[plugin].rfc
 | 
			
		||||
 | 
			
		||||
            desc = (spec, self.plugin[plugin].description)
 | 
			
		||||
            log.debug("Loaded Plugin %s%s" % desc)
 | 
			
		||||
        except:
 | 
			
		||||
            log.exception("Unable to load plugin: %s", plugin)
 | 
			
		||||
        if not self.plugin.registered(plugin):
 | 
			
		||||
            load_plugin(plugin, module)
 | 
			
		||||
        self.plugin.enable(plugin, pconfig)
 | 
			
		||||
 | 
			
		||||
    def register_plugins(self):
 | 
			
		||||
        """
 | 
			
		||||
        Register and initialize all built-in plugins.
 | 
			
		||||
        """Register and initialize all built-in plugins.
 | 
			
		||||
 | 
			
		||||
        Optionally, the list of plugins loaded may be limited to those
 | 
			
		||||
        contained in self.plugin_whitelist.
 | 
			
		||||
        contained in :attr:`plugin_whitelist`.
 | 
			
		||||
 | 
			
		||||
        Plugin configurations stored in self.plugin_config will be used.
 | 
			
		||||
        Plugin configurations stored in :attr:`plugin_config` will be used.
 | 
			
		||||
        """
 | 
			
		||||
        if self.plugin_whitelist:
 | 
			
		||||
            plugin_list = self.plugin_whitelist
 | 
			
		||||
@@ -266,29 +254,20 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
        for plugin in plugin_list:
 | 
			
		||||
            if plugin in plugins.__all__:
 | 
			
		||||
                self.register_plugin(plugin,
 | 
			
		||||
                                     self.plugin_config.get(plugin, {}))
 | 
			
		||||
                self.register_plugin(plugin)
 | 
			
		||||
            else:
 | 
			
		||||
                raise NameError("Plugin %s not in plugins.__all__." % plugin)
 | 
			
		||||
 | 
			
		||||
        # Resolve plugin inter-dependencies.
 | 
			
		||||
        for plugin in self.plugin:
 | 
			
		||||
            self.plugin[plugin].post_init()
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, key):
 | 
			
		||||
        """
 | 
			
		||||
        Return a plugin given its name, if it has been registered.
 | 
			
		||||
        """
 | 
			
		||||
        """Return a plugin given its name, if it has been registered."""
 | 
			
		||||
        if key in self.plugin:
 | 
			
		||||
            return self.plugin[key]
 | 
			
		||||
        else:
 | 
			
		||||
            log.warning("""Plugin "%s" is not loaded.""" % key)
 | 
			
		||||
            log.warning("Plugin '%s' is not loaded.", key)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def get(self, key, default):
 | 
			
		||||
        """
 | 
			
		||||
        Return a plugin given its name, if it has been registered.
 | 
			
		||||
        """
 | 
			
		||||
        """Return a plugin given its name, if it has been registered."""
 | 
			
		||||
        return self.plugin.get(key, default)
 | 
			
		||||
 | 
			
		||||
    def Message(self, *args, **kwargs):
 | 
			
		||||
@@ -304,16 +283,18 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        return Presence(self, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new Iq stanza with a given Id and from JID.
 | 
			
		||||
        """Create a new Iq stanza with a given Id and from JID.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            id     -- An ideally unique ID value for this stanza thread.
 | 
			
		||||
                      Defaults to 0.
 | 
			
		||||
            ifrom  -- The from JID to use for this stanza.
 | 
			
		||||
            ito    -- The destination JID for this stanza.
 | 
			
		||||
            itype  -- The Iq's type, one of: get, set, result, or error.
 | 
			
		||||
            iquery -- Optional namespace for adding a query element.
 | 
			
		||||
        :param id: An ideally unique ID value for this stanza thread.
 | 
			
		||||
                   Defaults to 0.
 | 
			
		||||
        :param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID` 
 | 
			
		||||
                      to use for this stanza.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
        :param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type, 
 | 
			
		||||
                      one of: ``'get'``, ``'set'``, ``'result'``,
 | 
			
		||||
                      or ``'error'``.
 | 
			
		||||
        :param iquery: Optional namespace for adding a query element.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()
 | 
			
		||||
        iq['id'] = str(id)
 | 
			
		||||
@@ -324,17 +305,17 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'get'.
 | 
			
		||||
        """Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'get'``.
 | 
			
		||||
 | 
			
		||||
        Optionally, a query element may be added.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            queryxmlns -- The namespace of the query to use.
 | 
			
		||||
            ito        -- The destination JID for this stanza.
 | 
			
		||||
            ifrom      -- The from JID to use for this stanza.
 | 
			
		||||
            iq         -- Optionally use an existing stanza instead
 | 
			
		||||
                          of generating a new one.
 | 
			
		||||
        :param queryxmlns: The namespace of the query to use.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                      to use for this stanza.
 | 
			
		||||
        :param iq: Optionally use an existing stanza instead
 | 
			
		||||
                   of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
@@ -348,14 +329,16 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'result' with the given ID value.
 | 
			
		||||
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type 
 | 
			
		||||
        ``'result'`` with the given ID value.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            id    -- An ideally unique ID value. May use self.new_id().
 | 
			
		||||
            ito   -- The destination JID for this stanza.
 | 
			
		||||
            ifrom -- The from JID to use for this stanza.
 | 
			
		||||
            iq    -- Optionally use an existing stanza instead
 | 
			
		||||
                     of generating a new one.
 | 
			
		||||
        :param id: An ideally unique ID value. May use :meth:`new_id()`.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                      to use for this stanza.
 | 
			
		||||
        :param iq: Optionally use an existing stanza instead
 | 
			
		||||
                   of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
@@ -371,17 +354,22 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'set'.
 | 
			
		||||
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'set'``.
 | 
			
		||||
 | 
			
		||||
        Optionally, a substanza may be given to use as the
 | 
			
		||||
        stanza's payload.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            sub   -- A stanza or XML object to use as the Iq's payload.
 | 
			
		||||
            ito   -- The destination JID for this stanza.
 | 
			
		||||
            ifrom -- The from JID to use for this stanza.
 | 
			
		||||
            iq    -- Optionally use an existing stanza instead
 | 
			
		||||
                     of generating a new one.
 | 
			
		||||
        :param sub: Either an 
 | 
			
		||||
                    :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
 | 
			
		||||
                    stanza object or an
 | 
			
		||||
                    :class:`~xml.etree.ElementTree.Element` XML object 
 | 
			
		||||
                    to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                      to use for this stanza.
 | 
			
		||||
        :param iq: Optionally use an existing stanza instead
 | 
			
		||||
                   of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
@@ -398,19 +386,20 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
                      condition='feature-not-implemented',
 | 
			
		||||
                      text=None, ito=None, ifrom=None, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create an Iq stanza of type 'error'.
 | 
			
		||||
        Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            id        -- An ideally unique ID value. May use self.new_id().
 | 
			
		||||
            type      -- The type of the error, such as 'cancel' or 'modify'.
 | 
			
		||||
                         Defaults to 'cancel'.
 | 
			
		||||
            condition -- The error condition.
 | 
			
		||||
                         Defaults to 'feature-not-implemented'.
 | 
			
		||||
            text      -- A message describing the cause of the error.
 | 
			
		||||
            ito       -- The destination JID for this stanza.
 | 
			
		||||
            ifrom     -- The from JID to use for this stanza.
 | 
			
		||||
            iq        -- Optionally use an existing stanza instead
 | 
			
		||||
                         of generating a new one.
 | 
			
		||||
        :param id: An ideally unique ID value. May use :meth:`new_id()`.
 | 
			
		||||
        :param type: The type of the error, such as ``'cancel'`` or 
 | 
			
		||||
                     ``'modify'``. Defaults to ``'cancel'``.
 | 
			
		||||
        :param condition: The error condition. Defaults to 
 | 
			
		||||
                          ``'feature-not-implemented'``.
 | 
			
		||||
        :param text: A message describing the cause of the error.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                      to use for this stanza.
 | 
			
		||||
        :param iq: Optionally use an existing stanza instead
 | 
			
		||||
                   of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
@@ -426,15 +415,16 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create or modify an Iq stanza to use the given
 | 
			
		||||
        query namespace.
 | 
			
		||||
        Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza 
 | 
			
		||||
        to use the given query namespace.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq    -- Optional Iq stanza to modify. A new
 | 
			
		||||
                     stanza is created otherwise.
 | 
			
		||||
            xmlns -- The query's namespace.
 | 
			
		||||
            ito   -- The destination JID for this stanza.
 | 
			
		||||
            ifrom -- The from JID to use for this stanza.
 | 
			
		||||
        :param iq: Optionally use an existing stanza instead
 | 
			
		||||
                   of generating a new one.
 | 
			
		||||
        :param xmlns: The query's namespace.
 | 
			
		||||
        :param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                    for this stanza.
 | 
			
		||||
        :param ifrom: The ``'from'`` :class:`~sleekxmpp.xmlstream.jid.JID`
 | 
			
		||||
                      to use for this stanza.
 | 
			
		||||
        """
 | 
			
		||||
        if not iq:
 | 
			
		||||
            iq = self.Iq()
 | 
			
		||||
@@ -446,12 +436,10 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        return iq
 | 
			
		||||
 | 
			
		||||
    def make_query_roster(self, iq=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create a roster query element.
 | 
			
		||||
        """Create a roster query element.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq -- Optional Iq stanza to modify. A new stanza
 | 
			
		||||
                  is created otherwise.
 | 
			
		||||
        :param iq: Optionally use an existing stanza instead
 | 
			
		||||
                   of generating a new one.
 | 
			
		||||
        """
 | 
			
		||||
        if iq:
 | 
			
		||||
            iq['query'] = 'jabber:iq:roster'
 | 
			
		||||
@@ -460,18 +448,19 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def make_message(self, mto, mbody=None, msubject=None, mtype=None,
 | 
			
		||||
                     mhtml=None, mfrom=None, mnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create and initialize a new Message stanza.
 | 
			
		||||
        Create and initialize a new 
 | 
			
		||||
        :class:`~sleekxmpp.stanza.message.Message` stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            mto      -- The recipient of the message.
 | 
			
		||||
            mbody    -- The main contents of the message.
 | 
			
		||||
            msubject -- Optional subject for the message.
 | 
			
		||||
            mtype    -- The message's type, such as 'chat' or 'groupchat'.
 | 
			
		||||
            mhtml    -- Optional HTML body content.
 | 
			
		||||
            mfrom    -- The sender of the message. if sending from a client,
 | 
			
		||||
                        be aware that some servers require that the full JID
 | 
			
		||||
                        of the sender be used.
 | 
			
		||||
            mnick    -- Optional nickname of the sender.
 | 
			
		||||
        :param mto: The recipient of the message.
 | 
			
		||||
        :param mbody: The main contents of the message.
 | 
			
		||||
        :param msubject: Optional subject for the message.
 | 
			
		||||
        :param mtype: The message's type, such as ``'chat'`` or
 | 
			
		||||
                      ``'groupchat'``.
 | 
			
		||||
        :param mhtml: Optional HTML body content in the form of a string.
 | 
			
		||||
        :param mfrom: The sender of the message. if sending from a client,
 | 
			
		||||
                      be aware that some servers require that the full JID
 | 
			
		||||
                      of the sender be used.
 | 
			
		||||
        :param mnick: Optional nickname of the sender.
 | 
			
		||||
        """
 | 
			
		||||
        message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
 | 
			
		||||
        message['body'] = mbody
 | 
			
		||||
@@ -485,16 +474,16 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def make_presence(self, pshow=None, pstatus=None, ppriority=None,
 | 
			
		||||
                      pto=None, ptype=None, pfrom=None, pnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create and initialize a new Presence stanza.
 | 
			
		||||
        Create and initialize a new 
 | 
			
		||||
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            pshow     -- The presence's show value.
 | 
			
		||||
            pstatus   -- The presence's status message.
 | 
			
		||||
            ppriority -- This connections' priority.
 | 
			
		||||
            pto       -- The recipient of a directed presence.
 | 
			
		||||
            ptype     -- The type of presence, such as 'subscribe'.
 | 
			
		||||
            pfrom     -- The sender of the presence.
 | 
			
		||||
            pnick     -- Optional nickname of the presence's sender.
 | 
			
		||||
        :param pshow: The presence's show value.
 | 
			
		||||
        :param pstatus: The presence's status message.
 | 
			
		||||
        :param ppriority: This connection's priority.
 | 
			
		||||
        :param pto: The recipient of a directed presence.
 | 
			
		||||
        :param ptype: The type of presence, such as ``'subscribe'``.
 | 
			
		||||
        :param pfrom: The sender of the presence.
 | 
			
		||||
        :param pnick: Optional nickname of the presence's sender.
 | 
			
		||||
        """
 | 
			
		||||
        presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
 | 
			
		||||
        if pshow is not None:
 | 
			
		||||
@@ -509,18 +498,19 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def send_message(self, mto, mbody, msubject=None, mtype=None,
 | 
			
		||||
                     mhtml=None, mfrom=None, mnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a Message stanza.
 | 
			
		||||
        Create, initialize, and send a new 
 | 
			
		||||
        :class:`~sleekxmpp.stanza.message.Message` stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            mto      -- The recipient of the message.
 | 
			
		||||
            mbody    -- The main contents of the message.
 | 
			
		||||
            msubject -- Optional subject for the message.
 | 
			
		||||
            mtype    -- The message's type, such as 'chat' or 'groupchat'.
 | 
			
		||||
            mhtml    -- Optional HTML body content.
 | 
			
		||||
            mfrom    -- The sender of the message. if sending from a client,
 | 
			
		||||
                        be aware that some servers require that the full JID
 | 
			
		||||
                        of the sender be used.
 | 
			
		||||
            mnick    -- Optional nickname of the sender.
 | 
			
		||||
        :param mto: The recipient of the message.
 | 
			
		||||
        :param mbody: The main contents of the message.
 | 
			
		||||
        :param msubject: Optional subject for the message.
 | 
			
		||||
        :param mtype: The message's type, such as ``'chat'`` or
 | 
			
		||||
                      ``'groupchat'``.
 | 
			
		||||
        :param mhtml: Optional HTML body content in the form of a string.
 | 
			
		||||
        :param mfrom: The sender of the message. if sending from a client,
 | 
			
		||||
                      be aware that some servers require that the full JID
 | 
			
		||||
                      of the sender be used.
 | 
			
		||||
        :param mnick: Optional nickname of the sender.
 | 
			
		||||
        """
 | 
			
		||||
        self.make_message(mto, mbody, msubject, mtype,
 | 
			
		||||
                          mhtml, mfrom, mnick).send()
 | 
			
		||||
@@ -528,16 +518,16 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def send_presence(self, pshow=None, pstatus=None, ppriority=None,
 | 
			
		||||
                      pto=None, pfrom=None, ptype=None, pnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a Presence stanza.
 | 
			
		||||
        Create, initialize, and send a new 
 | 
			
		||||
        :class:`~sleekxmpp.stanza.presence.Presence` stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            pshow     -- The presence's show value.
 | 
			
		||||
            pstatus   -- The presence's status message.
 | 
			
		||||
            ppriority -- This connections' priority.
 | 
			
		||||
            pto       -- The recipient of a directed presence.
 | 
			
		||||
            ptype     -- The type of presence, such as 'subscribe'.
 | 
			
		||||
            pfrom     -- The sender of the presence.
 | 
			
		||||
            pnick     -- Optional nickname of the presence's sender.
 | 
			
		||||
        :param pshow: The presence's show value.
 | 
			
		||||
        :param pstatus: The presence's status message.
 | 
			
		||||
        :param ppriority: This connection's priority.
 | 
			
		||||
        :param pto: The recipient of a directed presence.
 | 
			
		||||
        :param ptype: The type of presence, such as ``'subscribe'``.
 | 
			
		||||
        :param pfrom: The sender of the presence.
 | 
			
		||||
        :param pnick: Optional nickname of the presence's sender.
 | 
			
		||||
        """
 | 
			
		||||
        # Python2.6 chokes on Unicode strings for dict keys.
 | 
			
		||||
        args = {str('pto'): pto,
 | 
			
		||||
@@ -555,13 +545,14 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
    def send_presence_subscription(self, pto, pfrom=None,
 | 
			
		||||
                                   ptype='subscribe', pnick=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create, initialize, and send a Presence stanza of type 'subscribe'.
 | 
			
		||||
        Create, initialize, and send a new 
 | 
			
		||||
        :class:`~sleekxmpp.stanza.presence.Presence` stanza of
 | 
			
		||||
        type ``'subscribe'``.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            pto   -- The recipient of a directed presence.
 | 
			
		||||
            pfrom -- The sender of the presence.
 | 
			
		||||
            ptype -- The type of presence. Defaults to 'subscribe'.
 | 
			
		||||
            pnick -- Nickname of the presence's sender.
 | 
			
		||||
        :param pto: The recipient of a directed presence.
 | 
			
		||||
        :param pfrom: The sender of the presence.
 | 
			
		||||
        :param ptype: The type of presence, such as ``'subscribe'``.
 | 
			
		||||
        :param pnick: Optional nickname of the presence's sender.
 | 
			
		||||
        """
 | 
			
		||||
        presence = self.makePresence(ptype=ptype,
 | 
			
		||||
                                     pfrom=pfrom,
 | 
			
		||||
@@ -574,9 +565,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def jid(self):
 | 
			
		||||
        """
 | 
			
		||||
        Attribute accessor for bare jid
 | 
			
		||||
        """
 | 
			
		||||
        """Attribute accessor for bare jid"""
 | 
			
		||||
        log.warning("jid property deprecated. Use boundjid.bare")
 | 
			
		||||
        return self.boundjid.bare
 | 
			
		||||
 | 
			
		||||
@@ -587,9 +576,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def fulljid(self):
 | 
			
		||||
        """
 | 
			
		||||
        Attribute accessor for full jid
 | 
			
		||||
        """
 | 
			
		||||
        """Attribute accessor for full jid"""
 | 
			
		||||
        log.warning("fulljid property deprecated. Use boundjid.full")
 | 
			
		||||
        return self.boundjid.full
 | 
			
		||||
 | 
			
		||||
@@ -600,9 +587,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def resource(self):
 | 
			
		||||
        """
 | 
			
		||||
        Attribute accessor for jid resource
 | 
			
		||||
        """
 | 
			
		||||
        """Attribute accessor for jid resource"""
 | 
			
		||||
        log.warning("resource property deprecated. Use boundjid.resource")
 | 
			
		||||
        return self.boundjid.resource
 | 
			
		||||
 | 
			
		||||
@@ -613,9 +598,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def username(self):
 | 
			
		||||
        """
 | 
			
		||||
        Attribute accessor for jid usernode
 | 
			
		||||
        """
 | 
			
		||||
        """Attribute accessor for jid usernode"""
 | 
			
		||||
        log.warning("username property deprecated. Use boundjid.user")
 | 
			
		||||
        return self.boundjid.user
 | 
			
		||||
 | 
			
		||||
@@ -626,9 +609,7 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def server(self):
 | 
			
		||||
        """
 | 
			
		||||
        Attribute accessor for jid host
 | 
			
		||||
        """
 | 
			
		||||
        """Attribute accessor for jid host"""
 | 
			
		||||
        log.warning("server property deprecated. Use boundjid.host")
 | 
			
		||||
        return self.boundjid.server
 | 
			
		||||
 | 
			
		||||
@@ -639,47 +620,33 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def auto_authorize(self):
 | 
			
		||||
        """
 | 
			
		||||
        Auto accept or deny subscription requests.
 | 
			
		||||
        """Auto accept or deny subscription requests.
 | 
			
		||||
 | 
			
		||||
        If True, auto accept subscription requests.
 | 
			
		||||
        If False, auto deny subscription requests.
 | 
			
		||||
        If None, don't automatically respond.
 | 
			
		||||
        If ``True``, auto accept subscription requests.
 | 
			
		||||
        If ``False``, auto deny subscription requests.
 | 
			
		||||
        If ``None``, don't automatically respond.
 | 
			
		||||
        """
 | 
			
		||||
        return self.roster.auto_authorize
 | 
			
		||||
 | 
			
		||||
    @auto_authorize.setter
 | 
			
		||||
    def auto_authorize(self, value):
 | 
			
		||||
        """
 | 
			
		||||
        Auto accept or deny subscription requests.
 | 
			
		||||
 | 
			
		||||
        If True, auto accept subscription requests.
 | 
			
		||||
        If False, auto deny subscription requests.
 | 
			
		||||
        If None, don't automatically respond.
 | 
			
		||||
        """
 | 
			
		||||
        self.roster.auto_authorize = value
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def auto_subscribe(self):
 | 
			
		||||
        """
 | 
			
		||||
        Auto send requests for mutual subscriptions.
 | 
			
		||||
        """Auto send requests for mutual subscriptions.
 | 
			
		||||
 | 
			
		||||
        If True, auto send mutual subscription requests.
 | 
			
		||||
        If ``True``, auto send mutual subscription requests.
 | 
			
		||||
        """
 | 
			
		||||
        return self.roster.auto_subscribe
 | 
			
		||||
 | 
			
		||||
    @auto_subscribe.setter
 | 
			
		||||
    def auto_subscribe(self, value):
 | 
			
		||||
        """
 | 
			
		||||
        Auto send requests for mutual subscriptions.
 | 
			
		||||
 | 
			
		||||
        If True, auto send mutual subscription requests.
 | 
			
		||||
        """
 | 
			
		||||
        self.roster.auto_subscribe = value
 | 
			
		||||
 | 
			
		||||
    def set_jid(self, jid):
 | 
			
		||||
        """Rip a JID apart and claim it as our own."""
 | 
			
		||||
        log.debug("setting jid to %s" % jid)
 | 
			
		||||
        log.debug("setting jid to %s", jid)
 | 
			
		||||
        self.boundjid.full = jid
 | 
			
		||||
 | 
			
		||||
    def getjidresource(self, fulljid):
 | 
			
		||||
@@ -700,6 +667,8 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
 | 
			
		||||
    def _handle_message(self, msg):
 | 
			
		||||
        """Process incoming message stanzas."""
 | 
			
		||||
        if not self.is_component and not msg['to'].bare:
 | 
			
		||||
            msg['to'] = self.boundjid
 | 
			
		||||
        self.event('message', msg)
 | 
			
		||||
 | 
			
		||||
    def _handle_available(self, presence):
 | 
			
		||||
@@ -713,17 +682,16 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        self.roster[pto][pfrom].handle_unavailable(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_new_subscription(self, stanza):
 | 
			
		||||
        """
 | 
			
		||||
        Attempt to automatically handle subscription requests.
 | 
			
		||||
        """Attempt to automatically handle subscription requests.
 | 
			
		||||
 | 
			
		||||
        Subscriptions will be approved if the request is from
 | 
			
		||||
        a whitelisted JID, of self.auto_authorize is True. They
 | 
			
		||||
        will be rejected if self.auto_authorize is False. Setting
 | 
			
		||||
        self.auto_authorize to None will disable automatic
 | 
			
		||||
        a whitelisted JID, of :attr:`auto_authorize` is True. They
 | 
			
		||||
        will be rejected if :attr:`auto_authorize` is False. Setting
 | 
			
		||||
        :attr:`auto_authorize` to ``None`` will disable automatic
 | 
			
		||||
        subscription handling (except for whitelisted JIDs).
 | 
			
		||||
 | 
			
		||||
        If a subscription is accepted, a request for a mutual
 | 
			
		||||
        subscription will be sent if self.auto_subscribe is True.
 | 
			
		||||
        subscription will be sent if :attr:`auto_subscribe` is ``True``.
 | 
			
		||||
        """
 | 
			
		||||
        roster = self.roster[stanza['to'].bare]
 | 
			
		||||
        item = self.roster[stanza['to'].bare][stanza['from'].bare]
 | 
			
		||||
@@ -762,12 +730,15 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        self.roster[pto][pfrom].handle_unsubscribed(presence)
 | 
			
		||||
 | 
			
		||||
    def _handle_presence(self, presence):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming presence stanzas.
 | 
			
		||||
        """Process incoming presence stanzas.
 | 
			
		||||
 | 
			
		||||
        Update the roster with presence information.
 | 
			
		||||
        """
 | 
			
		||||
        self.event("presence_%s" % presence['type'], presence)
 | 
			
		||||
        if not self.is_component and not presence['to'].bare:
 | 
			
		||||
            presence['to'] = self.boundjid
 | 
			
		||||
 | 
			
		||||
        self.event('presence', presence)
 | 
			
		||||
        self.event('presence_%s' % presence['type'], presence)
 | 
			
		||||
 | 
			
		||||
        # Check for changes in subscription state.
 | 
			
		||||
        if presence['type'] in ('subscribe', 'subscribed',
 | 
			
		||||
@@ -779,24 +750,26 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    def exception(self, exception):
 | 
			
		||||
        """
 | 
			
		||||
        Process any uncaught exceptions, notably IqError and
 | 
			
		||||
        IqTimeout exceptions.
 | 
			
		||||
        """Process any uncaught exceptions, notably 
 | 
			
		||||
        :class:`~sleekxmpp.exceptions.IqError` and
 | 
			
		||||
        :class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
 | 
			
		||||
 | 
			
		||||
        Overrides XMLStream.exception.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            exception -- An unhandled exception object.
 | 
			
		||||
        :param exception: An unhandled :class:`Exception` object.
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(exception, IqError):
 | 
			
		||||
            iq = exception.iq
 | 
			
		||||
            log.error('%s: %s' % (iq['error']['condition'],
 | 
			
		||||
                                  iq['error']['text']))
 | 
			
		||||
            log.error('%s: %s', iq['error']['condition'],
 | 
			
		||||
                                iq['error']['text'])
 | 
			
		||||
            log.warning('You should catch IqError exceptions')
 | 
			
		||||
        elif isinstance(exception, IqTimeout):
 | 
			
		||||
            iq = exception.iq
 | 
			
		||||
            log.error('Request timed out: %s' % iq)
 | 
			
		||||
            log.error('Request timed out: %s', iq)
 | 
			
		||||
            log.warning('You should catch IqTimeout exceptions')
 | 
			
		||||
        elif isinstance(exception, SyntaxError):
 | 
			
		||||
            # Hide stream parsing errors that occur when the
 | 
			
		||||
            # stream is disconnected (they've been handled, we
 | 
			
		||||
            # don't need to make a mess in the logs).
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            log.exception(exception)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,27 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
    sleekxmpp.clientxmpp
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
    This module provides XMPP functionality that
 | 
			
		||||
    is specific to client connections.
 | 
			
		||||
 | 
			
		||||
    Part of SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
 | 
			
		||||
    :copyright: (c) 2011 Nathanael C. Fritz
 | 
			
		||||
    :license: MIT, see LICENSE for more details
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import absolute_import, unicode_literals
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import base64
 | 
			
		||||
import sys
 | 
			
		||||
import hashlib
 | 
			
		||||
import random
 | 
			
		||||
import threading
 | 
			
		||||
 | 
			
		||||
import sleekxmpp
 | 
			
		||||
from sleekxmpp import plugins
 | 
			
		||||
from sleekxmpp import stanza
 | 
			
		||||
from sleekxmpp import features
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.basexmpp import BaseXMPP
 | 
			
		||||
from sleekxmpp.stanza import *
 | 
			
		||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.exceptions import XMPPError
 | 
			
		||||
from sleekxmpp.xmlstream import XMLStream
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath, MatchXPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
 | 
			
		||||
# Flag indicating if DNS SRV records are available for use.
 | 
			
		||||
try:
 | 
			
		||||
@@ -41,46 +38,42 @@ log = logging.getLogger(__name__)
 | 
			
		||||
class ClientXMPP(BaseXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    SleekXMPP's client class. ( Use only for good, not for evil.)
 | 
			
		||||
    SleekXMPP's client class. (Use only for good, not for evil.)
 | 
			
		||||
 | 
			
		||||
    Typical Use:
 | 
			
		||||
    xmpp = ClientXMPP('user@server.tld/resource', 'password')
 | 
			
		||||
    xmpp.process(block=False) // when block is True, it blocks the current
 | 
			
		||||
    //                           thread. False by default.
 | 
			
		||||
    Typical use pattern:
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        connect          -- Overrides XMLStream.connect.
 | 
			
		||||
        del_roster_item  -- Delete a roster item.
 | 
			
		||||
        get_roster       -- Retrieve the roster from the server.
 | 
			
		||||
        register_feature -- Register a stream feature.
 | 
			
		||||
        update_roster    -- Update a roster item.
 | 
			
		||||
        xmpp = ClientXMPP('user@server.tld/resource', 'password')
 | 
			
		||||
        # ... Register plugins and event handlers ...
 | 
			
		||||
        xmpp.connect()
 | 
			
		||||
        xmpp.process(block=False) # block=True will block the current
 | 
			
		||||
                                  # thread. By default, block=False
 | 
			
		||||
 | 
			
		||||
    :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 
 | 
			
		||||
                    :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
 | 
			
		||||
    :param escape_quotes: **Deprecated.**
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, ssl=False, plugin_config={},
 | 
			
		||||
                 plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new SleekXMPP client.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid              -- The JID of the XMPP user account.
 | 
			
		||||
            password         -- The password for the XMPP user account.
 | 
			
		||||
            ssl              -- Deprecated.
 | 
			
		||||
            plugin_config    -- A dictionary of plugin configurations.
 | 
			
		||||
            plugin_whitelist -- A list of approved plugins that will be loaded
 | 
			
		||||
                                when calling register_plugins.
 | 
			
		||||
            escape_quotes    -- Deprecated.
 | 
			
		||||
        """
 | 
			
		||||
        BaseXMPP.__init__(self, jid, 'jabber:client')
 | 
			
		||||
 | 
			
		||||
        self.set_jid(jid)
 | 
			
		||||
        self.password = password
 | 
			
		||||
        self.escape_quotes = escape_quotes
 | 
			
		||||
        self.plugin_config = plugin_config
 | 
			
		||||
        self.plugin_whitelist = plugin_whitelist
 | 
			
		||||
        self.default_port = 5222
 | 
			
		||||
 | 
			
		||||
        self.credentials = {}
 | 
			
		||||
 | 
			
		||||
        self.password = password
 | 
			
		||||
 | 
			
		||||
        self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
 | 
			
		||||
                self.boundjid.host,
 | 
			
		||||
                "xmlns:stream='%s'" % self.stream_ns,
 | 
			
		||||
@@ -91,6 +84,8 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        self._stream_feature_handlers = {}
 | 
			
		||||
        self._stream_feature_order = []
 | 
			
		||||
 | 
			
		||||
        self.dns_service = 'xmpp-client'
 | 
			
		||||
 | 
			
		||||
        #TODO: Use stream state here
 | 
			
		||||
        self.authenticated = False
 | 
			
		||||
        self.sessionstarted = False
 | 
			
		||||
@@ -98,6 +93,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        self.bindfail = False
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('connected', self._handle_connected)
 | 
			
		||||
        self.add_event_handler('session_bind', self._handle_session_bind)
 | 
			
		||||
 | 
			
		||||
        self.register_stanza(StreamFeatures)
 | 
			
		||||
 | 
			
		||||
@@ -107,9 +103,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                         self._handle_stream_features))
 | 
			
		||||
        self.register_handler(
 | 
			
		||||
                Callback('Roster Update',
 | 
			
		||||
                         MatchXPath('{%s}iq/{%s}query' % (
 | 
			
		||||
                             self.default_ns,
 | 
			
		||||
                             'jabber:iq:roster')),
 | 
			
		||||
                         StanzaPath('iq@type=set/roster'),
 | 
			
		||||
                         self._handle_roster))
 | 
			
		||||
 | 
			
		||||
        # Setup default stream features
 | 
			
		||||
@@ -118,76 +112,59 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        self.register_plugin('feature_session')
 | 
			
		||||
        self.register_plugin('feature_mechanisms',
 | 
			
		||||
                pconfig={'use_mech': sasl_mech} if sasl_mech else None)
 | 
			
		||||
        self.register_plugin('feature_rosterver')
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def password(self):
 | 
			
		||||
        return self.credentials.get('password', '')
 | 
			
		||||
 | 
			
		||||
    @password.setter
 | 
			
		||||
    def password(self, value):
 | 
			
		||||
        self.credentials['password'] = value
 | 
			
		||||
 | 
			
		||||
    def connect(self, address=tuple(), reattempt=True,
 | 
			
		||||
                use_tls=True, use_ssl=False):
 | 
			
		||||
        """
 | 
			
		||||
        Connect to the XMPP server.
 | 
			
		||||
        """Connect to the XMPP server.
 | 
			
		||||
 | 
			
		||||
        When no address is given, a SRV lookup for the server will
 | 
			
		||||
        be attempted. If that fails, the server user in the JID
 | 
			
		||||
        will be used.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            address   -- A tuple containing the server's host and port.
 | 
			
		||||
            reattempt -- If True, reattempt the connection if an
 | 
			
		||||
                         error occurs. Defaults to True.
 | 
			
		||||
            use_tls   -- Indicates if TLS should be used for the
 | 
			
		||||
                         connection. Defaults to True.
 | 
			
		||||
            use_ssl   -- Indicates if the older SSL connection method
 | 
			
		||||
                         should be used. Defaults to False.
 | 
			
		||||
        :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 use_ssl: Indicates if the older SSL connection method
 | 
			
		||||
                        should be used. Defaults to ``False``.
 | 
			
		||||
        """
 | 
			
		||||
        self.session_started_event.clear()
 | 
			
		||||
        if not address:
 | 
			
		||||
 | 
			
		||||
        # If an address was provided, disable using DNS SRV lookup;
 | 
			
		||||
        # otherwise, use the domain from the client JID with the standard
 | 
			
		||||
        # XMPP client port and allow SRV lookup.
 | 
			
		||||
        if address:
 | 
			
		||||
            self.dns_service = None
 | 
			
		||||
        else:
 | 
			
		||||
            address = (self.boundjid.host, 5222)
 | 
			
		||||
            self.dns_service = 'xmpp-client'
 | 
			
		||||
 | 
			
		||||
        self._expected_server_name = self.boundjid.host
 | 
			
		||||
 | 
			
		||||
        return XMLStream.connect(self, address[0], address[1],
 | 
			
		||||
                                 use_tls=use_tls, use_ssl=use_ssl,
 | 
			
		||||
                                 reattempt=reattempt)
 | 
			
		||||
 | 
			
		||||
    def get_dns_records(self, domain, port=None):
 | 
			
		||||
        """
 | 
			
		||||
        Get the DNS records for a domain.
 | 
			
		||||
        Overriddes XMLStream.get_dns_records to use SRV.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            domain -- The domain in question.
 | 
			
		||||
            port   -- If the results don't include a port, use this one.
 | 
			
		||||
        """
 | 
			
		||||
        if port is None:
 | 
			
		||||
            port = self.default_port
 | 
			
		||||
        if DNSPYTHON:
 | 
			
		||||
            try:
 | 
			
		||||
                record = "_xmpp-client._tcp.%s" % domain
 | 
			
		||||
                answers = []
 | 
			
		||||
                for answer in dns.resolver.query(record, dns.rdatatype.SRV):
 | 
			
		||||
                    address = (answer.target.to_text()[:-1], answer.port)
 | 
			
		||||
                    answers.append((address, answer.priority, answer.weight))
 | 
			
		||||
            except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
 | 
			
		||||
                log.warning("No SRV records for %s" % domain)
 | 
			
		||||
                answers = super(ClientXMPP, self).get_dns_records(domain, port)
 | 
			
		||||
            except dns.exception.Timeout:
 | 
			
		||||
                log.warning("DNS resolution timed out " + \
 | 
			
		||||
                            "for SRV record of %s" % domain)
 | 
			
		||||
                answers = super(ClientXMPP, self).get_dns_records(domain, port)
 | 
			
		||||
            return answers
 | 
			
		||||
        else:
 | 
			
		||||
            log.warning("dnspython is not installed -- " + \
 | 
			
		||||
                        "relying on OS A record resolution")
 | 
			
		||||
            return [((domain, port), 0, 0)]
 | 
			
		||||
 | 
			
		||||
    def register_feature(self, name, handler, restart=False, order=5000):
 | 
			
		||||
        """
 | 
			
		||||
        Register a stream feature.
 | 
			
		||||
        """Register a stream feature handler.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            name    -- The name of the stream feature.
 | 
			
		||||
            handler -- The function to execute if the feature is received.
 | 
			
		||||
            restart -- Indicates if feature processing should halt with
 | 
			
		||||
                       this feature. Defaults to False.
 | 
			
		||||
            order   -- The relative ordering in which the feature should
 | 
			
		||||
                       be negotiated. Lower values will be attempted
 | 
			
		||||
                       earlier when available.
 | 
			
		||||
        :param name: The name of the stream feature.
 | 
			
		||||
        :param handler: The function to execute if the feature is received.
 | 
			
		||||
        :param restart: Indicates if feature processing should halt with
 | 
			
		||||
                        this feature. Defaults to ``False``.
 | 
			
		||||
        :param order: The relative ordering in which the feature should
 | 
			
		||||
                      be negotiated. Lower values will be attempted
 | 
			
		||||
                      earlier when available.
 | 
			
		||||
        """
 | 
			
		||||
        self._stream_feature_handlers[name] = (handler, restart)
 | 
			
		||||
        self._stream_feature_order.append((order, name))
 | 
			
		||||
@@ -195,61 +172,67 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
 | 
			
		||||
    def update_roster(self, jid, name=None, subscription=None, groups=[],
 | 
			
		||||
                            block=True, timeout=None, callback=None):
 | 
			
		||||
        """
 | 
			
		||||
        Add or change a roster item.
 | 
			
		||||
        """Add or change a roster item.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid          -- The JID of the entry to modify.
 | 
			
		||||
            name         -- The user's nickname for this JID.
 | 
			
		||||
            subscription -- The subscription status. May be one of
 | 
			
		||||
                            'to', 'from', 'both', or 'none'. If set
 | 
			
		||||
                            to 'remove', the entry will be deleted.
 | 
			
		||||
            groups       -- The roster groups that contain this item.
 | 
			
		||||
            block        -- Specify if the roster request will block
 | 
			
		||||
                            until a response is received, or a timeout
 | 
			
		||||
                            occurs. Defaults to True.
 | 
			
		||||
            timeout      -- The length of time (in seconds) to wait
 | 
			
		||||
                            for a response before continuing if blocking
 | 
			
		||||
                            is used. Defaults to self.response_timeout.
 | 
			
		||||
            callback     -- Optional reference to a stream handler function.
 | 
			
		||||
                            Will be executed when the roster is received.
 | 
			
		||||
                            Implies block=False.
 | 
			
		||||
        :param jid: The JID of the entry to modify.
 | 
			
		||||
        :param name: The user's nickname for this JID.
 | 
			
		||||
        :param subscription: The subscription status. May be one of
 | 
			
		||||
                             ``'to'``, ``'from'``, ``'both'``, or
 | 
			
		||||
                             ``'none'``. If set to ``'remove'``,
 | 
			
		||||
                             the entry will be deleted.
 | 
			
		||||
        :param groups: The roster groups that contain this item.
 | 
			
		||||
        :param block: Specify if the roster request will block
 | 
			
		||||
                      until a response is received, or a timeout
 | 
			
		||||
                      occurs. Defaults to ``True``.
 | 
			
		||||
        :param timeout: The length of time (in seconds) to wait
 | 
			
		||||
                        for a response before continuing if blocking
 | 
			
		||||
                        is used. Defaults to 
 | 
			
		||||
            :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
 | 
			
		||||
        :param callback: Optional reference to a stream handler function.
 | 
			
		||||
                         Will be executed when the roster is received.
 | 
			
		||||
                         Implies ``block=False``.
 | 
			
		||||
        """
 | 
			
		||||
        return self.client_roster.update(jid, name, subscription, groups,
 | 
			
		||||
                                         block, timeout, callback)
 | 
			
		||||
 | 
			
		||||
    def del_roster_item(self, jid):
 | 
			
		||||
        """
 | 
			
		||||
        Remove an item from the roster by setting its subscription
 | 
			
		||||
        status to 'remove'.
 | 
			
		||||
        """Remove an item from the roster.
 | 
			
		||||
        
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid -- The JID of the item to remove.
 | 
			
		||||
        This is done by setting its subscription status to ``'remove'``.
 | 
			
		||||
 | 
			
		||||
        :param jid: The JID of the item to remove.
 | 
			
		||||
        """
 | 
			
		||||
        return self.client_roster.remove(jid)
 | 
			
		||||
 | 
			
		||||
    def get_roster(self, block=True, timeout=None, callback=None):
 | 
			
		||||
        """
 | 
			
		||||
        Request the roster from the server.
 | 
			
		||||
        """Request the roster from the server.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            block    -- Specify if the roster request will block until a
 | 
			
		||||
                        response is received, or a timeout occurs.
 | 
			
		||||
                        Defaults to True.
 | 
			
		||||
            timeout  -- The length of time (in seconds) to wait for a response
 | 
			
		||||
        :param block: Specify if the roster request will block until a
 | 
			
		||||
                      response is received, or a timeout occurs.
 | 
			
		||||
                      Defaults to ``True``.
 | 
			
		||||
        :param timeout: The length of time (in seconds) to wait for a response
 | 
			
		||||
                        before continuing if blocking is used.
 | 
			
		||||
                        Defaults to self.response_timeout.
 | 
			
		||||
            callback -- Optional reference to a stream handler function. Will
 | 
			
		||||
                        be executed when the roster is received.
 | 
			
		||||
                        Implies block=False.
 | 
			
		||||
                        Defaults to 
 | 
			
		||||
            :attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
 | 
			
		||||
        :param callback: Optional reference to a stream handler function. Will
 | 
			
		||||
                         be executed when the roster is received.
 | 
			
		||||
                         Implies ``block=False``.
 | 
			
		||||
        """
 | 
			
		||||
        iq = self.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq.enable('roster')
 | 
			
		||||
        response = iq.send(block, timeout, callback)
 | 
			
		||||
        if 'rosterver' in self.features:
 | 
			
		||||
            iq['roster']['ver'] = self.client_roster.version
 | 
			
		||||
 | 
			
		||||
        if callback is None:
 | 
			
		||||
            return self._handle_roster(response, request=True)
 | 
			
		||||
        if not block and callback is None:
 | 
			
		||||
            callback = lambda resp: self._handle_roster(resp)
 | 
			
		||||
 | 
			
		||||
        response = iq.send(block, timeout, callback)
 | 
			
		||||
        self.event('roster_received', response)
 | 
			
		||||
 | 
			
		||||
        if block: 
 | 
			
		||||
            self._handle_roster(response)
 | 
			
		||||
            return response
 | 
			
		||||
 | 
			
		||||
    def _handle_connected(self, event=None):
 | 
			
		||||
        #TODO: Use stream state here
 | 
			
		||||
@@ -260,11 +243,9 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        self.features = set()
 | 
			
		||||
 | 
			
		||||
    def _handle_stream_features(self, features):
 | 
			
		||||
        """
 | 
			
		||||
        Process the received stream features.
 | 
			
		||||
        """Process the received stream features.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            features -- The features stanza.
 | 
			
		||||
        :param features: The features stanza.
 | 
			
		||||
        """
 | 
			
		||||
        for order, name in self._stream_feature_order:
 | 
			
		||||
            if name in features['features']:
 | 
			
		||||
@@ -274,32 +255,43 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                    # restarting the XML stream.
 | 
			
		||||
                    return True
 | 
			
		||||
 | 
			
		||||
    def _handle_roster(self, iq, request=False):
 | 
			
		||||
        """
 | 
			
		||||
        Update the roster after receiving a roster stanza.
 | 
			
		||||
    def _handle_roster(self, iq):
 | 
			
		||||
        """Update the roster after receiving a roster stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            iq      -- The roster stanza.
 | 
			
		||||
            request -- Indicates if this stanza is a response
 | 
			
		||||
                       to a request for the roster.
 | 
			
		||||
        :param iq: The roster stanza.
 | 
			
		||||
        """
 | 
			
		||||
        if iq['type'] == 'set' or (iq['type'] == 'result' and request):
 | 
			
		||||
            for jid in iq['roster']['items']:
 | 
			
		||||
                item = iq['roster']['items'][jid]
 | 
			
		||||
                roster = self.roster[iq['to'].bare]
 | 
			
		||||
                roster[jid]['name'] = item['name']
 | 
			
		||||
                roster[jid]['groups'] = item['groups']
 | 
			
		||||
                roster[jid]['from'] = item['subscription'] in ['from', 'both']
 | 
			
		||||
                roster[jid]['to'] = item['subscription'] in ['to', 'both']
 | 
			
		||||
                roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
 | 
			
		||||
            self.event('roster_received', iq)
 | 
			
		||||
        if iq['type'] == 'set':
 | 
			
		||||
            if iq['from'].bare and iq['from'].bare != self.boundjid.bare:
 | 
			
		||||
                raise XMPPError(condition='service-unavailable')
 | 
			
		||||
 | 
			
		||||
        roster = self.client_roster
 | 
			
		||||
        if iq['roster']['ver']:
 | 
			
		||||
            roster.version = iq['roster']['ver']
 | 
			
		||||
        for jid in iq['roster']['items']:
 | 
			
		||||
            item = iq['roster']['items'][jid]
 | 
			
		||||
            roster[jid]['name'] = item['name']
 | 
			
		||||
            roster[jid]['groups'] = item['groups']
 | 
			
		||||
            roster[jid]['from'] = item['subscription'] in ['from', 'both']
 | 
			
		||||
            roster[jid]['to'] = item['subscription'] in ['to', 'both']
 | 
			
		||||
            roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
 | 
			
		||||
 | 
			
		||||
            roster[jid].save(remove=(item['subscription'] == 'remove'))
 | 
			
		||||
                 
 | 
			
		||||
        self.event("roster_update", iq)
 | 
			
		||||
        if iq['type'] == 'set':
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            iq.enable('roster')
 | 
			
		||||
            iq.send()
 | 
			
		||||
        return True
 | 
			
		||||
            resp = self.Iq(stype='result',
 | 
			
		||||
                           sto=iq['from'],
 | 
			
		||||
                           sid=iq['id'])
 | 
			
		||||
            resp.enable('roster')
 | 
			
		||||
            resp.send()
 | 
			
		||||
 | 
			
		||||
    def _handle_session_bind(self, jid):
 | 
			
		||||
        """Set the client roster to the JID set by the server.
 | 
			
		||||
 | 
			
		||||
        :param :class:`sleekxmpp.xmlstream.jid.JID` jid: The bound JID as
 | 
			
		||||
            dictated by the server. The same as :attr:`boundjid`.
 | 
			
		||||
        """
 | 
			
		||||
        self.client_roster = self.roster[jid]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# To comply with PEP8, method names now use underscores.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,28 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
    sleekxmpp.clientxmpp
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
    This module provides XMPP functionality that
 | 
			
		||||
    is specific to external server component connections.
 | 
			
		||||
 | 
			
		||||
    Part of SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
 | 
			
		||||
    :copyright: (c) 2011 Nathanael C. Fritz
 | 
			
		||||
    :license: MIT, see LICENSE for more details
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import absolute_import
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import base64
 | 
			
		||||
import sys
 | 
			
		||||
import hashlib
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import plugins
 | 
			
		||||
from sleekxmpp import stanza
 | 
			
		||||
from sleekxmpp.basexmpp import BaseXMPP
 | 
			
		||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.xmlstream import XMLStream
 | 
			
		||||
from sleekxmpp.xmlstream import ET
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatchXPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
@@ -32,28 +35,22 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
 | 
			
		||||
    Use only for good, not for evil.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        connect              -- Overrides XMLStream.connect.
 | 
			
		||||
        incoming_filter      -- Overrides XMLStream.incoming_filter.
 | 
			
		||||
        start_stream_handler -- Overrides XMLStream.start_stream_handler.
 | 
			
		||||
    :param jid: The JID of the component.
 | 
			
		||||
    :param secret: The secret or password for the component.
 | 
			
		||||
    :param host: The server accepting the component.
 | 
			
		||||
    :param port: The port used to connect to the server.
 | 
			
		||||
    :param plugin_config: A dictionary of plugin configurations.
 | 
			
		||||
    :param plugin_whitelist: A list of approved plugins that 
 | 
			
		||||
                    will be loaded when calling 
 | 
			
		||||
                    :meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
 | 
			
		||||
    :param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
 | 
			
		||||
                      should be used instead of the standard
 | 
			
		||||
                      ``'jabber:component:accept'`` namespace.
 | 
			
		||||
                      Defaults to ``False``.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, secret, host, port,
 | 
			
		||||
    def __init__(self, jid, secret, host=None, port=None,
 | 
			
		||||
                 plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
 | 
			
		||||
        """
 | 
			
		||||
        Arguments:
 | 
			
		||||
            jid              -- The JID of the component.
 | 
			
		||||
            secret           -- The secret or password for the component.
 | 
			
		||||
            host             -- The server accepting the component.
 | 
			
		||||
            port             -- The port used to connect to the server.
 | 
			
		||||
            plugin_config    -- A dictionary of plugin configurations.
 | 
			
		||||
            plugin_whitelist -- A list of desired plugins to load
 | 
			
		||||
                                when using register_plugins.
 | 
			
		||||
            use_js_ns        -- Indicates if the 'jabber:client' namespace
 | 
			
		||||
                                should be used instead of the standard
 | 
			
		||||
                                'jabber:component:accept' namespace.
 | 
			
		||||
                                Defaults to False.
 | 
			
		||||
        """
 | 
			
		||||
        if use_jc_ns:
 | 
			
		||||
            default_ns = 'jabber:client'
 | 
			
		||||
        else:
 | 
			
		||||
@@ -81,26 +78,48 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        self.add_event_handler('presence_probe',
 | 
			
		||||
                               self._handle_probe)
 | 
			
		||||
 | 
			
		||||
    def connect(self):
 | 
			
		||||
        """
 | 
			
		||||
        Connect to the server.
 | 
			
		||||
    def connect(self, host=None, port=None, use_ssl=False, 
 | 
			
		||||
                      use_tls=False, reattempt=True):
 | 
			
		||||
        """Connect to the server.
 | 
			
		||||
 | 
			
		||||
        Overrides XMLStream.connect.
 | 
			
		||||
        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`.
 | 
			
		||||
        :param port: Port to connect to on the server.
 | 
			
		||||
                     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.
 | 
			
		||||
        """
 | 
			
		||||
        log.debug("Connecting to %s:%s" % (self.server_host,
 | 
			
		||||
                                               self.server_port))
 | 
			
		||||
        return XMLStream.connect(self, self.server_host,
 | 
			
		||||
                                       self.server_port)
 | 
			
		||||
        if host is None:
 | 
			
		||||
            host = self.server_host
 | 
			
		||||
        if port is None:
 | 
			
		||||
            port = self.server_port
 | 
			
		||||
 | 
			
		||||
        self.server_name = self.boundjid.host
 | 
			
		||||
 | 
			
		||||
        if use_tls:
 | 
			
		||||
            log.info("XEP-0114 components can not use TLS")
 | 
			
		||||
 | 
			
		||||
        log.debug("Connecting to %s:%s", host, port)
 | 
			
		||||
        return XMLStream.connect(self, host=host, port=port,
 | 
			
		||||
                                       use_ssl=use_ssl,
 | 
			
		||||
                                       use_tls=False,
 | 
			
		||||
                                       reattempt=reattempt)
 | 
			
		||||
 | 
			
		||||
    def incoming_filter(self, xml):
 | 
			
		||||
        """
 | 
			
		||||
        Pre-process incoming XML stanzas by converting any 'jabber:client'
 | 
			
		||||
        namespaced elements to the component's default namespace.
 | 
			
		||||
        Pre-process incoming XML stanzas by converting any
 | 
			
		||||
        ``'jabber:client'`` namespaced elements to the component's
 | 
			
		||||
        default namespace.
 | 
			
		||||
 | 
			
		||||
        Overrides XMLStream.incoming_filter.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The XML stanza to pre-process.
 | 
			
		||||
        :param xml: The XML stanza to pre-process.
 | 
			
		||||
        """
 | 
			
		||||
        if xml.tag.startswith('{jabber:client}'):
 | 
			
		||||
            xml.tag = xml.tag.replace('jabber:client', self.default_ns)
 | 
			
		||||
@@ -117,10 +136,7 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        Once the streams are established, attempt to handshake
 | 
			
		||||
        with the server to be accepted as a component.
 | 
			
		||||
 | 
			
		||||
        Overrides BaseXMPP.start_stream_handler.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The incoming stream's root element.
 | 
			
		||||
        :param xml: The incoming stream's root element.
 | 
			
		||||
        """
 | 
			
		||||
        BaseXMPP.start_stream_handler(self, xml)
 | 
			
		||||
 | 
			
		||||
@@ -136,11 +152,9 @@ class ComponentXMPP(BaseXMPP):
 | 
			
		||||
        self.send_xml(handshake, now=True)
 | 
			
		||||
 | 
			
		||||
    def _handle_handshake(self, xml):
 | 
			
		||||
        """
 | 
			
		||||
        The handshake has been accepted.
 | 
			
		||||
        """The handshake has been accepted.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xml -- The reply handshake stanza.
 | 
			
		||||
        :param xml: The reply handshake stanza.
 | 
			
		||||
        """
 | 
			
		||||
        self.session_started_event.set()
 | 
			
		||||
        self.event("session_start")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
    sleekxmpp.exceptions
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
    Part of SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
 | 
			
		||||
    :copyright: (c) 2011 Nathanael C. Fritz
 | 
			
		||||
    :license: MIT, see LICENSE for more details
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -13,37 +16,35 @@ class XMPPError(Exception):
 | 
			
		||||
    A generic exception that may be raised while processing an XMPP stanza
 | 
			
		||||
    to indicate that an error response stanza should be sent.
 | 
			
		||||
 | 
			
		||||
    The exception method for stanza objects extending RootStanza will create
 | 
			
		||||
    an error stanza and initialize any additional substanzas using the
 | 
			
		||||
    extension information included in the exception.
 | 
			
		||||
    The exception method for stanza objects extending
 | 
			
		||||
    :class:`~sleekxmpp.stanza.rootstanza.RootStanza` will create an error
 | 
			
		||||
    stanza and initialize any additional substanzas using the extension
 | 
			
		||||
    information included in the exception.
 | 
			
		||||
 | 
			
		||||
    Meant for use in SleekXMPP plugins and applications using SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    Extension information can be included to add additional XML elements
 | 
			
		||||
    to the generated error stanza.
 | 
			
		||||
 | 
			
		||||
    :param condition: The XMPP defined error condition.
 | 
			
		||||
                      Defaults to ``'undefined-condition'``.
 | 
			
		||||
    :param text: Human readable text describing the error.
 | 
			
		||||
    :param etype: The XMPP error type, such as ``'cancel'`` or ``'modify'``.
 | 
			
		||||
                  Defaults to ``'cancel'``.
 | 
			
		||||
    :param extension: Tag name of the extension's XML content.
 | 
			
		||||
    :param extension_ns: XML namespace of the extensions' XML content.
 | 
			
		||||
    :param extension_args: Content and attributes for the extension
 | 
			
		||||
                           element. Same as the additional arguments to
 | 
			
		||||
                           the :class:`~xml.etree.ElementTree.Element`
 | 
			
		||||
                           constructor.
 | 
			
		||||
    :param clear: Indicates if the stanza's contents should be
 | 
			
		||||
                  removed before replying with an error.
 | 
			
		||||
                  Defaults to ``True``.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, condition='undefined-condition', text=None,
 | 
			
		||||
                etype='cancel', extension=None, extension_ns=None,
 | 
			
		||||
                extension_args=None, clear=True):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new XMPPError exception.
 | 
			
		||||
 | 
			
		||||
        Extension information can be included to add additional XML elements
 | 
			
		||||
        to the generated error stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            condition      -- The XMPP defined error condition.
 | 
			
		||||
                              Defaults to 'undefined-condition'.
 | 
			
		||||
            text           -- Human readable text describing the error.
 | 
			
		||||
            etype          -- The XMPP error type, such as cancel or modify.
 | 
			
		||||
                              Defaults to 'cancel'.
 | 
			
		||||
            extension      -- Tag name of the extension's XML content.
 | 
			
		||||
            extension_ns   -- XML namespace of the extensions' XML content.
 | 
			
		||||
            extension_args -- Content and attributes for the extension
 | 
			
		||||
                              element. Same as the additional arguments to
 | 
			
		||||
                              the ET.Element constructor.
 | 
			
		||||
            clear          -- Indicates if the stanza's contents should be
 | 
			
		||||
                              removed before replying with an error.
 | 
			
		||||
                              Defaults to True.
 | 
			
		||||
        """
 | 
			
		||||
        if extension_args is None:
 | 
			
		||||
            extension_args = {}
 | 
			
		||||
 | 
			
		||||
@@ -68,6 +69,8 @@ class IqTimeout(XMPPError):
 | 
			
		||||
                condition='remote-server-timeout',
 | 
			
		||||
                etype='cancel')
 | 
			
		||||
 | 
			
		||||
        #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response 
 | 
			
		||||
        #: did not arrive before the timeout expired.
 | 
			
		||||
        self.iq = iq
 | 
			
		||||
 | 
			
		||||
class IqError(XMPPError):
 | 
			
		||||
@@ -83,4 +86,5 @@ class IqError(XMPPError):
 | 
			
		||||
                text=iq['error']['text'],
 | 
			
		||||
                etype=iq['error']['type'])
 | 
			
		||||
 | 
			
		||||
        #: The :class:`~sleekxmpp.stanza.iq.Iq` error result stanza.
 | 
			
		||||
        self.iq = iq
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,10 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
__all__ = ['feature_starttls', 'feature_mechanisms', 'feature_bind']
 | 
			
		||||
__all__ = [
 | 
			
		||||
    'feature_starttls', 
 | 
			
		||||
    'feature_mechanisms', 
 | 
			
		||||
    'feature_bind', 
 | 
			
		||||
    'feature_session',
 | 
			
		||||
    'feature_rosterver'
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -6,5 +6,14 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_bind.bind import feature_bind
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_bind.bind import FeatureBind
 | 
			
		||||
from sleekxmpp.features.feature_bind.stanza import Bind
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(FeatureBind)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
feature_bind = FeatureBind
 | 
			
		||||
 
 | 
			
		||||
@@ -11,22 +11,20 @@ import logging
 | 
			
		||||
from sleekxmpp.stanza import Iq, StreamFeatures
 | 
			
		||||
from sleekxmpp.features.feature_bind import stanza
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins import BasePlugin, register_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class feature_bind(base_plugin):
 | 
			
		||||
class FeatureBind(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'feature_bind'
 | 
			
		||||
    description = 'RFC 6120: Stream Feature: Resource Binding'
 | 
			
		||||
    dependencies = set()
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.name = 'Bind Resource'
 | 
			
		||||
        self.rfc = '6120'
 | 
			
		||||
        self.description = 'Resource Binding Stream Feature'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_feature('bind',
 | 
			
		||||
                self._handle_bind_resource,
 | 
			
		||||
                restart=False,
 | 
			
		||||
@@ -42,7 +40,7 @@ class feature_bind(base_plugin):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            features -- The stream features stanza.
 | 
			
		||||
        """
 | 
			
		||||
        log.debug("Requesting resource: %s" % self.xmpp.boundjid.resource)
 | 
			
		||||
        log.debug("Requesting resource: %s", self.xmpp.boundjid.resource)
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq.enable('bind')
 | 
			
		||||
@@ -52,10 +50,11 @@ class feature_bind(base_plugin):
 | 
			
		||||
 | 
			
		||||
        self.xmpp.set_jid(response['bind']['jid'])
 | 
			
		||||
        self.xmpp.bound = True
 | 
			
		||||
        self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.features.add('bind')
 | 
			
		||||
 | 
			
		||||
        log.info("Node set to: %s" % self.xmpp.boundjid.full)
 | 
			
		||||
        log.info("Node set to: %s", self.xmpp.boundjid.full)
 | 
			
		||||
 | 
			
		||||
        if 'session' not in features['features']:
 | 
			
		||||
            log.debug("Established Session")
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Iq, StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Bind(ElementBase):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,17 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.mechanisms import feature_mechanisms
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.mechanisms import FeatureMechanisms
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza import Mechanisms
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza import Auth
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza import Success
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza import Failure
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(FeatureMechanisms)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
feature_mechanisms = FeatureMechanisms
 | 
			
		||||
 
 | 
			
		||||
@@ -9,36 +9,48 @@
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty import suelta
 | 
			
		||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError
 | 
			
		||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLPrepFailure
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins import BasePlugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatchXPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class feature_mechanisms(base_plugin):
 | 
			
		||||
class FeatureMechanisms(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'feature_mechanisms'
 | 
			
		||||
    description = 'RFC 6120: Stream Feature: SASL'
 | 
			
		||||
    dependencies = set()
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.name = 'SASL Mechanisms'
 | 
			
		||||
        self.rfc = '6120'
 | 
			
		||||
        self.description = "SASL Stream Feature"
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.use_mech = self.config.get('use_mech', None)
 | 
			
		||||
 | 
			
		||||
        if not self.use_mech and not self.xmpp.boundjid.user:
 | 
			
		||||
            self.use_mech = 'ANONYMOUS'
 | 
			
		||||
 | 
			
		||||
        def tls_active():
 | 
			
		||||
            return 'starttls' in self.xmpp.features
 | 
			
		||||
 | 
			
		||||
        def basic_callback(mech, values):
 | 
			
		||||
            if 'username' in values:
 | 
			
		||||
                values['username'] = self.xmpp.boundjid.user
 | 
			
		||||
            if 'password' in values:
 | 
			
		||||
                values['password'] = self.xmpp.password
 | 
			
		||||
            creds = self.xmpp.credentials
 | 
			
		||||
            for value in values:
 | 
			
		||||
                if value == 'username':
 | 
			
		||||
                    values['username'] = self.xmpp.boundjid.user
 | 
			
		||||
                elif value == 'password':
 | 
			
		||||
                    values['password'] = creds['password']
 | 
			
		||||
                elif value == 'email':
 | 
			
		||||
                    jid = self.xmpp.boundjid.bare
 | 
			
		||||
                    values['email'] = creds.get('email', jid)
 | 
			
		||||
                elif value in creds:
 | 
			
		||||
                    values[value] = creds[value]
 | 
			
		||||
            mech.fulfill(values)
 | 
			
		||||
 | 
			
		||||
        sasl_callback = self.config.get('sasl_callback', None)
 | 
			
		||||
@@ -53,6 +65,9 @@ class feature_mechanisms(base_plugin):
 | 
			
		||||
                                tls_active=tls_active,
 | 
			
		||||
                                mech=self.use_mech)
 | 
			
		||||
 | 
			
		||||
        self.mech_list = set()
 | 
			
		||||
        self.attempted_mechs = set()
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(StreamFeatures, stanza.Mechanisms)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_stanza(stanza.Success)
 | 
			
		||||
@@ -60,19 +75,18 @@ class feature_mechanisms(base_plugin):
 | 
			
		||||
        self.xmpp.register_stanza(stanza.Auth)
 | 
			
		||||
        self.xmpp.register_stanza(stanza.Challenge)
 | 
			
		||||
        self.xmpp.register_stanza(stanza.Response)
 | 
			
		||||
        self.xmpp.register_stanza(stanza.Abort)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('SASL Success',
 | 
			
		||||
                         MatchXPath(stanza.Success.tag_name()),
 | 
			
		||||
                         self._handle_success,
 | 
			
		||||
                         instream=True,
 | 
			
		||||
                         once=True))
 | 
			
		||||
                         instream=True))
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('SASL Failure',
 | 
			
		||||
                         MatchXPath(stanza.Failure.tag_name()),
 | 
			
		||||
                         self._handle_fail,
 | 
			
		||||
                         instream=True,
 | 
			
		||||
                         once=True))
 | 
			
		||||
                         instream=True))
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('SASL Challenge',
 | 
			
		||||
                         MatchXPath(stanza.Challenge.tag_name()),
 | 
			
		||||
@@ -95,35 +109,63 @@ class feature_mechanisms(base_plugin):
 | 
			
		||||
            # server has incorrectly offered it again.
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        mech_list = features['mechanisms']
 | 
			
		||||
        if not self.use_mech:
 | 
			
		||||
            self.mech_list = set(features['mechanisms'])
 | 
			
		||||
        else:
 | 
			
		||||
            self.mech_list = set([self.use_mech])
 | 
			
		||||
        return self._send_auth()
 | 
			
		||||
 | 
			
		||||
    def _send_auth(self):
 | 
			
		||||
        mech_list = self.mech_list - self.attempted_mechs
 | 
			
		||||
        self.mech = self.sasl.choose_mechanism(mech_list)
 | 
			
		||||
 | 
			
		||||
        if self.mech is not None:
 | 
			
		||||
        if mech_list and self.mech is not None:
 | 
			
		||||
            resp = stanza.Auth(self.xmpp)
 | 
			
		||||
            resp['mechanism'] = self.mech.name
 | 
			
		||||
            resp['value'] = self.mech.process()
 | 
			
		||||
            resp.send(now=True)
 | 
			
		||||
            try:
 | 
			
		||||
                resp['value'] = self.mech.process()
 | 
			
		||||
            except SASLCancelled:
 | 
			
		||||
                self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
                self._send_auth()
 | 
			
		||||
            except SASLError:
 | 
			
		||||
                self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
                self._send_auth()
 | 
			
		||||
            except SASLPrepFailure:
 | 
			
		||||
                log.exception("A credential value did not pass SASLprep.")
 | 
			
		||||
                self.xmpp.disconnect()
 | 
			
		||||
            else:
 | 
			
		||||
                resp.send(now=True)
 | 
			
		||||
        else:
 | 
			
		||||
            log.error("No appropriate login method.")
 | 
			
		||||
            self.xmpp.event("no_auth", direct=True)
 | 
			
		||||
            self.attempted_mechs = set()
 | 
			
		||||
            self.xmpp.disconnect()
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def _handle_challenge(self, stanza):
 | 
			
		||||
        """SASL challenge received. Process and send response."""
 | 
			
		||||
        resp = self.stanza.Response(self.xmpp)
 | 
			
		||||
        resp['value'] = self.mech.process(stanza['value'])
 | 
			
		||||
        resp.send(now=True)
 | 
			
		||||
        try:
 | 
			
		||||
            resp['value'] = self.mech.process(stanza['value'])
 | 
			
		||||
        except SASLCancelled:
 | 
			
		||||
            self.stanza.Abort(self.xmpp).send()
 | 
			
		||||
        except SASLError:
 | 
			
		||||
            self.stanza.Abort(self.xmpp).send()
 | 
			
		||||
        else:
 | 
			
		||||
            resp.send(now=True)
 | 
			
		||||
 | 
			
		||||
    def _handle_success(self, stanza):
 | 
			
		||||
        """SASL authentication succeeded. Restart the stream."""
 | 
			
		||||
        self.attempted_mechs = set()
 | 
			
		||||
        self.xmpp.authenticated = True
 | 
			
		||||
        self.xmpp.features.add('mechanisms')
 | 
			
		||||
        self.xmpp.event('auth_success', stanza, direct=True)
 | 
			
		||||
        raise RestartStream()
 | 
			
		||||
 | 
			
		||||
    def _handle_fail(self, stanza):
 | 
			
		||||
        """SASL authentication failed. Disconnect and shutdown."""
 | 
			
		||||
        log.info("Authentication failed: %s" % stanza['condition'])
 | 
			
		||||
        self.attempted_mechs.add(self.mech.name)
 | 
			
		||||
        log.info("Authentication failed: %s", stanza['condition'])
 | 
			
		||||
        self.xmpp.event("failed_auth", stanza, direct=True)
 | 
			
		||||
        self.xmpp.disconnect()
 | 
			
		||||
        self._send_auth()
 | 
			
		||||
        return True
 | 
			
		||||
 
 | 
			
		||||
@@ -13,3 +13,4 @@ from sleekxmpp.features.feature_mechanisms.stanza.success import Success
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza.failure import Failure
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza.challenge import Challenge
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza.response import Response
 | 
			
		||||
from sleekxmpp.features.feature_mechanisms.stanza.abort import Abort
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								sleekxmpp/features/feature_mechanisms/stanza/abort.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								sleekxmpp/features/feature_mechanisms/stanza/abort.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2011  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Abort(StanzaBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'abort'
 | 
			
		||||
    namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
 | 
			
		||||
    interfaces = set()
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml):
 | 
			
		||||
        StanzaBase.setup(self, xml)
 | 
			
		||||
        self.xml.tag = self.tag_name()
 | 
			
		||||
@@ -10,9 +10,7 @@ import base64
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty.suelta.util import bytes
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Auth(StanzaBase):
 | 
			
		||||
@@ -25,15 +23,28 @@ class Auth(StanzaBase):
 | 
			
		||||
    interfaces = set(('mechanism', 'value'))
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
 | 
			
		||||
    #: Some SASL mechs require sending values as is, 
 | 
			
		||||
    #: without converting base64.
 | 
			
		||||
    plain_mechs = set(['X-MESSENGER-OAUTH2'])
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml):
 | 
			
		||||
        StanzaBase.setup(self, xml)
 | 
			
		||||
        self.xml.tag = self.tag_name()
 | 
			
		||||
 | 
			
		||||
    def get_value(self):
 | 
			
		||||
        return base64.b64decode(bytes(self.xml.text))
 | 
			
		||||
        if not self['mechanism'] in self.plain_mechs:
 | 
			
		||||
            return base64.b64decode(bytes(self.xml.text))
 | 
			
		||||
        else:
 | 
			
		||||
            return self.xml.text
 | 
			
		||||
 | 
			
		||||
    def set_value(self, values):
 | 
			
		||||
        self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
 | 
			
		||||
        if not self['mechanism'] in self.plain_mechs:
 | 
			
		||||
            if values:
 | 
			
		||||
                self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
 | 
			
		||||
            else:
 | 
			
		||||
                self.xml.text = '='
 | 
			
		||||
        else:
 | 
			
		||||
            self.xml.text = bytes(values).decode('utf-8')
 | 
			
		||||
 | 
			
		||||
    def del_value(self):
 | 
			
		||||
        self.xml.text = ''
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,7 @@ import base64
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty.suelta.util import bytes
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Challenge(StanzaBase):
 | 
			
		||||
@@ -33,7 +31,10 @@ class Challenge(StanzaBase):
 | 
			
		||||
        return base64.b64decode(bytes(self.xml.text))
 | 
			
		||||
 | 
			
		||||
    def set_value(self, values):
 | 
			
		||||
        self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
 | 
			
		||||
        if values:
 | 
			
		||||
            self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
 | 
			
		||||
        else:
 | 
			
		||||
            self.xml.text = '='
 | 
			
		||||
 | 
			
		||||
    def del_value(self):
 | 
			
		||||
        self.xml.text = ''
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Failure(StanzaBase):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Mechanisms(ElementBase):
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,7 @@ import base64
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty.suelta.util import bytes
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Response(StanzaBase):
 | 
			
		||||
@@ -33,7 +31,10 @@ class Response(StanzaBase):
 | 
			
		||||
        return base64.b64decode(bytes(self.xml.text))
 | 
			
		||||
 | 
			
		||||
    def set_value(self, values):
 | 
			
		||||
        self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
 | 
			
		||||
        if values:
 | 
			
		||||
            self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
 | 
			
		||||
        else:
 | 
			
		||||
            self.xml.text = '='
 | 
			
		||||
 | 
			
		||||
    def del_value(self):
 | 
			
		||||
        self.xml.text = ''
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, StanzaBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Success(StanzaBase):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								sleekxmpp/features/feature_rosterver/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								sleekxmpp/features/feature_rosterver/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2012  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_rosterver.rosterver import FeatureRosterVer
 | 
			
		||||
from sleekxmpp.features.feature_rosterver.stanza import RosterVer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(FeatureRosterVer)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
feature_rosterver = FeatureRosterVer
 | 
			
		||||
							
								
								
									
										42
									
								
								sleekxmpp/features/feature_rosterver/rosterver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								sleekxmpp/features/feature_rosterver/rosterver.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2012  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Iq, StreamFeatures
 | 
			
		||||
from sleekxmpp.features.feature_rosterver import stanza
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.plugins.base import BasePlugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FeatureRosterVer(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'feature_rosterver'
 | 
			
		||||
    description = 'RFC 6121: Stream Feature: Roster Versioning'
 | 
			
		||||
    dependences = set()
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xmpp.register_feature('rosterver',
 | 
			
		||||
                self._handle_rosterver,
 | 
			
		||||
                restart=False,
 | 
			
		||||
                order=9000)
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(StreamFeatures, stanza.RosterVer)
 | 
			
		||||
 | 
			
		||||
    def _handle_rosterver(self, features):
 | 
			
		||||
        """Enable using roster versioning.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            features -- The stream features stanza.
 | 
			
		||||
        """
 | 
			
		||||
        log.debug("Enabling roster versioning.")
 | 
			
		||||
        self.xmpp.features.add('rosterver')
 | 
			
		||||
							
								
								
									
										17
									
								
								sleekxmpp/features/feature_rosterver/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sleekxmpp/features/feature_rosterver/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2012  Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RosterVer(ElementBase):
 | 
			
		||||
 | 
			
		||||
    name = 'ver'
 | 
			
		||||
    namespace = 'urn:xmpp:features:rosterver'
 | 
			
		||||
    interfaces = set()
 | 
			
		||||
    plugin_attrib = 'rosterver'
 | 
			
		||||
@@ -6,5 +6,14 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_session.session import feature_session
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_session.session import FeatureSession
 | 
			
		||||
from sleekxmpp.features.feature_session.stanza import Session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(FeatureSession)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
feature_session = FeatureSession
 | 
			
		||||
 
 | 
			
		||||
@@ -10,9 +10,7 @@ import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Iq, StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins import BasePlugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_session import stanza
 | 
			
		||||
 | 
			
		||||
@@ -20,14 +18,14 @@ from sleekxmpp.features.feature_session import stanza
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class feature_session(base_plugin):
 | 
			
		||||
class FeatureSession(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'feature_session'
 | 
			
		||||
    description = 'RFC 3920: Stream Feature: Start Session'
 | 
			
		||||
    dependencies = set()
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.name = 'Start Session'
 | 
			
		||||
        self.rfc = '3920'
 | 
			
		||||
        self.description = 'Start Session Stream Feature'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_feature('session',
 | 
			
		||||
                self._handle_start_session,
 | 
			
		||||
                restart=False,
 | 
			
		||||
@@ -46,7 +44,7 @@ class feature_session(base_plugin):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq.enable('session')
 | 
			
		||||
        response = iq.send(now=True)
 | 
			
		||||
        iq.send(now=True)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.features.add('session')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import Iq, StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Session(ElementBase):
 | 
			
		||||
 
 | 
			
		||||
@@ -6,5 +6,14 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_starttls.starttls import feature_starttls
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.features.feature_starttls.starttls import FeatureSTARTTLS
 | 
			
		||||
from sleekxmpp.features.feature_starttls.stanza import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(FeatureSTARTTLS)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
feature_starttls = FeatureSTARTTLS
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import StanzaBase, ElementBase
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class STARTTLS(ElementBase):
 | 
			
		||||
 
 | 
			
		||||
@@ -10,23 +10,23 @@ import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.stanza import StreamFeatures
 | 
			
		||||
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import *
 | 
			
		||||
from sleekxmpp.xmlstream.handler import *
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins import BasePlugin
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatchXPath
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.features.feature_starttls import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class feature_starttls(base_plugin):
 | 
			
		||||
class FeatureSTARTTLS(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'feature_starttls'
 | 
			
		||||
    description = 'RFC 6120: Stream Feature: STARTTLS'
 | 
			
		||||
    dependencies = set()
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.name = "STARTTLS"
 | 
			
		||||
        self.rfc = '6120'
 | 
			
		||||
        self.description = "STARTTLS Stream Feature"
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('STARTTLS Proceed',
 | 
			
		||||
                        MatchXPath(stanza.Proceed.tag_name()),
 | 
			
		||||
@@ -58,8 +58,8 @@ class feature_starttls(base_plugin):
 | 
			
		||||
            self.xmpp.send(features['starttls'], now=True)
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            log.warning("The module tlslite is required to log in" +\
 | 
			
		||||
                            " to some servers, and has not been found.")
 | 
			
		||||
            log.warning("The module tlslite is required to log in" + \
 | 
			
		||||
                        " to some servers, and has not been found.")
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def _handle_starttls_proceed(self, proceed):
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,50 @@
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
__all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033',
 | 
			
		||||
           'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082',
 | 
			
		||||
           'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199',
 | 
			
		||||
           'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify']
 | 
			
		||||
 | 
			
		||||
# Don't automatically load xep_0078
 | 
			
		||||
from sleekxmpp.plugins.base import PluginManager, PluginNotFound, BasePlugin
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin, load_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    # Non-standard
 | 
			
		||||
    'gmail_notify',  # Gmail searching and notifications
 | 
			
		||||
 | 
			
		||||
    # XEPS
 | 
			
		||||
    'xep_0004',  # Data Forms
 | 
			
		||||
    'xep_0009',  # Jabber-RPC
 | 
			
		||||
    'xep_0012',  # Last Activity
 | 
			
		||||
    'xep_0027',  # Current Jabber OpenPGP Usage
 | 
			
		||||
    'xep_0030',  # Service Discovery
 | 
			
		||||
    'xep_0033',  # Extended Stanza Addresses
 | 
			
		||||
    'xep_0045',  # Multi-User Chat (Client)
 | 
			
		||||
    'xep_0047',  # In-Band Bytestreams
 | 
			
		||||
    'xep_0050',  # Ad-hoc Commands
 | 
			
		||||
    'xep_0054',  # vcard-temp
 | 
			
		||||
    'xep_0059',  # Result Set Management
 | 
			
		||||
    'xep_0060',  # Pubsub (Client)
 | 
			
		||||
    'xep_0066',  # Out of Band Data
 | 
			
		||||
    'xep_0077',  # In-Band Registration
 | 
			
		||||
#   'xep_0078',  # Non-SASL auth. Don't automatically load
 | 
			
		||||
    'xep_0080',  # User Location
 | 
			
		||||
    'xep_0082',  # XMPP Date and Time Profiles
 | 
			
		||||
    'xep_0085',  # Chat State Notifications
 | 
			
		||||
    'xep_0086',  # Legacy Error Codes
 | 
			
		||||
    'xep_0092',  # Software Version
 | 
			
		||||
    'xep_0107',  # User Mood
 | 
			
		||||
    'xep_0108',  # User Activity
 | 
			
		||||
    'xep_0115',  # Entity Capabilities
 | 
			
		||||
    'xep_0118',  # User Tune
 | 
			
		||||
    'xep_0128',  # Extended Service Discovery
 | 
			
		||||
    'xep_0153',  # vCard-Based Avatars
 | 
			
		||||
    'xep_0163',  # Personal Eventing Protocol
 | 
			
		||||
    'xep_0172',  # User Nickname
 | 
			
		||||
    'xep_0184',  # Message Receipts
 | 
			
		||||
    'xep_0198',  # Stream Management
 | 
			
		||||
    'xep_0199',  # Ping
 | 
			
		||||
    'xep_0202',  # Entity Time
 | 
			
		||||
    'xep_0203',  # Delayed Delivery
 | 
			
		||||
    'xep_0224',  # Attention
 | 
			
		||||
    'xep_0231',  # Bits of Binary
 | 
			
		||||
    'xep_0249',  # Direct MUC Invitations
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,91 +1,301 @@
 | 
			
		||||
"""
 | 
			
		||||
    SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
    Copyright (C) 2010 Nathanael C. Fritz
 | 
			
		||||
    This file is part of SleekXMPP.
 | 
			
		||||
# -*- encoding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
    sleekxmpp.plugins.base
 | 
			
		||||
    ~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
    This module provides XMPP functionality that
 | 
			
		||||
    is specific to client connections.
 | 
			
		||||
 | 
			
		||||
    Part of SleekXMPP: The Sleek XMPP Library
 | 
			
		||||
 | 
			
		||||
    :copyright: (c) 2012 Nathanael C. Fritz
 | 
			
		||||
    :license: MIT, see LICENSE for more details
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import logging
 | 
			
		||||
import threading
 | 
			
		||||
 | 
			
		||||
class base_plugin(object):
 | 
			
		||||
 | 
			
		||||
if sys.version_info >= (3, 0):
 | 
			
		||||
    unicode = str
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#: Associate short string names of plugins with implementations. The
 | 
			
		||||
#: plugin names are based on the spec used by the plugin, such as
 | 
			
		||||
#: `'xep_0030'` for a plugin that implements XEP-0030.
 | 
			
		||||
PLUGIN_REGISTRY = {}
 | 
			
		||||
 | 
			
		||||
#: In order to do cascading plugin disabling, reverse dependencies
 | 
			
		||||
#: must be tracked. 
 | 
			
		||||
PLUGIN_DEPENDENTS = {}
 | 
			
		||||
 | 
			
		||||
#: Only allow one thread to manipulate the plugin registry at a time. 
 | 
			
		||||
REGISTRY_LOCK = threading.RLock()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PluginNotFound(Exception):
 | 
			
		||||
    """Raised if an unknown plugin is accessed."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_plugin(impl, name=None):
 | 
			
		||||
    """Add a new plugin implementation to the registry.
 | 
			
		||||
 | 
			
		||||
    :param class impl: The plugin class.
 | 
			
		||||
 | 
			
		||||
    The implementation class must provide a :attr:`~BasePlugin.name`
 | 
			
		||||
    value that will be used as a short name for enabling and disabling
 | 
			
		||||
    the plugin. The name should be based on the specification used by
 | 
			
		||||
    the plugin. For example, a plugin implementing XEP-0030 would be
 | 
			
		||||
    named `'xep_0030'`.
 | 
			
		||||
    """
 | 
			
		||||
    The base_plugin class serves as a base for user created plugins
 | 
			
		||||
    that provide support for existing or experimental XEPS.
 | 
			
		||||
    if name is None:
 | 
			
		||||
        name = impl.name
 | 
			
		||||
    with REGISTRY_LOCK:
 | 
			
		||||
        PLUGIN_REGISTRY[name] = impl
 | 
			
		||||
        if name not in PLUGIN_DEPENDENTS:
 | 
			
		||||
            PLUGIN_DEPENDENTS[name] = set()
 | 
			
		||||
        for dep in impl.dependencies:
 | 
			
		||||
            if dep not in PLUGIN_DEPENDENTS:
 | 
			
		||||
                PLUGIN_DEPENDENTS[dep] = set()
 | 
			
		||||
            PLUGIN_DEPENDENTS[dep].add(name)
 | 
			
		||||
 | 
			
		||||
    Each plugin has a dictionary for configuration options, as well
 | 
			
		||||
    as a name and description.
 | 
			
		||||
 | 
			
		||||
    The lifecycle of a plugin is:
 | 
			
		||||
        1. The plugin is instantiated during registration.
 | 
			
		||||
        2. Once the XML stream begins processing, the method
 | 
			
		||||
           plugin_init() is called (if the plugin is configured
 | 
			
		||||
           as enabled with {'enable': True}).
 | 
			
		||||
        3. After all plugins have been initialized, the
 | 
			
		||||
           method post_init() is called.
 | 
			
		||||
def load_plugin(name, module=None):
 | 
			
		||||
    """Find and import a plugin module so that it can be registered.
 | 
			
		||||
 | 
			
		||||
    Recommended event handlers:
 | 
			
		||||
        session_start -- Plugins which require the use of the current
 | 
			
		||||
                         bound JID SHOULD wait for the session_start
 | 
			
		||||
                         event to perform any initialization (or
 | 
			
		||||
                         resetting). This is a transitive recommendation,
 | 
			
		||||
                         plugins that use other plugins which use the
 | 
			
		||||
                         bound JID should also wait for session_start
 | 
			
		||||
                         before making such calls.
 | 
			
		||||
        session_end   -- If the plugin keeps any per-session state,
 | 
			
		||||
                         such as joined MUC rooms, such state SHOULD
 | 
			
		||||
                         be cleared when the session_end event is raised.
 | 
			
		||||
    This function is called to import plugins that have selected for
 | 
			
		||||
    enabling, but no matching registered plugin has been found.
 | 
			
		||||
 | 
			
		||||
    Attributes:
 | 
			
		||||
        xep          -- The XEP number the plugin implements, if any.
 | 
			
		||||
        description  -- A short description of the plugin, typically
 | 
			
		||||
                        the long name of the implemented XEP.
 | 
			
		||||
        xmpp         -- The main SleekXMPP instance.
 | 
			
		||||
        config       -- A dictionary of custom configuration values.
 | 
			
		||||
                        The value 'enable' is special and controls
 | 
			
		||||
                        whether or not the plugin is initialized
 | 
			
		||||
                        after registration.
 | 
			
		||||
        post_initted -- Executed after all plugins have been initialized
 | 
			
		||||
                        to handle any cross-plugin interactions, such as
 | 
			
		||||
                        registering service discovery items.
 | 
			
		||||
        enable       -- Indicates that the plugin is enabled for use and
 | 
			
		||||
                        will be initialized after registration.
 | 
			
		||||
 | 
			
		||||
    Methods:
 | 
			
		||||
        plugin_init  -- Initialize the plugin state.
 | 
			
		||||
        post_init    -- Handle any cross-plugin concerns.
 | 
			
		||||
    :param str name: The name of the plugin. It is expected that
 | 
			
		||||
                     plugins are in packages matching their name,
 | 
			
		||||
                     even though the plugin class name does not
 | 
			
		||||
                     have to match.
 | 
			
		||||
    :param str module: The name of the base module to search 
 | 
			
		||||
                       for the plugin.
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        if not module:
 | 
			
		||||
            try:
 | 
			
		||||
                module = 'sleekxmpp.plugins.%s' % name
 | 
			
		||||
                __import__(module)
 | 
			
		||||
                mod = sys.modules[module]
 | 
			
		||||
            except:
 | 
			
		||||
                module = 'sleekxmpp.features.%s' % name
 | 
			
		||||
                __import__(module)
 | 
			
		||||
                mod = sys.modules[module]
 | 
			
		||||
        elif isinstance(module, (str, unicode)):
 | 
			
		||||
            __import__(module)
 | 
			
		||||
            mod = sys.modules[module]
 | 
			
		||||
        else:
 | 
			
		||||
            mod = module
 | 
			
		||||
 | 
			
		||||
        # Add older style plugins to the registry.
 | 
			
		||||
        if hasattr(mod, name):
 | 
			
		||||
            plugin = getattr(mod, name)
 | 
			
		||||
            if hasattr(plugin, 'xep') or hasattr(plugin, 'rfc'):
 | 
			
		||||
                plugin.name = name
 | 
			
		||||
                # Mark the plugin as an older style plugin so
 | 
			
		||||
                # we can work around dependency issues.
 | 
			
		||||
                plugin.old_style = True
 | 
			
		||||
            register_plugin(plugin, name)
 | 
			
		||||
    except:
 | 
			
		||||
        log.exception("Unable to load plugin: %s", name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PluginManager(object): 
 | 
			
		||||
    def __init__(self, xmpp, config=None):
 | 
			
		||||
        """
 | 
			
		||||
        Instantiate a new plugin and store the given configuration.
 | 
			
		||||
        #: We will track all enabled plugins in a set so that we
 | 
			
		||||
        #: can enable plugins in batches and pull in dependencies
 | 
			
		||||
        #: without problems.
 | 
			
		||||
        self._enabled = set()
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            xmpp   -- The main SleekXMPP instance.
 | 
			
		||||
            config -- A dictionary of configuration values.
 | 
			
		||||
        #: Maintain references to active plugins.
 | 
			
		||||
        self._plugins = {}
 | 
			
		||||
 | 
			
		||||
        self._plugin_lock = threading.RLock()
 | 
			
		||||
 | 
			
		||||
        #: Globally set default plugin configuration. This will
 | 
			
		||||
        #: be used for plugins that are auto-enabled through
 | 
			
		||||
        #: dependency loading.
 | 
			
		||||
        self.config = config if config else {}
 | 
			
		||||
 | 
			
		||||
        self.xmpp = xmpp
 | 
			
		||||
 | 
			
		||||
    def register(self, plugin, enable=True):
 | 
			
		||||
        """Register a new plugin, and optionally enable it.
 | 
			
		||||
 | 
			
		||||
        :param class plugin: The implementation class of the plugin
 | 
			
		||||
                             to register.
 | 
			
		||||
        :param bool enable: If ``True``, immediately enable the
 | 
			
		||||
                            plugin after registration.
 | 
			
		||||
        """
 | 
			
		||||
        register_plugin(plugin)
 | 
			
		||||
        if enable:
 | 
			
		||||
            self.enable(plugin.name)
 | 
			
		||||
 | 
			
		||||
    def enable(self, name, config=None, enabled=None):
 | 
			
		||||
        """Enable a plugin, including any dependencies.
 | 
			
		||||
 | 
			
		||||
        :param string name: The short name of the plugin.
 | 
			
		||||
        :param dict config: Optional settings dictionary for
 | 
			
		||||
                            configuring plugin behaviour.
 | 
			
		||||
        """
 | 
			
		||||
        top_level = False
 | 
			
		||||
        if enabled is None:
 | 
			
		||||
            enabled = set()
 | 
			
		||||
 | 
			
		||||
        with self._plugin_lock:
 | 
			
		||||
            if name not in self._enabled:
 | 
			
		||||
                enabled.add(name)
 | 
			
		||||
                self._enabled.add(name)
 | 
			
		||||
                if not self.registered(name):
 | 
			
		||||
                    load_plugin(name)
 | 
			
		||||
 | 
			
		||||
                plugin_class = PLUGIN_REGISTRY.get(name, None)
 | 
			
		||||
                if not plugin_class:
 | 
			
		||||
                    raise PluginNotFound(name)
 | 
			
		||||
 | 
			
		||||
                if config is None:
 | 
			
		||||
                    config = self.config.get(name, None)
 | 
			
		||||
 | 
			
		||||
                plugin = plugin_class(self.xmpp, config)
 | 
			
		||||
                self._plugins[name] = plugin
 | 
			
		||||
                for dep in plugin.dependencies:
 | 
			
		||||
                    self.enable(dep, enabled=enabled)
 | 
			
		||||
                plugin.plugin_init()
 | 
			
		||||
                log.debug("Loaded Plugin: %s", plugin.description)
 | 
			
		||||
 | 
			
		||||
        if top_level:
 | 
			
		||||
            for name in enabled:
 | 
			
		||||
                if hasattr(self.plugins[name], 'old_style'):
 | 
			
		||||
                    # Older style plugins require post_init()
 | 
			
		||||
                    # to run just before stream processing begins,
 | 
			
		||||
                    # so we don't call it here.
 | 
			
		||||
                    pass
 | 
			
		||||
                self.plugins[name].post_init()
 | 
			
		||||
 | 
			
		||||
    def enable_all(self, names=None, config=None):
 | 
			
		||||
        """Enable all registered plugins.
 | 
			
		||||
        
 | 
			
		||||
        :param list names: A list of plugin names to enable. If
 | 
			
		||||
                           none are provided, all registered plugins
 | 
			
		||||
                           will be enabled.
 | 
			
		||||
        :param dict config: A dictionary mapping plugin names to
 | 
			
		||||
                            configuration dictionaries, as used by
 | 
			
		||||
                            :meth:`~PluginManager.enable`.
 | 
			
		||||
        """
 | 
			
		||||
        names = names if names else PLUGIN_REGISTRY.keys()
 | 
			
		||||
        if config is None:
 | 
			
		||||
            config = {}
 | 
			
		||||
        self.xep = None
 | 
			
		||||
        self.rfc = None
 | 
			
		||||
        self.description = 'Base Plugin'
 | 
			
		||||
        for name in names:
 | 
			
		||||
            self.enable(name, config.get(name, {}))
 | 
			
		||||
 | 
			
		||||
    def enabled(self, name):
 | 
			
		||||
        """Check if a plugin has been enabled.
 | 
			
		||||
 | 
			
		||||
        :param string name: The name of the plugin to check.
 | 
			
		||||
        :return: boolean
 | 
			
		||||
        """
 | 
			
		||||
        return name in self._enabled
 | 
			
		||||
 | 
			
		||||
    def registered(self, name):
 | 
			
		||||
        """Check if a plugin has been registered.
 | 
			
		||||
 | 
			
		||||
        :param string name: The name of the plugin to check.
 | 
			
		||||
        :return: boolean
 | 
			
		||||
        """
 | 
			
		||||
        return name in PLUGIN_REGISTRY
 | 
			
		||||
 | 
			
		||||
    def disable(self, name, _disabled=None):
 | 
			
		||||
        """Disable a plugin, including any dependent upon it.
 | 
			
		||||
 | 
			
		||||
        :param string name: The name of the plugin to disable.
 | 
			
		||||
        :param set _disabled: Private set used to track the
 | 
			
		||||
                              disabled status of plugins during
 | 
			
		||||
                              the cascading process.
 | 
			
		||||
        """
 | 
			
		||||
        if _disabled is None:
 | 
			
		||||
            _disabled = set()
 | 
			
		||||
        with self._plugin_lock:
 | 
			
		||||
            if name not in _disabled and name in self._enabled:
 | 
			
		||||
                _disabled.add(name)
 | 
			
		||||
                plugin = self._plugins.get(name, None)
 | 
			
		||||
                if plugin is None:
 | 
			
		||||
                    raise PluginNotFound(name)
 | 
			
		||||
                for dep in PLUGIN_DEPENDENTS[name]:
 | 
			
		||||
                    self.disable(dep, _disabled)
 | 
			
		||||
                plugin.plugin_end()
 | 
			
		||||
                if name in self._enabled:
 | 
			
		||||
                    self._enabled.remove(name)
 | 
			
		||||
                del self._plugins[name]
 | 
			
		||||
 | 
			
		||||
    def __keys__(self):
 | 
			
		||||
        """Return the set of enabled plugins."""
 | 
			
		||||
        return self._plugins.keys()
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        Allow plugins to be accessed through the manager as if
 | 
			
		||||
        it were a dictionary.
 | 
			
		||||
        """
 | 
			
		||||
        plugin = self._plugins.get(name, None)
 | 
			
		||||
        if plugin is None:
 | 
			
		||||
            raise PluginNotFound(name)
 | 
			
		||||
        return plugin
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        """Return an iterator over the set of enabled plugins."""
 | 
			
		||||
        return self._plugins.__iter__()
 | 
			
		||||
 | 
			
		||||
    def __len__(self):
 | 
			
		||||
        """Return the number of enabled plugins."""
 | 
			
		||||
        return len(self._plugins)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BasePlugin(object):
 | 
			
		||||
 | 
			
		||||
    #: A short name for the plugin based on the implemented specification.
 | 
			
		||||
    #: For example, a plugin for XEP-0030 would use `'xep_0030'`.
 | 
			
		||||
    name = ''
 | 
			
		||||
 | 
			
		||||
    #: A longer name for the plugin, describing its purpose. For example,
 | 
			
		||||
    #: a plugin for XEP-0030 would use `'Service Discovery'` as its
 | 
			
		||||
    #: description value.
 | 
			
		||||
    description = ''
 | 
			
		||||
 | 
			
		||||
    #: Some plugins may depend on others in order to function properly.
 | 
			
		||||
    #: Any plugin names included in :attr:`~BasePlugin.dependencies` will
 | 
			
		||||
    #: be initialized as needed if this plugin is enabled.
 | 
			
		||||
    dependencies = set()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, xmpp, config=None):
 | 
			
		||||
        self.xmpp = xmpp
 | 
			
		||||
        self.config = config
 | 
			
		||||
        self.post_inited = False
 | 
			
		||||
        self.enable = config.get('enable', True)
 | 
			
		||||
        if self.enable:
 | 
			
		||||
            self.plugin_init()
 | 
			
		||||
        if self.xmpp:
 | 
			
		||||
            self.api = self.xmpp.api.wrap(self.name)
 | 
			
		||||
 | 
			
		||||
        #: A plugin's behaviour may be configurable, in which case those
 | 
			
		||||
        #: configuration settings will be provided as a dictionary.
 | 
			
		||||
        self.config = config if config is not None else {}
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """
 | 
			
		||||
        Initialize plugin state, such as registering any stream or
 | 
			
		||||
        event handlers, or new stanza types.
 | 
			
		||||
        """
 | 
			
		||||
        """Initialize plugin state, such as registering event handlers."""
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        """Cleanup plugin state, and prepare for plugin removal."""
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        """Initialize any cross-plugin state.
 | 
			
		||||
       
 | 
			
		||||
        Only needed if the plugin has circular dependencies.
 | 
			
		||||
        """
 | 
			
		||||
        Perform any cross-plugin interactions, such as registering
 | 
			
		||||
        service discovery identities or items.
 | 
			
		||||
        """
 | 
			
		||||
        self.post_inited = True
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
base_plugin = BasePlugin
 | 
			
		||||
 
 | 
			
		||||
@@ -121,7 +121,7 @@ class gmail_notify(base.base_plugin):
 | 
			
		||||
    def handle_gmail(self, iq):
 | 
			
		||||
        mailbox = iq['mailbox']
 | 
			
		||||
        approx = ' approximately' if mailbox['estimated'] else ''
 | 
			
		||||
        log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched']))
 | 
			
		||||
        log.info('Gmail: Received%s %s emails', approx, mailbox['total-matched'])
 | 
			
		||||
        self.last_result_time = mailbox['result-time']
 | 
			
		||||
        self.xmpp.event('gmail_messages', iq)
 | 
			
		||||
 | 
			
		||||
@@ -140,7 +140,7 @@ class gmail_notify(base.base_plugin):
 | 
			
		||||
        if query is None:
 | 
			
		||||
            log.info("Gmail: Checking for new emails")
 | 
			
		||||
        else:
 | 
			
		||||
            log.info('Gmail: Searching for emails matching: "%s"' % query)
 | 
			
		||||
            log.info('Gmail: Searching for emails matching: "%s"', query)
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['to'] = self.xmpp.boundjid.bare
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ class jobs(base.base_plugin):
 | 
			
		||||
		iq['psstate']['payload'] = state
 | 
			
		||||
		result = iq.send()
 | 
			
		||||
		if result is None or type(result) == bool or result['type'] != 'result':
 | 
			
		||||
			log.error("Unable to change %s:%s to %s" % (node, jobid, state))
 | 
			
		||||
			log.error("Unable to change %s:%s to %s", node, jobid, state)
 | 
			
		||||
			return False
 | 
			
		||||
		return True
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,17 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza import Form
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza import FormField, FieldOption
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.dataforms import xep_0004
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.dataforms import XEP_0004
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(XEP_0004)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
xep_0004 = XEP_0004
 | 
			
		||||
xep_0004.makeForm = xep_0004.make_form
 | 
			
		||||
xep_0004.buildForm = xep_0004.build_form
 | 
			
		||||
 
 | 
			
		||||
@@ -6,29 +6,27 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.thirdparty import OrderedDict
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Message
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET
 | 
			
		||||
from sleekxmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from sleekxmpp.plugins.base import base_plugin
 | 
			
		||||
from sleekxmpp.plugins import BasePlugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0004 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0004.stanza import Form, FormField, FieldOption
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class xep_0004(base_plugin):
 | 
			
		||||
class XEP_0004(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0004: Data Forms
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0004'
 | 
			
		||||
        self.description = 'Data Forms'
 | 
			
		||||
        self.stanza = stanza
 | 
			
		||||
    name = 'xep_0004'
 | 
			
		||||
    description = 'XEP-0004: Data Forms'
 | 
			
		||||
    dependencies = set(['xep_0030'])
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xmpp.registerHandler(
 | 
			
		||||
            Callback('Data Form',
 | 
			
		||||
                 StanzaPath('message/form'),
 | 
			
		||||
@@ -38,6 +36,8 @@ class xep_0004(base_plugin):
 | 
			
		||||
        register_stanza_plugin(Form, FormField, iterable=True)
 | 
			
		||||
        register_stanza_plugin(Message, Form)
 | 
			
		||||
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature('jabber:x:data')
 | 
			
		||||
 | 
			
		||||
    def make_form(self, ftype='form', title='', instructions=''):
 | 
			
		||||
        f = Form()
 | 
			
		||||
        f['type'] = ftype
 | 
			
		||||
@@ -45,16 +45,8 @@ class xep_0004(base_plugin):
 | 
			
		||||
        f['instructions'] = instructions
 | 
			
		||||
        return f
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
 | 
			
		||||
 | 
			
		||||
    def handle_form(self, message):
 | 
			
		||||
        self.xmpp.event("message_xform", message)
 | 
			
		||||
 | 
			
		||||
    def build_form(self, xml):
 | 
			
		||||
        return Form(xml=xml)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
xep_0004.makeForm = xep_0004.make_form
 | 
			
		||||
xep_0004.buildForm = xep_0004.build_form
 | 
			
		||||
 
 | 
			
		||||
@@ -79,19 +79,21 @@ class FormField(ElementBase):
 | 
			
		||||
        reqXML = self.xml.find('{%s}required' % self.namespace)
 | 
			
		||||
        return reqXML is not None
 | 
			
		||||
 | 
			
		||||
    def get_value(self):
 | 
			
		||||
    def get_value(self, convert=True):
 | 
			
		||||
        valsXML = self.xml.findall('{%s}value' % self.namespace)
 | 
			
		||||
        if len(valsXML) == 0:
 | 
			
		||||
            return None
 | 
			
		||||
        elif self._type == 'boolean':
 | 
			
		||||
            return valsXML[0].text in self.true_values
 | 
			
		||||
            if convert:
 | 
			
		||||
                return valsXML[0].text in self.true_values
 | 
			
		||||
            return valsXML[0].text
 | 
			
		||||
        elif self._type in self.multi_value_types or len(valsXML) > 1:
 | 
			
		||||
            values = []
 | 
			
		||||
            for valXML in valsXML:
 | 
			
		||||
                if valXML.text is None:
 | 
			
		||||
                    valXML.text = ''
 | 
			
		||||
                values.append(valXML.text)
 | 
			
		||||
            if self._type == 'text-multi':
 | 
			
		||||
            if self._type == 'text-multi' and convert:
 | 
			
		||||
                values = "\n".join(values)
 | 
			
		||||
            return values
 | 
			
		||||
        else:
 | 
			
		||||
@@ -136,6 +138,8 @@ class FormField(ElementBase):
 | 
			
		||||
                valXML.text = '0'
 | 
			
		||||
                self.xml.append(valXML)
 | 
			
		||||
        elif self._type in self.multi_value_types or self._type in ('', None):
 | 
			
		||||
            if isinstance(value, bool):
 | 
			
		||||
                value = [value]
 | 
			
		||||
            if not isinstance(value, list):
 | 
			
		||||
                value = value.replace('\r', '')
 | 
			
		||||
                value = value.split('\n')
 | 
			
		||||
 
 | 
			
		||||
@@ -96,11 +96,11 @@ class Form(ElementBase):
 | 
			
		||||
        self.xml.append(itemXML)
 | 
			
		||||
        reported_vars = self['reported'].keys()
 | 
			
		||||
        for var in reported_vars:
 | 
			
		||||
            fieldXML = ET.Element('{%s}field' % FormField.namespace)
 | 
			
		||||
            itemXML.append(fieldXML)
 | 
			
		||||
            field = FormField(xml=fieldXML)
 | 
			
		||||
            field = FormField()
 | 
			
		||||
            field._type = self['reported'][var]['type']
 | 
			
		||||
            field['var'] = var
 | 
			
		||||
            field['value'] = values.get(var, None)
 | 
			
		||||
            itemXML.append(field.xml)
 | 
			
		||||
 | 
			
		||||
    def add_reported(self, var, ftype=None, label='', desc='', **kwargs):
 | 
			
		||||
        kwtype = kwargs.get('type', None)
 | 
			
		||||
@@ -159,7 +159,7 @@ class Form(ElementBase):
 | 
			
		||||
        items = []
 | 
			
		||||
        itemsXML = self.xml.findall('{%s}item' % self.namespace)
 | 
			
		||||
        for itemXML in itemsXML:
 | 
			
		||||
            item = {}
 | 
			
		||||
            item = OrderedDict()
 | 
			
		||||
            fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
 | 
			
		||||
            for fieldXML in fieldsXML:
 | 
			
		||||
                field = FormField(xml=fieldXML)
 | 
			
		||||
@@ -168,7 +168,7 @@ class Form(ElementBase):
 | 
			
		||||
        return items
 | 
			
		||||
 | 
			
		||||
    def get_reported(self):
 | 
			
		||||
        fields = {}
 | 
			
		||||
        fields = OrderedDict()
 | 
			
		||||
        xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
 | 
			
		||||
                                     FormField.namespace))
 | 
			
		||||
        for field in xml:
 | 
			
		||||
@@ -177,7 +177,7 @@ class Form(ElementBase):
 | 
			
		||||
        return fields
 | 
			
		||||
 | 
			
		||||
    def get_values(self):
 | 
			
		||||
        values = {}
 | 
			
		||||
        values = OrderedDict()
 | 
			
		||||
        fields = self['fields']
 | 
			
		||||
        for var in fields:
 | 
			
		||||
            values[var] = fields[var]['value']
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,15 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins.xep_0009 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.rpc import xep_0009
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.rpc import XEP_0009
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(XEP_0009)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Retain some backwards compatibility
 | 
			
		||||
xep_0009 = XEP_0009
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,14 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
from sleekxmpp.xmlstream import ET
 | 
			
		||||
import base64
 | 
			
		||||
import logging
 | 
			
		||||
import time
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
if sys.version_info > (3, 0):
 | 
			
		||||
    unicode = str
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -42,46 +46,46 @@ def py2xml(*args):
 | 
			
		||||
 | 
			
		||||
def _py2xml(*args):
 | 
			
		||||
    for x in args:
 | 
			
		||||
        val = ET.Element("value")
 | 
			
		||||
        val = ET.Element("{%s}value" % _namespace)
 | 
			
		||||
        if x is None:
 | 
			
		||||
            nil = ET.Element("nil")
 | 
			
		||||
            nil = ET.Element("{%s}nil" % _namespace)
 | 
			
		||||
            val.append(nil)
 | 
			
		||||
        elif type(x) is int:
 | 
			
		||||
            i4 = ET.Element("i4")
 | 
			
		||||
            i4 = ET.Element("{%s}i4" % _namespace)
 | 
			
		||||
            i4.text = str(x)
 | 
			
		||||
            val.append(i4)
 | 
			
		||||
        elif type(x) is bool:
 | 
			
		||||
            boolean = ET.Element("boolean")
 | 
			
		||||
            boolean = ET.Element("{%s}boolean" % _namespace)
 | 
			
		||||
            boolean.text = str(int(x))
 | 
			
		||||
            val.append(boolean)
 | 
			
		||||
        elif type(x) is str:
 | 
			
		||||
            string = ET.Element("string")
 | 
			
		||||
        elif type(x) in (str, unicode):
 | 
			
		||||
            string = ET.Element("{%s}string" % _namespace)
 | 
			
		||||
            string.text = x
 | 
			
		||||
            val.append(string)
 | 
			
		||||
        elif type(x) is float:
 | 
			
		||||
            double = ET.Element("double")
 | 
			
		||||
            double = ET.Element("{%s}double" % _namespace)
 | 
			
		||||
            double.text = str(x)
 | 
			
		||||
            val.append(double)
 | 
			
		||||
        elif type(x) is rpcbase64:
 | 
			
		||||
            b64 = ET.Element("base64")
 | 
			
		||||
            b64 = ET.Element("{%s}base64" % _namespace)
 | 
			
		||||
            b64.text = x.encoded()
 | 
			
		||||
            val.append(b64)
 | 
			
		||||
        elif type(x) is rpctime:
 | 
			
		||||
            iso = ET.Element("dateTime.iso8601")
 | 
			
		||||
            iso = ET.Element("{%s}dateTime.iso8601" % _namespace)
 | 
			
		||||
            iso.text = str(x)
 | 
			
		||||
            val.append(iso)
 | 
			
		||||
        elif type(x) in (list, tuple):
 | 
			
		||||
            array = ET.Element("array")
 | 
			
		||||
            data = ET.Element("data")
 | 
			
		||||
            array = ET.Element("{%s}array" % _namespace)
 | 
			
		||||
            data = ET.Element("{%s}data" % _namespace)
 | 
			
		||||
            for y in x:
 | 
			
		||||
                data.append(_py2xml(y))
 | 
			
		||||
            array.append(data)
 | 
			
		||||
            val.append(array)
 | 
			
		||||
        elif type(x) is dict:
 | 
			
		||||
            struct = ET.Element("struct")
 | 
			
		||||
            struct = ET.Element("{%s}struct" % _namespace)
 | 
			
		||||
            for y in x.keys():
 | 
			
		||||
                member = ET.Element("member")
 | 
			
		||||
                name = ET.Element("name")
 | 
			
		||||
                member = ET.Element("{%s}member" % _namespace)
 | 
			
		||||
                name = ET.Element("{%s}name" % _namespace)
 | 
			
		||||
                name.text = y
 | 
			
		||||
                member.append(name)
 | 
			
		||||
                member.append(_py2xml(x[y]))
 | 
			
		||||
@@ -105,18 +109,18 @@ def _xml2py(value):
 | 
			
		||||
    if value.find('{%s}int' % namespace) is not None:
 | 
			
		||||
        return int(value.find('{%s}int' % namespace).text)
 | 
			
		||||
    if value.find('{%s}boolean' % namespace) is not None:
 | 
			
		||||
        return bool(value.find('{%s}boolean' % namespace).text)
 | 
			
		||||
        return bool(int(value.find('{%s}boolean' % namespace).text))
 | 
			
		||||
    if value.find('{%s}string' % namespace) is not None:
 | 
			
		||||
        return value.find('{%s}string' % namespace).text
 | 
			
		||||
    if value.find('{%s}double' % namespace) is not None:
 | 
			
		||||
        return float(value.find('{%s}double' % namespace).text)
 | 
			
		||||
    if value.find('{%s}base64') is not None:
 | 
			
		||||
        return rpcbase64(value.find('base64' % namespace).text)
 | 
			
		||||
    if value.find('{%s}Base64') is not None:
 | 
			
		||||
    if value.find('{%s}base64' % namespace) is not None:
 | 
			
		||||
        return rpcbase64(value.find('{%s}base64' % namespace).text.encode())
 | 
			
		||||
    if value.find('{%s}Base64' % namespace) is not None:
 | 
			
		||||
        # Older versions of XEP-0009 used Base64
 | 
			
		||||
        return rpcbase64(value.find('Base64' % namespace).text)
 | 
			
		||||
    if value.find('{%s}dateTime.iso8601') is not None:
 | 
			
		||||
        return rpctime(value.find('{%s}dateTime.iso8601'))
 | 
			
		||||
        return rpcbase64(value.find('{%s}Base64' % namespace).text.encode())
 | 
			
		||||
    if value.find('{%s}dateTime.iso8601' % namespace) is not None:
 | 
			
		||||
        return rpctime(value.find('{%s}dateTime.iso8601' % namespace).text)
 | 
			
		||||
    if value.find('{%s}struct' % namespace) is not None:
 | 
			
		||||
        struct = {}
 | 
			
		||||
        for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace):
 | 
			
		||||
@@ -138,13 +142,13 @@ class rpcbase64(object):
 | 
			
		||||
        self.data = data
 | 
			
		||||
 | 
			
		||||
    def decode(self):
 | 
			
		||||
        return base64.decodestring(self.data)
 | 
			
		||||
        return base64.b64decode(self.data)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.decode()
 | 
			
		||||
        return self.decode().decode()
 | 
			
		||||
 | 
			
		||||
    def encoded(self):
 | 
			
		||||
        return self.data
 | 
			
		||||
        return self.data.decode()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -152,7 +156,7 @@ class rpctime(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self,data=None):
 | 
			
		||||
        #assume string data is in iso format YYYYMMDDTHH:MM:SS
 | 
			
		||||
        if type(data) is str:
 | 
			
		||||
        if type(data) in (str, unicode):
 | 
			
		||||
            self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
 | 
			
		||||
        elif type(data) is time.struct_time:
 | 
			
		||||
            self.timestamp = data
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
def _intercept(method, name, public):
 | 
			
		||||
    def _resolver(instance, *args, **kwargs):
 | 
			
		||||
        log.debug("Locally calling %s.%s with arguments %s." % (instance.FQN(), method.__name__, args))
 | 
			
		||||
        log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
 | 
			
		||||
        try:
 | 
			
		||||
            value = method(instance, *args, **kwargs)
 | 
			
		||||
            if value == NotImplemented:
 | 
			
		||||
@@ -113,6 +113,9 @@ class ACL:
 | 
			
		||||
    def check(cls, rules, jid, resource):
 | 
			
		||||
        if rules is None:
 | 
			
		||||
            return cls.DENY                  # No rules means no access!
 | 
			
		||||
        jid = str(jid)     # Check the string representation of the JID.
 | 
			
		||||
        if not jid:
 | 
			
		||||
            return cls.DENY                  # Can't check an empty JID.
 | 
			
		||||
        for rule in rules:
 | 
			
		||||
            policy = cls._check(rule, jid, resource)
 | 
			
		||||
            if policy is not None:
 | 
			
		||||
@@ -381,7 +384,7 @@ class Proxy(Endpoint):
 | 
			
		||||
                try:
 | 
			
		||||
                    if attribute._rpc:
 | 
			
		||||
                        def _remote_call(*args, **kwargs):
 | 
			
		||||
                            log.debug("Remotely calling '%s.%s' with arguments %s." % (self._endpoint.FQN(), attribute._rpc_name, args))
 | 
			
		||||
                            log.debug("Remotely calling '%s.%s' with arguments %s.", self._endpoint.FQN(), attribute._rpc_name, args)
 | 
			
		||||
                            return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs)
 | 
			
		||||
                        return _remote_call
 | 
			
		||||
                except:
 | 
			
		||||
@@ -449,7 +452,7 @@ class RemoteSession(object):
 | 
			
		||||
        self._event.wait()
 | 
			
		||||
 | 
			
		||||
    def _notify(self, event):
 | 
			
		||||
        log.debug("RPC Session as %s started." % self._client.boundjid.full)
 | 
			
		||||
        log.debug("RPC Session as %s started.", self._client.boundjid.full)
 | 
			
		||||
        self._client.sendPresence()
 | 
			
		||||
        self._event.set()
 | 
			
		||||
        pass
 | 
			
		||||
@@ -461,7 +464,7 @@ class RemoteSession(object):
 | 
			
		||||
        if name is None:
 | 
			
		||||
            name = method.__name__
 | 
			
		||||
        key = "%s.%s" % (endpoint, name)
 | 
			
		||||
        log.debug("Registering call handler for %s (%s)." % (key, method))
 | 
			
		||||
        log.debug("Registering call handler for %s (%s).", key, method)
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            if key in self._entries:
 | 
			
		||||
                raise KeyError("A handler for %s has already been regisered!" % endpoint)
 | 
			
		||||
@@ -469,7 +472,7 @@ class RemoteSession(object):
 | 
			
		||||
        return key
 | 
			
		||||
 | 
			
		||||
    def _register_acl(self, endpoint, acl):
 | 
			
		||||
        log.debug("Registering ACL %s for endpoint %s." % (repr(acl), endpoint))
 | 
			
		||||
        log.debug("Registering ACL %s for endpoint %s.", repr(acl), endpoint)
 | 
			
		||||
        with self._lock:
 | 
			
		||||
            self._acls[endpoint] = acl
 | 
			
		||||
 | 
			
		||||
@@ -562,7 +565,7 @@ class RemoteSession(object):
 | 
			
		||||
            iq.send()
 | 
			
		||||
            return future.get_value(30)
 | 
			
		||||
        else:
 | 
			
		||||
            log.debug("[RemoteSession] _call_remote %s" % callback)
 | 
			
		||||
            log.debug("[RemoteSession] _call_remote %s", callback)
 | 
			
		||||
            self._register_callback(pid, callback)
 | 
			
		||||
            iq.send()
 | 
			
		||||
 | 
			
		||||
@@ -601,11 +604,11 @@ class RemoteSession(object):
 | 
			
		||||
            error.send()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            if isinstance(e, KeyError):
 | 
			
		||||
                log.error("No handler available for %s!" % pmethod)
 | 
			
		||||
                log.error("No handler available for %s!", pmethod)
 | 
			
		||||
                error = self._client.plugin['xep_0009']._item_not_found(iq)
 | 
			
		||||
            else:
 | 
			
		||||
                traceback.print_exc(file=sys.stderr)
 | 
			
		||||
                log.error("An unexpected problem occurred invoking method %s!" % pmethod)
 | 
			
		||||
                log.error("An unexpected problem occurred invoking method %s!", pmethod)
 | 
			
		||||
                error = self._client.plugin['xep_0009']._undefined_condition(iq)
 | 
			
		||||
            #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e
 | 
			
		||||
            error.send()
 | 
			
		||||
 
 | 
			
		||||
@@ -6,28 +6,28 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from sleekxmpp.plugins import base
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
 | 
			
		||||
from sleekxmpp.stanza.iq import Iq
 | 
			
		||||
from sleekxmpp.xmlstream.handler.callback import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
 | 
			
		||||
from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
 | 
			
		||||
from xml.etree import cElementTree as ET
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from sleekxmpp import Iq
 | 
			
		||||
from sleekxmpp.xmlstream import ET, register_stanza_plugin
 | 
			
		||||
from sleekxmpp.xmlstream.handler import Callback
 | 
			
		||||
from sleekxmpp.xmlstream.matcher import MatchXPath
 | 
			
		||||
from sleekxmpp.plugins import BasePlugin
 | 
			
		||||
from sleekxmpp.plugins.xep_0009 import stanza
 | 
			
		||||
from sleekxmpp.plugins.xep_0009.stanza.RPC import RPCQuery, MethodCall, MethodResponse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class XEP_0009(BasePlugin):
 | 
			
		||||
 | 
			
		||||
class xep_0009(base.base_plugin):
 | 
			
		||||
    name = 'xep_0009'
 | 
			
		||||
    description = 'XEP-0009: Jabber-RPC'
 | 
			
		||||
    dependencies = set(['xep_0030'])
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xep = '0009'
 | 
			
		||||
        self.description = 'Jabber-RPC'
 | 
			
		||||
        #self.stanza = sleekxmpp.plugins.xep_0009.stanza
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, RPCQuery)
 | 
			
		||||
        register_stanza_plugin(RPCQuery, MethodCall)
 | 
			
		||||
        register_stanza_plugin(RPCQuery, MethodResponse)
 | 
			
		||||
@@ -51,10 +51,8 @@ class xep_0009(base.base_plugin):
 | 
			
		||||
        self.xmpp.add_event_handler('error', self._handle_error)
 | 
			
		||||
        #self.activeCalls = []
 | 
			
		||||
 | 
			
		||||
    def post_init(self):
 | 
			
		||||
        base.base_plugin.post_init(self)
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_identity('automation','rpc')
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature('jabber:iq:rpc')
 | 
			
		||||
        self.xmpp['xep_0030'].add_identity('automation','rpc')
 | 
			
		||||
 | 
			
		||||
    def make_iq_method_call(self, pto, pmethod, params):
 | 
			
		||||
        iq = self.xmpp.makeIqSet()
 | 
			
		||||
@@ -128,22 +126,22 @@ class xep_0009(base.base_plugin):
 | 
			
		||||
    def _handle_method_call(self, iq):
 | 
			
		||||
        type = iq['type']
 | 
			
		||||
        if type == 'set':
 | 
			
		||||
            log.debug("Incoming Jabber-RPC call from %s" % iq['from'])
 | 
			
		||||
            log.debug("Incoming Jabber-RPC call from %s", iq['from'])
 | 
			
		||||
            self.xmpp.event('jabber_rpc_method_call', iq)
 | 
			
		||||
        else:
 | 
			
		||||
            if type == 'error' and ['rpc_query'] is None:
 | 
			
		||||
                self.handle_error(iq)
 | 
			
		||||
            else:
 | 
			
		||||
                log.debug("Incoming Jabber-RPC error from %s" % iq['from'])
 | 
			
		||||
                log.debug("Incoming Jabber-RPC error from %s", iq['from'])
 | 
			
		||||
                self.xmpp.event('jabber_rpc_error', iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_method_response(self, iq):
 | 
			
		||||
        if iq['rpc_query']['method_response']['fault'] is not None:
 | 
			
		||||
            log.debug("Incoming Jabber-RPC fault from %s" % iq['from'])
 | 
			
		||||
            log.debug("Incoming Jabber-RPC fault from %s", iq['from'])
 | 
			
		||||
            #self._on_jabber_rpc_method_fault(iq)
 | 
			
		||||
            self.xmpp.event('jabber_rpc_method_fault', iq)
 | 
			
		||||
        else:
 | 
			
		||||
            log.debug("Incoming Jabber-RPC response from %s" % iq['from'])
 | 
			
		||||
            log.debug("Incoming Jabber-RPC response from %s", iq['from'])
 | 
			
		||||
            self.xmpp.event('jabber_rpc_method_response', iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_error(self, iq):
 | 
			
		||||
@@ -218,4 +216,3 @@ class xep_0009(base.base_plugin):
 | 
			
		||||
    def _extract_method(self, stanza):
 | 
			
		||||
        xml = ET.fromstring("%s" % stanza)
 | 
			
		||||
        return xml.find("./methodCall/methodName").text
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user