Compare commits
	
		
			1087 Commits
		
	
	
		
			sleek-0.9-
			...
			sleek-1.1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | ||
| 
						 | 
					7945b3e738 | ||
| 
						 | 
					d4c1ff5309 | ||
| 
						 | 
					22868c3924 | ||
| 
						 | 
					2de1be188c | ||
| 
						 | 
					9faecec2db | ||
| 
						 | 
					5d7111fe3b | ||
| 
						 | 
					0c86f8288d | ||
| 
						 | 
					5a6a65fd9f | ||
| 
						 | 
					43c4d23896 | ||
| 
						 | 
					9f9e8db814 | ||
| 
						 | 
					b8efcc7cf0 | ||
| 
						 | 
					2f29d18e53 | ||
| 
						 | 
					888e286a09 | ||
| 
						 | 
					1a93a187f0 | ||
| 
						 | 
					a8d5da5091 | ||
| 
						 | 
					e2720fac9e | ||
| 
						 | 
					4374729f20 | ||
| 
						 | 
					87999333cb | ||
| 
						 | 
					335dc2927b | ||
| 
						 | 
					ccbef6b696 | ||
| 
						 | 
					3e384d3cfe | ||
| 
						 | 
					e33949c397 | ||
| 
						 | 
					eccac859ad | ||
| 
						 | 
					7dd586f2fd | ||
| 
						 | 
					3607c5b792 | ||
| 
						 | 
					e37adace62 | ||
| 
						 | 
					d10f591bf4 | ||
| 
						 | 
					262da78ca7 | ||
| 
						 | 
					0b83edf439 | ||
| 
						 | 
					cf7fcf496e | ||
| 
						 | 
					1765271f84 | ||
| 
						 | 
					0ec79f8dc3 | ||
| 
						 | 
					6f72c05ebf | ||
| 
						 | 
					20cacc84ba | ||
| 
						 | 
					24a14a0284 | ||
| 
						 | 
					982c2d9b83 | ||
| 
						 | 
					efa4a9b330 | ||
| 
						 | 
					39ec1cff19 | ||
| 
						 | 
					24c5f8d374 | ||
| 
						 | 
					d6b0158ddb | ||
| 
						 | 
					7e5e9542e9 | ||
| 
						 | 
					d7fc2aaa9c | ||
| 
						 | 
					8471a485d1 | ||
| 
						 | 
					462b375c8f | ||
| 
						 | 
					afbd506cfc | ||
| 
						 | 
					ec01e45ed1 | ||
| 
						 | 
					993829b23f | ||
| 
						 | 
					002257b820 | ||
| 
						 | 
					0af35c2224 | ||
| 
						 | 
					76bc0a2ba6 | ||
| 
						 | 
					d2dc4824ee | ||
| 
						 | 
					3f9ca0366b | ||
| 
						 | 
					b68785e19e | ||
| 
						 | 
					a1bbb719e1 | ||
| 
						 | 
					46f23f7348 | ||
| 
						 | 
					09252baa71 | ||
| 
						 | 
					3623a7a16a | ||
| 
						 | 
					cc504ab07c | ||
| 
						 | 
					2500a0649b | ||
| 
						 | 
					5ec4e4a026 | ||
| 
						 | 
					c3df4dd052 | ||
| 
						 | 
					730c3fada0 | ||
| 
						 | 
					628978fc8c | ||
| 
						 | 
					7fb9d68714 | ||
| 
						 | 
					e0a1c477d0 | ||
| 
						 | 
					b70565720f | ||
| 
						 | 
					33ac0c9dd6 | ||
| 
						 | 
					4699bdff60 | ||
| 
						 | 
					354641a3ce | ||
| 
						 | 
					58a43e40c7 | ||
| 
						 | 
					6b7fde10d3 | ||
| 
						 | 
					13fdab0139 | ||
| 
						 | 
					2ce617b2ce | ||
| 
						 | 
					63e0496c30 | ||
| 
						 | 
					850e3bb99b | ||
| 
						 | 
					2d90deb96a | ||
| 
						 | 
					3fb3f63e51 | ||
| 
						 | 
					d12949ff1c | ||
| 
						 | 
					e3e985220e | ||
| 
						 | 
					802dd8393d | ||
| 
						 | 
					fe6bc31c60 | ||
| 
						 | 
					2162d6042e | ||
| 
						 | 
					b8a4ffece9 | ||
| 
						 | 
					d929e0deb2 | ||
| 
						 | 
					4c08c9c524 | ||
| 
						 | 
					63b8444abe | ||
| 
						 | 
					82546d776d | ||
| 
						 | 
					84f9505a8d | ||
| 
						 | 
					ede59ab40e | ||
| 
						 | 
					2a80824076 | ||
| 
						 | 
					f92f96325a | ||
| 
						 | 
					b98555c512 | ||
| 
						 | 
					e02a42a008 | ||
| 
						 | 
					3e51126e18 | ||
| 
						 | 
					a714fa82b2 | ||
| 
						 | 
					e86e6eae81 | ||
| 
						 | 
					592c25f352 | ||
| 
						 | 
					015f662249 | ||
| 
						 | 
					8d998d71a3 | ||
| 
						 | 
					f75b6bf955 | ||
| 
						 | 
					fb78bf0996 | ||
| 
						 | 
					cd7cd30b4c | ||
| 
						 | 
					4ea22ff69b | ||
| 
						 | 
					3853898ab3 | ||
| 
						 | 
					7d8aa4157b | ||
| 
						 | 
					3fc20e10f5 | ||
| 
						 | 
					004eabf809 | ||
| 
						 | 
					62230fc970 | ||
| 
						 | 
					961668d420 | ||
| 
						 | 
					01061a0355 | ||
| 
						 | 
					9fdd85d9f1 | ||
| 
						 | 
					331db30f8f | ||
| 
						 | 
					017d7ec62b | ||
| 
						 | 
					76826b5495 | ||
| 
						 | 
					de315ff6d8 | ||
| 
						 | 
					c26b716164 | ||
| 
						 | 
					dcaddb8042 | ||
| 
						 | 
					5ef197e5fd | ||
| 
						 | 
					52ed02bd06 | ||
| 
						 | 
					bd427849fb | ||
| 
						 | 
					127d7acb91 | ||
| 
						 | 
					484efff156 | ||
| 
						 | 
					8f1d0e7a79 | ||
| 
						 | 
					88184ff955 | ||
| 
						 | 
					bd8c110f00 | ||
| 
						 | 
					0050c51124 | ||
| 
						 | 
					9b7ed73f95 | ||
| 
						 | 
					a189cb8333 | ||
| 
						 | 
					0d4825d3ea | ||
| 
						 | 
					156b3200e3 | ||
| 
						 | 
					572becad44 | ||
| 
						 | 
					75f23d1130 | ||
| 
						 | 
					e83fae3a6f | ||
| 
						 | 
					5be5b8c02b | ||
| 
						 | 
					6c4cb2bf00 | ||
| 
						 | 
					148a23579c | ||
| 
						 | 
					ea95811c4c | ||
| 
						 | 
					08cb5f42e7 | ||
| 
						 | 
					9abf37bbd1 | ||
| 
						 | 
					168203c94d | ||
| 
						 | 
					47bc50d9fb | ||
| 
						 | 
					93a4a3f8a0 | ||
| 
						 | 
					940e3eba35 | ||
| 
						 | 
					b7cd119b0c | ||
| 
						 | 
					7f90de887a | ||
| 
						 | 
					6c8a135612 | ||
| 
						 | 
					caec2976d7 | ||
| 
						 | 
					4d8933abdf | ||
| 
						 | 
					6eac0606cf | ||
| 
						 | 
					89cffd43f4 | ||
| 
						 | 
					b9764cc120 | ||
| 
						 | 
					7cd39a6aad | ||
| 
						 | 
					a8f57d012f | ||
| 
						 | 
					a0767f6af6 | ||
| 
						 | 
					9ffdba8643 | ||
| 
						 | 
					9591cd3a7e | ||
| 
						 | 
					afeb8a679a | ||
| 
						 | 
					db92fa2330 | ||
| 
						 | 
					d94517d9ca | ||
| 
						 | 
					0bec040cfe | ||
| 
						 | 
					3918ddb075 | ||
| 
						 | 
					d4091dbde6 | ||
| 
						 | 
					d7fe724145 | ||
| 
						 | 
					ad978700fc | ||
| 
						 | 
					e022b2a36c | ||
| 
						 | 
					ad032e5ed7 | ||
| 
						 | 
					45412fd404 | ||
| 
						 | 
					ccc6ab1281 | ||
| 
						 | 
					712da4c46e | ||
| 
						 | 
					b2d42b1d6c | ||
| 
						 | 
					1d22a04721 | ||
| 
						 | 
					5efb170e1d | ||
| 
						 | 
					cccccdcc0a | ||
| 
						 | 
					8d384ce44f | ||
| 
						 | 
					4d6e7c7dbb | ||
| 
						 | 
					9c5885c6b6 | ||
| 
						 | 
					ec3a14e6d9 | ||
| 
						 | 
					c98f5d4450 | ||
| 
						 | 
					2e8e542bc9 | ||
| 
						 | 
					7ccc67c06d | ||
| 
						 | 
					20df6348a4 | ||
| 
						 | 
					48fb7006f7 | ||
| 
						 | 
					9a6eb333e6 | ||
| 
						 | 
					086bf89d69 | ||
| 
						 | 
					0224d028e7 | ||
| 
						 | 
					540d749695 | ||
| 
						 | 
					219df582da | ||
| 
						 | 
					b898b14b77 | ||
| 
						 | 
					fba235a801 | ||
| 
						 | 
					b0297af38d | ||
| 
						 | 
					04def6d925 | ||
| 
						 | 
					7057984831 | ||
| 
						 | 
					2a2ac73845 | ||
| 
						 | 
					634f5d691b | ||
| 
						 | 
					754ac5092a | ||
| 
						 | 
					9ed972ffeb | ||
| 
						 | 
					3b1f3fddf0 | ||
| 
						 | 
					fa716457a5 | ||
| 
						 | 
					847510c6b5 | ||
| 
						 | 
					774e0f2022 | ||
| 
						 | 
					d8d9e8df16 | ||
| 
						 | 
					d1e12cd46f | ||
| 
						 | 
					adf6d49fd1 | ||
| 
						 | 
					0826a44d4b | ||
| 
						 | 
					ce145b04ac | ||
| 
						 | 
					29d775e675 | ||
| 
						 | 
					251a47db8c | ||
| 
						 | 
					e4b3a191d6 | ||
| 
						 | 
					393d702e77 | ||
| 
						 | 
					4efd41f1ba | ||
| 
						 | 
					58aa944a5e | ||
| 
						 | 
					a3597d6deb | ||
| 
						 | 
					8fada4d015 | ||
| 
						 | 
					5d11ab269d | ||
| 
						 | 
					dd41a85efc | ||
| 
						 | 
					6d59f55fd4 | ||
| 
						 | 
					7a60e4b458 | ||
| 
						 | 
					937dce8e65 | ||
| 
						 | 
					e2d18170b0 | ||
| 
						 | 
					e219c0f976 | ||
| 
						 | 
					5c1562f36b | ||
| 
						 | 
					823c13707d | ||
| 
						 | 
					8eb59072b4 | ||
| 
						 | 
					4266ee0fa4 | ||
| 
						 | 
					3a62908703 | ||
| 
						 | 
					20d053807d | ||
| 
						 | 
					8aa4396e44 | ||
| 
						 | 
					8f9100c762 | ||
| 
						 | 
					788a5b73f9 | ||
| 
						 | 
					1469323350 | ||
| 
						 | 
					83a73ac9b7 | ||
| 
						 | 
					5ed27bf5f6 | ||
| 
						 | 
					a81162edd2 | ||
| 
						 | 
					8080b4cae2 | ||
| 
						 | 
					1735c194cd | ||
| 
						 | 
					6997b2fbf8 | ||
| 
						 | 
					b81ab97900 | ||
| 
						 | 
					384e1a92b7 | ||
| 
						 | 
					ccb0eeefbd | ||
| 
						 | 
					62bdaab7c7 | ||
| 
						 | 
					ec9aed5b75 | ||
| 
						 | 
					9851a2a057 | ||
| 
						 | 
					bb2bc64d15 | ||
| 
						 | 
					7152d93dd0 | ||
| 
						 | 
					baa1eaf73a | ||
| 
						 | 
					4c7da3899e | ||
| 
						 | 
					4bb226147a | ||
| 
						 | 
					4d3593ac86 | ||
| 
						 | 
					c49a8e9114 | ||
| 
						 | 
					6b274a2543 | ||
| 
						 | 
					a269be485f | ||
| 
						 | 
					d3bd9cd31d | ||
| 
						 | 
					6a07e7cbe3 | ||
| 
						 | 
					e694e4a791 | ||
| 
						 | 
					e2de82ac8d | ||
| 
						 | 
					e3b14bc5a9 | ||
| 
						 | 
					9f1648328f | ||
| 
						 | 
					8e9b3d0760 | ||
| 
						 | 
					b1439df72a | ||
| 
						 | 
					8e46aa7054 | ||
| 
						 | 
					5399fdd3a9 | ||
| 
						 | 
					8a22597180 | ||
| 
						 | 
					016aac69f6 | ||
| 
						 | 
					e919906c8c | ||
| 
						 | 
					46dc6eac88 | ||
| 
						 | 
					ea8c40c7b6 | ||
| 
						 | 
					b9bf30e095 | ||
| 
						 | 
					1d891858b6 | ||
| 
						 | 
					b60c51ef13 | ||
| 
						 | 
					f02b0564e0 | ||
| 
						 | 
					ae5ce17035 | ||
| 
						 | 
					6b05938573 | ||
| 
						 | 
					2e1befc8c6 | ||
| 
						 | 
					87ccd804ff | ||
| 
						 | 
					d7ba7cc72a | ||
| 
						 | 
					f125c11a81 | ||
| 
						 | 
					77601f7262 | ||
| 
						 | 
					d94811d81d | ||
| 
						 | 
					6d45971411 | ||
| 
						 | 
					84e2589f22 | ||
| 
						 | 
					bf2f2782b7 | ||
| 
						 | 
					d9be51b2ef | ||
| 
						 | 
					a3d111be12 | ||
| 
						 | 
					4916a12b6f | ||
| 
						 | 
					d6f2e51b05 | ||
| 
						 | 
					feb7f892ea | ||
| 
						 | 
					a420771665 | ||
| 
						 | 
					694673b9bd | ||
| 
						 | 
					393259c24b | ||
| 
						 | 
					f2449009d1 | ||
| 
						 | 
					833f95b53a | ||
| 
						 | 
					306bdd8021 | ||
| 
						 | 
					756c4c032f | ||
| 
						 | 
					4b1fadde4b | ||
| 
						 | 
					a9685a00b3 | ||
| 
						 | 
					e1360ae049 | ||
| 
						 | 
					86a6b40fd8 | ||
| 
						 | 
					7ef6abb2a3 | ||
| 
						 | 
					af45b51f4f | ||
| 
						 | 
					566ec8a5f9 | ||
| 
						 | 
					b048f8d733 | ||
| 
						 | 
					dbf6780345 | ||
| 
						 | 
					450c313340 | ||
| 
						 | 
					dca8516cec | ||
| 
						 | 
					f2c99798a6 | ||
| 
						 | 
					f65f88325b | ||
| 
						 | 
					996ca52471 | ||
| 
						 | 
					6244857746 | ||
| 
						 | 
					5635265203 | ||
| 
						 | 
					42c8f6ae87 | ||
| 
						 | 
					c2161ca56b | ||
| 
						 | 
					45ccb31356 | ||
| 
						 | 
					1a81b2f464 | ||
| 
						 | 
					77251452c1 | ||
| 
						 | 
					4df3aa569b | ||
| 
						 | 
					2e2e16e281 | ||
| 
						 | 
					d5b3a52827 | ||
| 
						 | 
					e4f3b777f9 | ||
| 
						 | 
					a278f79bdb | ||
| 
						 | 
					d709f8db65 | ||
| 
						 | 
					75584d7ad7 | ||
| 
						 | 
					e0f9025e7c | ||
| 
						 | 
					9004e8bbf2 | ||
| 
						 | 
					8b5511c7ec | ||
| 
						 | 
					34f6195ca5 | ||
| 
						 | 
					70af52d74c | ||
| 
						 | 
					ca2b4a188a | ||
| 
						 | 
					0d32638379 | ||
| 
						 | 
					c4b1212c44 | ||
| 
						 | 
					3463bf46c6 | ||
| 
						 | 
					13a01beb07 | ||
| 
						 | 
					145f577bde | ||
| 
						 | 
					30da68f47b | ||
| 
						 | 
					72ead3d598 | ||
| 
						 | 
					4b71fba64c | ||
| 
						 | 
					606c369173 | ||
| 
						 | 
					1ed06bebcd | ||
| 
						 | 
					aa1996eba6 | ||
| 
						 | 
					683f717cf7 | ||
| 
						 | 
					3c871920b1 | ||
| 
						 | 
					de6170a13d | ||
| 
						 | 
					8dbe6f6546 | ||
| 
						 | 
					5313338c3a | ||
| 
						 | 
					1a270dc05c | ||
| 
						 | 
					bd9bf3f1c7 | ||
| 
						 | 
					cd800d636a | ||
| 
						 | 
					40642b2cd1 | ||
| 
						 | 
					35ef8f9090 | ||
| 
						 | 
					38dc35840e | ||
| 
						 | 
					b4004cd4d6 | ||
| 
						 | 
					0c8a8314b2 | ||
| 
						 | 
					4e757c2b56 | ||
| 
						 | 
					c3be6ea0b2 | ||
| 
						 | 
					da332365d4 | ||
| 
						 | 
					f7e7bf601e | ||
| 
						 | 
					6f4c2f22f3 | ||
| 
						 | 
					493df57035 | ||
| 
						 | 
					897a9ac333 | ||
| 
						 | 
					acc2d071ac | ||
| 
						 | 
					d3b1f8c476 | ||
| 
						 | 
					f1db2fc156 | ||
| 
						 | 
					2004ddd678 | ||
| 
						 | 
					cb85d4a529 | ||
| 
						 | 
					ead3af3135 | ||
| 
						 | 
					a2891d7608 | ||
| 
						 | 
					d7dea0c6cc | ||
| 
						 | 
					65931bb384 | ||
| 
						 | 
					632827f213 | ||
| 
						 | 
					b71550cec7 | ||
| 
						 | 
					b68e7bed40 | ||
| 
						 | 
					4be6482ff3 | ||
| 
						 | 
					a21178007f | ||
| 
						 | 
					2e6c27f665 | ||
| 
						 | 
					0a3a7b5a70 | ||
| 
						 | 
					3a12cdbd13 | ||
| 
						 | 
					7d93d1824b | ||
| 
						 | 
					ba0d699d83 | ||
| 
						 | 
					c6ac40c476 | ||
| 
						 | 
					a5d53b3349 | ||
| 
						 | 
					4487a90623 | ||
| 
						 | 
					05da8cc3d1 | ||
| 
						 | 
					fe3f8dde4b | ||
| 
						 | 
					23e499998f | ||
| 
						 | 
					acdf9e2d22 | ||
| 
						 | 
					2076d506b4 | ||
| 
						 | 
					68ce47c905 | ||
| 
						 | 
					7c7fa0f008 | ||
| 
						 | 
					a8e3657487 | ||
| 
						 | 
					13a2f719f4 | ||
| 
						 | 
					2908751020 | ||
| 
						 | 
					c156a4f723 | ||
| 
						 | 
					8b29431cde | ||
| 
						 | 
					4b145958fa | ||
| 
						 | 
					e08b0054b2 | ||
| 
						 | 
					596e135a03 | ||
| 
						 | 
					e55e213c78 | ||
| 
						 | 
					8749f5e09b | ||
| 
						 | 
					b3353183f3 | ||
| 
						 | 
					3657bf6636 | ||
| 
						 | 
					f97f6e5985 | ||
| 
						 | 
					34c374a1e1 | ||
| 
						 | 
					506eccf84d | ||
| 
						 | 
					982bf3b2ec | ||
| 
						 | 
					53a5026301 | ||
| 
						 | 
					0aee445e69 | ||
| 
						 | 
					adade2e5ec | ||
| 
						 | 
					cbc42c29fb | ||
| 
						 | 
					874c51d74d | ||
| 
						 | 
					f9ac95ddb7 | ||
| 
						 | 
					0ea014fe41 | ||
| 
						 | 
					62b190d0ff | ||
| 
						 | 
					4b57b8131f | ||
| 
						 | 
					988a90a176 | ||
| 
						 | 
					67775fb8bd | ||
| 
						 | 
					e81683beee | ||
| 
						 | 
					d9c25ee65c | ||
| 
						 | 
					1ebc7f4d4b | ||
| 
						 | 
					2c5b77ae2e | ||
| 
						 | 
					d8aae88526 | ||
| 
						 | 
					2f4bdfee1b | ||
| 
						 | 
					c16913c999 | ||
| 
						 | 
					f4451fe6b7 | ||
| 
						 | 
					8d4e77aba6 | ||
| 
						 | 
					f474d378ef | ||
| 
						 | 
					defc252c7d | ||
| 
						 | 
					19bd1e0485 | ||
| 
						 | 
					5f2fc67c40 | ||
| 
						 | 
					8ead33fc3b | ||
| 
						 | 
					ab25301953 | ||
| 
						 | 
					291b118aca | ||
| 
						 | 
					12b61365ad | ||
| 
						 | 
					db7fb10e95 | ||
| 
						 | 
					9214dc6610 | ||
| 
						 | 
					58b95e4ae4 | ||
| 
						 | 
					60d3afe6b6 | ||
| 
						 | 
					afeb8f3f7c | ||
| 
						 | 
					cdbc0570ca | ||
| 
						 | 
					e648f08bad | ||
| 
						 | 
					7ba6d5e02d | ||
| 
						 | 
					ea48bb5ac5 | ||
| 
						 | 
					6ee8a2980c | ||
| 
						 | 
					debf909359 | ||
| 
						 | 
					b8114b25ed | ||
| 
						 | 
					0d4d84b2fa | ||
| 
						 | 
					d2aca3e7e0 | ||
| 
						 | 
					26aca2b789 | ||
| 
						 | 
					5424ede413 | ||
| 
						 | 
					20112f8e16 | ||
| 
						 | 
					4260a754e5 | ||
| 
						 | 
					ce30f72738 | ||
| 
						 | 
					69d430dd75 | ||
| 
						 | 
					673545c7e4 | ||
| 
						 | 
					45991e47ee | ||
| 
						 | 
					b8f40eb843 | ||
| 
						 | 
					b73a859031 | ||
| 
						 | 
					9dbf246f0b | ||
| 
						 | 
					4fb77ac878 | ||
| 
						 | 
					d0c506f930 | ||
| 
						 | 
					7351fe1a02 | ||
| 
						 | 
					38c2f51f83 | ||
| 
						 | 
					1bf34caa5b | ||
| 
						 | 
					5769935720 | ||
| 
						 | 
					0214db7545 | ||
| 
						 | 
					ffc6f031d9 | ||
| 
						 | 
					9e248bb852 | ||
| 
						 | 
					973890e2c9 | ||
| 
						 | 
					9c08e56ed0 | ||
| 
						 | 
					4f69a03bb1 | ||
| 
						 | 
					c2c18acd6a | ||
| 
						 | 
					12ead36f96 | ||
| 
						 | 
					41a642e06c | ||
| 
						 | 
					c6ed4b8a1d | ||
| 
						 | 
					65aa6573df | ||
| 
						 | 
					b888610525 | ||
| 
						 | 
					6d68706326 | ||
| 
						 | 
					5bdcd9ef9d | ||
| 
						 | 
					2eff35cc7a | ||
| 
						 | 
					ac330b5c6c | ||
| 
						 | 
					46ffa8e9fe | ||
| 
						 | 
					03847497cc | ||
| 
						 | 
					185d7cf28e | ||
| 
						 | 
					8aa3d0c047 | ||
| 
						 | 
					9e3d506651 | ||
| 
						 | 
					2f3ff37a24 | ||
| 
						 | 
					1f09d60a52 | ||
| 
						 | 
					d528884723 | ||
| 
						 | 
					d9aff3d36f | ||
| 
						 | 
					04cc48775d | ||
| 
						 | 
					27ebb6e8f6 | ||
| 
						 | 
					8f55704928 | ||
| 
						 | 
					d88999691c | ||
| 
						 | 
					c4699b92e6 | ||
| 
						 | 
					ce69213a1e | ||
| 
						 | 
					77eab6544f | ||
| 
						 | 
					11264fe0a8 | ||
| 
						 | 
					11a6e6d2e0 | ||
| 
						 | 
					6e34b2cfdd | ||
| 
						 | 
					e18354ae0e | ||
| 
						 | 
					4375ac7d8b | ||
| 
						 | 
					faec86b3be | ||
| 
						 | 
					505a63da3a | ||
| 
						 | 
					93fbcad277 | ||
| 
						 | 
					3625573c7d | ||
| 
						 | 
					d9e7f555e6 | ||
| 
						 | 
					2755d732a4 | ||
| 
						 | 
					2d18d905a5 | ||
| 
						 | 
					4eb4d729ee | ||
| 
						 | 
					8b5c1010de | ||
| 
						 | 
					95ad8a1878 | ||
| 
						 | 
					aeb7999e6a | ||
| 
						 | 
					8468332381 | ||
| 
						 | 
					dc001bb201 | ||
| 
						 | 
					0d0b963fe5 | ||
| 
						 | 
					a41a4369c6 | ||
| 
						 | 
					7ad7a29a8f | ||
| 
						 | 
					b0e036d03c | ||
| 
						 | 
					a8b948cd33 | ||
| 
						 | 
					e02ffe8547 | ||
| 
						 | 
					42bfca1c87 | ||
| 
						 | 
					0fffbb8200 | ||
| 
						 | 
					21c32c6e1c | ||
| 
						 | 
					75a051556f | ||
| 
						 | 
					78141fe5f3 | ||
| 
						 | 
					88d21d210c | ||
| 
						 | 
					799645f13f | ||
| 
						 | 
					f234dc02cf | ||
| 
						 | 
					c294c1a85c | ||
| 
						 | 
					cbe76c8a70 | ||
| 
						 | 
					77b8f0f4bb | ||
| 
						 | 
					259f91d2bd | ||
| 
						 | 
					ed366b338d | ||
| 
						 | 
					9e2cada19e | ||
| 
						 | 
					d0ccbf6b7a | ||
| 
						 | 
					e1866ab328 | ||
| 
						 | 
					3ffa09ba7c | ||
| 
						 | 
					a7410f2146 | ||
| 
						 | 
					178608f4c0 | ||
| 
						 | 
					19ee6979a5 | ||
| 
						 | 
					9f0baec7b2 | ||
| 
						 | 
					433c147627 | ||
| 
						 | 
					9a34c9a9a1 | ||
| 
						 | 
					2662131124 | ||
| 
						 | 
					bb219595a7 | ||
| 
						 | 
					fcdd57ce54 | ||
| 
						 | 
					5522443e0e | ||
| 
						 | 
					55cfe69fef | ||
| 
						 | 
					6de87a1cbf | ||
| 
						 | 
					7c10ff16fb | ||
| 
						 | 
					c258d2f19d | ||
| 
						 | 
					d576e32f7a | ||
| 
						 | 
					4a2e7c5393 | ||
| 
						 | 
					0b4320a196 | ||
| 
						 | 
					9bef4b4d4d | ||
| 
						 | 
					5c3066ba30 | ||
| 
						 | 
					576eefb097 | ||
| 
						 | 
					aebd115ba2 | ||
| 
						 | 
					6dfea828be | ||
| 
						 | 
					3749c1b88c | ||
| 
						 | 
					998741b87e | ||
| 
						 | 
					9c62bce206 | ||
| 
						 | 
					f5ae27da4f | ||
| 
						 | 
					89fb15e896 | ||
| 
						 | 
					906aa0bd68 | ||
| 
						 | 
					bb6f4af8e2 | ||
| 
						 | 
					6677df39f2 | ||
| 
						 | 
					a2c515bc97 | ||
| 
						 | 
					ca6ce26b0d | ||
| 
						 | 
					37ff17b0cb | ||
| 
						 | 
					00d7952001 | ||
| 
						 | 
					56766508b3 | ||
| 
						 | 
					5c59f5baca | ||
| 
						 | 
					e16b37d2be | ||
| 
						 | 
					d68bc2ba07 | ||
| 
						 | 
					10298a6eab | ||
| 
						 | 
					a3580dcef9 | ||
| 
						 | 
					1eaa9cb28c | ||
| 
						 | 
					5d458bf6c2 | ||
| 
						 | 
					2fa58a74ab | ||
| 
						 | 
					c8f406d1b3 | ||
| 
						 | 
					203986dd7c | ||
| 
						 | 
					f4ecf0bac4 | ||
| 
						 | 
					345656926e | ||
| 
						 | 
					c05ddcb7f5 | ||
| 
						 | 
					eb9e72fe3e | ||
| 
						 | 
					8a0616b3e0 | ||
| 
						 | 
					b71cfe0492 | ||
| 
						 | 
					fac3bca1f6 | ||
| 
						 | 
					d150b35464 | ||
| 
						 | 
					21b7109c06 | ||
| 
						 | 
					e4240dd593 | ||
| 
						 | 
					2f6f4fc16d | ||
| 
						 | 
					fe49b8c377 | ||
| 
						 | 
					b580a3138d | ||
| 
						 | 
					c20fab0f6c | ||
| 
						 | 
					c721fb4126 | ||
| 
						 | 
					415520200e | ||
| 
						 | 
					747001d33c | ||
| 
						 | 
					b0fb205c16 | ||
| 
						 | 
					4b52007e8c | ||
| 
						 | 
					5da7bd1866 | ||
| 
						 | 
					22134c302b | ||
| 
						 | 
					b40a489796 | ||
| 
						 | 
					7a5ef28492 | ||
| 
						 | 
					c09e9c702c | ||
| 
						 | 
					48ba7292bc | ||
| 
						 | 
					4d1f071f83 | ||
| 
						 | 
					0d0c044a68 | ||
| 
						 | 
					3c0dfb56e6 | ||
| 
						 | 
					e077204a16 | ||
| 
						 | 
					58f77d898f | ||
| 
						 | 
					c54466596f | ||
| 
						 | 
					aa1dbe97e0 | ||
| 
						 | 
					fec69be731 | ||
| 
						 | 
					956fdf6970 | ||
| 
						 | 
					183a3f1b87 | ||
| 
						 | 
					18683d2b75 | ||
| 
						 | 
					41ab2b8460 | ||
| 
						 | 
					939ae298c2 | ||
| 
						 | 
					851e90c572 | ||
| 
						 | 
					ecde696468 | ||
| 
						 | 
					1cedea2804 | ||
| 
						 | 
					cbed8029ba | ||
| 
						 | 
					1da3e5b35e | ||
| 
						 | 
					a96a046e27 | ||
| 
						 | 
					60a183b011 | ||
| 
						 | 
					a49f511a2f | ||
| 
						 | 
					25f43bd219 | ||
| 
						 | 
					d148f633f3 | ||
| 
						 | 
					e8e934fa95 | ||
| 
						 | 
					bd92ef6acf | ||
| 
						 | 
					aa02ecd154 | ||
| 
						 | 
					aad185fe29 | ||
| 
						 | 
					2b6454786a | ||
| 
						 | 
					a349a2a317 | ||
| 
						 | 
					2cb82afc2c | ||
| 
						 | 
					c8989c04f3 | ||
| 
						 | 
					241aba8c76 | ||
| 
						 | 
					ec860bf9e2 | ||
| 
						 | 
					73a3d07ad9 | ||
| 
						 | 
					07208a3eaf | ||
| 
						 | 
					d0a5c539d8 | ||
| 
						 | 
					d70a6e6f32 | ||
| 
						 | 
					66e92c6c9f | ||
| 
						 | 
					ca2c421e6c | ||
| 
						 | 
					9fcd2e93a3 | ||
| 
						 | 
					75afefb5c6 | ||
| 
						 | 
					b67b930596 | ||
| 
						 | 
					5c9b47afbd | ||
| 
						 | 
					7ad0143687 | ||
| 
						 | 
					de24e9ed45 | ||
| 
						 | 
					9724efa123 | ||
| 
						 | 
					690eaf8d3c | ||
| 
						 | 
					f505e229d6 | ||
| 
						 | 
					9ca4bba2de | ||
| 
						 | 
					bb927c7e6a | ||
| 
						 | 
					14f1c3ba51 | ||
| 
						 | 
					278a8bb443 | ||
| 
						 | 
					85ee30539d | ||
| 
						 | 
					f74baf1c23 | ||
| 
						 | 
					b5a14a0190 | ||
| 
						 | 
					fec8578cf6 | ||
| 
						 | 
					f80b3285d4 | ||
| 
						 | 
					130a148d34 | ||
| 
						 | 
					16104b6e56 | ||
| 
						 | 
					d5e42ac0e7 | ||
| 
						 | 
					e6bec8681e | ||
| 
						 | 
					797e92a6a3 | ||
| 
						 | 
					1ef112966b | ||
| 
						 | 
					078c71ed3f | ||
| 
						 | 
					bae082f437 | ||
| 
						 | 
					35212c7991 | ||
| 
						 | 
					48f0843ace | ||
| 
						 | 
					b1c997be1d | ||
| 
						 | 
					d0cb400c54 | ||
| 
						 | 
					7f8179d91e | ||
| 
						 | 
					37ada49802 | ||
| 
						 | 
					5c76d969f7 | ||
| 
						 | 
					059cc9ccc4 | ||
| 
						 | 
					309c9e74eb | ||
| 
						 | 
					6041cd1952 | ||
| 
						 | 
					acb53ba371 | ||
| 
						 | 
					646a609c0b | ||
| 
						 | 
					8bb0f5e34c | ||
| 
						 | 
					3c939313d2 | ||
| 
						 | 
					9962f1a664 | ||
| 
						 | 
					253de8518c | ||
| 
						 | 
					a38735cb2a | ||
| 
						 | 
					e700a54d11 | ||
| 
						 | 
					6469cdb4ca | ||
| 
						 | 
					18e27d65ce | ||
| 
						 | 
					0c39567f20 | ||
| 
						 | 
					f5491c901f | ||
| 
						 | 
					f5cae85af5 | ||
| 
						 | 
					01e8040a07 | ||
| 
						 | 
					aa916c9ac8 | ||
| 
						 | 
					332eea3b3b | ||
| 
						 | 
					109af1b1b6 | ||
| 
						 | 
					629f6e76a9 | ||
| 
						 | 
					82a3918aa4 | ||
| 
						 | 
					cff3079a04 | ||
| 
						 | 
					4f864a07f5 | ||
| 
						 | 
					938066bd50 | ||
| 
						 | 
					9fee87c258 | ||
| 
						 | 
					fd573880eb | ||
| 
						 | 
					2f1ba368e2 | ||
| 
						 | 
					bde1818400 | ||
| 
						 | 
					3a28f9e5d2 | ||
| 
						 | 
					0bda5fd3f2 | ||
| 
						 | 
					1e3a6e1b5f | ||
| 
						 | 
					fa92bc866b | ||
| 
						 | 
					f4bc9d9722 | ||
| 
						 | 
					9cfe19c1e1 | ||
| 
						 | 
					f18c790824 | ||
| 
						 | 
					f165b4b52b | ||
| 
						 | 
					7ebc006516 | ||
| 
						 | 
					5ca4ede5ac | ||
| 
						 | 
					35f4ef3452 | ||
| 
						 | 
					828cba875f | ||
| 
						 | 
					3920ee3941 | ||
| 
						 | 
					feaa7539af | ||
| 
						 | 
					c004f042f9 | ||
| 
						 | 
					ae41c08fec | ||
| 
						 | 
					223507f36f | 
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,9 @@
 | 
				
			|||||||
*.pyc
 | 
					*.pyc
 | 
				
			||||||
.project
 | 
					 | 
				
			||||||
build/
 | 
					build/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					MANIFEST
 | 
				
			||||||
 | 
					docs/_build/
 | 
				
			||||||
*.swp
 | 
					*.swp
 | 
				
			||||||
.pydevproject
 | 
					.tox/
 | 
				
			||||||
.settings
 | 
					.coverage
 | 
				
			||||||
 | 
					sleekxmpp.egg-info/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								INSTALL
									
									
									
									
									
								
							@@ -1,8 +1,12 @@
 | 
				
			|||||||
Pre-requisites:
 | 
					Pre-requisites:
 | 
				
			||||||
Python 3.1 or 2.6
 | 
					- Python 3.1 or 2.6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Install:
 | 
					Install:
 | 
				
			||||||
python3 setup.py install
 | 
					> python3 setup.py install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Root install:
 | 
					Root install:
 | 
				
			||||||
sudo python3 setup.py install
 | 
					> sudo python3 setup.py install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To test:
 | 
				
			||||||
 | 
					> cd examples
 | 
				
			||||||
 | 
					> python echo_client.py -v -j [USER@example.com] -p [PASSWORD]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										152
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								LICENSE
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
Copyright (c) 2010 ICRL
 | 
					Copyright (c) 2010 Nathanael C. Fritz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
of this software and associated documentation files (the "Software"), to deal
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
@@ -17,3 +17,153 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			|||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
					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
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
				
			||||||
THE SOFTWARE.
 | 
					THE SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licenses of Bundled Third Party Code
 | 
				
			||||||
 | 
					------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dateutil - Extensions to the standard python 2.3+ datetime module.
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					    * Neither the name of the copyright holder nor the names of its
 | 
				
			||||||
 | 
					      contributors may be used to endorse or promote products derived from
 | 
				
			||||||
 | 
					      this software without specific prior written permission.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
				
			||||||
 | 
					"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 OWNER OR
 | 
				
			||||||
 | 
					CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 | 
				
			||||||
 | 
					EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
				
			||||||
 | 
					PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 | 
				
			||||||
 | 
					PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 | 
				
			||||||
 | 
					LIABILITY, 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fixed_datetime
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2008, Red Innovation Ltd., Finland
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 | 
					    * Neither the name of Red Innovation nor the names of its contributors 
 | 
				
			||||||
 | 
					      may be used to endorse or promote products derived from this software 
 | 
				
			||||||
 | 
					      without specific prior written permission.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
 | 
				
			||||||
 | 
					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 RED INNOVATION 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OrderedDict - A port of the Python 2.7+ OrderedDict to Python 2.6
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2009 Raymond Hettinger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person
 | 
				
			||||||
 | 
					obtaining a copy of this software and associated documentation files
 | 
				
			||||||
 | 
					(the "Software"), to deal in the Software without restriction,
 | 
				
			||||||
 | 
					including without limitation the rights to use, copy, modify, merge,
 | 
				
			||||||
 | 
					publish, distribute, sublicense, and/or sell copies of the Software,
 | 
				
			||||||
 | 
					and to permit persons to whom the Software is furnished to do so,
 | 
				
			||||||
 | 
					subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The above copyright notice and this permission notice shall be
 | 
				
			||||||
 | 
					    included in all copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 | 
				
			||||||
 | 
					    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 | 
				
			||||||
 | 
					    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 | 
				
			||||||
 | 
					    NONINFRINGEMENT. IN NO EVENT SHALL THE 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This software is subject to "The MIT License"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright 2007-2010 David Alan Cridland
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in
 | 
				
			||||||
 | 
					all copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					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.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								MANIFEST
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								MANIFEST
									
									
									
									
									
								
							@@ -1,39 +0,0 @@
 | 
				
			|||||||
setup.py
 | 
					 | 
				
			||||||
sleekxmpp/__init__.py
 | 
					 | 
				
			||||||
sleekxmpp/basexmpp.py
 | 
					 | 
				
			||||||
sleekxmpp/clientxmpp.py
 | 
					 | 
				
			||||||
sleekxmpp/example.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/__init__.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/base.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/gmail_notify.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0004.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0009.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0030.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0045.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0050.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0060.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0078.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0086.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0092.py
 | 
					 | 
				
			||||||
sleekxmpp/plugins/xep_0199.py
 | 
					 | 
				
			||||||
sleekxmpp/stanza/__init__.py
 | 
					 | 
				
			||||||
sleekxmpp/stanza/iq.py
 | 
					 | 
				
			||||||
sleekxmpp/stanza/message.py
 | 
					 | 
				
			||||||
sleekxmpp/stanza/presence.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/__init__.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/stanzabase.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/statemachine.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/test.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/testclient.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/xmlstream.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/handler/__init__.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/handler/base.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/handler/callback.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/handler/waiter.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/handler/xmlcallback.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/handler/xmlwaiter.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/matcher/__init__.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/matcher/base.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/matcher/many.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/matcher/xmlmask.py
 | 
					 | 
				
			||||||
sleekxmpp/xmlstream/matcher/xpath.py
 | 
					 | 
				
			||||||
							
								
								
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					include README.rst
 | 
				
			||||||
 | 
					include LICENSE
 | 
				
			||||||
 | 
					include testall.py
 | 
				
			||||||
 | 
					recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png
 | 
				
			||||||
 | 
					recursive-include examples *.py
 | 
				
			||||||
 | 
					recursive-include tests *.py
 | 
				
			||||||
							
								
								
									
										42
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								README
									
									
									
									
									
								
							@@ -1,42 +0,0 @@
 | 
				
			|||||||
SleekXMPP is an XMPP library written for Python 3.1+ (with 2.6 compatibility).
 | 
					 | 
				
			||||||
Hosted at http://wiki.github.com/fritzy/SleekXMPP/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre
 | 
					 | 
				
			||||||
If you're coming here from The Definitive Guide, please read http://wiki.github.com/fritzy/SleekXMPP/xmpp-the-definitive-guide
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SleekXMPP has several design goals/philosophies:
 | 
					 | 
				
			||||||
- Low number of dependencies.
 | 
					 | 
				
			||||||
- Every XEP as a plugin.
 | 
					 | 
				
			||||||
- Rewarding to work with.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The goals for 1.0 include (and we're getting close):
 | 
					 | 
				
			||||||
- Nearly Full test coverage of stanzas.
 | 
					 | 
				
			||||||
- Wide range of functional tests.
 | 
					 | 
				
			||||||
- Stanza objects for all interaction with the stream
 | 
					 | 
				
			||||||
- Documentation on using and extending SleekXMPP.
 | 
					 | 
				
			||||||
- Complete documentation on all implemented stanza objects
 | 
					 | 
				
			||||||
- Documentation on all examples used in XMPP: The Definitive Guide
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1.1 will include:
 | 
					 | 
				
			||||||
- More functional and unit tests
 | 
					 | 
				
			||||||
- PEP-8 compliance
 | 
					 | 
				
			||||||
- XEP-225 support
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Since 0.2, here's the Changelog:
 | 
					 | 
				
			||||||
- MANY bugfixes
 | 
					 | 
				
			||||||
- Re-implementation of handlers/threading to greatly simplify and remove bugs (no more spawning threads in handlers)
 | 
					 | 
				
			||||||
- Stanza objects for jabber:client and all implemented XEPs
 | 
					 | 
				
			||||||
- Raising XMPPError for jabber:client and extended errors in handlers
 | 
					 | 
				
			||||||
- Robust error handling and better insurance of iq responses
 | 
					 | 
				
			||||||
- Stanza objects have made life a lot easier!
 | 
					 | 
				
			||||||
- Massive audit/cleanup.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Credits
 | 
					 | 
				
			||||||
----------------
 | 
					 | 
				
			||||||
Main Author: Nathan Fritz fritz@netflint.net
 | 
					 | 
				
			||||||
Contributors: Kevin Smith & Lance Stout
 | 
					 | 
				
			||||||
Patches: Remko Tronçon
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Feel free to add fritzy@netflint.net to your roster for direct support and comments.
 | 
					 | 
				
			||||||
Join sleekxmpp-discussion@googlegroups.com / http://groups.google.com/group/sleekxmpp-discussion for email discussion.
 | 
					 | 
				
			||||||
Join sleek@conference.jabber.org for groupchat discussion.
 | 
					 | 
				
			||||||
							
								
								
									
										180
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
				
			|||||||
 | 
					SleekXMPP
 | 
				
			||||||
 | 
					#########
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+,
 | 
				
			||||||
 | 
					and is featured in examples in
 | 
				
			||||||
 | 
					`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ 
 | 
				
			||||||
 | 
					by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
 | 
				
			||||||
 | 
					here from reading the Definitive Guide, please see the notes on updating
 | 
				
			||||||
 | 
					the examples to the latest version of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP's design goals and philosphy are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Low number of dependencies**
 | 
				
			||||||
 | 
					    Installing and using SleekXMPP should be as simple as possible, without
 | 
				
			||||||
 | 
					    having to deal with long dependency chains.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    As part of reducing the number of dependencies, some third party
 | 
				
			||||||
 | 
					    modules are included with SleekXMPP in the ``thirdparty`` directory.
 | 
				
			||||||
 | 
					    Imports from this module first try to import an existing installed
 | 
				
			||||||
 | 
					    version before loading the packaged version, when possible.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Every XEP as a plugin**
 | 
				
			||||||
 | 
					    Following Python's "batteries included" approach, the goal is to
 | 
				
			||||||
 | 
					    provide support for all currently active XEPs (final and draft). Since
 | 
				
			||||||
 | 
					    adding XEP support is done through easy to create plugins, the hope is
 | 
				
			||||||
 | 
					    to also provide a solid base for implementing and creating experimental
 | 
				
			||||||
 | 
					    XEPs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Rewarding to work with**
 | 
				
			||||||
 | 
					    As much as possible, SleekXMPP should allow things to "just work" using
 | 
				
			||||||
 | 
					    sensible defaults and appropriate abstractions. XML can be ugly to work
 | 
				
			||||||
 | 
					    with, but it doesn't have to be that way.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Get the Code
 | 
				
			||||||
 | 
					------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Get the latest stable version from PyPI::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pip install sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The latest source code for SleekXMPP may be found on `Github
 | 
				
			||||||
 | 
					<http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the
 | 
				
			||||||
 | 
					``master`` branch, while the latest development version is in the
 | 
				
			||||||
 | 
					``develop`` branch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Latest Release**
 | 
				
			||||||
 | 
					    - `1.1.1 <http://github.com/fritzy/SleekXMPP/zipball/1.1.1>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**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
 | 
				
			||||||
 | 
					install the ``python3`` branch::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    git clone http://github.com/rthalley/dnspython
 | 
				
			||||||
 | 
					    cd dnspython
 | 
				
			||||||
 | 
					    git checkout python3
 | 
				
			||||||
 | 
					    python3 setup.py install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Discussion
 | 
				
			||||||
 | 
					----------
 | 
				
			||||||
 | 
					A mailing list and XMPP chat room are available for discussing and getting
 | 
				
			||||||
 | 
					help with SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Mailing List**
 | 
				
			||||||
 | 
					    `SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Chat**
 | 
				
			||||||
 | 
					    `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Documentation and Testing
 | 
				
			||||||
 | 
					-------------------------
 | 
				
			||||||
 | 
					Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``.
 | 
				
			||||||
 | 
					To generate the Sphinx documentation, follow the commands below. The HTML output will
 | 
				
			||||||
 | 
					be in ``docs/_build/html``::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cd docs
 | 
				
			||||||
 | 
					    make html
 | 
				
			||||||
 | 
					    open _build/html/index.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To run the test suite for SleekXMPP::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    python testall.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The SleekXMPP Boilerplate
 | 
				
			||||||
 | 
					-------------------------
 | 
				
			||||||
 | 
					Projects using SleekXMPP tend to follow a basic pattern for setting up client/component
 | 
				
			||||||
 | 
					connections and configuration. Here is the gist of the boilerplate needed for a SleekXMPP
 | 
				
			||||||
 | 
					based project. See the documetation or examples directory for more detailed archetypes for
 | 
				
			||||||
 | 
					SleekXMPP projects::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 former member of 
 | 
				
			||||||
 | 
					    the XMPP Council.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Co-Author:** Lance Stout
 | 
				
			||||||
 | 
					    `lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_, 
 | 
				
			||||||
 | 
					    `@lancestout <http://twitter.com/lancestout>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Contributors:**
 | 
				
			||||||
 | 
					    - Brian Beggs (`macdiesel <http://github.com/macdiesel>`_)
 | 
				
			||||||
 | 
					    - Dann Martens (`dannmartens <http://github.com/dannmartens>`_)
 | 
				
			||||||
 | 
					    - Florent Le Coz (`louiz <http://github.com/louiz>`_)
 | 
				
			||||||
 | 
					    - Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk)
 | 
				
			||||||
 | 
					    - Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be)
 | 
				
			||||||
 | 
					    - Te-jé Rogers (`te-je <http://github.com/te-je>`_)
 | 
				
			||||||
 | 
					    - Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_)
 | 
				
			||||||
@@ -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
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								docs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					_build/*
 | 
				
			||||||
							
								
								
									
										130
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								docs/Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
				
			|||||||
 | 
					# Makefile for Sphinx documentation
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# You can set these variables from the command line.
 | 
				
			||||||
 | 
					SPHINXOPTS    =
 | 
				
			||||||
 | 
					SPHINXBUILD   = sphinx-build
 | 
				
			||||||
 | 
					PAPER         =
 | 
				
			||||||
 | 
					BUILDDIR      = _build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Internal variables.
 | 
				
			||||||
 | 
					PAPEROPT_a4     = -D latex_paper_size=a4
 | 
				
			||||||
 | 
					PAPEROPT_letter = -D latex_paper_size=letter
 | 
				
			||||||
 | 
					ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					help:
 | 
				
			||||||
 | 
						@echo "Please use \`make <target>' where <target> is one of"
 | 
				
			||||||
 | 
						@echo "  html       to make standalone HTML files"
 | 
				
			||||||
 | 
						@echo "  dirhtml    to make HTML files named index.html in directories"
 | 
				
			||||||
 | 
						@echo "  singlehtml to make a single large HTML file"
 | 
				
			||||||
 | 
						@echo "  pickle     to make pickle files"
 | 
				
			||||||
 | 
						@echo "  json       to make JSON files"
 | 
				
			||||||
 | 
						@echo "  htmlhelp   to make HTML files and a HTML help project"
 | 
				
			||||||
 | 
						@echo "  qthelp     to make HTML files and a qthelp project"
 | 
				
			||||||
 | 
						@echo "  devhelp    to make HTML files and a Devhelp project"
 | 
				
			||||||
 | 
						@echo "  epub       to make an epub"
 | 
				
			||||||
 | 
						@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
 | 
				
			||||||
 | 
						@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
 | 
				
			||||||
 | 
						@echo "  text       to make text files"
 | 
				
			||||||
 | 
						@echo "  man        to make manual pages"
 | 
				
			||||||
 | 
						@echo "  changes    to make an overview of all changed/added/deprecated items"
 | 
				
			||||||
 | 
						@echo "  linkcheck  to check all external links for integrity"
 | 
				
			||||||
 | 
						@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clean:
 | 
				
			||||||
 | 
						-rm -rf $(BUILDDIR)/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dirhtml:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					singlehtml:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pickle:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished; now you can process the pickle files."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					json:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished; now you can process the JSON files."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					htmlhelp:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished; now you can run HTML Help Workshop with the" \
 | 
				
			||||||
 | 
						      ".hhp project file in $(BUILDDIR)/htmlhelp."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qthelp:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished; now you can run "qcollectiongenerator" with the" \
 | 
				
			||||||
 | 
						      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
 | 
				
			||||||
 | 
						@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SleekXMPP.qhcp"
 | 
				
			||||||
 | 
						@echo "To view the help file:"
 | 
				
			||||||
 | 
						@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SleekXMPP.qhc"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					devhelp:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished."
 | 
				
			||||||
 | 
						@echo "To view the help file:"
 | 
				
			||||||
 | 
						@echo "# mkdir -p $$HOME/.local/share/devhelp/SleekXMPP"
 | 
				
			||||||
 | 
						@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SleekXMPP"
 | 
				
			||||||
 | 
						@echo "# devhelp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					epub:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					latex:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
 | 
				
			||||||
 | 
						@echo "Run \`make' in that directory to run these through (pdf)latex" \
 | 
				
			||||||
 | 
						      "(use \`make latexpdf' here to do that automatically)."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					latexpdf:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
 | 
				
			||||||
 | 
						@echo "Running LaTeX files through pdflatex..."
 | 
				
			||||||
 | 
						make -C $(BUILDDIR)/latex all-pdf
 | 
				
			||||||
 | 
						@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					text:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished. The text files are in $(BUILDDIR)/text."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					man:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					changes:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "The overview file is in $(BUILDDIR)/changes."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					linkcheck:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
 | 
				
			||||||
 | 
						@echo
 | 
				
			||||||
 | 
						@echo "Link check complete; look for any errors in the above output " \
 | 
				
			||||||
 | 
						      "or in $(BUILDDIR)/linkcheck/output.txt."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					doctest:
 | 
				
			||||||
 | 
						$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
 | 
				
			||||||
 | 
						@echo "Testing of doctests in the sources finished, look at the " \
 | 
				
			||||||
 | 
						      "results in $(BUILDDIR)/doctest/output.txt."
 | 
				
			||||||
							
								
								
									
										452
									
								
								docs/_static/agogo.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										452
									
								
								docs/_static/agogo.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,452 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * agogo.css_t
 | 
				
			||||||
 | 
					 * ~~~~~~~~~~~
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Sphinx stylesheet -- agogo theme.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
				
			||||||
 | 
					 * :license: BSD, see LICENSE for details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* {
 | 
				
			||||||
 | 
					  margin: 0px;
 | 
				
			||||||
 | 
					  padding: 0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					  font-family: "Verdana", Arial, sans-serif;
 | 
				
			||||||
 | 
					  line-height: 1.4em;
 | 
				
			||||||
 | 
					  color: black;
 | 
				
			||||||
 | 
					  background-color: #eeeeec;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Page layout */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header, div.content, div.footer {
 | 
				
			||||||
 | 
					  width: 70em;
 | 
				
			||||||
 | 
					  margin-left: auto;
 | 
				
			||||||
 | 
					  margin-right: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header-wrapper {
 | 
				
			||||||
 | 
					  background: url(bgtop.png) top left repeat-x;
 | 
				
			||||||
 | 
					  border-bottom: 3px solid #2e3436;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Default body styles */
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					  color: #ce5c00;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.bodywrapper a, div.footer a {
 | 
				
			||||||
 | 
					  text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.clearer {
 | 
				
			||||||
 | 
					  clear: both;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.left {
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.right {
 | 
				
			||||||
 | 
					  float: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.line-block {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin-top: 1em;
 | 
				
			||||||
 | 
					    margin-bottom: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.line-block .line-block {
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					    margin-left: 1.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1, h2, h3, h4 {
 | 
				
			||||||
 | 
					  font-family: "Georgia", "Times New Roman", serif;
 | 
				
			||||||
 | 
					  font-weight: normal;
 | 
				
			||||||
 | 
					  color: #3465a4;
 | 
				
			||||||
 | 
					  margin-bottom: .8em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
					  color: #204a87;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h2 {
 | 
				
			||||||
 | 
					  padding-bottom: .5em;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid #3465a4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink {
 | 
				
			||||||
 | 
					  visibility: hidden;
 | 
				
			||||||
 | 
					  color: #dddddd;
 | 
				
			||||||
 | 
					  padding-left: .3em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1:hover > a.headerlink,
 | 
				
			||||||
 | 
					h2:hover > a.headerlink,
 | 
				
			||||||
 | 
					h3:hover > a.headerlink,
 | 
				
			||||||
 | 
					h4:hover > a.headerlink,
 | 
				
			||||||
 | 
					h5:hover > a.headerlink,
 | 
				
			||||||
 | 
					h6:hover > a.headerlink,
 | 
				
			||||||
 | 
					dt:hover > a.headerlink {
 | 
				
			||||||
 | 
					  visibility: visible;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img {
 | 
				
			||||||
 | 
					  border: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition {
 | 
				
			||||||
 | 
					  margin-top: 10px;
 | 
				
			||||||
 | 
					  margin-bottom: 10px;
 | 
				
			||||||
 | 
					  padding: 2px 7px 1px 7px;
 | 
				
			||||||
 | 
					  border-left: 0.2em solid black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.admonition-title {
 | 
				
			||||||
 | 
					  margin: 0px 10px 5px 0px;
 | 
				
			||||||
 | 
					  font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dt:target, .highlighted {
 | 
				
			||||||
 | 
					  background-color: #fbe54e;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Header */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header {
 | 
				
			||||||
 | 
					  padding-top: 10px;
 | 
				
			||||||
 | 
					  padding-bottom: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header h1 {
 | 
				
			||||||
 | 
					  font-family: "Georgia", "Times New Roman", serif;
 | 
				
			||||||
 | 
					  font-weight: normal;
 | 
				
			||||||
 | 
					  font-size: 180%;
 | 
				
			||||||
 | 
					  letter-spacing: .08em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header h1 a {
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header div.rel {
 | 
				
			||||||
 | 
					  margin-top: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header div.rel a {
 | 
				
			||||||
 | 
					  color: #fcaf3e;
 | 
				
			||||||
 | 
					  letter-spacing: .1em;
 | 
				
			||||||
 | 
					  text-transform: uppercase;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.logo {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.logo {
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Content */
 | 
				
			||||||
 | 
					div.content-wrapper {
 | 
				
			||||||
 | 
					  background-color: white;
 | 
				
			||||||
 | 
					  padding-top: 20px;
 | 
				
			||||||
 | 
					  padding-bottom: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document {
 | 
				
			||||||
 | 
					  width: 50em;
 | 
				
			||||||
 | 
					  float: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body {
 | 
				
			||||||
 | 
					  padding-right: 2em;
 | 
				
			||||||
 | 
					  text-align: justify;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document ul {
 | 
				
			||||||
 | 
					  margin: 1.5em;
 | 
				
			||||||
 | 
					  list-style-type: square;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document dd {
 | 
				
			||||||
 | 
					  margin-left: 1.2em;
 | 
				
			||||||
 | 
					  margin-top: .4em;
 | 
				
			||||||
 | 
					  margin-bottom: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document .section {
 | 
				
			||||||
 | 
					  margin-top: 1.7em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.document .section:first-child {
 | 
				
			||||||
 | 
					  margin-top: 0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document div.highlight {
 | 
				
			||||||
 | 
					  padding: 3px;
 | 
				
			||||||
 | 
					  background-color: #eeeeec;
 | 
				
			||||||
 | 
					  border-top: 2px solid #dddddd;
 | 
				
			||||||
 | 
					  border-bottom: 2px solid #dddddd;
 | 
				
			||||||
 | 
					  margin-top: .8em;
 | 
				
			||||||
 | 
					  margin-bottom: .8em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document h2 {
 | 
				
			||||||
 | 
					  margin-top: .7em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document p {
 | 
				
			||||||
 | 
					  margin-bottom: .5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document li.toctree-l1 {
 | 
				
			||||||
 | 
					  margin-bottom: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document .descname {
 | 
				
			||||||
 | 
					  font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document .docutils.literal {
 | 
				
			||||||
 | 
					  background-color: #eeeeec;
 | 
				
			||||||
 | 
					  padding: 1px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document .docutils.xref.literal {
 | 
				
			||||||
 | 
					  background-color: transparent;
 | 
				
			||||||
 | 
					  padding: 0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document blockquote {
 | 
				
			||||||
 | 
					  margin: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document ol {
 | 
				
			||||||
 | 
					  margin: 1.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Sidebar */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar {
 | 
				
			||||||
 | 
					  width: 20em;
 | 
				
			||||||
 | 
					  float: right;
 | 
				
			||||||
 | 
					  font-size: .9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar a, div.header a {
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar a:hover, div.header a:hover {
 | 
				
			||||||
 | 
					  text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar h3 {
 | 
				
			||||||
 | 
					  color: #2e3436;
 | 
				
			||||||
 | 
					  text-transform: uppercase;
 | 
				
			||||||
 | 
					  font-size: 130%;
 | 
				
			||||||
 | 
					  letter-spacing: .1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar ul {
 | 
				
			||||||
 | 
					  list-style-type: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar li.toctree-l1 a {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  padding: 1px;
 | 
				
			||||||
 | 
					  border: 1px solid #dddddd;
 | 
				
			||||||
 | 
					  background-color: #eeeeec;
 | 
				
			||||||
 | 
					  margin-bottom: .4em;
 | 
				
			||||||
 | 
					  padding-left: 3px;
 | 
				
			||||||
 | 
					  color: #2e3436;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar li.toctree-l2 a {
 | 
				
			||||||
 | 
					  background-color: transparent;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  margin-left: 1em;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid #dddddd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar li.toctree-l3 a {
 | 
				
			||||||
 | 
					  background-color: transparent;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  margin-left: 2em;
 | 
				
			||||||
 | 
					  border-bottom: 1px solid #dddddd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar li.toctree-l2:last-child a {
 | 
				
			||||||
 | 
					  border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar li.toctree-l1.current a {
 | 
				
			||||||
 | 
					  border-right: 5px solid #fcaf3e;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar li.toctree-l1.current li.toctree-l2 a {
 | 
				
			||||||
 | 
					  border-right: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Footer */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer-wrapper {
 | 
				
			||||||
 | 
					  background: url(bgfooter.png) top left repeat-x;
 | 
				
			||||||
 | 
					  border-top: 4px solid #babdb6;
 | 
				
			||||||
 | 
					  padding-top: 10px;
 | 
				
			||||||
 | 
					  padding-bottom: 10px;
 | 
				
			||||||
 | 
					  min-height: 80px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer, div.footer a {
 | 
				
			||||||
 | 
					  color: #888a85;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer .right {
 | 
				
			||||||
 | 
					  text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer .left {
 | 
				
			||||||
 | 
					  text-transform: uppercase;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Styles copied from basic theme */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.align-left, .figure.align-left, object.align-left {
 | 
				
			||||||
 | 
					    clear: left;
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					    margin-right: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.align-right, .figure.align-right, object.align-right {
 | 
				
			||||||
 | 
					    clear: right;
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    margin-left: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.align-center, .figure.align-center, object.align-center {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  margin-left: auto;
 | 
				
			||||||
 | 
					  margin-right: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.align-left {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.align-center {
 | 
				
			||||||
 | 
					    clear: both;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.align-right {
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- search page ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search {
 | 
				
			||||||
 | 
					    margin: 10px 0 0 20px;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search li {
 | 
				
			||||||
 | 
					    padding: 5px 0 5px 20px;
 | 
				
			||||||
 | 
					    background-image: url(file.png);
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: 0 7px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search li a {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search li div.context {
 | 
				
			||||||
 | 
					    color: #888;
 | 
				
			||||||
 | 
					    margin: 2px 0 0 30px;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.keywordmatches li.goodmatch a {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- index page ------------------------------------------------------------ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.contentstable {
 | 
				
			||||||
 | 
					    width: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.contentstable p.biglink {
 | 
				
			||||||
 | 
					    line-height: 150%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.biglink {
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					span.linkdescr {
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    padding-top: 5px;
 | 
				
			||||||
 | 
					    font-size: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- general index --------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable td {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    vertical-align: top;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable dl, table.indextable dd {
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable tr.pcap {
 | 
				
			||||||
 | 
					    height: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable tr.cap {
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					    background-color: #f2f2f2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.toggler {
 | 
				
			||||||
 | 
					    margin-right: 3px;
 | 
				
			||||||
 | 
					    margin-top: 3px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- viewcode extension ---------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-link {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-back {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    font-family:: "Verdana", Arial, sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.viewcode-block:target {
 | 
				
			||||||
 | 
					    margin: -1px -3px;
 | 
				
			||||||
 | 
					    padding: 0 3px;
 | 
				
			||||||
 | 
					    background-color: #f4debf;
 | 
				
			||||||
 | 
					    border-top: 1px solid #ac9;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ac9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										532
									
								
								docs/_static/basic.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								docs/_static/basic.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,532 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * basic.css
 | 
				
			||||||
 | 
					 * ~~~~~~~~~
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Sphinx stylesheet -- basic theme.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
				
			||||||
 | 
					 * :license: BSD, see LICENSE for details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- main layout ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.clearer {
 | 
				
			||||||
 | 
					    clear: both;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- relbar ---------------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    font-size: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related h3 {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related ul {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0 0 0 10px;
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related li {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related li.right {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    margin-right: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- sidebar --------------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebarwrapper {
 | 
				
			||||||
 | 
					    padding: 10px 5px 0 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar {
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					    width: 230px;
 | 
				
			||||||
 | 
					    margin-left: -100%;
 | 
				
			||||||
 | 
					    font-size: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar ul {
 | 
				
			||||||
 | 
					    list-style: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar ul ul,
 | 
				
			||||||
 | 
					div.sphinxsidebar ul.want-points {
 | 
				
			||||||
 | 
					    margin-left: 20px;
 | 
				
			||||||
 | 
					    list-style: square;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar ul ul {
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar form {
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar input {
 | 
				
			||||||
 | 
					    border: 1px solid #98dbcc;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img {
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- search page ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search {
 | 
				
			||||||
 | 
					    margin: 10px 0 0 20px;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search li {
 | 
				
			||||||
 | 
					    padding: 5px 0 5px 20px;
 | 
				
			||||||
 | 
					    background-image: url(file.png);
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: 0 7px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search li a {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.search li div.context {
 | 
				
			||||||
 | 
					    color: #888;
 | 
				
			||||||
 | 
					    margin: 2px 0 0 30px;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul.keywordmatches li.goodmatch a {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- index page ------------------------------------------------------------ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.contentstable {
 | 
				
			||||||
 | 
					    width: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.contentstable p.biglink {
 | 
				
			||||||
 | 
					    line-height: 150%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.biglink {
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					span.linkdescr {
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					    padding-top: 5px;
 | 
				
			||||||
 | 
					    font-size: 90%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- general index --------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable td {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    vertical-align: top;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable dl, table.indextable dd {
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable tr.pcap {
 | 
				
			||||||
 | 
					    height: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.indextable tr.cap {
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					    background-color: #f2f2f2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.toggler {
 | 
				
			||||||
 | 
					    margin-right: 3px;
 | 
				
			||||||
 | 
					    margin-top: 3px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.modindex-jumpbox {
 | 
				
			||||||
 | 
					    border-top: 1px solid #ddd;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ddd;
 | 
				
			||||||
 | 
					    margin: 1em 0 1em 0;
 | 
				
			||||||
 | 
					    padding: 0.4em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.genindex-jumpbox {
 | 
				
			||||||
 | 
					    border-top: 1px solid #ddd;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ddd;
 | 
				
			||||||
 | 
					    margin: 1em 0 1em 0;
 | 
				
			||||||
 | 
					    padding: 0.4em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- general body styles --------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink {
 | 
				
			||||||
 | 
					    visibility: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1:hover > a.headerlink,
 | 
				
			||||||
 | 
					h2:hover > a.headerlink,
 | 
				
			||||||
 | 
					h3:hover > a.headerlink,
 | 
				
			||||||
 | 
					h4:hover > a.headerlink,
 | 
				
			||||||
 | 
					h5:hover > a.headerlink,
 | 
				
			||||||
 | 
					h6:hover > a.headerlink,
 | 
				
			||||||
 | 
					dt:hover > a.headerlink {
 | 
				
			||||||
 | 
					    visibility: visible;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body p.caption {
 | 
				
			||||||
 | 
					    text-align: inherit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body td {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.field-list ul {
 | 
				
			||||||
 | 
					    padding-left: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.first {
 | 
				
			||||||
 | 
					    margin-top: 0 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.rubric {
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.align-left, .figure.align-left, object.align-left {
 | 
				
			||||||
 | 
					    clear: left;
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					    margin-right: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.align-right, .figure.align-right, object.align-right {
 | 
				
			||||||
 | 
					    clear: right;
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    margin-left: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.align-center, .figure.align-center, object.align-center {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  margin-left: auto;
 | 
				
			||||||
 | 
					  margin-right: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.align-left {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.align-center {
 | 
				
			||||||
 | 
					    clear: both;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.align-right {
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- sidebars -------------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sidebar {
 | 
				
			||||||
 | 
					    margin: 0 0 0.5em 1em;
 | 
				
			||||||
 | 
					    border: 1px solid #ddb;
 | 
				
			||||||
 | 
					    padding: 7px 7px 0 7px;
 | 
				
			||||||
 | 
					    background-color: #efefef;
 | 
				
			||||||
 | 
					    width: 40%;
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    -mox-border-radius: 4px;
 | 
				
			||||||
 | 
					    -webkit-border-radius: 4px;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.sidebar-title {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    text-transform: uppercase;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- topics ---------------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.topic {
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					    padding: 7px 7px 0 7px;
 | 
				
			||||||
 | 
					    margin: 10px 0 10px 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.topic-title {
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- admonitions ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition {
 | 
				
			||||||
 | 
					    margin-top: 10px;
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					    padding: 7px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition dt {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition dl {
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.admonition-title {
 | 
				
			||||||
 | 
					    margin: 0px 10px 5px 0px;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body p.centered {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    margin-top: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- tables ---------------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.docutils {
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					    border-collapse: collapse;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.docutils td, table.docutils th {
 | 
				
			||||||
 | 
					    padding: 1px 8px 1px 5px;
 | 
				
			||||||
 | 
					    border-top: 0;
 | 
				
			||||||
 | 
					    border-left: 0;
 | 
				
			||||||
 | 
					    border-right: 0;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #aaa;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.field-list td, table.field-list th {
 | 
				
			||||||
 | 
					    border: 0 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.footnote td, table.footnote th {
 | 
				
			||||||
 | 
					    border: 0 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th {
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    padding-right: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.citation {
 | 
				
			||||||
 | 
					    border-left: solid 1px gray;
 | 
				
			||||||
 | 
					    margin-left: 1px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.citation td {
 | 
				
			||||||
 | 
					    border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- other body styles ----------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ol.arabic {
 | 
				
			||||||
 | 
					    list-style: decimal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ol.loweralpha {
 | 
				
			||||||
 | 
					    list-style: lower-alpha;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ol.upperalpha {
 | 
				
			||||||
 | 
					    list-style: upper-alpha;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ol.lowerroman {
 | 
				
			||||||
 | 
					    list-style: lower-roman;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ol.upperroman {
 | 
				
			||||||
 | 
					    list-style: upper-roman;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dl {
 | 
				
			||||||
 | 
					    margin-bottom: 15px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dd p {
 | 
				
			||||||
 | 
					    margin-top: 0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dd ul, dd table {
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dd {
 | 
				
			||||||
 | 
					    margin-top: 3px;
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					    margin-left: 30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dt:target, .highlighted {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dl.glossary dt {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.field-list ul {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding-left: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.field-list p {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.refcount {
 | 
				
			||||||
 | 
					    color: #060;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.optional {
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.versionmodified {
 | 
				
			||||||
 | 
					    font-style: italic;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.system-message {
 | 
				
			||||||
 | 
					    background-color: #fda;
 | 
				
			||||||
 | 
					    padding: 5px;
 | 
				
			||||||
 | 
					    border: 3px solid red;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footnote:target  {
 | 
				
			||||||
 | 
					    background-color: #ffa;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.line-block {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin-top: 1em;
 | 
				
			||||||
 | 
					    margin-bottom: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.line-block .line-block {
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					    margin-left: 1.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.guilabel, .menuselection {
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.accelerator {
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.classifier {
 | 
				
			||||||
 | 
					    font-style: oblique;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- code displays --------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					    overflow-y: hidden;  /* fixes display issues on Chrome browsers */
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					td.linenos pre {
 | 
				
			||||||
 | 
					    padding: 5px 0px;
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					    color: #aaa;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.highlighttable {
 | 
				
			||||||
 | 
					    margin-left: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.highlighttable td {
 | 
				
			||||||
 | 
					    padding: 0 0.5em 0 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tt.descname {
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    font-size: 1.2em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tt.descclassname {
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tt.xref, a tt {
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
 | 
				
			||||||
 | 
					    background-color: transparent;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-link {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-back {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.viewcode-block:target {
 | 
				
			||||||
 | 
					    margin: -1px -10px;
 | 
				
			||||||
 | 
					    padding: 0 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- math display ---------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img.math {
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body div.math p {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					span.eqno {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- printout stylesheet --------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media print {
 | 
				
			||||||
 | 
					    div.document,
 | 
				
			||||||
 | 
					    div.documentwrapper,
 | 
				
			||||||
 | 
					    div.bodywrapper {
 | 
				
			||||||
 | 
					        margin: 0 !important;
 | 
				
			||||||
 | 
					        width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    div.sphinxsidebar,
 | 
				
			||||||
 | 
					    div.related,
 | 
				
			||||||
 | 
					    div.footer,
 | 
				
			||||||
 | 
					    #top-link {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										256
									
								
								docs/_static/default.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								docs/_static/default.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,256 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * default.css_t
 | 
				
			||||||
 | 
					 * ~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Sphinx stylesheet -- default theme.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
				
			||||||
 | 
					 * :license: BSD, see LICENSE for details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@import url("basic.css");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- page layout ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					    font-size: 100%;
 | 
				
			||||||
 | 
					    background-color: #11303d;
 | 
				
			||||||
 | 
					    color: #000;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document {
 | 
				
			||||||
 | 
					    background-color: #1c4e63;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.documentwrapper {
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.bodywrapper {
 | 
				
			||||||
 | 
					    margin: 0 0 0 230px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body {
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    color: #000000;
 | 
				
			||||||
 | 
					    padding: 0 20px 30px 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer {
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    padding: 9px 0 9px 0;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 75%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer a {
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related {
 | 
				
			||||||
 | 
					    background-color: #133f52;
 | 
				
			||||||
 | 
					    line-height: 30px;
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related a {
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar h3 {
 | 
				
			||||||
 | 
					    font-family: 'Trebuchet MS', sans-serif;
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    font-size: 1.4em;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar h3 a {
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar h4 {
 | 
				
			||||||
 | 
					    font-family: 'Trebuchet MS', sans-serif;
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    margin: 5px 0 0 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar p {
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar p.topless {
 | 
				
			||||||
 | 
					    margin: 5px 10px 10px 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar ul {
 | 
				
			||||||
 | 
					    margin: 10px;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar a {
 | 
				
			||||||
 | 
					    color: #98dbcc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar input {
 | 
				
			||||||
 | 
					    border: 1px solid #98dbcc;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- hyperlink styles ------------------------------------------------------ */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    color: #355f7c;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:visited {
 | 
				
			||||||
 | 
					    color: #355f7c;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:hover {
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- body styles ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body h1,
 | 
				
			||||||
 | 
					div.body h2,
 | 
				
			||||||
 | 
					div.body h3,
 | 
				
			||||||
 | 
					div.body h4,
 | 
				
			||||||
 | 
					div.body h5,
 | 
				
			||||||
 | 
					div.body h6 {
 | 
				
			||||||
 | 
					    font-family: 'Trebuchet MS', sans-serif;
 | 
				
			||||||
 | 
					    background-color: #f2f2f2;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    color: #20435c;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ccc;
 | 
				
			||||||
 | 
					    margin: 20px -20px 10px -20px;
 | 
				
			||||||
 | 
					    padding: 3px 0 3px 10px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body h1 { margin-top: 0; font-size: 200%; }
 | 
				
			||||||
 | 
					div.body h2 { font-size: 160%; }
 | 
				
			||||||
 | 
					div.body h3 { font-size: 140%; }
 | 
				
			||||||
 | 
					div.body h4 { font-size: 120%; }
 | 
				
			||||||
 | 
					div.body h5 { font-size: 110%; }
 | 
				
			||||||
 | 
					div.body h6 { font-size: 100%; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink {
 | 
				
			||||||
 | 
					    color: #c60f0f;
 | 
				
			||||||
 | 
					    font-size: 0.8em;
 | 
				
			||||||
 | 
					    padding: 0 4px 0 4px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink:hover {
 | 
				
			||||||
 | 
					    background-color: #c60f0f;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body p, div.body dd, div.body li {
 | 
				
			||||||
 | 
					    text-align: justify;
 | 
				
			||||||
 | 
					    line-height: 130%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition p.admonition-title + p {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition p {
 | 
				
			||||||
 | 
					    margin-bottom: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition pre {
 | 
				
			||||||
 | 
					    margin-bottom: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition ul, div.admonition ol {
 | 
				
			||||||
 | 
					    margin-bottom: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.note {
 | 
				
			||||||
 | 
					    background-color: #eee;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.seealso {
 | 
				
			||||||
 | 
					    background-color: #ffc;
 | 
				
			||||||
 | 
					    border: 1px solid #ff6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.topic {
 | 
				
			||||||
 | 
					    background-color: #eee;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.warning {
 | 
				
			||||||
 | 
					    background-color: #ffe4e4;
 | 
				
			||||||
 | 
					    border: 1px solid #f66;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.admonition-title {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.admonition-title:after {
 | 
				
			||||||
 | 
					    content: ":";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					    padding: 5px;
 | 
				
			||||||
 | 
					    background-color: #eeffcc;
 | 
				
			||||||
 | 
					    color: #333333;
 | 
				
			||||||
 | 
					    line-height: 120%;
 | 
				
			||||||
 | 
					    border: 1px solid #ac9;
 | 
				
			||||||
 | 
					    border-left: none;
 | 
				
			||||||
 | 
					    border-right: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tt {
 | 
				
			||||||
 | 
					    background-color: #ecf0f3;
 | 
				
			||||||
 | 
					    padding: 0 1px 0 1px;
 | 
				
			||||||
 | 
					    font-size: 0.95em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					th {
 | 
				
			||||||
 | 
					    background-color: #ede;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.warning tt {
 | 
				
			||||||
 | 
					    background: #efc2c2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.note tt {
 | 
				
			||||||
 | 
					    background: #d6d6d6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-back {
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.viewcode-block:target {
 | 
				
			||||||
 | 
					    background-color: #f4debf;
 | 
				
			||||||
 | 
					    border-top: 1px solid #ac9;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ac9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/Museo_Slab_500italic.otf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT-Italic.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/OFLGoudyStMTT.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Bold.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Light.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Regular.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/fonts/YanoneKaffeesatz-Thin.ttf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										431
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										431
									
								
								docs/_static/haiku.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,431 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * haiku.css_t
 | 
				
			||||||
 | 
					 * ~~~~~~~~~~~
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Sphinx stylesheet -- haiku theme.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Adapted from http://haiku-os.org/docs/Haiku-doc.css.
 | 
				
			||||||
 | 
					 * Original copyright message:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     Copyright 2008-2009, Haiku. All rights reserved.
 | 
				
			||||||
 | 
					 *     Distributed under the terms of the MIT License.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 *     Authors:
 | 
				
			||||||
 | 
					 *              Francois Revol <revol@free.fr>
 | 
				
			||||||
 | 
					 *              Stephan Assmus <superstippi@gmx.de>
 | 
				
			||||||
 | 
					 *              Braden Ewing <brewin@gmail.com>
 | 
				
			||||||
 | 
					 *              Humdinger <humdingerb@gmail.com>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
				
			||||||
 | 
					 * :license: BSD, see LICENSE for details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@import url("basic.css");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@font-face {
 | 
				
			||||||
 | 
					    font-family: "Museo Slab";
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					    src: local("Museo Slab"),
 | 
				
			||||||
 | 
					         url("fonts/Museo_Slab_500.otf") format("opentype");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@font-face {
 | 
				
			||||||
 | 
					    font-family: "Yanone Kaffeesatz";
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					    src: local("Yanone Kaffeesatz"),
 | 
				
			||||||
 | 
					         url("fonts/YanoneKaffeesatz-Bold.ttf") format("truetype");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@font-face {
 | 
				
			||||||
 | 
					    font-family: "Yanone Kaffeesatz";
 | 
				
			||||||
 | 
					    font-weight: lighter;
 | 
				
			||||||
 | 
					    font-style: normal;
 | 
				
			||||||
 | 
					    src: local("Yanone Kaffeesatz"),
 | 
				
			||||||
 | 
					         url("fonts/YanoneKaffeesatz-Regular.ttf") format("truetype");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html {
 | 
				
			||||||
 | 
					    margin: 0px;
 | 
				
			||||||
 | 
					    padding: 0px;
 | 
				
			||||||
 | 
					    background: #FFF url(header.png) top left repeat-x;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					    padding: 0px;
 | 
				
			||||||
 | 
					    font-family: "Helvetica Neueu", Helvetica, sans-serif;
 | 
				
			||||||
 | 
					    min-width: 30em;
 | 
				
			||||||
 | 
					    max-width: 70em;
 | 
				
			||||||
 | 
					    color: #444;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer {
 | 
				
			||||||
 | 
					    padding: 8px;
 | 
				
			||||||
 | 
					    font-size: 11px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    letter-spacing: 0.5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* link colors and text decoration */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:link {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #00ADEE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:visited {
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #00ADEE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:hover, a:active {
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					    color: #F46DBA;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Some headers act as anchors, don't give them a hover effect */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 a:hover, a:active {
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #CFCFCF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h2 a:hover, a:active {
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #CFCFCF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3 a:hover, a:active {
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #CFCFCF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h4 a:hover, a:active {
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #CFCFCF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink {
 | 
				
			||||||
 | 
					    color: #a7ce38;
 | 
				
			||||||
 | 
					    padding-left: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink:hover {
 | 
				
			||||||
 | 
					    color: #a7ce38;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* basic text elements */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.content {
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					    margin-top: 20px;
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    letter-spacing: 1px;
 | 
				
			||||||
 | 
					    color: #CFCFCF;
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding-top: 15px;
 | 
				
			||||||
 | 
					    font-family: "Yanone Kaffeesatz";
 | 
				
			||||||
 | 
					    text-shadow: 1px 1px 1px rgba(175, 175, 175, .8);
 | 
				
			||||||
 | 
					    font-variant: small-caps;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.header h1 a {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    color: #00ADEE;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.header h2 {
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    letter-spacing: 1px;
 | 
				
			||||||
 | 
					    text-transform: uppercase;
 | 
				
			||||||
 | 
					    color: #aaa;
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					    margin-top: -3px;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    font-family: "Yanone Kaffeesatz";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.header img.rightlogo {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.title {
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    color: #CFCFCF;
 | 
				
			||||||
 | 
					    border-bottom: dotted thin #e0e0e0;
 | 
				
			||||||
 | 
					    margin-bottom: 25px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.topnav {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    z-index: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.topnav p {
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 0px;
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					    font-size: 0.8em;
 | 
				
			||||||
 | 
					    width: 750px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.bottomnav {
 | 
				
			||||||
 | 
					    background: #eeeeee;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.bottomnav p {
 | 
				
			||||||
 | 
					    margin-right: 40px;
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					    font-size: 0.8em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.uplink {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* contents box */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.index {
 | 
				
			||||||
 | 
					    margin: 0px 0px 30px 30px;
 | 
				
			||||||
 | 
					    padding: 1px;
 | 
				
			||||||
 | 
					    border-width: 1px;
 | 
				
			||||||
 | 
					    border-style: dotted;
 | 
				
			||||||
 | 
					    border-color: #e0e0e0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table.index tr.heading {
 | 
				
			||||||
 | 
					    background-color: #e0e0e0;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table.index tr.index {
 | 
				
			||||||
 | 
					    background-color: #eeeeee;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table.index td {
 | 
				
			||||||
 | 
					    padding: 5px 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table.index a:link, table.index a:visited {
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					    color: #4A7389;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					table.index a:hover, table.index a:active {
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					    color: #ff4500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Haiku User Guide styles and layout */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Rounded corner boxes */
 | 
				
			||||||
 | 
					/* Common declarations */
 | 
				
			||||||
 | 
					div.admonition {
 | 
				
			||||||
 | 
					    -webkit-border-radius: 10px;
 | 
				
			||||||
 | 
					    -khtml-border-radius: 10px;
 | 
				
			||||||
 | 
					    -moz-border-radius: 10px;
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    border-style: dotted;
 | 
				
			||||||
 | 
					    border-width: thin;
 | 
				
			||||||
 | 
					    border-color: #dcdcdc;
 | 
				
			||||||
 | 
					    padding: 10px 15px 10px 15px;
 | 
				
			||||||
 | 
					    margin-bottom: 15px;
 | 
				
			||||||
 | 
					    margin-top: 15px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.note {
 | 
				
			||||||
 | 
					    padding: 10px 15px 10px 15px;
 | 
				
			||||||
 | 
					    background-color: #e4ffde;
 | 
				
			||||||
 | 
					    /*background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat;*/
 | 
				
			||||||
 | 
					    min-height: 42px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.warning {
 | 
				
			||||||
 | 
					    padding: 10px 15px 10px 15px;
 | 
				
			||||||
 | 
					    background-color: #fffbc6;
 | 
				
			||||||
 | 
					    /*background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat;*/
 | 
				
			||||||
 | 
					    min-height: 42px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					div.seealso {
 | 
				
			||||||
 | 
					    background: #e4ffde;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* More layout and styles */
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
					    font-size: 1.6em;
 | 
				
			||||||
 | 
					    color: #aaa;
 | 
				
			||||||
 | 
					    border-bottom: dotted thin #e0e0e0;
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					    font-family: "Museo Slab";
 | 
				
			||||||
 | 
					    text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h2 {
 | 
				
			||||||
 | 
					    font-size: 1.5em;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    color: #aaa;
 | 
				
			||||||
 | 
					    border-bottom: dotted thin #e0e0e0;
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					    font-family: "Museo Slab";
 | 
				
			||||||
 | 
					    text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3 {
 | 
				
			||||||
 | 
					    font-size: 1.4em;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    color: #aaa;
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					    font-family: "Museo Slab";
 | 
				
			||||||
 | 
					    text-shadow: 1px 1px 1px rgba(175, 175, 175, .25);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h4 {
 | 
				
			||||||
 | 
					    font-size: 1.3em;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    color: #CFCFCF;
 | 
				
			||||||
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p {
 | 
				
			||||||
 | 
					    text-align: justify;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p.last {
 | 
				
			||||||
 | 
					    margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ol {
 | 
				
			||||||
 | 
					    padding-left: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul {
 | 
				
			||||||
 | 
					    padding-left: 5px;
 | 
				
			||||||
 | 
					    margin-top: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					li {
 | 
				
			||||||
 | 
					    line-height: 1.3;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.content ul > li {
 | 
				
			||||||
 | 
					    -moz-background-clip:border;
 | 
				
			||||||
 | 
					    -moz-background-inline-policy:continuous;
 | 
				
			||||||
 | 
					    -moz-background-origin:padding;
 | 
				
			||||||
 | 
					    background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em;
 | 
				
			||||||
 | 
					    list-style-image: none;
 | 
				
			||||||
 | 
					    list-style-type: none;
 | 
				
			||||||
 | 
					    padding: 0 0 0 1.666em;
 | 
				
			||||||
 | 
					    margin-bottom: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					td {
 | 
				
			||||||
 | 
					    vertical-align: top;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tt {
 | 
				
			||||||
 | 
					    background-color: #e2e2e2;
 | 
				
			||||||
 | 
					    font-size: 1.0em;
 | 
				
			||||||
 | 
					    font-family: monospace;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					    margin: 0 0 12px 0;
 | 
				
			||||||
 | 
					    padding: 0.8em;
 | 
				
			||||||
 | 
					    background-image: url(noise_dk.png);
 | 
				
			||||||
 | 
					    background-color: #222;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hr {
 | 
				
			||||||
 | 
					    border-top: 1px solid #ccc;
 | 
				
			||||||
 | 
					    border-bottom: 0;
 | 
				
			||||||
 | 
					    border-right: 0;
 | 
				
			||||||
 | 
					    border-left: 0;
 | 
				
			||||||
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
 | 
					    margin-top: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* printer only pretty stuff */
 | 
				
			||||||
 | 
					@media print {
 | 
				
			||||||
 | 
					    .noprint {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /* for acronyms we want their definitions inlined at print time */
 | 
				
			||||||
 | 
					    acronym[title]:after {
 | 
				
			||||||
 | 
					        font-size: small;
 | 
				
			||||||
 | 
					        content: " (" attr(title) ")";
 | 
				
			||||||
 | 
					        font-style: italic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /* and not have mozilla dotted underline */
 | 
				
			||||||
 | 
					    acronym {
 | 
				
			||||||
 | 
					        border: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    div.topnav, div.bottomnav, div.header, table.index {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    div.content {
 | 
				
			||||||
 | 
					        margin: 0px;
 | 
				
			||||||
 | 
					        padding: 0px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    html {
 | 
				
			||||||
 | 
					        background: #FFF;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-back {
 | 
				
			||||||
 | 
					    font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.viewcode-block:target {
 | 
				
			||||||
 | 
					    border-top: 1px solid #ac9;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ac9;
 | 
				
			||||||
 | 
					    margin: -1px -12px;
 | 
				
			||||||
 | 
					    padding: 0 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#from_andyet {
 | 
				
			||||||
 | 
					    -webkit-box-shadow: #CCC 0px 0px 3px;
 | 
				
			||||||
 | 
					    background: rgba(255, 255, 255, 1);
 | 
				
			||||||
 | 
					    bottom: 0px;
 | 
				
			||||||
 | 
					    right: 17px;
 | 
				
			||||||
 | 
					    padding: 3px 10px;
 | 
				
			||||||
 | 
					    position: fixed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#from_andyet h2 {
 | 
				
			||||||
 | 
					    background-image: url("images/from_&yet.png");
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    height: 29px;
 | 
				
			||||||
 | 
					    line-height: 0;
 | 
				
			||||||
 | 
					    text-indent: -9999em;
 | 
				
			||||||
 | 
					    width: 79px;
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin: 0px;
 | 
				
			||||||
 | 
					    padding: 0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/header.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 16 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/arch_layers.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/images/from_&yet.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.7 KiB  | 
							
								
								
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_static/ir_black.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					.highlight .hll { background-color: #ffffcc }
 | 
				
			||||||
 | 
					.highlight  { background: #000000; color: #f6f3e8; }
 | 
				
			||||||
 | 
					.highlight .c { color: #7C7C7C; } /* Comment */
 | 
				
			||||||
 | 
					.highlight .err { color: #f6f3e8; } /* Error */
 | 
				
			||||||
 | 
					.highlight .g { color: #f6f3e8; } /* Generic */
 | 
				
			||||||
 | 
					.highlight .k { color: #00ADEE; } /* Keyword */
 | 
				
			||||||
 | 
					.highlight .l { color: #f6f3e8; } /* Literal */
 | 
				
			||||||
 | 
					.highlight .n { color: #f6f3e8; } /* Name */
 | 
				
			||||||
 | 
					.highlight .o { color: #f6f3e8; } /* Operator */
 | 
				
			||||||
 | 
					.highlight .x { color: #f6f3e8; } /* Other */
 | 
				
			||||||
 | 
					.highlight .p { color: #f6f3e8; } /* Punctuation */
 | 
				
			||||||
 | 
					.highlight .cm { color: #7C7C7C; } /* Comment.Multiline */
 | 
				
			||||||
 | 
					.highlight .cp { color: #96CBFE; } /* Comment.Preproc */
 | 
				
			||||||
 | 
					.highlight .c1 { color: #7C7C7C; } /* Comment.Single */
 | 
				
			||||||
 | 
					.highlight .cs { color: #7C7C7C; } /* Comment.Special */
 | 
				
			||||||
 | 
					.highlight .gd { color: #f6f3e8; } /* Generic.Deleted */
 | 
				
			||||||
 | 
					.highlight .ge { color: #f6f3e8; } /* Generic.Emph */
 | 
				
			||||||
 | 
					.highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */
 | 
				
			||||||
 | 
					.highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */
 | 
				
			||||||
 | 
					.highlight .gi { color: #f6f3e8; } /* Generic.Inserted */
 | 
				
			||||||
 | 
					.highlight .go { color: #070707; } /* Generic.Output */
 | 
				
			||||||
 | 
					.highlight .gp { color: #f6f3e8; } /* Generic.Prompt */
 | 
				
			||||||
 | 
					.highlight .gs { color: #f6f3e8; } /* Generic.Strong */
 | 
				
			||||||
 | 
					.highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */
 | 
				
			||||||
 | 
					.highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */
 | 
				
			||||||
 | 
					.highlight .kc { color: #6699CC; } /* Keyword.Constant */
 | 
				
			||||||
 | 
					.highlight .kd { color: #6699CC; } /* Keyword.Declaration */
 | 
				
			||||||
 | 
					.highlight .kn { color: #6699CC; } /* Keyword.Namespace */
 | 
				
			||||||
 | 
					.highlight .kp { color: #6699CC; } /* Keyword.Pseudo */
 | 
				
			||||||
 | 
					.highlight .kr { color: #6699CC; } /* Keyword.Reserved */
 | 
				
			||||||
 | 
					.highlight .kt { color: #FFFFB6; } /* Keyword.Type */
 | 
				
			||||||
 | 
					.highlight .ld { color: #f6f3e8; } /* Literal.Date */
 | 
				
			||||||
 | 
					.highlight .m { color: #FF73FD; } /* Literal.Number */
 | 
				
			||||||
 | 
					.highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */
 | 
				
			||||||
 | 
					.highlight .na { color: #f6f3e8; } /* Name.Attribute */
 | 
				
			||||||
 | 
					.highlight .nb { color: #f6f3e8; } /* Name.Builtin */
 | 
				
			||||||
 | 
					.highlight .nc { color: #f6f3e8; } /* Name.Class */
 | 
				
			||||||
 | 
					.highlight .no { color: #99CC99; } /* Name.Constant */
 | 
				
			||||||
 | 
					.highlight .nd { color: #f6f3e8; } /* Name.Decorator */
 | 
				
			||||||
 | 
					.highlight .ni { color: #E18964; } /* Name.Entity */
 | 
				
			||||||
 | 
					.highlight .ne { color: #f6f3e8; } /* Name.Exception */
 | 
				
			||||||
 | 
					.highlight .nf { color: #F64DBA; } /* Name.Function */
 | 
				
			||||||
 | 
					.highlight .nl { color: #f6f3e8; } /* Name.Label */
 | 
				
			||||||
 | 
					.highlight .nn { color: #f6f3e8; } /* Name.Namespace */
 | 
				
			||||||
 | 
					.highlight .nx { color: #f6f3e8; } /* Name.Other */
 | 
				
			||||||
 | 
					.highlight .py { color: #f6f3e8; } /* Name.Property */
 | 
				
			||||||
 | 
					.highlight .nt { color: #00ADEE; } /* Name.Tag */
 | 
				
			||||||
 | 
					.highlight .nv { color: #C6C5FE; } /* Name.Variable */
 | 
				
			||||||
 | 
					.highlight .ow { color: #ffffff; } /* Operator.Word */
 | 
				
			||||||
 | 
					.highlight .w { color: #f6f3e8; } /* Text.Whitespace */
 | 
				
			||||||
 | 
					.highlight .mf { color: #FF73FD; } /* Literal.Number.Float */
 | 
				
			||||||
 | 
					.highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */
 | 
				
			||||||
 | 
					.highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */
 | 
				
			||||||
 | 
					.highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */
 | 
				
			||||||
 | 
					.highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */
 | 
				
			||||||
 | 
					.highlight .sc { color: #A8FF60; } /* Literal.String.Char */
 | 
				
			||||||
 | 
					.highlight .sd { color: #A8FF60; } /* Literal.String.Doc */
 | 
				
			||||||
 | 
					.highlight .s2 { color: #A8FF60; } /* Literal.String.Double */
 | 
				
			||||||
 | 
					.highlight .se { color: #A8FF60; } /* Literal.String.Escape */
 | 
				
			||||||
 | 
					.highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */
 | 
				
			||||||
 | 
					.highlight .si { color: #A8FF60; } /* Literal.String.Interpol */
 | 
				
			||||||
 | 
					.highlight .sx { color: #A8FF60; } /* Literal.String.Other */
 | 
				
			||||||
 | 
					.highlight .sr { color: #A8FF60; } /* Literal.String.Regex */
 | 
				
			||||||
 | 
					.highlight .s1 { color: #A8FF60; } /* Literal.String.Single */
 | 
				
			||||||
 | 
					.highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */
 | 
				
			||||||
 | 
					.highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */
 | 
				
			||||||
 | 
					.highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */
 | 
				
			||||||
 | 
					.highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */
 | 
				
			||||||
 | 
					.highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */
 | 
				
			||||||
 | 
					.highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */
 | 
				
			||||||
							
								
								
									
										245
									
								
								docs/_static/nature.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								docs/_static/nature.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * nature.css_t
 | 
				
			||||||
 | 
					 * ~~~~~~~~~~~~
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Sphinx stylesheet -- nature theme.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
				
			||||||
 | 
					 * :license: BSD, see LICENSE for details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					@import url("basic.css");
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					/* -- page layout ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: Arial, sans-serif;
 | 
				
			||||||
 | 
					    font-size: 100%;
 | 
				
			||||||
 | 
					    background-color: #111;
 | 
				
			||||||
 | 
					    color: #555;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.documentwrapper {
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.bodywrapper {
 | 
				
			||||||
 | 
					    margin: 0 0 0 230px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hr {
 | 
				
			||||||
 | 
					    border: 1px solid #B1B4B6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.document {
 | 
				
			||||||
 | 
					    background-color: #eee;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.body {
 | 
				
			||||||
 | 
					    background-color: #ffffff;
 | 
				
			||||||
 | 
					    color: #3E4349;
 | 
				
			||||||
 | 
					    padding: 0 30px 30px 30px;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.footer {
 | 
				
			||||||
 | 
					    color: #555;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    padding: 13px 0;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 75%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.footer a {
 | 
				
			||||||
 | 
					    color: #444;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.related {
 | 
				
			||||||
 | 
					    background-color: #6BA81E;
 | 
				
			||||||
 | 
					    line-height: 32px;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    text-shadow: 0px 1px 0 #444;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.related a {
 | 
				
			||||||
 | 
					    color: #E2F3CC;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar {
 | 
				
			||||||
 | 
					    font-size: 0.75em;
 | 
				
			||||||
 | 
					    line-height: 1.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebarwrapper{
 | 
				
			||||||
 | 
					    padding: 20px 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar h3,
 | 
				
			||||||
 | 
					div.sphinxsidebar h4 {
 | 
				
			||||||
 | 
					    font-family: Arial, sans-serif;
 | 
				
			||||||
 | 
					    color: #222;
 | 
				
			||||||
 | 
					    font-size: 1.2em;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 5px 10px;
 | 
				
			||||||
 | 
					    background-color: #ddd;
 | 
				
			||||||
 | 
					    text-shadow: 1px 1px 0 white
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar h4{
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar h3 a {
 | 
				
			||||||
 | 
					    color: #444;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar p {
 | 
				
			||||||
 | 
					    color: #888;
 | 
				
			||||||
 | 
					    padding: 5px 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar p.topless {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar ul {
 | 
				
			||||||
 | 
					    margin: 10px 20px;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    color: #000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar a {
 | 
				
			||||||
 | 
					    color: #444;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.sphinxsidebar input {
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar input[type=text]{
 | 
				
			||||||
 | 
					    margin-left: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					/* -- body styles ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    color: #005B81;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					a:hover {
 | 
				
			||||||
 | 
					    color: #E32E00;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.body h1,
 | 
				
			||||||
 | 
					div.body h2,
 | 
				
			||||||
 | 
					div.body h3,
 | 
				
			||||||
 | 
					div.body h4,
 | 
				
			||||||
 | 
					div.body h5,
 | 
				
			||||||
 | 
					div.body h6 {
 | 
				
			||||||
 | 
					    font-family: Arial, sans-serif;
 | 
				
			||||||
 | 
					    background-color: #BED4EB;
 | 
				
			||||||
 | 
					    font-weight: normal;
 | 
				
			||||||
 | 
					    color: #212224;
 | 
				
			||||||
 | 
					    margin: 30px 0px 10px 0px;
 | 
				
			||||||
 | 
					    padding: 5px 0 5px 10px;
 | 
				
			||||||
 | 
					    text-shadow: 0px 1px 0 white
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
 | 
				
			||||||
 | 
					div.body h2 { font-size: 150%; background-color: #C8D5E3; }
 | 
				
			||||||
 | 
					div.body h3 { font-size: 120%; background-color: #D8DEE3; }
 | 
				
			||||||
 | 
					div.body h4 { font-size: 110%; background-color: #D8DEE3; }
 | 
				
			||||||
 | 
					div.body h5 { font-size: 100%; background-color: #D8DEE3; }
 | 
				
			||||||
 | 
					div.body h6 { font-size: 100%; background-color: #D8DEE3; }
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					a.headerlink {
 | 
				
			||||||
 | 
					    color: #c60f0f;
 | 
				
			||||||
 | 
					    font-size: 0.8em;
 | 
				
			||||||
 | 
					    padding: 0 4px 0 4px;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					a.headerlink:hover {
 | 
				
			||||||
 | 
					    background-color: #c60f0f;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.body p, div.body dd, div.body li {
 | 
				
			||||||
 | 
					    line-height: 1.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.admonition p.admonition-title + p {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.highlight{
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.note {
 | 
				
			||||||
 | 
					    background-color: #eee;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.seealso {
 | 
				
			||||||
 | 
					    background-color: #ffc;
 | 
				
			||||||
 | 
					    border: 1px solid #ff6;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.topic {
 | 
				
			||||||
 | 
					    background-color: #eee;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					div.warning {
 | 
				
			||||||
 | 
					    background-color: #ffe4e4;
 | 
				
			||||||
 | 
					    border: 1px solid #f66;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					p.admonition-title {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					p.admonition-title:after {
 | 
				
			||||||
 | 
					    content: ":";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					    background-color: White;
 | 
				
			||||||
 | 
					    color: #222;
 | 
				
			||||||
 | 
					    line-height: 1.2em;
 | 
				
			||||||
 | 
					    border: 1px solid #C6C9CB;
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					    margin: 1.5em 0 1.5em 0;
 | 
				
			||||||
 | 
					    -webkit-box-shadow: 1px 1px 1px #d8d8d8;
 | 
				
			||||||
 | 
					    -moz-box-shadow: 1px 1px 1px #d8d8d8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					tt {
 | 
				
			||||||
 | 
					    background-color: #ecf0f3;
 | 
				
			||||||
 | 
					    color: #222;
 | 
				
			||||||
 | 
					    /* padding: 1px 2px; */
 | 
				
			||||||
 | 
					    font-size: 1.1em;
 | 
				
			||||||
 | 
					    font-family: monospace;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-back {
 | 
				
			||||||
 | 
					    font-family: Arial, sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.viewcode-block:target {
 | 
				
			||||||
 | 
					    background-color: #f4debf;
 | 
				
			||||||
 | 
					    border-top: 1px solid #ac9;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ac9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								docs/_static/noise_dk.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/_static/noise_dk.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_static/pygments.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					.highlight .hll { background-color: #ffffcc }
 | 
				
			||||||
 | 
					.highlight  { background: #000000; color: #f6f3e8; }
 | 
				
			||||||
 | 
					.highlight .c { color: #7C7C7C; } /* Comment */
 | 
				
			||||||
 | 
					.highlight .err { color: #f6f3e8; } /* Error */
 | 
				
			||||||
 | 
					.highlight .g { color: #f6f3e8; } /* Generic */
 | 
				
			||||||
 | 
					.highlight .k { color: #00ADEE; } /* Keyword */
 | 
				
			||||||
 | 
					.highlight .l { color: #f6f3e8; } /* Literal */
 | 
				
			||||||
 | 
					.highlight .n { color: #f6f3e8; } /* Name */
 | 
				
			||||||
 | 
					.highlight .o { color: #f6f3e8; } /* Operator */
 | 
				
			||||||
 | 
					.highlight .x { color: #f6f3e8; } /* Other */
 | 
				
			||||||
 | 
					.highlight .p { color: #f6f3e8; } /* Punctuation */
 | 
				
			||||||
 | 
					.highlight .cm { color: #7C7C7C; } /* Comment.Multiline */
 | 
				
			||||||
 | 
					.highlight .cp { color: #96CBFE; } /* Comment.Preproc */
 | 
				
			||||||
 | 
					.highlight .c1 { color: #7C7C7C; } /* Comment.Single */
 | 
				
			||||||
 | 
					.highlight .cs { color: #7C7C7C; } /* Comment.Special */
 | 
				
			||||||
 | 
					.highlight .gd { color: #f6f3e8; } /* Generic.Deleted */
 | 
				
			||||||
 | 
					.highlight .ge { color: #f6f3e8; } /* Generic.Emph */
 | 
				
			||||||
 | 
					.highlight .gr { color: #ffffff; background-color: #ff0000 } /* Generic.Error */
 | 
				
			||||||
 | 
					.highlight .gh { color: #f6f3e8; font-weight: bold; } /* Generic.Heading */
 | 
				
			||||||
 | 
					.highlight .gi { color: #f6f3e8; } /* Generic.Inserted */
 | 
				
			||||||
 | 
					.highlight .go { color: #070707; } /* Generic.Output */
 | 
				
			||||||
 | 
					.highlight .gp { color: #f6f3e8; } /* Generic.Prompt */
 | 
				
			||||||
 | 
					.highlight .gs { color: #f6f3e8; } /* Generic.Strong */
 | 
				
			||||||
 | 
					.highlight .gu { color: #f6f3e8; font-weight: bold; } /* Generic.Subheading */
 | 
				
			||||||
 | 
					.highlight .gt { color: #ffffff; font-weight: bold; background-color: #FF6C60 } /* Generic.Traceback */
 | 
				
			||||||
 | 
					.highlight .kc { color: #6699CC; } /* Keyword.Constant */
 | 
				
			||||||
 | 
					.highlight .kd { color: #6699CC; } /* Keyword.Declaration */
 | 
				
			||||||
 | 
					.highlight .kn { color: #6699CC; } /* Keyword.Namespace */
 | 
				
			||||||
 | 
					.highlight .kp { color: #6699CC; } /* Keyword.Pseudo */
 | 
				
			||||||
 | 
					.highlight .kr { color: #6699CC; } /* Keyword.Reserved */
 | 
				
			||||||
 | 
					.highlight .kt { color: #FFFFB6; } /* Keyword.Type */
 | 
				
			||||||
 | 
					.highlight .ld { color: #f6f3e8; } /* Literal.Date */
 | 
				
			||||||
 | 
					.highlight .m { color: #FF73FD; } /* Literal.Number */
 | 
				
			||||||
 | 
					.highlight .s { color: #F46DBA;/*#A8FF60;*/ } /* Literal.String */
 | 
				
			||||||
 | 
					.highlight .na { color: #f6f3e8; } /* Name.Attribute */
 | 
				
			||||||
 | 
					.highlight .nb { color: #f6f3e8; } /* Name.Builtin */
 | 
				
			||||||
 | 
					.highlight .nc { color: #f6f3e8; } /* Name.Class */
 | 
				
			||||||
 | 
					.highlight .no { color: #99CC99; } /* Name.Constant */
 | 
				
			||||||
 | 
					.highlight .nd { color: #f6f3e8; } /* Name.Decorator */
 | 
				
			||||||
 | 
					.highlight .ni { color: #E18964; } /* Name.Entity */
 | 
				
			||||||
 | 
					.highlight .ne { color: #f6f3e8; } /* Name.Exception */
 | 
				
			||||||
 | 
					.highlight .nf { color: #F64DBA; } /* Name.Function */
 | 
				
			||||||
 | 
					.highlight .nl { color: #f6f3e8; } /* Name.Label */
 | 
				
			||||||
 | 
					.highlight .nn { color: #f6f3e8; } /* Name.Namespace */
 | 
				
			||||||
 | 
					.highlight .nx { color: #f6f3e8; } /* Name.Other */
 | 
				
			||||||
 | 
					.highlight .py { color: #f6f3e8; } /* Name.Property */
 | 
				
			||||||
 | 
					.highlight .nt { color: #00ADEE; } /* Name.Tag */
 | 
				
			||||||
 | 
					.highlight .nv { color: #C6C5FE; } /* Name.Variable */
 | 
				
			||||||
 | 
					.highlight .ow { color: #ffffff; } /* Operator.Word */
 | 
				
			||||||
 | 
					.highlight .w { color: #f6f3e8; } /* Text.Whitespace */
 | 
				
			||||||
 | 
					.highlight .mf { color: #FF73FD; } /* Literal.Number.Float */
 | 
				
			||||||
 | 
					.highlight .mh { color: #FF73FD; } /* Literal.Number.Hex */
 | 
				
			||||||
 | 
					.highlight .mi { color: #FF73FD; } /* Literal.Number.Integer */
 | 
				
			||||||
 | 
					.highlight .mo { color: #FF73FD; } /* Literal.Number.Oct */
 | 
				
			||||||
 | 
					.highlight .sb { color: #A8FF60; } /* Literal.String.Backtick */
 | 
				
			||||||
 | 
					.highlight .sc { color: #A8FF60; } /* Literal.String.Char */
 | 
				
			||||||
 | 
					.highlight .sd { color: #A8FF60; } /* Literal.String.Doc */
 | 
				
			||||||
 | 
					.highlight .s2 { color: #A8FF60; } /* Literal.String.Double */
 | 
				
			||||||
 | 
					.highlight .se { color: #A8FF60; } /* Literal.String.Escape */
 | 
				
			||||||
 | 
					.highlight .sh { color: #A8FF60; } /* Literal.String.Heredoc */
 | 
				
			||||||
 | 
					.highlight .si { color: #A8FF60; } /* Literal.String.Interpol */
 | 
				
			||||||
 | 
					.highlight .sx { color: #A8FF60; } /* Literal.String.Other */
 | 
				
			||||||
 | 
					.highlight .sr { color: #A8FF60; } /* Literal.String.Regex */
 | 
				
			||||||
 | 
					.highlight .s1 { color: #A8FF60; } /* Literal.String.Single */
 | 
				
			||||||
 | 
					.highlight .ss { color: #A8FF60; } /* Literal.String.Symbol */
 | 
				
			||||||
 | 
					.highlight .bp { color: #f6f3e8; } /* Name.Builtin.Pseudo */
 | 
				
			||||||
 | 
					.highlight .vc { color: #C6C5FE; } /* Name.Variable.Class */
 | 
				
			||||||
 | 
					.highlight .vg { color: #C6C5FE; } /* Name.Variable.Global */
 | 
				
			||||||
 | 
					.highlight .vi { color: #C6C5FE; } /* Name.Variable.Instance */
 | 
				
			||||||
 | 
					.highlight .il { color: #FF73FD; } /* Literal.Number.Integer.Long */
 | 
				
			||||||
							
								
								
									
										339
									
								
								docs/_static/sphinxdoc.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								docs/_static/sphinxdoc.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,339 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * sphinxdoc.css_t
 | 
				
			||||||
 | 
					 * ~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Sphinx stylesheet -- sphinxdoc theme.  Originally created by
 | 
				
			||||||
 | 
					 * Armin Ronacher for Werkzeug.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
 | 
				
			||||||
 | 
					 * :license: BSD, see LICENSE for details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@import url("basic.css");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- page layout ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body {
 | 
				
			||||||
 | 
					    font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
 | 
				
			||||||
 | 
					                 'Verdana', sans-serif;
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    letter-spacing: -0.01em;
 | 
				
			||||||
 | 
					    line-height: 150%;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    background-color: #BFD1D4;
 | 
				
			||||||
 | 
					    color: black;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    border: 1px solid #aaa;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    margin: 0px 80px 0px 80px;
 | 
				
			||||||
 | 
					    min-width: 740px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.document {
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					    background-image: url(contents.png);
 | 
				
			||||||
 | 
					    background-repeat: repeat-x;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.bodywrapper {
 | 
				
			||||||
 | 
					    margin: 0 240px 0 0;
 | 
				
			||||||
 | 
					    border-right: 1px solid #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0.5em 20px 20px 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related {
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related ul {
 | 
				
			||||||
 | 
					    background-image: url(navigation.png);
 | 
				
			||||||
 | 
					    height: 2em;
 | 
				
			||||||
 | 
					    border-top: 1px solid #ddd;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ddd;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related ul li {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    height: 2em;
 | 
				
			||||||
 | 
					    float: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related ul li.right {
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    margin-right: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related ul li a {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0 5px 0 5px;
 | 
				
			||||||
 | 
					    line-height: 1.75em;
 | 
				
			||||||
 | 
					    color: #EE9816;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.related ul li a:hover {
 | 
				
			||||||
 | 
					    color: #3CA8E7;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebarwrapper {
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0.5em 15px 15px 0;
 | 
				
			||||||
 | 
					    width: 210px;
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					    text-align: left;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar h3, div.sphinxsidebar h4 {
 | 
				
			||||||
 | 
					    margin: 1em 0 0.5em 0;
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					    padding: 0.1em 0 0.1em 0.5em;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border: 1px solid #86989B;
 | 
				
			||||||
 | 
					    background-color: #AFC1C4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar h3 a {
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar ul {
 | 
				
			||||||
 | 
					    padding-left: 1.5em;
 | 
				
			||||||
 | 
					    margin-top: 7px;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    line-height: 130%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.sphinxsidebar ul ul {
 | 
				
			||||||
 | 
					    margin-left: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer {
 | 
				
			||||||
 | 
					    background-color: #E3EFF1;
 | 
				
			||||||
 | 
					    color: #86989B;
 | 
				
			||||||
 | 
					    padding: 3px 8px 3px 0;
 | 
				
			||||||
 | 
					    clear: both;
 | 
				
			||||||
 | 
					    font-size: 0.8em;
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.footer a {
 | 
				
			||||||
 | 
					    color: #86989B;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* -- body styles ----------------------------------------------------------- */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p {    
 | 
				
			||||||
 | 
					    margin: 0.8em 0 0.5em 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					    color: #CA7900;
 | 
				
			||||||
 | 
					    text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:hover {
 | 
				
			||||||
 | 
					    color: #2491CF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body a {
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0.7em 0 0.3em 0;
 | 
				
			||||||
 | 
					    font-size: 1.5em;
 | 
				
			||||||
 | 
					    color: #11557C;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h2 {
 | 
				
			||||||
 | 
					    margin: 1.3em 0 0.2em 0;
 | 
				
			||||||
 | 
					    font-size: 1.35em;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3 {
 | 
				
			||||||
 | 
					    margin: 1em 0 -0.3em 0;
 | 
				
			||||||
 | 
					    font-size: 1.2em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
 | 
				
			||||||
 | 
					    color: black!important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					    margin: 0 0 0 0.3em;
 | 
				
			||||||
 | 
					    padding: 0 0.2em 0 0.2em;
 | 
				
			||||||
 | 
					    color: #aaa!important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
 | 
				
			||||||
 | 
					h5:hover a.anchor, h6:hover a.anchor {
 | 
				
			||||||
 | 
					    display: inline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
 | 
				
			||||||
 | 
					h5 a.anchor:hover, h6 a.anchor:hover {
 | 
				
			||||||
 | 
					    color: #777;
 | 
				
			||||||
 | 
					    background-color: #eee;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink {
 | 
				
			||||||
 | 
					    color: #c60f0f!important;
 | 
				
			||||||
 | 
					    font-size: 1em;
 | 
				
			||||||
 | 
					    margin-left: 6px;
 | 
				
			||||||
 | 
					    padding: 0 4px 0 4px;
 | 
				
			||||||
 | 
					    text-decoration: none!important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a.headerlink:hover {
 | 
				
			||||||
 | 
					    background-color: #ccc;
 | 
				
			||||||
 | 
					    color: white!important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cite, code, tt {
 | 
				
			||||||
 | 
					    font-family: 'Consolas', 'Deja Vu Sans Mono',
 | 
				
			||||||
 | 
					                 'Bitstream Vera Sans Mono', monospace;
 | 
				
			||||||
 | 
					    font-size: 0.95em;
 | 
				
			||||||
 | 
					    letter-spacing: 0.01em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tt {
 | 
				
			||||||
 | 
					    background-color: #f2f2f2;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ddd;
 | 
				
			||||||
 | 
					    color: #333;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tt.descname, tt.descclassname, tt.xref {
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hr {
 | 
				
			||||||
 | 
					    border: 1px solid #abc;
 | 
				
			||||||
 | 
					    margin: 2em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a tt {
 | 
				
			||||||
 | 
					    border: 0;
 | 
				
			||||||
 | 
					    color: #CA7900;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a tt:hover {
 | 
				
			||||||
 | 
					    color: #2491CF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					    font-family: 'Consolas', 'Deja Vu Sans Mono',
 | 
				
			||||||
 | 
					                 'Bitstream Vera Sans Mono', monospace;
 | 
				
			||||||
 | 
					    font-size: 0.95em;
 | 
				
			||||||
 | 
					    letter-spacing: 0.015em;
 | 
				
			||||||
 | 
					    line-height: 120%;
 | 
				
			||||||
 | 
					    padding: 0.5em;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					    background-color: #f8f8f8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre a {
 | 
				
			||||||
 | 
					    color: inherit;
 | 
				
			||||||
 | 
					    text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					td.linenos pre {
 | 
				
			||||||
 | 
					    padding: 0.5em 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.quotebar {
 | 
				
			||||||
 | 
					    background-color: #f8f8f8;
 | 
				
			||||||
 | 
					    max-width: 250px;
 | 
				
			||||||
 | 
					    float: right;
 | 
				
			||||||
 | 
					    padding: 2px 7px;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.topic {
 | 
				
			||||||
 | 
					    background-color: #f8f8f8;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table {
 | 
				
			||||||
 | 
					    border-collapse: collapse;
 | 
				
			||||||
 | 
					    margin: 0 -0.5em 0 -0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					table td, table th {
 | 
				
			||||||
 | 
					    padding: 0.2em 0.5em 0.2em 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition, div.warning {
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					    margin: 1em 0 1em 0;
 | 
				
			||||||
 | 
					    border: 1px solid #86989B;
 | 
				
			||||||
 | 
					    background-color: #f7f7f7;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition p, div.warning p {
 | 
				
			||||||
 | 
					    margin: 0.5em 1em 0.5em 1em;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition pre, div.warning pre {
 | 
				
			||||||
 | 
					    margin: 0.4em 1em 0.4em 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition p.admonition-title,
 | 
				
			||||||
 | 
					div.warning p.admonition-title {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0.1em 0 0.1em 0.5em;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #86989B;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    background-color: #AFC1C4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.warning {
 | 
				
			||||||
 | 
					    border: 1px solid #940000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.warning p.admonition-title {
 | 
				
			||||||
 | 
					    background-color: #CF0000;
 | 
				
			||||||
 | 
					    border-bottom-color: #940000;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.admonition ul, div.admonition ol,
 | 
				
			||||||
 | 
					div.warning ul, div.warning ol {
 | 
				
			||||||
 | 
					    margin: 0.1em 0.5em 0.5em 3em;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.versioninfo {
 | 
				
			||||||
 | 
					    margin: 1em 0 0 0;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					    background-color: #DDEAF0;
 | 
				
			||||||
 | 
					    padding: 8px;
 | 
				
			||||||
 | 
					    line-height: 1.3em;
 | 
				
			||||||
 | 
					    font-size: 0.9em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.viewcode-back {
 | 
				
			||||||
 | 
					    font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
 | 
				
			||||||
 | 
					                 'Verdana', sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					div.viewcode-block:target {
 | 
				
			||||||
 | 
					    background-color: #f4debf;
 | 
				
			||||||
 | 
					    border-top: 1px solid #ac9;
 | 
				
			||||||
 | 
					    border-bottom: 1px solid #ac9;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										70
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/_templates/layout.html
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					{#
 | 
				
			||||||
 | 
					    haiku/layout.html
 | 
				
			||||||
 | 
					    ~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Sphinx layout template for the haiku theme.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
 | 
				
			||||||
 | 
					    :license: BSD, see LICENSE for details.
 | 
				
			||||||
 | 
					#}
 | 
				
			||||||
 | 
					{% extends "basic/layout.html" %}
 | 
				
			||||||
 | 
					{% set script_files = script_files + ['_static/theme_extras.js'] %}
 | 
				
			||||||
 | 
					{% set css_files = css_files + ['_static/print.css'] %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{# do not display relbars #}
 | 
				
			||||||
 | 
					{% block relbar1 %}{% endblock %}
 | 
				
			||||||
 | 
					{% block relbar2 %}{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% macro nav() %}
 | 
				
			||||||
 | 
					        <p>
 | 
				
			||||||
 | 
					        {%- block haikurel1 %}
 | 
				
			||||||
 | 
					        {%- endblock %}
 | 
				
			||||||
 | 
					        {%- if prev %}
 | 
				
			||||||
 | 
					        «  <a href="{{ prev.link|e }}">{{ prev.title }}</a>
 | 
				
			||||||
 | 
					          ::  
 | 
				
			||||||
 | 
					        {%- endif %}
 | 
				
			||||||
 | 
					        <a class="uplink" href="{{ pathto(master_doc) }}">{{ _('Contents') }}</a>
 | 
				
			||||||
 | 
					        {%- if next %}
 | 
				
			||||||
 | 
					          ::  
 | 
				
			||||||
 | 
					        <a href="{{ next.link|e }}">{{ next.title }}</a>  »
 | 
				
			||||||
 | 
					        {%- endif %}
 | 
				
			||||||
 | 
					        {%- block haikurel2 %}
 | 
				
			||||||
 | 
					        {%- endblock %}
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					{% endmacro %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					      <div class="header">
 | 
				
			||||||
 | 
					        {%- block haikuheader %}
 | 
				
			||||||
 | 
					        {%- if theme_full_logo != "false" %}
 | 
				
			||||||
 | 
					        <a href="{{ pathto('index') }}">
 | 
				
			||||||
 | 
					          <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        {%- else %}
 | 
				
			||||||
 | 
					        {%- if logo -%}
 | 
				
			||||||
 | 
					          <img class="rightlogo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
 | 
				
			||||||
 | 
					        {%- endif -%}
 | 
				
			||||||
 | 
					        <h1 class="heading"><a href="{{ pathto('index') }}">
 | 
				
			||||||
 | 
					          <span>{{ title|striptags }}</span></a></h1>
 | 
				
			||||||
 | 
					        <h2 class="heading"><span>{{ shorttitle|e }}</span></h2>
 | 
				
			||||||
 | 
					        {%- endif %}
 | 
				
			||||||
 | 
					        {%- endblock %}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="topnav">
 | 
				
			||||||
 | 
					      {{ nav() }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="content">
 | 
				
			||||||
 | 
					        {#{%- if display_toc %}
 | 
				
			||||||
 | 
					        <div id="toc">
 | 
				
			||||||
 | 
					          <h3>Table Of Contents</h3>
 | 
				
			||||||
 | 
					          {{ toc }}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        {%- endif %}#}
 | 
				
			||||||
 | 
					        {% block body %}{% endblock %}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="bottomnav">
 | 
				
			||||||
 | 
					      {{ nav() }}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								docs/api/basexmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/basexmpp.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					========
 | 
				
			||||||
 | 
					BaseXMPP
 | 
				
			||||||
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. module:: sleekxmpp.basexmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. autoclass:: BaseXMPP
 | 
				
			||||||
 | 
					    :members:
 | 
				
			||||||
							
								
								
									
										8
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					==========
 | 
				
			||||||
 | 
					ClientXMPP
 | 
				
			||||||
 | 
					==========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. module:: sleekxmpp.clientxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. autoclass:: ClientXMPP
 | 
				
			||||||
 | 
					    :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:
 | 
				
			||||||
							
								
								
									
										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:
 | 
				
			||||||
							
								
								
									
										177
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,177 @@
 | 
				
			|||||||
 | 
					.. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP Architecture
 | 
				
			||||||
 | 
					======================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The core of SleekXMPP is contained in four classes: ``XMLStream``,
 | 
				
			||||||
 | 
					``BaseXMPP``, ``ClientXMPP``, and ``ComponentXMPP``. Along side this
 | 
				
			||||||
 | 
					stack is a library for working with XML objects that eliminates most
 | 
				
			||||||
 | 
					of the tedium of creating/manipulating XML.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. image:: _static/images/arch_layers.png
 | 
				
			||||||
 | 
					    :height: 300px
 | 
				
			||||||
 | 
					    :align: center
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. index:: XMLStream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Foundation: XMLStream
 | 
				
			||||||
 | 
					-------------------------
 | 
				
			||||||
 | 
					: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
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					: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
 | 
				
			||||||
 | 
					application.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Short-lived threads may also be spawned as requested for threaded
 | 
				
			||||||
 | 
					:term:`event handlers <event handler>`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					How XML Text is Turned into Action
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					To demonstrate the flow of information, let's consider what happens
 | 
				
			||||||
 | 
					when this bit of XML is received (with an assumed namespace of
 | 
				
			||||||
 | 
					``jabber:client``):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <message to="user@example.com" from="friend@example.net">
 | 
				
			||||||
 | 
					      <body>Hej!</body>
 | 
				
			||||||
 | 
					    </message>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **Convert XML strings into objects.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Incoming text is parsed and converted into XML objects (using
 | 
				
			||||||
 | 
					   ElementTree) which are then wrapped into what are referred to as
 | 
				
			||||||
 | 
					   :term:`Stanza objects <stanza object>`. The appropriate class for the
 | 
				
			||||||
 | 
					   new object is determined using a map of namespaced element names to
 | 
				
			||||||
 | 
					   classes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   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.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   These objects are then compared against the stored patterns associated
 | 
				
			||||||
 | 
					   with the registered callback handlers. For each match, a copy of the
 | 
				
			||||||
 | 
					   :term:`stanza object` is paired with a reference to the handler and
 | 
				
			||||||
 | 
					   placed into the event queue.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. **Process the event queue.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   The event queue is the heart of SleekXMPP. Nearly every action that
 | 
				
			||||||
 | 
					   takes place is first inserted into this queue, whether that be received
 | 
				
			||||||
 | 
					   stanzas, custom events, or scheduled events.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   When the stanza is pulled out of the event queue with an associated
 | 
				
			||||||
 | 
					   callback, the callback function is executed with the stanza as its only
 | 
				
			||||||
 | 
					   parameter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   .. warning:: 
 | 
				
			||||||
 | 
					       The callback, aka :term:`stream handler`, is executed in the main event
 | 
				
			||||||
 | 
					       processing thread. If the handler blocks, event processing will also
 | 
				
			||||||
 | 
					       block.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. **Raise Custom Events**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Since a :term:`stream handler` shouldn't block, if extensive processing
 | 
				
			||||||
 | 
					   for a stanza is required (such as needing to send and receive an
 | 
				
			||||||
 | 
					   :class:`~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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   .. 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 :meth:`BaseXMPP._handle_message` follows this pattern, and
 | 
				
			||||||
 | 
					   raises a ``'message'`` event::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       self.event('message', msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   The event call then places the message object back into the event queue
 | 
				
			||||||
 | 
					   paired with an :term:`event handler`::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       ('event', 'message', msg_copy1, custom_event_handler_1)
 | 
				
			||||||
 | 
					       ('event', 'message', msg_copy2, custom_evetn_handler_2) 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. **Process Custom Events**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   The stanza and :term:`event handler` are then pulled from the event
 | 
				
			||||||
 | 
					   queue, and the handler is executed, passing the stanza as its only
 | 
				
			||||||
 | 
					   argument. If the handler was registered as threaded, then a new thread
 | 
				
			||||||
 | 
					   will be spawned for it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   .. note::
 | 
				
			||||||
 | 
					       Events may be raised without needing :term:`stanza objects <stanza object>`. 
 | 
				
			||||||
 | 
					       For example, you could use ``self.event('custom', {'a': 'b'})``. 
 | 
				
			||||||
 | 
					       You don't even need any arguments: ``self.event('no_parameters')``. 
 | 
				
			||||||
 | 
					       However, every event handler MUST accept at least one argument.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Finally, after a long trek, our message is handed off to the user's
 | 
				
			||||||
 | 
					   custom handler in order to do awesome stuff::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       msg.reply()
 | 
				
			||||||
 | 
					       msg['body'] = "Hey! This is awesome!"
 | 
				
			||||||
 | 
					       msg.send()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. index:: BaseXMPP, XMLStream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Raising XMPP Awareness: BaseXMPP
 | 
				
			||||||
 | 
					--------------------------------
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					:class:`~sleekxmpp.basexmpp.BaseXMPP`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. index:: ClientXMPP, BaseXMPP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ClientXMPP
 | 
				
			||||||
 | 
					----------
 | 
				
			||||||
 | 
					: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
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					:class:`~sleekxmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of
 | 
				
			||||||
 | 
					:class:`~sleekxmpp.basexmpp.BaseXMPP` that implements the component handshake
 | 
				
			||||||
 | 
					protocol.
 | 
				
			||||||
							
								
								
									
										222
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								docs/conf.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# SleekXMPP documentation build configuration file, created by
 | 
				
			||||||
 | 
					# sphinx-quickstart on Tue Aug  9 22:27:06 2011.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This file is execfile()d with the current directory set to its containing dir.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Note that not all possible configuration values are present in this
 | 
				
			||||||
 | 
					# autogenerated file.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# All configuration values have a default; values that are commented out
 | 
				
			||||||
 | 
					# serve to show the default.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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('..'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -- General configuration -----------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If your documentation needs a minimal Sphinx version, state it here.
 | 
				
			||||||
 | 
					#needs_sphinx = '1.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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', 'sphinx.ext.intersphinx']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Add any paths that contain templates here, relative to this directory.
 | 
				
			||||||
 | 
					templates_path = ['_templates']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The suffix of source filenames.
 | 
				
			||||||
 | 
					source_suffix = '.rst'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The encoding of source files.
 | 
				
			||||||
 | 
					#source_encoding = 'utf-8-sig'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The master toctree document.
 | 
				
			||||||
 | 
					master_doc = 'index'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# General information about the project.
 | 
				
			||||||
 | 
					project = u'SleekXMPP'
 | 
				
			||||||
 | 
					copyright = u'2011, Nathan Fritz, Lance Stout'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The version info for the project you're documenting, acts as replacement for
 | 
				
			||||||
 | 
					# |version| and |release|, also used in various other places throughout the
 | 
				
			||||||
 | 
					# built documents.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The short X.Y version.
 | 
				
			||||||
 | 
					version = '1.0'
 | 
				
			||||||
 | 
					# The full version, including alpha/beta/rc tags.
 | 
				
			||||||
 | 
					release = '1.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The language for content autogenerated by Sphinx. Refer to documentation
 | 
				
			||||||
 | 
					# for a list of supported languages.
 | 
				
			||||||
 | 
					#language = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# There are two options for replacing |today|: either, you set today to some
 | 
				
			||||||
 | 
					# non-false value, then it is used:
 | 
				
			||||||
 | 
					#today = ''
 | 
				
			||||||
 | 
					# Else, today_fmt is used as the format for a strftime call.
 | 
				
			||||||
 | 
					#today_fmt = '%B %d, %Y'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# List of patterns, relative to source directory, that match files and
 | 
				
			||||||
 | 
					# directories to ignore when looking for source files.
 | 
				
			||||||
 | 
					exclude_patterns = ['_build']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The reST default role (used for this markup: `text`) to use for all documents.
 | 
				
			||||||
 | 
					#default_role = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, '()' will be appended to :func: etc. cross-reference text.
 | 
				
			||||||
 | 
					#add_function_parentheses = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, the current module name will be prepended to all description
 | 
				
			||||||
 | 
					# unit titles (such as .. function::).
 | 
				
			||||||
 | 
					#add_module_names = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, sectionauthor and moduleauthor directives will be shown in the
 | 
				
			||||||
 | 
					# output. They are ignored by default.
 | 
				
			||||||
 | 
					#show_authors = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The name of the Pygments (syntax highlighting) style to use.
 | 
				
			||||||
 | 
					pygments_style = 'tango'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# A list of ignored prefixes for module index sorting.
 | 
				
			||||||
 | 
					#modindex_common_prefix = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -- Options for HTML output ---------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The theme to use for HTML and HTML Help pages.  See the documentation for
 | 
				
			||||||
 | 
					# a list of builtin themes.
 | 
				
			||||||
 | 
					html_theme = 'haiku'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Theme options are theme-specific and customize the look and feel of a theme
 | 
				
			||||||
 | 
					# further.  For a list of options available for each theme, see the
 | 
				
			||||||
 | 
					# documentation.
 | 
				
			||||||
 | 
					#html_theme_options = {'headingcolor': '#CFCFCF', 'linkcolor': '#4A7389'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 00ADEE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Add any paths that contain custom themes here, relative to this directory.
 | 
				
			||||||
 | 
					#html_theme_path = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The name for this set of Sphinx documents.  If None, it defaults to
 | 
				
			||||||
 | 
					# "<project> v<release> documentation".
 | 
				
			||||||
 | 
					html_title = 'SleekXMPP'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# A shorter title for the navigation bar.  Default is the same as html_title.
 | 
				
			||||||
 | 
					html_short_title = '%s Documentation' % release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The name of an image file (relative to this directory) to place at the top
 | 
				
			||||||
 | 
					# of the sidebar.
 | 
				
			||||||
 | 
					#html_logo = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The name of an image file (within the static path) to use as favicon of the
 | 
				
			||||||
 | 
					# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 | 
				
			||||||
 | 
					# pixels large.
 | 
				
			||||||
 | 
					#html_favicon = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Add any paths that contain custom static files (such as style sheets) here,
 | 
				
			||||||
 | 
					# relative to this directory. They are copied after the builtin static files,
 | 
				
			||||||
 | 
					# so a file named "default.css" will overwrite the builtin "default.css".
 | 
				
			||||||
 | 
					html_static_path = ['_static']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 | 
				
			||||||
 | 
					# using the given strftime format.
 | 
				
			||||||
 | 
					#html_last_updated_fmt = '%b %d, %Y'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, SmartyPants will be used to convert quotes and dashes to
 | 
				
			||||||
 | 
					# typographically correct entities.
 | 
				
			||||||
 | 
					#html_use_smartypants = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Custom sidebar templates, maps document names to template names.
 | 
				
			||||||
 | 
					#html_sidebars = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Additional templates that should be rendered to pages, maps page names to
 | 
				
			||||||
 | 
					# template names.
 | 
				
			||||||
 | 
					html_additional_pages = {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If false, no module index is generated.
 | 
				
			||||||
 | 
					#html_domain_indices = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If false, no index is generated.
 | 
				
			||||||
 | 
					#html_use_index = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, the index is split into individual pages for each letter.
 | 
				
			||||||
 | 
					#html_split_index = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, links to the reST sources are added to the pages.
 | 
				
			||||||
 | 
					#html_show_sourcelink = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
 | 
				
			||||||
 | 
					#html_show_sphinx = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
 | 
				
			||||||
 | 
					#html_show_copyright = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, an OpenSearch description file will be output, and all pages will
 | 
				
			||||||
 | 
					# contain a <link> tag referring to it.  The value of this option must be the
 | 
				
			||||||
 | 
					# base URL from which the finished HTML is served.
 | 
				
			||||||
 | 
					#html_use_opensearch = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This is the file name suffix for HTML files (e.g. ".xhtml").
 | 
				
			||||||
 | 
					#html_file_suffix = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Output file base name for HTML help builder.
 | 
				
			||||||
 | 
					htmlhelp_basename = 'SleekXMPPdoc'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -- Options for LaTeX output --------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The paper size ('letter' or 'a4').
 | 
				
			||||||
 | 
					#latex_paper_size = 'letter'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The font size ('10pt', '11pt' or '12pt').
 | 
				
			||||||
 | 
					#latex_font_size = '10pt'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Grouping the document tree into LaTeX files. List of tuples
 | 
				
			||||||
 | 
					# (source start file, target name, title, author, documentclass [howto/manual]).
 | 
				
			||||||
 | 
					latex_documents = [
 | 
				
			||||||
 | 
					  ('index', 'SleekXMPP.tex', u'SleekXMPP Documentation',
 | 
				
			||||||
 | 
					   u'Nathan Fritz, Lance Stout', 'manual'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# The name of an image file (relative to this directory) to place at the top of
 | 
				
			||||||
 | 
					# the title page.
 | 
				
			||||||
 | 
					#latex_logo = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# For "manual" documents, if this is true, then toplevel headings are parts,
 | 
				
			||||||
 | 
					# not chapters.
 | 
				
			||||||
 | 
					#latex_use_parts = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, show page references after internal links.
 | 
				
			||||||
 | 
					#latex_show_pagerefs = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If true, show URL addresses after external links.
 | 
				
			||||||
 | 
					#latex_show_urls = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Additional stuff for the LaTeX preamble.
 | 
				
			||||||
 | 
					#latex_preamble = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Documents to append as an appendix to all manuals.
 | 
				
			||||||
 | 
					#latex_appendices = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If false, no module index is generated.
 | 
				
			||||||
 | 
					#latex_domain_indices = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -- Options for manual page output --------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# One entry per manual page. List of tuples
 | 
				
			||||||
 | 
					# (source start file, name, description, authors, manual section).
 | 
				
			||||||
 | 
					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')}
 | 
				
			||||||
							
								
								
									
										679
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										679
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,679 @@
 | 
				
			|||||||
 | 
					.. _create-plugin:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Creating a SleekXMPP Plugin
 | 
				
			||||||
 | 
					===========================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					One of the goals of SleekXMPP is to provide support for every draft or final
 | 
				
			||||||
 | 
					XMPP extension (`XEP <http://xmpp.org/extensions/>`_). To do this, SleekXMPP has a
 | 
				
			||||||
 | 
					plugin mechanism for adding the functionalities required by each XEP. But even
 | 
				
			||||||
 | 
					though plugins were made to quickly implement and prototype the official XMPP
 | 
				
			||||||
 | 
					extensions, there is no reason you can't create your own plugin to implement
 | 
				
			||||||
 | 
					your own custom XMPP-based protocol.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This guide will help walk you through the steps to
 | 
				
			||||||
 | 
					implement a rudimentary version of `XEP-0077 In-band
 | 
				
			||||||
 | 
					Registration <http://xmpp.org/extensions/xep-0077.html>`_. In-band registration
 | 
				
			||||||
 | 
					was implemented in example 14-6 (page 223) of `XMPP: The Definitive
 | 
				
			||||||
 | 
					Guide <http://oreilly.com/catalog/9780596521271>`_ because there was no SleekXMPP
 | 
				
			||||||
 | 
					plugin for XEP-0077 at the time of writing. We will partially fix that issue
 | 
				
			||||||
 | 
					here by turning the example implementation from *XMPP: The Definitive Guide*
 | 
				
			||||||
 | 
					into a plugin. Again, note that this will not a complete implementation, and a
 | 
				
			||||||
 | 
					different, more robust, official plugin for XEP-0077 may be added to SleekXMPP
 | 
				
			||||||
 | 
					in the future.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The example plugin created in this guide is for the server side of the
 | 
				
			||||||
 | 
					    registration process only. It will **NOT** be able to register new accounts
 | 
				
			||||||
 | 
					    on an XMPP server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					First Steps
 | 
				
			||||||
 | 
					-----------
 | 
				
			||||||
 | 
					Every plugin inherits from the class :mod:`base_plugin <sleekxmpp.plugins.base.base_plugin>`,
 | 
				
			||||||
 | 
					and must include a ``plugin_init`` method. While the
 | 
				
			||||||
 | 
					plugins distributed with SleekXMPP must be placed in the plugins directory
 | 
				
			||||||
 | 
					``sleekxmpp/plugins`` to be loaded, custom plugins may be loaded from any
 | 
				
			||||||
 | 
					module. To do so, use the following form when registering the plugin:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    self.register_plugin('myplugin', module=mod_containing_my_plugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The plugin name must be the same as the plugin's class name.
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					Now, we can open our favorite text editors and create ``xep_0077.py`` in
 | 
				
			||||||
 | 
					``SleekXMPP/sleekxmpp/plugins``. We want to do some basic house-keeping and
 | 
				
			||||||
 | 
					declare the name and description of the XEP we are implementing. If you
 | 
				
			||||||
 | 
					are creating your own custom plugin, you don't need to include the ``xep``
 | 
				
			||||||
 | 
					attribute.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Creating a SleekXMPP Plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This is a minimal implementation of XEP-0077 to serve
 | 
				
			||||||
 | 
					    as a tutorial for creating SleekXMPP plugins.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from sleekxmpp.plugins.base import base_plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class xep_0077(base_plugin):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        XEP-0077 In-Band Registration
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def plugin_init(self):
 | 
				
			||||||
 | 
					            self.description = "In-Band Registration"
 | 
				
			||||||
 | 
					            self.xep = "0077"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now that we have a basic plugin, we need to edit
 | 
				
			||||||
 | 
					``sleekxmpp/plugins/__init__.py`` to include our new plugin by adding
 | 
				
			||||||
 | 
					``'xep_0077'`` to the ``__all__`` declaration.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Interacting with Other Plugins
 | 
				
			||||||
 | 
					------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In-band registration is a feature that should be advertised through `Service
 | 
				
			||||||
 | 
					Discovery <http://xmpp.org/extensions/xep-0030.html>`_. To do that, we tell the
 | 
				
			||||||
 | 
					``xep_0030`` plugin to add the ``"jabber:iq:register"`` feature. We put this
 | 
				
			||||||
 | 
					call in a method named ``post_init`` which will be called once the plugin has
 | 
				
			||||||
 | 
					been loaded; by doing so we advertise that we can do registrations only after we
 | 
				
			||||||
 | 
					finish activating the plugin.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The ``post_init`` method needs to call ``base_plugin.post_init(self)``
 | 
				
			||||||
 | 
					which will mark that ``post_init`` has been called for the plugin. Once the
 | 
				
			||||||
 | 
					SleekXMPP object begins processing, ``post_init`` will be called on any plugins
 | 
				
			||||||
 | 
					that have not already run ``post_init``. This allows you to register plugins and
 | 
				
			||||||
 | 
					their dependencies without needing to worry about the order in which you do so.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Note:** by adding this call we have introduced a dependency on the XEP-0030
 | 
				
			||||||
 | 
					plugin. Be sure to register ``'xep_0030'`` as well as ``'xep_0077'``. SleekXMPP
 | 
				
			||||||
 | 
					does not automatically load plugin dependencies for you.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def post_init(self):
 | 
				
			||||||
 | 
					        base_plugin.post_init(self)
 | 
				
			||||||
 | 
					        self.xmpp['xep_0030'].add_feature("jabber:iq:register")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Creating Custom Stanza Objects
 | 
				
			||||||
 | 
					------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now, the IQ stanzas needed to implement our version of XEP-0077 are not very
 | 
				
			||||||
 | 
					complex, and we could just interact with the XML objects directly just like
 | 
				
			||||||
 | 
					in the *XMPP: The Definitive Guide* example. However, creating custom stanza
 | 
				
			||||||
 | 
					objects is good practice.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We will create a new ``Registration`` stanza. Following the *XMPP: The
 | 
				
			||||||
 | 
					Definitive Guide* example, we will add support for a username and password
 | 
				
			||||||
 | 
					field. We also need two flags: ``registered`` and ``remove``. The ``registered``
 | 
				
			||||||
 | 
					flag is sent when an already registered user attempts to register, along with
 | 
				
			||||||
 | 
					their registration data. The ``remove`` flag is a request to unregister a user's
 | 
				
			||||||
 | 
					account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Adding additional `fields specified in
 | 
				
			||||||
 | 
					XEP-0077 <http://xmpp.org/extensions/xep-0077.html#registrar-formtypes-register>`_
 | 
				
			||||||
 | 
					will not be difficult and is left as an exercise for the reader.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Our ``Registration`` class needs to start with a few descriptions of its
 | 
				
			||||||
 | 
					behaviour:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``namespace``
 | 
				
			||||||
 | 
					    The namespace our stanza object lives in. In this case,
 | 
				
			||||||
 | 
					    ``"jabber:iq:register"``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``name``
 | 
				
			||||||
 | 
					    The name of the root XML element. In this case, the ``query`` element.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``plugin_attrib``
 | 
				
			||||||
 | 
					    The name to access this type of stanza. In particular, given a
 | 
				
			||||||
 | 
					    registration stanza, the ``Registration`` object can be found using:
 | 
				
			||||||
 | 
					    ``iq_object['register']``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``interfaces``
 | 
				
			||||||
 | 
					    A list of dictionary-like keys that can be used with the stanza object.
 | 
				
			||||||
 | 
					    When using ``"key"``, if there exists a method of the form ``getKey``,
 | 
				
			||||||
 | 
					    ``setKey``, or``delKey`` (depending on context) then the result of calling
 | 
				
			||||||
 | 
					    that method will be returned. Otherwise, the value of the attribute ``key``
 | 
				
			||||||
 | 
					    of the main stanza element is returned if one exists.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Note:** The accessor methods currently use title case, and not camel case.
 | 
				
			||||||
 | 
					    Thus if you need to access an item named ``"methodName"`` you will need to
 | 
				
			||||||
 | 
					    use ``getMethodname``. This naming convention might change to full camel
 | 
				
			||||||
 | 
					    case in a future version of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``sub_interfaces``
 | 
				
			||||||
 | 
					    A subset of ``interfaces``, but these keys map to the text of any
 | 
				
			||||||
 | 
					    subelements that are direct children of the main stanza element. Thus,
 | 
				
			||||||
 | 
					    referencing ``iq_object['register']['username']`` will either execute
 | 
				
			||||||
 | 
					    ``getUsername`` or return the value in the ``username`` element of the
 | 
				
			||||||
 | 
					    query.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If you need to access an element, say ``elem``, that is not a direct child
 | 
				
			||||||
 | 
					    of the main stanza element, you will need to add ``getElem``, ``setElem``,
 | 
				
			||||||
 | 
					    and ``delElem``. See the note above about naming conventions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
 | 
				
			||||||
 | 
					    from sleekxmpp import Iq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Registration(ElementBase):
 | 
				
			||||||
 | 
					        namespace = 'jabber:iq:register'
 | 
				
			||||||
 | 
					        name = 'query'
 | 
				
			||||||
 | 
					        plugin_attrib = 'register'
 | 
				
			||||||
 | 
					        interfaces = set(('username', 'password', 'registered', 'remove'))
 | 
				
			||||||
 | 
					        sub_interfaces = interfaces
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def getRegistered(self):
 | 
				
			||||||
 | 
					            present = self.xml.find('{%s}registered' % self.namespace)
 | 
				
			||||||
 | 
					            return present is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def getRemove(self):
 | 
				
			||||||
 | 
					            present = self.xml.find('{%s}remove' % self.namespace)
 | 
				
			||||||
 | 
					            return present is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setRegistered(self, registered):
 | 
				
			||||||
 | 
					            if registered:
 | 
				
			||||||
 | 
					                self.addField('registered')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                del self['registered']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setRemove(self, remove):
 | 
				
			||||||
 | 
					            if remove:
 | 
				
			||||||
 | 
					                self.addField('remove')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                del self['remove']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def addField(self, name):
 | 
				
			||||||
 | 
					            itemXML = ET.Element('{%s}%s' % (self.namespace, name))
 | 
				
			||||||
 | 
					            self.xml.append(itemXML)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Setting a ``sub_interface`` attribute to ``""`` will remove that subelement.
 | 
				
			||||||
 | 
					Since we want to include empty registration fields in our form, we need the
 | 
				
			||||||
 | 
					``addField`` method to add the empty elements.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since the ``registered`` and ``remove`` elements are just flags, we need to add
 | 
				
			||||||
 | 
					custom logic to enforce the binary behavior.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Extracting Stanzas from the XML Stream
 | 
				
			||||||
 | 
					--------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now that we have a custom stanza object, we need to be able to detect when we
 | 
				
			||||||
 | 
					receive one. To do this, we register a stream handler that will pattern match
 | 
				
			||||||
 | 
					stanzas off of the XML stream against our stanza object's element name and
 | 
				
			||||||
 | 
					namespace. To do so, we need to create a ``Callback`` object which contains
 | 
				
			||||||
 | 
					an XML fragment that can identify our stanza type. We can add this handler
 | 
				
			||||||
 | 
					registration to our ``plugin_init`` method.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Also, we need to associate our ``Registration`` class with IQ stanzas;
 | 
				
			||||||
 | 
					that requires the use of the ``register_stanza_plugin`` function (in
 | 
				
			||||||
 | 
					``sleekxmpp.xmlstream.stanzabase``) which takes the class of a parent stanza
 | 
				
			||||||
 | 
					type followed by the substanza type. In our case, the parent stanza is an IQ
 | 
				
			||||||
 | 
					stanza, and the substanza is our registration query.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The ``__handleRegistration`` method referenced in the callback will be our
 | 
				
			||||||
 | 
					handler function to process registration requests.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def plugin_init(self):
 | 
				
			||||||
 | 
					        self.description = "In-Band Registration"
 | 
				
			||||||
 | 
					        self.xep = "0077"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.xmpp.registerHandler(
 | 
				
			||||||
 | 
					          Callback('In-Band Registration',
 | 
				
			||||||
 | 
					            MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
 | 
				
			||||||
 | 
					            self.__handleRegistration))
 | 
				
			||||||
 | 
					        register_stanza_plugin(Iq, Registration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Handling Incoming Stanzas and Triggering Events
 | 
				
			||||||
 | 
					-----------------------------------------------
 | 
				
			||||||
 | 
					There are six situations that we need to handle to finish our implementation of
 | 
				
			||||||
 | 
					XEP-0077.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Registration Form Request from a New User:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <iq type="result">
 | 
				
			||||||
 | 
					         <query xmlns="jabber:iq:register">
 | 
				
			||||||
 | 
					          <username />
 | 
				
			||||||
 | 
					          <password />
 | 
				
			||||||
 | 
					         </query>
 | 
				
			||||||
 | 
					        </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Registration Form Request from an Existing User:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <iq type="result">
 | 
				
			||||||
 | 
					         <query xmlns="jabber:iq:register">
 | 
				
			||||||
 | 
					          <registered />
 | 
				
			||||||
 | 
					          <username>Foo</username>
 | 
				
			||||||
 | 
					          <password>hunter2</password>
 | 
				
			||||||
 | 
					         </query>
 | 
				
			||||||
 | 
					        </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Unregister Account:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <iq type="result">
 | 
				
			||||||
 | 
					         <query xmlns="jabber:iq:register" />
 | 
				
			||||||
 | 
					        </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Incomplete Registration:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <iq type="error">
 | 
				
			||||||
 | 
					          <query xmlns="jabber:iq:register">
 | 
				
			||||||
 | 
					            <username>Foo</username>
 | 
				
			||||||
 | 
					          </query>
 | 
				
			||||||
 | 
					         <error code="406" type="modify">
 | 
				
			||||||
 | 
					          <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
 | 
				
			||||||
 | 
					         </error>
 | 
				
			||||||
 | 
					        </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Conflicting Registrations:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <iq type="error">
 | 
				
			||||||
 | 
					         <query xmlns="jabber:iq:register">
 | 
				
			||||||
 | 
					          <username>Foo</username>
 | 
				
			||||||
 | 
					          <password>hunter2</password>
 | 
				
			||||||
 | 
					         </query>
 | 
				
			||||||
 | 
					         <error code="409" type="cancel">
 | 
				
			||||||
 | 
					          <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
 | 
				
			||||||
 | 
					         </error>
 | 
				
			||||||
 | 
					        </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Successful Registration:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <iq type="result">
 | 
				
			||||||
 | 
					         <query xmlns="jabber:iq:register" />
 | 
				
			||||||
 | 
					        </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Cases 1 and 2: Registration Requests
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					Responding to registration requests depends on if the requesting user already
 | 
				
			||||||
 | 
					has an account. If there is an account, the response should include the
 | 
				
			||||||
 | 
					``registered`` flag and the user's current registration information. Otherwise,
 | 
				
			||||||
 | 
					we just send the fields for our registration form.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We will handle both cases by creating a ``sendRegistrationForm`` method that
 | 
				
			||||||
 | 
					will create either an empty of full form depending on if we provide it with
 | 
				
			||||||
 | 
					user data. Since we need to know which form fields to include (especially if we
 | 
				
			||||||
 | 
					add support for the other fields specified in XEP-0077), we will also create a
 | 
				
			||||||
 | 
					method ``setForm`` which will take the names of the fields we wish to include.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def plugin_init(self):
 | 
				
			||||||
 | 
					        self.description = "In-Band Registration"
 | 
				
			||||||
 | 
					        self.xep = "0077"
 | 
				
			||||||
 | 
					        self.form_fields = ('username', 'password')
 | 
				
			||||||
 | 
					        ... remainder of plugin_init
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __handleRegistration(self, iq):
 | 
				
			||||||
 | 
					        if iq['type'] == 'get':
 | 
				
			||||||
 | 
					            # Registration form requested
 | 
				
			||||||
 | 
					            userData = self.backend[iq['from'].bare]
 | 
				
			||||||
 | 
					            self.sendRegistrationForm(iq, userData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def setForm(self, *fields):
 | 
				
			||||||
 | 
					        self.form_fields = fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sendRegistrationForm(self, iq, userData=None):
 | 
				
			||||||
 | 
					        reg = iq['register']
 | 
				
			||||||
 | 
					        if userData is None:
 | 
				
			||||||
 | 
					            userData = {}
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            reg['registered'] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for field in self.form_fields:
 | 
				
			||||||
 | 
					            data = userData.get(field, '')
 | 
				
			||||||
 | 
					            if data:
 | 
				
			||||||
 | 
					                # Add field with existing data
 | 
				
			||||||
 | 
					                reg[field] = data
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # Add a blank field
 | 
				
			||||||
 | 
					                reg.addField(field)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        iq.reply().setPayload(reg.xml)
 | 
				
			||||||
 | 
					        iq.send()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note how we are able to access our ``Registration`` stanza object with
 | 
				
			||||||
 | 
					``iq['register']``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A User Backend
 | 
				
			||||||
 | 
					++++++++++++++
 | 
				
			||||||
 | 
					You might have noticed the reference to ``self.backend``, which is an object
 | 
				
			||||||
 | 
					that abstracts away storing and retrieving user information. Since it is not
 | 
				
			||||||
 | 
					much more than a dictionary, we will leave the implementation details to the
 | 
				
			||||||
 | 
					final, full source code example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Case 3: Unregister an Account
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					The next simplest case to consider is responding to a request to remove
 | 
				
			||||||
 | 
					an account. If we receive a ``remove`` flag, we instruct the backend to
 | 
				
			||||||
 | 
					remove the user's account. Since your application may need to know about
 | 
				
			||||||
 | 
					when users are registered or unregistered, we trigger an event using
 | 
				
			||||||
 | 
					``self.xmpp.event('unregister_user', iq)``. See the component examples below for
 | 
				
			||||||
 | 
					how to respond to that event.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     def __handleRegistration(self, iq):
 | 
				
			||||||
 | 
					        if iq['type'] == 'get':
 | 
				
			||||||
 | 
					            # Registration form requested
 | 
				
			||||||
 | 
					            userData = self.backend[iq['from'].bare]
 | 
				
			||||||
 | 
					            self.sendRegistrationForm(iq, userData)
 | 
				
			||||||
 | 
					        elif iq['type'] == 'set':
 | 
				
			||||||
 | 
					            # Remove an account
 | 
				
			||||||
 | 
					            if iq['register']['remove']:
 | 
				
			||||||
 | 
					                self.backend.unregister(iq['from'].bare)
 | 
				
			||||||
 | 
					                self.xmpp.event('unregistered_user', iq)
 | 
				
			||||||
 | 
					                iq.reply().send()
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Case 4: Incomplete Registration
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					For the next case we need to check the user's registration to ensure it has all
 | 
				
			||||||
 | 
					of the fields we wanted. The simple option that we will use is to loop over the
 | 
				
			||||||
 | 
					field names and check each one; however, this means that all fields we send to
 | 
				
			||||||
 | 
					the user are required. Adding optional fields is left to the reader.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since we have received an incomplete form, we need to send an error message back
 | 
				
			||||||
 | 
					to the user. We have to send a few different types of errors, so we will also
 | 
				
			||||||
 | 
					create a ``_sendError`` method that will add the appropriate ``error`` element
 | 
				
			||||||
 | 
					to the IQ reply.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __handleRegistration(self, iq):
 | 
				
			||||||
 | 
					        if iq['type'] == 'get':
 | 
				
			||||||
 | 
					            # Registration form requested
 | 
				
			||||||
 | 
					            userData = self.backend[iq['from'].bare]
 | 
				
			||||||
 | 
					            self.sendRegistrationForm(iq, userData)
 | 
				
			||||||
 | 
					        elif iq['type'] == 'set':
 | 
				
			||||||
 | 
					            if iq['register']['remove']:
 | 
				
			||||||
 | 
					                # Remove an account
 | 
				
			||||||
 | 
					                self.backend.unregister(iq['from'].bare)
 | 
				
			||||||
 | 
					                self.xmpp.event('unregistered_user', iq)
 | 
				
			||||||
 | 
					                iq.reply().send()
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for field in self.form_fields:
 | 
				
			||||||
 | 
					                if not iq['register'][field]:
 | 
				
			||||||
 | 
					                    # Incomplete Registration
 | 
				
			||||||
 | 
					                    self._sendError(iq, '406', 'modify', 'not-acceptable'
 | 
				
			||||||
 | 
					                                    "Please fill in all fields.")
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _sendError(self, iq, code, error_type, name, text=''):
 | 
				
			||||||
 | 
					        iq.reply().setPayload(iq['register'].xml)
 | 
				
			||||||
 | 
					        iq.error()
 | 
				
			||||||
 | 
					        iq['error']['code'] = code
 | 
				
			||||||
 | 
					        iq['error']['type'] = error_type
 | 
				
			||||||
 | 
					        iq['error']['condition'] = name
 | 
				
			||||||
 | 
					        iq['error']['text'] = text
 | 
				
			||||||
 | 
					        iq.send()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Cases 5 and 6: Conflicting and Successful Registration
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					We are down to the final decision on if we have a successful registration. We
 | 
				
			||||||
 | 
					send the user's data to the backend with the ``self.backend.register`` method.
 | 
				
			||||||
 | 
					If it returns ``True``, then registration has been successful. Otherwise,
 | 
				
			||||||
 | 
					there has been a conflict with usernames and registration has failed. Like
 | 
				
			||||||
 | 
					with unregistering an account, we trigger an event indicating that a user has
 | 
				
			||||||
 | 
					been registered by using ``self.xmpp.event('registered_user', iq)``. See the
 | 
				
			||||||
 | 
					component examples below for how to respond to this event.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __handleRegistration(self, iq):
 | 
				
			||||||
 | 
					        if iq['type'] == 'get':
 | 
				
			||||||
 | 
					            # Registration form requested
 | 
				
			||||||
 | 
					            userData = self.backend[iq['from'].bare]
 | 
				
			||||||
 | 
					            self.sendRegistrationForm(iq, userData)
 | 
				
			||||||
 | 
					        elif iq['type'] == 'set':
 | 
				
			||||||
 | 
					            if iq['register']['remove']:
 | 
				
			||||||
 | 
					                # Remove an account
 | 
				
			||||||
 | 
					                self.backend.unregister(iq['from'].bare)
 | 
				
			||||||
 | 
					                self.xmpp.event('unregistered_user', iq)
 | 
				
			||||||
 | 
					                iq.reply().send()
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for field in self.form_fields:
 | 
				
			||||||
 | 
					                if not iq['register'][field]:
 | 
				
			||||||
 | 
					                    # Incomplete Registration
 | 
				
			||||||
 | 
					                    self._sendError(iq, '406', 'modify', 'not-acceptable',
 | 
				
			||||||
 | 
					                                    "Please fill in all fields.")
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.backend.register(iq['from'].bare, iq['register']):
 | 
				
			||||||
 | 
					                # Successful registration
 | 
				
			||||||
 | 
					                self.xmpp.event('registered_user', iq)
 | 
				
			||||||
 | 
					                iq.reply().setPayload(iq['register'].xml)
 | 
				
			||||||
 | 
					                iq.send()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # Conflicting registration
 | 
				
			||||||
 | 
					                self._sendError(iq, '409', 'cancel', 'conflict',
 | 
				
			||||||
 | 
					                                "That username is already taken.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example Component Using the XEP-0077 Plugin
 | 
				
			||||||
 | 
					-------------------------------------------
 | 
				
			||||||
 | 
					Alright, the moment we've been working towards - actually using our plugin to
 | 
				
			||||||
 | 
					simplify our other applications. Here is a basic component that simply manages
 | 
				
			||||||
 | 
					user registrations and sends the user a welcoming message when they register,
 | 
				
			||||||
 | 
					and a farewell message when they delete their account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that we have to register the ``'xep_0030'`` plugin first,
 | 
				
			||||||
 | 
					and that we specified the form fields we wish to use with
 | 
				
			||||||
 | 
					``self.xmpp.plugin['xep_0077'].setForm('username', 'password')``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import sleekxmpp.componentxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Example(sleekxmpp.componentxmpp.ComponentXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def __init__(self, jid, password):
 | 
				
			||||||
 | 
					            sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.registerPlugin('xep_0030')
 | 
				
			||||||
 | 
					            self.registerPlugin('xep_0077')
 | 
				
			||||||
 | 
					            self.plugin['xep_0077'].setForm('username', 'password')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.add_event_handler("registered_user", self.reg)
 | 
				
			||||||
 | 
					            self.add_event_handler("unregistered_user", self.unreg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def reg(self, iq):
 | 
				
			||||||
 | 
					            msg = "Welcome! %s" % iq['register']['username']
 | 
				
			||||||
 | 
					            self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def unreg(self, iq):
 | 
				
			||||||
 | 
					            msg = "Bye! %s" % iq['register']['username']
 | 
				
			||||||
 | 
					            self.sendMessage(iq['from'], msg, mfrom=self.fulljid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Congratulations!** We now have a basic, functioning implementation of
 | 
				
			||||||
 | 
					XEP-0077.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Complete Source Code for XEP-0077 Plugin
 | 
				
			||||||
 | 
					----------------------------------------
 | 
				
			||||||
 | 
					Here is a copy of a more complete implementation of the plugin we created, but
 | 
				
			||||||
 | 
					with some additional registration fields implemented.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Creating a SleekXMPP Plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    This is a minimal implementation of XEP-0077 to serve
 | 
				
			||||||
 | 
					    as a tutorial for creating SleekXMPP plugins.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    from sleekxmpp.plugins.base import base_plugin
 | 
				
			||||||
 | 
					    from sleekxmpp.xmlstream.handler.callback import Callback
 | 
				
			||||||
 | 
					    from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
 | 
				
			||||||
 | 
					    from sleekxmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin
 | 
				
			||||||
 | 
					    from sleekxmpp import Iq
 | 
				
			||||||
 | 
					    import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Registration(ElementBase):
 | 
				
			||||||
 | 
					        namespace = 'jabber:iq:register'
 | 
				
			||||||
 | 
					        name = 'query'
 | 
				
			||||||
 | 
					        plugin_attrib = 'register'
 | 
				
			||||||
 | 
					        interfaces = set(('username', 'password', 'email', 'nick', 'name', 
 | 
				
			||||||
 | 
					                          'first', 'last', 'address', 'city', 'state', 'zip', 
 | 
				
			||||||
 | 
					                          'phone', 'url', 'date', 'misc', 'text', 'key', 
 | 
				
			||||||
 | 
					                          'registered', 'remove', 'instructions'))
 | 
				
			||||||
 | 
					        sub_interfaces = interfaces
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def getRegistered(self):
 | 
				
			||||||
 | 
					            present = self.xml.find('{%s}registered' % self.namespace)
 | 
				
			||||||
 | 
					            return present is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def getRemove(self):
 | 
				
			||||||
 | 
					            present = self.xml.find('{%s}remove' % self.namespace)
 | 
				
			||||||
 | 
					            return present is not None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setRegistered(self, registered):
 | 
				
			||||||
 | 
					            if registered:
 | 
				
			||||||
 | 
					                self.addField('registered')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                del self['registered']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setRemove(self, remove):
 | 
				
			||||||
 | 
					            if remove:
 | 
				
			||||||
 | 
					                self.addField('remove')
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                del self['remove']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def addField(self, name):
 | 
				
			||||||
 | 
					            itemXML = ET.Element('{%s}%s' % (self.namespace, name))
 | 
				
			||||||
 | 
					            self.xml.append(itemXML)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class UserStore(object):
 | 
				
			||||||
 | 
					        def __init__(self):
 | 
				
			||||||
 | 
					            self.users = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def __getitem__(self, jid):
 | 
				
			||||||
 | 
					            return self.users.get(jid, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def register(self, jid, registration):
 | 
				
			||||||
 | 
					            username = registration['username']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def filter_usernames(user):
 | 
				
			||||||
 | 
					                return user != jid and self.users[user]['username'] == username
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            conflicts = filter(filter_usernames, self.users.keys())
 | 
				
			||||||
 | 
					            if conflicts:
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.users[jid] = registration
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def unregister(self, jid):
 | 
				
			||||||
 | 
					            del self.users[jid]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class xep_0077(base_plugin):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        XEP-0077 In-Band Registration
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def plugin_init(self):
 | 
				
			||||||
 | 
					            self.description = "In-Band Registration"
 | 
				
			||||||
 | 
					            self.xep = "0077"
 | 
				
			||||||
 | 
					            self.form_fields = ('username', 'password')
 | 
				
			||||||
 | 
					            self.form_instructions = ""
 | 
				
			||||||
 | 
					            self.backend = UserStore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.xmpp.registerHandler(
 | 
				
			||||||
 | 
					                Callback('In-Band Registration',
 | 
				
			||||||
 | 
					                         MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
 | 
				
			||||||
 | 
					                         self.__handleRegistration))
 | 
				
			||||||
 | 
					            register_stanza_plugin(Iq, Registration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def post_init(self):
 | 
				
			||||||
 | 
					            base_plugin.post_init(self)
 | 
				
			||||||
 | 
					            self.xmpp['xep_0030'].add_feature("jabber:iq:register")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def __handleRegistration(self, iq):
 | 
				
			||||||
 | 
					            if iq['type'] == 'get':
 | 
				
			||||||
 | 
					                # Registration form requested
 | 
				
			||||||
 | 
					                userData = self.backend[iq['from'].bare]
 | 
				
			||||||
 | 
					                self.sendRegistrationForm(iq, userData)
 | 
				
			||||||
 | 
					            elif iq['type'] == 'set':
 | 
				
			||||||
 | 
					                if iq['register']['remove']:
 | 
				
			||||||
 | 
					                    # Remove an account
 | 
				
			||||||
 | 
					                    self.backend.unregister(iq['from'].bare)
 | 
				
			||||||
 | 
					                    self.xmpp.event('unregistered_user', iq)
 | 
				
			||||||
 | 
					                    iq.reply().send()
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for field in self.form_fields:
 | 
				
			||||||
 | 
					                    if not iq['register'][field]:
 | 
				
			||||||
 | 
					                        # Incomplete Registration
 | 
				
			||||||
 | 
					                        self._sendError(iq, '406', 'modify', 'not-acceptable',
 | 
				
			||||||
 | 
					                                        "Please fill in all fields.")
 | 
				
			||||||
 | 
					                        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if self.backend.register(iq['from'].bare, iq['register']):
 | 
				
			||||||
 | 
					                    # Successful registration
 | 
				
			||||||
 | 
					                    self.xmpp.event('registered_user', iq)
 | 
				
			||||||
 | 
					                    iq.reply().setPayload(iq['register'].xml)
 | 
				
			||||||
 | 
					                    iq.send()
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # Conflicting registration
 | 
				
			||||||
 | 
					                    self._sendError(iq, '409', 'cancel', 'conflict',
 | 
				
			||||||
 | 
					                                    "That username is already taken.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setForm(self, *fields):
 | 
				
			||||||
 | 
					            self.form_fields = fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def setInstructions(self, instructions):
 | 
				
			||||||
 | 
					            self.form_instructions = instructions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def sendRegistrationForm(self, iq, userData=None):
 | 
				
			||||||
 | 
					            reg = iq['register']
 | 
				
			||||||
 | 
					            if userData is None:
 | 
				
			||||||
 | 
					                userData = {}
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                reg['registered'] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.form_instructions:
 | 
				
			||||||
 | 
					                reg['instructions'] = self.form_instructions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for field in self.form_fields:
 | 
				
			||||||
 | 
					                data = userData.get(field, '')
 | 
				
			||||||
 | 
					                if data:
 | 
				
			||||||
 | 
					                    # Add field with existing data
 | 
				
			||||||
 | 
					                    reg[field] = data
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # Add a blank field
 | 
				
			||||||
 | 
					                    reg.addField(field)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            iq.reply().setPayload(reg.xml)
 | 
				
			||||||
 | 
					            iq.send()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _sendError(self, iq, code, error_type, name, text=''):
 | 
				
			||||||
 | 
					            iq.reply().setPayload(iq['register'].xml)
 | 
				
			||||||
 | 
					            iq.error()
 | 
				
			||||||
 | 
					            iq['error']['code'] = code
 | 
				
			||||||
 | 
					            iq['error']['type'] = error_type
 | 
				
			||||||
 | 
					            iq['error']['condition'] = name
 | 
				
			||||||
 | 
					            iq['error']['text'] = text
 | 
				
			||||||
 | 
					            iq.send()
 | 
				
			||||||
							
								
								
									
										271
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,271 @@
 | 
				
			|||||||
 | 
					Event Index
 | 
				
			||||||
 | 
					===========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. glossary::
 | 
				
			||||||
 | 
					    :sorted:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    connected
 | 
				
			||||||
 | 
					        - **Data:** ``{}``
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Signal that a connection has been made with the XMPP server, but a session
 | 
				
			||||||
 | 
					        has not yet been established.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    changed_status
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Triggered when a presence stanza is received from a JID with a show type
 | 
				
			||||||
 | 
					        different than the last presence stanza from the same JID.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    changed_subscription
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Triggered whenever a presence stanza with a type of ``subscribe``,
 | 
				
			||||||
 | 
					        ``subscribed``, ``unsubscribe``, or ``unsubscribed`` is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Note that if the values ``xmpp.auto_authorize`` and ``xmpp.auto_subscribe``
 | 
				
			||||||
 | 
					        are set to ``True`` or ``False``, and not ``None``, then SleekXMPP will
 | 
				
			||||||
 | 
					        either accept or reject all subscription requests before your event handlers
 | 
				
			||||||
 | 
					        are called. Set these values to ``None`` if you wish to make more complex
 | 
				
			||||||
 | 
					        subscription decisions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chatstate_active
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chatstate_composing
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chatstate_gone
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chatstate_inactive
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chatstate_paused
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disco_info
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoInfo`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Triggered whenever a ``disco#info`` result stanza is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disco_items
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0030.stanza.DiscoItems`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0030.disco.xep_0030`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Triggered whenever a ``disco#items`` result stanza is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disconnected
 | 
				
			||||||
 | 
					        - **Data:** ``{}``
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.ClientXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Signal that the connection with the XMPP server has been lost.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    entity_time
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    failed_auth
 | 
				
			||||||
 | 
					        - **Data:** ``{}``
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.ClientXMPP`, :py:class:`~sleekxmpp.plugins.xep_0078.xep_0078`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Signal that the server has rejected the provided login credentials.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gmail_notify
 | 
				
			||||||
 | 
					        - **Data:** ``{}``
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Signal that there are unread emails for the Gmail account associated with the current XMPP account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    gmail_messages
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Iq`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.gmail_notify.gmail_notify`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Signal that there are unread emails for the Gmail account associated with the current XMPP account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    got_online
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        If a presence stanza is received from a JID which was previously marked as
 | 
				
			||||||
 | 
					        offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
 | 
				
			||||||
 | 
					        or '``xa``', then this event is triggered as well.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    got_offline
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Signal that an unavailable presence stanza has been received from a JID.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    groupchat_invite
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    groupchat_direct_invite
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Message` 
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    groupchat_message
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Message`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Triggered whenever a message is received from a multi-user chat room.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    groupchat_presence
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Triggered whenever a presence stanza is received from a user in a multi-user chat room.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    groupchat_subject
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Message`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0045.xep_0045`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Triggered whenever the subject of a multi-user chat room is changed, or announced when joining a room.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    killed
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    last_activity
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Message`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Makes the contents of message stanzas available whenever one is received. Be
 | 
				
			||||||
 | 
					        sure to check the message type in order to handle error messages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message_form
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Currently the same as :term:`message_xform`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message_xform
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Triggered whenever a data form is received inside a message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mucc::[room]::got_offline
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    muc::[room]::got_online
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    muc::[room]::message
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    muc::[room]::presence
 | 
				
			||||||
 | 
					        - **Data:**
 | 
				
			||||||
 | 
					        - **Source:**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_available
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``available``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_error
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``error``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_form
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form` 
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004` 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        This event is present in the XEP-0004 plugin code, but is currently not used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_probe
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``probe``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_subscribe
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``subscribe``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_subscribed
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``subscribed``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_unavailable
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``unavailable``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_unsubscribe
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``unsubscribe``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    presence_unsubscribed
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.Presence`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        A presence stanza with a type of '``unsubscribed``' is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    roster_update
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.stanza.Roster` 
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.ClientXMPP` 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        An IQ result containing roster entries is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sent_presence
 | 
				
			||||||
 | 
					        - **Data:** ``{}``
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        Signal that an initial presence stanza has been written to the XML stream.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session_end
 | 
				
			||||||
 | 
					        - **Data:** ``{}``
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`, 
 | 
				
			||||||
 | 
					          :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
 | 
				
			||||||
 | 
					          :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Signal that a connection to the XMPP server has been lost and the current
 | 
				
			||||||
 | 
					        stream session has ended. Currently equivalent to :term:`disconnected`, but
 | 
				
			||||||
 | 
					        future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
 | 
				
			||||||
 | 
					        will distinguish the two events.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Plugins that maintain session-based state should clear themselves when
 | 
				
			||||||
 | 
					        this event is fired.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session_start
 | 
				
			||||||
 | 
					        - **Data:** ``{}``
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`, 
 | 
				
			||||||
 | 
					          :py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
 | 
				
			||||||
 | 
					          :py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Signal that a connection to the XMPP server has been made and a session has been established.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    socket_error
 | 
				
			||||||
 | 
					        - **Data:** ``Socket`` exception object 
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stream_error
 | 
				
			||||||
 | 
					        - **Data:** :py:class:`~sleekxmpp.stanza.StreamError`
 | 
				
			||||||
 | 
					        - **Source:** :py:class:`~sleekxmpp.BaseXMPP`
 | 
				
			||||||
							
								
								
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					How to Use Stream Features
 | 
				
			||||||
 | 
					==========================
 | 
				
			||||||
							
								
								
									
										75
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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:
 | 
				
			||||||
							
								
								
									
										390
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,390 @@
 | 
				
			|||||||
 | 
					.. _echobot:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					===============================
 | 
				
			||||||
 | 
					SleekXMPP Quickstart - Echo Bot
 | 
				
			||||||
 | 
					===============================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    If you have any issues working through this quickstart guide
 | 
				
			||||||
 | 
					    or the other tutorials here, please either send a message to the
 | 
				
			||||||
 | 
					    `mailing list <http://groups.google.com/group/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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					As a basic starting project, we will create an echo bot which will reply to any
 | 
				
			||||||
 | 
					messages sent to it. We will also go through adding some basic command line configuration
 | 
				
			||||||
 | 
					for enabling or disabling debug log outputs and setting the username and password
 | 
				
			||||||
 | 
					for the bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For the command line options processing, we will use the built-in ``optparse``
 | 
				
			||||||
 | 
					module and the ``getpass`` module for reading in passwords.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TL;DR Just Give Me the Code
 | 
				
			||||||
 | 
					---------------------------
 | 
				
			||||||
 | 
					As you wish: :ref:`the completed example <echobot_complete>`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Overview
 | 
				
			||||||
 | 
					--------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To get started, here is a brief outline of the structure that the final project will have:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #!/usr/bin/env python
 | 
				
			||||||
 | 
					    # -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import sys
 | 
				
			||||||
 | 
					    import logging
 | 
				
			||||||
 | 
					    import getpass
 | 
				
			||||||
 | 
					    from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    '''Here we will create out echo bot class'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if __name__ == '__main__':
 | 
				
			||||||
 | 
					        '''Here we will configure and read command line options'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''Here we will instantiate our echo bot'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''Finally, we connect the bot and start listening for messages'''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Default Encoding
 | 
				
			||||||
 | 
					----------------
 | 
				
			||||||
 | 
					XMPP requires support for UTF-8 and so SleekXMPP must use UTF-8 as well. In
 | 
				
			||||||
 | 
					Python3 this is simple because Unicode is the default string type. For Python2.6+
 | 
				
			||||||
 | 
					the situation is not as easy because standard strings are simply byte arrays and
 | 
				
			||||||
 | 
					use ASCII. We can get Python to use UTF-8 as the default encoding by including:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					        reload(sys)
 | 
				
			||||||
 | 
					        sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. warning::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Until we are able to ensure that SleekXMPP will always use Unicode in Python2.6+, this
 | 
				
			||||||
 | 
					    may cause issues embedding SleekXMPP into other applications which assume ASCII encoding.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Creating the EchoBot Class
 | 
				
			||||||
 | 
					--------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There are three main types of entities within XMPP — servers, components, and
 | 
				
			||||||
 | 
					clients. Since our echo bot will only be responding to a few people, and won't need
 | 
				
			||||||
 | 
					to remember thousands of users, we will use a client connection. A client connection
 | 
				
			||||||
 | 
					is the same type that you use with your standard IM client such as Pidgin or Psi.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP comes with a :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` class
 | 
				
			||||||
 | 
					which we can extend to add our message echoing feature. :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>`
 | 
				
			||||||
 | 
					requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those
 | 
				
			||||||
 | 
					as well.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class EchoBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        def __init__(self, jid, password):
 | 
				
			||||||
 | 
					            super(EchoBot, self).__init__(jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Handling Session Start
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					The XMPP spec requires clients to broadcast its presence and retrieve its roster (buddy list) once
 | 
				
			||||||
 | 
					it connects and establishes a session with the XMPP server. Until these two tasks are completed,
 | 
				
			||||||
 | 
					some servers may not deliver or send messages or presence notifications to the client. So we now
 | 
				
			||||||
 | 
					need to be sure that we retrieve our roster and send an initial presence once the session has 
 | 
				
			||||||
 | 
					started. To do that, we will register an event handler for the :term:`session_start` event.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     def __init__(self, jid, password):
 | 
				
			||||||
 | 
					        super(EchoBot, self).__init__(jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.add_event_handler('session_start', self.start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since we want the method ``self.start`` to execute when the :term:`session_start` event is triggered,
 | 
				
			||||||
 | 
					we also need to define the ``self.start`` handler.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self, event):
 | 
				
			||||||
 | 
					        self.send_presence()
 | 
				
			||||||
 | 
					        self.get_roster()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. warning::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Not sending an initial presence and retrieving the roster when using a client instance can
 | 
				
			||||||
 | 
					    prevent your program from receiving presence notifications or messages depending on the
 | 
				
			||||||
 | 
					    XMPP server you have chosen.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Our event handler, like every event handler, accepts a single parameter which typically is the stanza
 | 
				
			||||||
 | 
					that was received that caused the event. In this case, ``event`` will just be an empty dictionary since
 | 
				
			||||||
 | 
					there is no associated data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Our first task of sending an initial presence is done using :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>`.
 | 
				
			||||||
 | 
					Calling :meth:`send_presence <sleekxmpp.basexmpp.BaseXMPP.send_presence>` without any arguments will send the simplest
 | 
				
			||||||
 | 
					stanza allowed in XMPP:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <presence />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The second requirement is fulfilled using :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>`, which
 | 
				
			||||||
 | 
					will send an IQ stanza requesting the roster to the server and then wait for the response. You may be wondering
 | 
				
			||||||
 | 
					what :meth:`get_roster <sleekxmpp.clientxmpp.ClientXMPP.get_roster>` returns since we are not saving any return
 | 
				
			||||||
 | 
					value. The roster data is saved by an internal handler to ``self.roster``, and in the case of a :class:`ClientXMPP
 | 
				
			||||||
 | 
					<sleekxmpp.clientxmpp.ClientXMPP>` instance to ``self.client_roster``. (The difference between ``self.roster`` and
 | 
				
			||||||
 | 
					``self.client_roster`` is that ``self.roster`` supports storing roster information for multiple JIDs, which is useful
 | 
				
			||||||
 | 
					for components, whereas ``self.client_roster`` stores roster data for just the client's JID.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is possible for a timeout to occur while waiting for the server to respond, which can happen if the
 | 
				
			||||||
 | 
					network is excessively slow or the server is no longer responding. In that case, an :class:`IQTimeout
 | 
				
			||||||
 | 
					<sleekxmpp.exceptions.IQTimeout>` is raised. Similarly, an :class:`IQError <sleekxmpp.exceptions.IQError>` exception can
 | 
				
			||||||
 | 
					be raised if the request contained bad data or requested the roster for the wrong user. In either case, you can wrap the
 | 
				
			||||||
 | 
					``get_roster()`` call in a ``try``/``except`` block to retry the roster retrieval process.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The XMPP stanzas from the roster retrieval process could look like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <iq type="get">
 | 
				
			||||||
 | 
					      <query xmlns="jabber:iq:roster" />
 | 
				
			||||||
 | 
					    </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <iq type="result" to="echobot@example.com" from="example.com">
 | 
				
			||||||
 | 
					      <query xmlns="jabber:iq:roster">
 | 
				
			||||||
 | 
					        <item jid="friend@example.com" subscription="both" />
 | 
				
			||||||
 | 
					      </query>
 | 
				
			||||||
 | 
					    </iq>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Responding to Messages
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					Now that an ``EchoBot`` instance handles :term:`session_start`, we can begin receiving and
 | 
				
			||||||
 | 
					responding to messages. Now we can register a handler for the :term:`message` event that is raised
 | 
				
			||||||
 | 
					whenever a messsage is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     def __init__(self, jid, password):
 | 
				
			||||||
 | 
					        super(EchoBot, self).__init__(jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.add_event_handler('session_start', self.start)
 | 
				
			||||||
 | 
					        self.add_event_handler('message', self.message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The :term:`message` event is fired whenever a ``<message />`` stanza is received, including for
 | 
				
			||||||
 | 
					group chat messages, errors, etc. Properly responding to messages thus requires checking the
 | 
				
			||||||
 | 
					``'type'`` interface of the message :term:`stanza object`. For responding to only messages
 | 
				
			||||||
 | 
					addressed to our bot (and not from a chat room), we check that the type is either ``normal``
 | 
				
			||||||
 | 
					or ``chat``. (Other potential types are ``error``, ``headline``, and ``groupchat``.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def message(self, msg):
 | 
				
			||||||
 | 
					        if msg['type'] in ('normal', 'chat'):
 | 
				
			||||||
 | 
					            msg.reply("Thanks for sending:\n%s" % msg['body']).send()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Let's take a closer look at the ``.reply()`` method used above. For message stanzas,
 | 
				
			||||||
 | 
					``.reply()`` accepts the parameter ``body`` (also as the first positional argument),
 | 
				
			||||||
 | 
					which is then used as the value of the ``<body />`` element of the message. 
 | 
				
			||||||
 | 
					Setting the appropriate ``to`` JID is also handled by ``.reply()``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Another way to have sent the reply message would be to use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`,
 | 
				
			||||||
 | 
					which is a convenience method for generating and sending a message based on the values passed to it. If we were to use
 | 
				
			||||||
 | 
					this method, the above code would look as so:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def message(self, msg):
 | 
				
			||||||
 | 
					        if msg['type'] in ('normal', 'chat'):
 | 
				
			||||||
 | 
					            self.send_message(mto=msg['from'],
 | 
				
			||||||
 | 
					                              mbody='Thanks for sending:\n%s' % msg['body'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Whichever method you choose to use, the results in action will look like this:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <message to="echobot@example.com" from="someuser@example.net" type="chat">
 | 
				
			||||||
 | 
					      <body>Hej!</body>
 | 
				
			||||||
 | 
					    </message>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <message to="someuser@example.net" type="chat">
 | 
				
			||||||
 | 
					      <body>Thanks for sending:
 | 
				
			||||||
 | 
					      Hej!</body>
 | 
				
			||||||
 | 
					    </message>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					    XMPP does not require stanzas sent by a client to include a ``from`` attribute, and
 | 
				
			||||||
 | 
					    leaves that responsibility to the XMPP server. However, if a sent stanza does
 | 
				
			||||||
 | 
					    include a ``from`` attribute, it must match the full JID of the client or some
 | 
				
			||||||
 | 
					    servers will reject it. SleekXMPP thus leaves out the ``from`` attribute when replying
 | 
				
			||||||
 | 
					    using a client connection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Command Line Arguments and Logging
 | 
				
			||||||
 | 
					----------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					While this isn't part of SleekXMPP itself, we do want our echo bot program to be able
 | 
				
			||||||
 | 
					to accept a JID and password from the command line instead of hard coding them. We will
 | 
				
			||||||
 | 
					use the ``optparse`` module for this, though there are several alternative methods, including
 | 
				
			||||||
 | 
					the newer ``argparse`` module.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We want to accept three parameters: the JID for the echo bot, its password, and a flag for
 | 
				
			||||||
 | 
					displaying the debugging logs. We also want these to be optional parameters, since passing
 | 
				
			||||||
 | 
					a password directly through the command line can be a security risk. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if __name__ == '__main__':
 | 
				
			||||||
 | 
					        optp = OptionParser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
				
			||||||
 | 
					                        action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                        const=logging.DEBUG, default=logging.INFO)
 | 
				
			||||||
 | 
					        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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if opts.jid is None:
 | 
				
			||||||
 | 
					            opts.jid = raw_input("Username: ")
 | 
				
			||||||
 | 
					        if opts.password is None:
 | 
				
			||||||
 | 
					            opts.password = getpass.getpass("Password: ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since we included a flag for enabling debugging logs, we need to configure the
 | 
				
			||||||
 | 
					``logging`` module to behave accordingly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if __name__ == '__main__':
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # .. option parsing from above ..
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logging.basicConfig(level=opts.loglevel,
 | 
				
			||||||
 | 
					                            format='%(levelname)-8s %(message)s')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Connecting to the Server and Processing
 | 
				
			||||||
 | 
					---------------------------------------
 | 
				
			||||||
 | 
					There are three steps remaining until our echo bot is complete:
 | 
				
			||||||
 | 
					    1. We need to instantiate the bot.
 | 
				
			||||||
 | 
					    2. The bot needs to connect to an XMPP server.
 | 
				
			||||||
 | 
					    3. We have to instruct the bot to start running and processing messages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Creating the bot is straightforward, but we can also perform some configuration
 | 
				
			||||||
 | 
					at this stage. For example, let's say we want our bot to support `service discovery
 | 
				
			||||||
 | 
					<http://xmpp.org/extensions/xep-0030.html>`_ and `pings <http://xmpp.org/extensions/xep-0199.html>`_:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if __name__ == '__main__':
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # .. option parsing and logging steps from above
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        xmpp = EchoBot(opts.jid, opts.password)
 | 
				
			||||||
 | 
					        xmpp.register_plugin('xep_0030') # Service Discovery
 | 
				
			||||||
 | 
					        xmpp.register_plugin('xep_0199') # Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If the ``EchoBot`` class had a hard dependency on a plugin, we could register that plugin in
 | 
				
			||||||
 | 
					the ``EchoBot.__init__`` method instead.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If you are using the OpenFire server, you will need to include an additional 
 | 
				
			||||||
 | 
					    configuration step. OpenFire supports a different version of SSL than what
 | 
				
			||||||
 | 
					    most servers and SleekXMPP support.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: python
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        import ssl
 | 
				
			||||||
 | 
					        xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Now we're ready to connect and begin echoing messages. If you have the package
 | 
				
			||||||
 | 
					``dnspython`` installed, then the :meth:`sleekxmpp.clientxmpp.ClientXMPP` method
 | 
				
			||||||
 | 
					will perform a DNS query to find the appropriate server to connect to for the
 | 
				
			||||||
 | 
					given JID. If you do not have ``dnspython``, then SleekXMPP will attempt to 
 | 
				
			||||||
 | 
					connect to the hostname used by the JID, unless an address tuple is supplied
 | 
				
			||||||
 | 
					to :meth:`sleekxmpp.clientxmpp.ClientXMPP`. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if __name__ == '__main__':
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # .. option parsing & echo bot configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if xmpp.connect():
 | 
				
			||||||
 | 
					            xmpp.process(block=True)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            print('Unable to connect')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    For Google Talk users withouth ``dnspython`` installed, the above code
 | 
				
			||||||
 | 
					    should look like:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if __name__ == '__main__':
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # .. option parsing & echo bot configuration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if xmpp.connect(('talk.google.com', 5222)):
 | 
				
			||||||
 | 
					                xmpp.process(block=True)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                print('Unable to connect')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To begin responding to messages, you'll see we called :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
 | 
				
			||||||
 | 
					which will start the event handling, send queue, and XML reader threads. It will also call
 | 
				
			||||||
 | 
					the :meth:`sleekxmpp.plugins.base.base_plugin.post_init` method on all registered plugins. By
 | 
				
			||||||
 | 
					passing ``block=True`` to :meth:`sleekxmpp.basexmpp.BaseXMPP.process` we are running the 
 | 
				
			||||||
 | 
					main processing loop in the main thread of execution. The :meth:`sleekxmpp.basexmpp.BaseXMPP.process`
 | 
				
			||||||
 | 
					call will not return until after SleekXMPP disconnects. If you need to run the client in the background
 | 
				
			||||||
 | 
					for another program, use ``block=False`` to spawn the processing loop in its own thread.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note:: 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Before 1.0, controlling the blocking behaviour of :meth:`sleekxmpp.basexmpp.BaseXMPP.process` was
 | 
				
			||||||
 | 
					    done via the ``threaded`` argument. This arrangement was a source of confusion because some users
 | 
				
			||||||
 | 
					    interpreted that as controlling whether or not SleekXMPP used threads at all, instead of how
 | 
				
			||||||
 | 
					    the processing loop itself was spawned.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The statements ``xmpp.process(threaded=False)`` and ``xmpp.process(block=True)`` are equivalent.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. _echobot_complete:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Final Product
 | 
				
			||||||
 | 
					-----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here then is what the final result should look like after working through the guide above. The code
 | 
				
			||||||
 | 
					can also be found in the SleekXMPP `examples directory <http://github.com/fritzy/SleekXMPP/tree/master/examples>`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. compound::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    You can run the code using:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        python echobot.py -d -j echobot@example.com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    which will prompt for the password and then begin echoing messages. To test, open
 | 
				
			||||||
 | 
					    your regular IM client and start a chat with the echo bot. Messages you send to it should
 | 
				
			||||||
 | 
					    be mirrored back to you. Be careful if you are using the same JID for the echo bot that
 | 
				
			||||||
 | 
					    you also have logged in with another IM client. Messages could be routed to your IM client instead
 | 
				
			||||||
 | 
					    of the bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. include:: ../../examples/echo_client.py
 | 
				
			||||||
 | 
					    :literal:
 | 
				
			||||||
							
								
								
									
										182
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								docs/getting_started/iq.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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')
 | 
				
			||||||
							
								
								
									
										208
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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:
 | 
				
			||||||
							
								
								
									
										2
									
								
								docs/getting_started/presence.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/presence.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					Manage Presence Subscriptions
 | 
				
			||||||
 | 
					=============================
 | 
				
			||||||
							
								
								
									
										42
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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:
 | 
				
			||||||
							
								
								
									
										2
									
								
								docs/getting_started/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/getting_started/scheduler.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					Send a Message Every 5 Minutes
 | 
				
			||||||
 | 
					==============================
 | 
				
			||||||
							
								
								
									
										94
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					Sign in, Send a Message, and Disconnect
 | 
				
			||||||
 | 
					=======================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    If you have any issues working through this quickstart guide
 | 
				
			||||||
 | 
					    or the other tutorials here, please either send a message to the
 | 
				
			||||||
 | 
					    `mailing list <http://groups.google.com/group/sleekxmpp-discussion>`_
 | 
				
			||||||
 | 
					    or join the chat room at `sleek@conference.jabber.org
 | 
				
			||||||
 | 
					    <xmpp:sleek@conference.jabber.org?join>`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A common use case for SleekXMPP is to send one-off messages from
 | 
				
			||||||
 | 
					time to time. For example, one use case could be sending out a notice when 
 | 
				
			||||||
 | 
					a shell script finishes a task.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We will create our one-shot bot based on the pattern explained in :ref:`echobot`. To
 | 
				
			||||||
 | 
					start, we create a client class based on :class:`ClientXMPP <sleekxmpp.clientxmpp.ClientXMPP>` and
 | 
				
			||||||
 | 
					register a handler for the :term:`session_start` event. We will also accept parameters
 | 
				
			||||||
 | 
					for the JID that will receive our message, and the string content of the message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class SendMsgBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        def __init__(self, jid, password, recipient, msg):
 | 
				
			||||||
 | 
					            super(SendMsgBot, self).__init__(jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.recipient = recipient
 | 
				
			||||||
 | 
					            self.msg = msg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.add_event_handler('session_start', self.start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def start(self, event):
 | 
				
			||||||
 | 
					            self.send_presence()
 | 
				
			||||||
 | 
					            self.get_roster()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that as in :ref:`echobot`, we need to include send an initial presence and request
 | 
				
			||||||
 | 
					the roster. Next, we want to send our message, and to do that we will use :meth:`send_message <sleekxmpp.basexmpp.BaseXMPP.send_message>`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self, event):
 | 
				
			||||||
 | 
					        self.send_presence()
 | 
				
			||||||
 | 
					        self.get_roster()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.send_message(mto=self.recipient, mbody=self.msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Finally, we need to disconnect the client using :meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>`.
 | 
				
			||||||
 | 
					Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
 | 
				
			||||||
 | 
					:meth:`disconnect <sleekxmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
 | 
				
			||||||
 | 
					for the client to disconnect before the send queue is processed and the message is actually
 | 
				
			||||||
 | 
					sent on the wire. To ensure that our message is processed, we use 
 | 
				
			||||||
 | 
					:meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self, event):
 | 
				
			||||||
 | 
					        self.send_presence()
 | 
				
			||||||
 | 
					        self.get_roster()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.send_message(mto=self.recipient, mbody=self.msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.disconnect(wait=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. warning::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If you happen to be adding stanzas to the send queue faster than the send thread
 | 
				
			||||||
 | 
					    can process them, then :meth:`disconnect(wait=True) <sleekxmpp.xmlstream.XMLStream.disconnect>`
 | 
				
			||||||
 | 
					    will block and not disconnect.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Final Product
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. compound::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The final step is to create a small runner script for initialising our ``SendMsgBot`` 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 send_client.py -d -j oneshot@example.com -t someone@example.net -m "This is a message"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    which will prompt for the password and then log in, send your message, and then disconnect. To test, open
 | 
				
			||||||
 | 
					    your regular IM client with the account you wish to send messages to. When you run the ``send_client.py``
 | 
				
			||||||
 | 
					    example and instruct it to send your IM client account a message, you should receive the message you
 | 
				
			||||||
 | 
					    gave. If the two JIDs you use also have a mutual presence subscription (they're on each other's buddy lists)
 | 
				
			||||||
 | 
					    then you will also see the ``SendMsgBot`` client come online and then go offline.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. include:: ../../examples/send_client.py
 | 
				
			||||||
 | 
					    :literal:
 | 
				
			||||||
							
								
								
									
										35
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					.. _glossary:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Glossary
 | 
				
			||||||
 | 
					========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. glossary::
 | 
				
			||||||
 | 
					    :sorted:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stream handler
 | 
				
			||||||
 | 
					        A callback function that accepts stanza objects pulled directly
 | 
				
			||||||
 | 
					        from the XML stream. A stream handler is encapsulated in a
 | 
				
			||||||
 | 
					        object that includes a :term:`Matcher` object, and which provides
 | 
				
			||||||
 | 
					        additional semantics. For example, the ``Waiter`` handler wrapper
 | 
				
			||||||
 | 
					        blocks thread execution until a matching stanza is received.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    event handler
 | 
				
			||||||
 | 
					        A callback function that responds to events raised by
 | 
				
			||||||
 | 
					        ``XMLStream.event``. An event handler may be marked as
 | 
				
			||||||
 | 
					        threaded, allowing it to execute outside of the main processing
 | 
				
			||||||
 | 
					        loop.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stanza object
 | 
				
			||||||
 | 
					        Informally may refer both to classes which extend ``ElementBase``
 | 
				
			||||||
 | 
					        or ``StanzaBase``, and to objects of such classes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        A stanza object is a wrapper for an XML object which exposes ``dict``
 | 
				
			||||||
 | 
					        like interfaces which may be assigned to, read from, or deleted.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stanza plugin
 | 
				
			||||||
 | 
					        A :term:`stanza object` which has been registered as a potential child
 | 
				
			||||||
 | 
					        of another stanza object. The plugin stanza may accessed through the
 | 
				
			||||||
 | 
					        parent stanza using the plugin's ``plugin_attrib`` as an interface.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    substanza
 | 
				
			||||||
 | 
					        See :term:`stanza plugin`
 | 
				
			||||||
							
								
								
									
										201
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
				
			|||||||
 | 
					XEP-0030: Working with Service Discovery
 | 
				
			||||||
 | 
					========================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					XMPP networks can be composed of many individual clients, components,
 | 
				
			||||||
 | 
					and servers. Determining the JIDs for these entities and the various
 | 
				
			||||||
 | 
					features they may support is the role of `XEP-0030, Service
 | 
				
			||||||
 | 
					Discovery <http://xmpp.org/extensions/xep-0030.html>`_, or "disco" for short.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Every XMPP entity may possess what are called nodes. A node is just a name for
 | 
				
			||||||
 | 
					some aspect of an XMPP entity. For example, if an XMPP entity provides `Ad-Hoc
 | 
				
			||||||
 | 
					Commands <http://xmpp.org/extensions/xep-0050.html>`_, then it will have a node
 | 
				
			||||||
 | 
					named ``http://jabber.org/protocol/commands`` which will contain information
 | 
				
			||||||
 | 
					about the commands provided. Other agents using these ad-hoc commands will
 | 
				
			||||||
 | 
					interact with the information provided by this node. Note that the node name is
 | 
				
			||||||
 | 
					just an identifier; there is no inherent meaning.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Working with service discovery is about creating and querying these nodes.
 | 
				
			||||||
 | 
					According to XEP-0030, a node may contain three types of information:
 | 
				
			||||||
 | 
					identities, features, and items. (Further, extensible, information types are
 | 
				
			||||||
 | 
					defined in `XEP-0128 <http://xmpp.org/extensions/xep-0128.html>`_, but they are
 | 
				
			||||||
 | 
					not yet implemented by SleekXMPP.) SleekXMPP provides methods to configure each
 | 
				
			||||||
 | 
					of these node attributes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Configuring Service Discovery
 | 
				
			||||||
 | 
					-----------------------------
 | 
				
			||||||
 | 
					The design focus for the XEP-0030 plug-in is handling info and items requests
 | 
				
			||||||
 | 
					in a dynamic fashion, allowing for complex policy decisions of who may receive
 | 
				
			||||||
 | 
					information and how much, or use alternate backend storage mechanisms for all
 | 
				
			||||||
 | 
					of the disco data. To do this, each action that the XEP-0030 plug-in performs
 | 
				
			||||||
 | 
					is handed off to what is called a "node handler," which is just a callback
 | 
				
			||||||
 | 
					function. These handlers are arranged in a hierarchy that allows for a single
 | 
				
			||||||
 | 
					handler to manage an entire domain of JIDs (say for a component), while allowing
 | 
				
			||||||
 | 
					other handler functions to override that global behaviour for certain JIDs, or
 | 
				
			||||||
 | 
					even further limited to only certain JID and node combinations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Dynamic Handler Hierarchy
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					* ``global``: (JID is None, node is None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Handlers assigned at this level for an action (such as ``add_feature``) provide a global default
 | 
				
			||||||
 | 
					  behaviour when the action is performed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``jid``: (JID assigned, node is None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  At this level, handlers provide a default behaviour for actions affecting any node owned by the
 | 
				
			||||||
 | 
					  JID in question. This level is most useful for component connections; there is effectively no
 | 
				
			||||||
 | 
					  difference between this and the global level when using a client connection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* ``node``: (JID assigned, node assigned)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  A handler for this level is responsible for carrying out an action for only one node, and is the
 | 
				
			||||||
 | 
					  most specific handler type available. These types of handlers will be most useful for "special"
 | 
				
			||||||
 | 
					  nodes that require special processing different than others provided by the JID, such as using
 | 
				
			||||||
 | 
					  access control lists, or consolidating data from other nodes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Default Static Handlers
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					The XEP-0030 plug-in provides a default set of handlers that work using in-memory
 | 
				
			||||||
 | 
					disco stanzas. Each handler simply performs the appropriate lookup or storage
 | 
				
			||||||
 | 
					operation using these stanzas without doing any complex operations such as
 | 
				
			||||||
 | 
					checking an ACL, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You may find it necessary at some point to revert a particular node or JID to
 | 
				
			||||||
 | 
					using the default, static handlers. To do so, use the method ``make_static()``.
 | 
				
			||||||
 | 
					You may also elect to only convert a given set of actions instead.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Creating a Node Handler
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					Every node handler receives three arguments: the JID, the node, and a data
 | 
				
			||||||
 | 
					parameter that will contain the relevant information for carrying out the
 | 
				
			||||||
 | 
					handler's action, typically a dictionary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The JID will always have a value, defaulting to ``xmpp.boundjid.full`` for
 | 
				
			||||||
 | 
					components or ``xmpp.boundjid.bare`` for clients. The node value may be None or
 | 
				
			||||||
 | 
					a string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Only handlers for the actions ``get_info`` and ``get_items`` need to have return
 | 
				
			||||||
 | 
					values. For these actions, DiscoInfo or DiscoItems stanzas are exepected as
 | 
				
			||||||
 | 
					output. It is also acceptable for handlers for these actions to generate an
 | 
				
			||||||
 | 
					XMPPError exception when necessary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example Node Handler:
 | 
				
			||||||
 | 
					+++++++++++++++++++++
 | 
				
			||||||
 | 
					Here is one of the built-in default handlers as an example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_identity(self, jid, node, data):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Add a new identity to the JID/node combination.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The data parameter may provide:
 | 
				
			||||||
 | 
					            category -- The general category to which the agent belongs.
 | 
				
			||||||
 | 
					            itype    -- A more specific designation with the category.
 | 
				
			||||||
 | 
					            name     -- Optional human readable name for this identity.
 | 
				
			||||||
 | 
					            lang     -- Optional standard xml:lang value.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.add_node(jid, node)
 | 
				
			||||||
 | 
					        self.nodes[(jid, node)]['info'].add_identity(
 | 
				
			||||||
 | 
					                data.get('category', ''),
 | 
				
			||||||
 | 
					                data.get('itype', ''),
 | 
				
			||||||
 | 
					                data.get('name', None),
 | 
				
			||||||
 | 
					                data.get('lang', None))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Adding Identities, Features, and Items
 | 
				
			||||||
 | 
					--------------------------------------
 | 
				
			||||||
 | 
					In order to maintain some backwards compatibility, the methods ``add_identity``,
 | 
				
			||||||
 | 
					``add_feature``, and ``add_item`` do not follow the method signature pattern of
 | 
				
			||||||
 | 
					the other API methods (i.e. jid, node, then other options), but rather retain
 | 
				
			||||||
 | 
					the parameter orders from previous plug-in versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Adding an Identity
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					Adding an identity may be done using either the older positional notation, or
 | 
				
			||||||
 | 
					with keyword parameters. The example below uses the keyword arguments, but in
 | 
				
			||||||
 | 
					the same order as expected using positional arguments.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    xmpp['xep_0030'].add_identity(category='client',
 | 
				
			||||||
 | 
					                                  itype='bot',
 | 
				
			||||||
 | 
					                                  name='Sleek',
 | 
				
			||||||
 | 
					                                  node='foo',
 | 
				
			||||||
 | 
					                                  jid=xmpp.boundjid.full,
 | 
				
			||||||
 | 
					                                  lang='no')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The JID and node values determine which handler will be used to perform the
 | 
				
			||||||
 | 
					``add_identity`` action.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The ``lang`` parameter allows for adding localized versions of identities using
 | 
				
			||||||
 | 
					the ``xml:lang`` attribute.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Adding a Feature
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					The position ordering for ``add_feature()`` is to include the feature, then
 | 
				
			||||||
 | 
					specify the node and then the JID. The JID and node values determine which
 | 
				
			||||||
 | 
					handler will be used to perform the ``add_feature`` action.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    xmpp['xep_0030'].add_feature(feature='jabber:x:data',
 | 
				
			||||||
 | 
					                                 node='foo',
 | 
				
			||||||
 | 
					                                 jid=xmpp.boundjid.full)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Adding an Item
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					The parameters to ``add_item()`` are potentially confusing due to the fact that
 | 
				
			||||||
 | 
					adding an item requires two JID and node combinations: the JID and node of the
 | 
				
			||||||
 | 
					item itself, and the JID and node that will own the item.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    xmpp['xep_0030'].add_item(jid='myitemjid@example.com',
 | 
				
			||||||
 | 
					                              name='An Item!',
 | 
				
			||||||
 | 
					                              node='owner_node',
 | 
				
			||||||
 | 
					                              subnode='item_node',
 | 
				
			||||||
 | 
					                              ijid=xmpp.boundjid.full)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    In this case, the owning JID and node are provided with the
 | 
				
			||||||
 | 
					    parameters ``ijid`` and ``node``. 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Peforming Disco Queries
 | 
				
			||||||
 | 
					-----------------------
 | 
				
			||||||
 | 
					The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
 | 
				
			||||||
 | 
					and their nodes for disco information. Since these methods are wrappers for
 | 
				
			||||||
 | 
					sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``
 | 
				
			||||||
 | 
					method. The ``get_items()`` method may also accept the boolean parameter
 | 
				
			||||||
 | 
					``iterator``, which when set to ``True`` will return an iterator object using
 | 
				
			||||||
 | 
					the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    info = self['xep_0030'].get_info(jid='foo@example.com',
 | 
				
			||||||
 | 
					                                     node='bar',
 | 
				
			||||||
 | 
					                                     ifrom='baz@mycomponent.example.com',
 | 
				
			||||||
 | 
					                                     block=True,
 | 
				
			||||||
 | 
					                                     timeout=30)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    items = self['xep_0030'].get_info(jid='foo@example.com',
 | 
				
			||||||
 | 
					                                      node='bar',
 | 
				
			||||||
 | 
					                                      iterator=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more examples on how to use basic disco queries, check the ``disco_browser.py``
 | 
				
			||||||
 | 
					example in the ``examples`` directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Local Queries
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					In some cases, it may be necessary to query the contents of a node owned by the
 | 
				
			||||||
 | 
					client itself, or one of a component's many JIDs. The same method is used as for
 | 
				
			||||||
 | 
					normal queries, with two differences. First, the parameter ``local=True`` must
 | 
				
			||||||
 | 
					be used. Second, the return value will be a DiscoInfo or DiscoItems stanza, not
 | 
				
			||||||
 | 
					a full Iq stanza.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    info = self['xep_0030'].get_info(node='foo', local=True)
 | 
				
			||||||
 | 
					    items = self['xep_0030'].get_items(jid='somejid@mycomponent.example.com',
 | 
				
			||||||
 | 
					                                       node='bar', 
 | 
				
			||||||
 | 
					                                       local=True)
 | 
				
			||||||
							
								
								
									
										4
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docs/handlersmatchers.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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
 | 
				
			||||||
 | 
					--------------------------
 | 
				
			||||||
							
								
								
									
										252
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,252 @@
 | 
				
			|||||||
 | 
					SleekXMPP
 | 
				
			||||||
 | 
					#########
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. sidebar:: Get the Code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pip install sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The latest source code for SleekXMPP may be found on `Github
 | 
				
			||||||
 | 
					    <http://github.com/fritzy/SleekXMPP>`_. Releases can be found in the
 | 
				
			||||||
 | 
					    ``master`` branch, while the latest development version is in the
 | 
				
			||||||
 | 
					    ``develop`` branch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Latest Stable Release**
 | 
				
			||||||
 | 
					        - `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Develop Releases**
 | 
				
			||||||
 | 
					        - `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    A mailing list and XMPP chat room are available for discussing and getting
 | 
				
			||||||
 | 
					    help with SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Mailing List**
 | 
				
			||||||
 | 
					        `SleekXMPP Discussion on Google Groups <http://groups.google.com/group/sleekxmpp-discussion>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    **Chat**
 | 
				
			||||||
 | 
					        `sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+,
 | 
				
			||||||
 | 
					and is featured in examples in
 | 
				
			||||||
 | 
					`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_ 
 | 
				
			||||||
 | 
					by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
 | 
				
			||||||
 | 
					here from reading the Definitive Guide, please see the notes on updating
 | 
				
			||||||
 | 
					the examples to the latest version of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP's design goals and philosphy are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Low number of dependencies**
 | 
				
			||||||
 | 
					    Installing and using SleekXMPP should be as simple as possible, without
 | 
				
			||||||
 | 
					    having to deal with long dependency chains.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    As part of reducing the number of dependencies, some third party
 | 
				
			||||||
 | 
					    modules are included with SleekXMPP in the ``thirdparty`` directory.
 | 
				
			||||||
 | 
					    Imports from this module first try to import an existing installed
 | 
				
			||||||
 | 
					    version before loading the packaged version, when possible.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Every XEP as a plugin**
 | 
				
			||||||
 | 
					    Following Python's "batteries included" approach, the goal is to
 | 
				
			||||||
 | 
					    provide support for all currently active XEPs (final and draft). Since
 | 
				
			||||||
 | 
					    adding XEP support is done through easy to create plugins, the hope is
 | 
				
			||||||
 | 
					    to also provide a solid base for implementing and creating experimental
 | 
				
			||||||
 | 
					    XEPs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Rewarding to work with**
 | 
				
			||||||
 | 
					    As much as possible, SleekXMPP should allow things to "just work" using
 | 
				
			||||||
 | 
					    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::
 | 
				
			||||||
 | 
					    :maxdepth: 1
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    getting_started/echobot
 | 
				
			||||||
 | 
					    getting_started/sendlogout
 | 
				
			||||||
 | 
					    getting_started/component
 | 
				
			||||||
 | 
					    getting_started/presence
 | 
				
			||||||
 | 
					    getting_started/muc
 | 
				
			||||||
 | 
					    getting_started/proxy
 | 
				
			||||||
 | 
					    getting_started/scheduler
 | 
				
			||||||
 | 
					    getting_started/iq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Tutorials, FAQs, and How To Guides
 | 
				
			||||||
 | 
					----------------------------------
 | 
				
			||||||
 | 
					.. toctree::
 | 
				
			||||||
 | 
					    :maxdepth: 1
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					    faq
 | 
				
			||||||
 | 
					    xeps
 | 
				
			||||||
 | 
					    xmpp_tdg
 | 
				
			||||||
 | 
					    howto/stanzas
 | 
				
			||||||
 | 
					    create_plugin
 | 
				
			||||||
 | 
					    features
 | 
				
			||||||
 | 
					    sasl
 | 
				
			||||||
 | 
					    handlersmatchers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Plugin Guides
 | 
				
			||||||
 | 
					~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					.. toctree:: 
 | 
				
			||||||
 | 
					    :maxdepth: 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    guide_xep_0030
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP Architecture and Design
 | 
				
			||||||
 | 
					---------------------------------
 | 
				
			||||||
 | 
					.. toctree::
 | 
				
			||||||
 | 
					    :maxdepth: 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    architecture
 | 
				
			||||||
 | 
					    plugin_arch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					API Reference
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					.. toctree::
 | 
				
			||||||
 | 
					    :maxdepth: 2
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    event_index
 | 
				
			||||||
 | 
					    api/clientxmpp
 | 
				
			||||||
 | 
					    api/componentxmpp
 | 
				
			||||||
 | 
					    api/basexmpp
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					.. toctree::
 | 
				
			||||||
 | 
					    :hidden:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    glossary
 | 
				
			||||||
 | 
					    license
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* :ref:`license`
 | 
				
			||||||
 | 
					* :ref:`glossary`
 | 
				
			||||||
 | 
					* :ref:`genindex`
 | 
				
			||||||
 | 
					* :ref:`modindex`
 | 
				
			||||||
 | 
					* :ref:`search`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Credits
 | 
				
			||||||
 | 
					-------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_
 | 
				
			||||||
 | 
					     `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
 | 
				
			||||||
 | 
					     `@fritzy <http://twitter.com/fritzy>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
 | 
				
			||||||
 | 
					     <http://code.google.com/p/seesmic-as3-xmpp/>`_, and a 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>`_)
 | 
				
			||||||
 | 
					    - Dann Martens (`dannmartens <http://github.com/dannmartens>`_)
 | 
				
			||||||
 | 
					    - Florent Le Coz (`louiz <http://github.com/louiz>`_)
 | 
				
			||||||
 | 
					    - Kevin Smith (`Kev <http://github.com/Kev>`_, http://kismith.co.uk)
 | 
				
			||||||
 | 
					    - Remko Tronçon (`remko <http://github.com/remko>`_, http://el-tramo.be)
 | 
				
			||||||
 | 
					    - Te-jé Rogers (`te-je <http://github.com/te-je>`_)
 | 
				
			||||||
 | 
					    - Thom Nichols (`tomstrummer <http://github.com/tomstrummer>`_)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								docs/license.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/license.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					.. _license: 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					License (MIT)
 | 
				
			||||||
 | 
					=============
 | 
				
			||||||
 | 
					.. include:: ../LICENSE
 | 
				
			||||||
							
								
								
									
										170
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								docs/make.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					@ECHO OFF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					REM Command file for Sphinx documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%SPHINXBUILD%" == "" (
 | 
				
			||||||
 | 
						set SPHINXBUILD=sphinx-build
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					set BUILDDIR=_build
 | 
				
			||||||
 | 
					set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
 | 
				
			||||||
 | 
					if NOT "%PAPER%" == "" (
 | 
				
			||||||
 | 
						set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "" goto help
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "help" (
 | 
				
			||||||
 | 
						:help
 | 
				
			||||||
 | 
						echo.Please use `make ^<target^>` where ^<target^> is one of
 | 
				
			||||||
 | 
						echo.  html       to make standalone HTML files
 | 
				
			||||||
 | 
						echo.  dirhtml    to make HTML files named index.html in directories
 | 
				
			||||||
 | 
						echo.  singlehtml to make a single large HTML file
 | 
				
			||||||
 | 
						echo.  pickle     to make pickle files
 | 
				
			||||||
 | 
						echo.  json       to make JSON files
 | 
				
			||||||
 | 
						echo.  htmlhelp   to make HTML files and a HTML help project
 | 
				
			||||||
 | 
						echo.  qthelp     to make HTML files and a qthelp project
 | 
				
			||||||
 | 
						echo.  devhelp    to make HTML files and a Devhelp project
 | 
				
			||||||
 | 
						echo.  epub       to make an epub
 | 
				
			||||||
 | 
						echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
 | 
				
			||||||
 | 
						echo.  text       to make text files
 | 
				
			||||||
 | 
						echo.  man        to make manual pages
 | 
				
			||||||
 | 
						echo.  changes    to make an overview over all changed/added/deprecated items
 | 
				
			||||||
 | 
						echo.  linkcheck  to check all external links for integrity
 | 
				
			||||||
 | 
						echo.  doctest    to run all doctests embedded in the documentation if enabled
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "clean" (
 | 
				
			||||||
 | 
						for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
 | 
				
			||||||
 | 
						del /q /s %BUILDDIR%\*
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "html" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished. The HTML pages are in %BUILDDIR%/html.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "dirhtml" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "singlehtml" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "pickle" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished; now you can process the pickle files.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "json" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished; now you can process the JSON files.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "htmlhelp" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished; now you can run HTML Help Workshop with the ^
 | 
				
			||||||
 | 
					.hhp project file in %BUILDDIR%/htmlhelp.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "qthelp" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished; now you can run "qcollectiongenerator" with the ^
 | 
				
			||||||
 | 
					.qhcp project file in %BUILDDIR%/qthelp, like this:
 | 
				
			||||||
 | 
						echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SleekXMPP.qhcp
 | 
				
			||||||
 | 
						echo.To view the help file:
 | 
				
			||||||
 | 
						echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SleekXMPP.ghc
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "devhelp" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "epub" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished. The epub file is in %BUILDDIR%/epub.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "latex" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "text" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished. The text files are in %BUILDDIR%/text.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "man" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Build finished. The manual pages are in %BUILDDIR%/man.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "changes" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.The overview file is in %BUILDDIR%/changes.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "linkcheck" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Link check complete; look for any errors in the above output ^
 | 
				
			||||||
 | 
					or in %BUILDDIR%/linkcheck/output.txt.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if "%1" == "doctest" (
 | 
				
			||||||
 | 
						%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
 | 
				
			||||||
 | 
						if errorlevel 1 exit /b 1
 | 
				
			||||||
 | 
						echo.
 | 
				
			||||||
 | 
						echo.Testing of doctests in the sources finished, look at the ^
 | 
				
			||||||
 | 
					results in %BUILDDIR%/doctest/output.txt.
 | 
				
			||||||
 | 
						goto end
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:end
 | 
				
			||||||
							
								
								
									
										2
									
								
								docs/plugin_arch.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/plugin_arch.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					Plugin Architecture
 | 
				
			||||||
 | 
					===================
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								docs/python-objects.inv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/python-objects.inv
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/sasl.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					How SASL Authentication Works
 | 
				
			||||||
 | 
					=============================
 | 
				
			||||||
							
								
								
									
										50
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								docs/xeps.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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
 | 
				
			||||||
							
								
								
									
										249
									
								
								docs/xmpp_tdg.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										249
									
								
								docs/xmpp_tdg.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,249 @@
 | 
				
			|||||||
 | 
					Following *XMPP: The Definitive Guide*
 | 
				
			||||||
 | 
					======================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SleekXMPP was featured in the first edition of the O'Reilly book 
 | 
				
			||||||
 | 
					`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271/>`_
 | 
				
			||||||
 | 
					by Peter Saint-Andre, Kevin Smith, and Remko Tronçon. The original source code
 | 
				
			||||||
 | 
					for the book's examples can be found at http://github.com/remko/xmpp-tdg. An
 | 
				
			||||||
 | 
					updated version of the source code, maintained to stay current with the latest
 | 
				
			||||||
 | 
					SleekXMPP release, is available at http://github.com/legastero/xmpp-tdg.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					However, since publication, SleekXMPP has advanced from version 0.2.1 to version
 | 
				
			||||||
 | 
					1.0 and there have been several major API changes. The most notable is the
 | 
				
			||||||
 | 
					introduction of :term:`stanza objects <stanza object>` which have simplified and
 | 
				
			||||||
 | 
					standardized interactions with the XMPP XML stream.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					What follows is a walk-through of *The Definitive Guide* highlighting the
 | 
				
			||||||
 | 
					changes needed to make the code examples work with version 1.0 of SleekXMPP.
 | 
				
			||||||
 | 
					These changes have been kept to a minimum to preserve the correlation with
 | 
				
			||||||
 | 
					the book's explanations, so be aware that some code may not use current best
 | 
				
			||||||
 | 
					practices.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example 2-2. (Page 26)
 | 
				
			||||||
 | 
					----------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Implementation of a basic bot that echoes all incoming messages back to its sender.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The echo bot example requires a change to the ``handleIncomingMessage`` method
 | 
				
			||||||
 | 
					to reflect the use of the ``Message`` :term:`stanza object`. The
 | 
				
			||||||
 | 
					``"jid"`` field of the message object should now be ``"from"`` to match the
 | 
				
			||||||
 | 
					``from`` attribute of the actual XML message stanza. Likewise, ``"message"``
 | 
				
			||||||
 | 
					changes to ``"body"`` to match the ``body`` element of the message stanza.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Updated Code
 | 
				
			||||||
 | 
					~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handleIncomingMessage(self, message):
 | 
				
			||||||
 | 
					        self.xmpp.sendMessage(message["from"], message["body"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_ |
 | 
				
			||||||
 | 
					`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/EchoBot/EchoBot.py>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example 14-1. (Page 215)
 | 
				
			||||||
 | 
					------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**CheshiR IM bot implementation.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The main event handling method in the Bot class is meant to process both message
 | 
				
			||||||
 | 
					events and presence update events. With the new changes in SleekXMPP 1.0,
 | 
				
			||||||
 | 
					extracting a CheshiR status "message" from both types of stanzas
 | 
				
			||||||
 | 
					requires accessing different attributes. In the case of a message stanza, the
 | 
				
			||||||
 | 
					``"body"`` attribute would contain the CheshiR message. For a presence event,
 | 
				
			||||||
 | 
					the information is stored in the ``"status"`` attribute. To handle both cases,
 | 
				
			||||||
 | 
					we can test the type of the given event object and look up the proper attribute
 | 
				
			||||||
 | 
					based on the type.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Like in the EchoBot example, the expression ``event["jid"]`` needs to change
 | 
				
			||||||
 | 
					to ``event["from"]`` in order to get a JID object for the stanza's sender.
 | 
				
			||||||
 | 
					Because other functions in CheshiR assume that the JID is a string, the ``jid``
 | 
				
			||||||
 | 
					attribute is used to access the string version of the JID. A check is also added
 | 
				
			||||||
 | 
					in case ``user`` is ``None``, but the check could (and probably should) be
 | 
				
			||||||
 | 
					placed in ``addMessageFromUser``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Another change is needed in ``handleMessageAddedToBackend`` where
 | 
				
			||||||
 | 
					an HTML-IM response is created. The HTML content should be enclosed in a single
 | 
				
			||||||
 | 
					element, such as a ``<p>`` tag.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Updated Code
 | 
				
			||||||
 | 
					~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleIncomingXMPPEvent(self, event):
 | 
				
			||||||
 | 
					    msgLocations = {sleekxmpp.stanza.presence.Presence: "status",
 | 
				
			||||||
 | 
					                    sleekxmpp.stanza.message.Message: "body"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    message = event[msgLocations[type(event)]]
 | 
				
			||||||
 | 
					    user = self.backend.getUserFromJID(event["from"].jid)
 | 
				
			||||||
 | 
					    if user is not None:
 | 
				
			||||||
 | 
					      self.backend.addMessageFromUser(message, user)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  def handleMessageAddedToBackend(self, message) :
 | 
				
			||||||
 | 
					    body = message.user + ": " + message.text
 | 
				
			||||||
 | 
					    htmlBody = "<p><a href='%(uri)s'>%(user)s</a>: %(message)s</p>" % {
 | 
				
			||||||
 | 
					      "uri": self.url + "/" + message.user,
 | 
				
			||||||
 | 
					      "user" : message.user, "message" : message.text }
 | 
				
			||||||
 | 
					    for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
 | 
				
			||||||
 | 
					      self.xmpp.sendMessage(subscriberJID, body, mhtml=htmlBody)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_ |
 | 
				
			||||||
 | 
					`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Bot.py>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example 14-3. (Page 217)
 | 
				
			||||||
 | 
					------------------------
 | 
				
			||||||
 | 
					**Configurable CheshiR IM bot implementation.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					    Since the CheshiR examples build on each other, see previous sections for
 | 
				
			||||||
 | 
					    corrections to code that is not marked as new in the book example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The main difference for the configurable IM bot is the handling for the
 | 
				
			||||||
 | 
					data form in ``handleConfigurationCommand``. The test for equality
 | 
				
			||||||
 | 
					with the string ``"1"`` is no longer required; SleekXMPP converts
 | 
				
			||||||
 | 
					boolean data form fields to the values ``True`` and ``False``
 | 
				
			||||||
 | 
					automatically.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For the method ``handleIncomingXMPPPresence``, the attribute
 | 
				
			||||||
 | 
					``"jid"`` is again converted to ``"from"`` to get a JID
 | 
				
			||||||
 | 
					object for the presence stanza's sender, and the ``jid`` attribute is
 | 
				
			||||||
 | 
					used to access the string version of that JID object. A check is also added in
 | 
				
			||||||
 | 
					case ``user`` is ``None``, but the check could (and probably
 | 
				
			||||||
 | 
					should) be placed in ``getShouldMonitorPresenceFromUser``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Updated Code
 | 
				
			||||||
 | 
					~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleConfigurationCommand(self, form, sessionId):
 | 
				
			||||||
 | 
					    values = form.getValues()
 | 
				
			||||||
 | 
					    monitorPresence =values["monitorPresence"]
 | 
				
			||||||
 | 
					    jid = self.xmpp.plugin["xep_0050"].sessions[sessionId]["jid"]
 | 
				
			||||||
 | 
					    user = self.backend.getUserFromJID(jid)
 | 
				
			||||||
 | 
					    self.backend.setShouldMonitorPresenceFromUser(user, monitorPresence)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleIncomingXMPPPresence(self, event):
 | 
				
			||||||
 | 
					    user = self.backend.getUserFromJID(event["from"].jid)
 | 
				
			||||||
 | 
					    if user is not None:
 | 
				
			||||||
 | 
					      if self.backend.getShouldMonitorPresenceFromUser(user):
 | 
				
			||||||
 | 
					        self.handleIncomingXMPPEvent(event)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_ |
 | 
				
			||||||
 | 
					`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/ConfigurableBot.py>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example 14-4. (Page 220)
 | 
				
			||||||
 | 
					------------------------
 | 
				
			||||||
 | 
					**CheshiR IM server component implementation.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					    Since the CheshiR examples build on each other, see previous sections for
 | 
				
			||||||
 | 
					    corrections to code that is not marked as new in the book example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Like several previous examples, a needed change is to replace
 | 
				
			||||||
 | 
					``subscription["from"]`` with ``subscription["from"].jid`` because the
 | 
				
			||||||
 | 
					``BaseXMPP`` method ``makePresence`` requires the JID to be a string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A correction needs to be made in ``handleXMPPPresenceProbe`` because a line was
 | 
				
			||||||
 | 
					left out of the original implementation; the variable ``user`` is undefined. The
 | 
				
			||||||
 | 
					JID of the user can be extracted from the presence stanza's ``from`` attribute.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Since this implementation of CheshiR uses an XMPP component, it must
 | 
				
			||||||
 | 
					include a ``from`` attribute in all messages that it sends. Adding the
 | 
				
			||||||
 | 
					``from`` attribute is done by including ``mfrom=self.xmpp.jid`` in calls to
 | 
				
			||||||
 | 
					``self.xmpp.sendMessage``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Updated Code
 | 
				
			||||||
 | 
					~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleXMPPPresenceProbe(self, event) :
 | 
				
			||||||
 | 
					    self.xmpp.sendPresence(pto = event["from"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleXMPPPresenceSubscription(self, subscription) :
 | 
				
			||||||
 | 
					    if subscription["type"] == "subscribe" :
 | 
				
			||||||
 | 
					      userJID = subscription["from"].jid
 | 
				
			||||||
 | 
					      self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribed")
 | 
				
			||||||
 | 
					      self.xmpp.sendPresence(pto = userJID)
 | 
				
			||||||
 | 
					      self.xmpp.sendPresenceSubscription(pto=userJID, ptype="subscribe")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleMessageAddedToBackend(self, message) :
 | 
				
			||||||
 | 
					    body = message.user + ": " + message.text
 | 
				
			||||||
 | 
					    for subscriberJID in self.backend.getSubscriberJIDs(message.user) :
 | 
				
			||||||
 | 
					      self.xmpp.sendMessage(subscriberJID, body, mfrom=self.xmpp.jid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_ |
 | 
				
			||||||
 | 
					`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/SimpleComponent.py>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example 14-6. (Page 223)
 | 
				
			||||||
 | 
					------------------------
 | 
				
			||||||
 | 
					**CheshiR IM server component with in-band registration support.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					    Since the CheshiR examples build on each other, see previous sections for
 | 
				
			||||||
 | 
					    corrections to code that is not marked as new in the book example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					After applying the changes from Example 14-4 above, the registrable component
 | 
				
			||||||
 | 
					implementation should work correctly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. tip::
 | 
				
			||||||
 | 
					    To see how to implement in-band registration as a SleekXMPP plugin,
 | 
				
			||||||
 | 
					    see the tutorial :ref:`tutorial-create-plugin`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_ |
 | 
				
			||||||
 | 
					`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/RegistrableComponent.py>`_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Example 14-7. (Page 225)
 | 
				
			||||||
 | 
					------------------------
 | 
				
			||||||
 | 
					**Extended CheshiR IM server component implementation.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. note::
 | 
				
			||||||
 | 
					    Since the CheshiR examples build on each other, see previous 
 | 
				
			||||||
 | 
					    sections for corrections to code that is not marked as new in the book
 | 
				
			||||||
 | 
					    example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					While the final code example can look daunting with all of the changes
 | 
				
			||||||
 | 
					made, it requires very few modifications to work with the latest version of
 | 
				
			||||||
 | 
					SleekXMPP. Most differences are the result of CheshiR's backend functions
 | 
				
			||||||
 | 
					expecting JIDs to be strings so that they can be stripped to bare JIDs. To
 | 
				
			||||||
 | 
					resolve these, use the ``jid`` attribute of the JID objects. Also,
 | 
				
			||||||
 | 
					references to ``"message"`` and ``"jid"`` attributes need to
 | 
				
			||||||
 | 
					be changed to either ``"body"`` or ``"status"``, and either
 | 
				
			||||||
 | 
					``"from"`` or ``"to"`` depending on if the object is a message
 | 
				
			||||||
 | 
					or presence stanza and which of the JIDs from the stanza is needed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Updated Code
 | 
				
			||||||
 | 
					~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleIncomingXMPPMessage(self, event) :
 | 
				
			||||||
 | 
					    message = self.addRecipientToMessage(event["body"], event["to"].jid)
 | 
				
			||||||
 | 
					    user = self.backend.getUserFromJID(event["from"].jid)
 | 
				
			||||||
 | 
					    self.backend.addMessageFromUser(message, user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleIncomingXMPPPresence(self, event) :
 | 
				
			||||||
 | 
					    if event["to"].jid == self.componentDomain :
 | 
				
			||||||
 | 
					      user = self.backend.getUserFromJID(event["from"].jid)
 | 
				
			||||||
 | 
					      self.backend.addMessageFromUser(event["status"], user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handleXMPPPresenceSubscription(self, subscription) :
 | 
				
			||||||
 | 
					    if subscription["type"] == "subscribe" :
 | 
				
			||||||
 | 
					      userJID = subscription["from"].jid
 | 
				
			||||||
 | 
					      user = self.backend.getUserFromJID(userJID)
 | 
				
			||||||
 | 
					      contactJID = subscription["to"]
 | 
				
			||||||
 | 
					      self.xmpp.sendPresenceSubscription(
 | 
				
			||||||
 | 
					          pfrom=contactJID, pto=userJID, ptype="subscribed", pnick=user)
 | 
				
			||||||
 | 
					      self.sendPresenceOfContactToUser(contactJID=contactJID, userJID=userJID)
 | 
				
			||||||
 | 
					      if contactJID == self.componentDomain :
 | 
				
			||||||
 | 
					        self.sendAllContactSubscriptionRequestsToUser(userJID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`View full source <http://github.com/legastero/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_ |
 | 
				
			||||||
 | 
					`View original code <http://github.com/remko/xmpp-tdg/blob/master/code/CheshiR/Component.py>`_ 
 | 
				
			||||||
							
								
								
									
										48
									
								
								example.py
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								example.py
									
									
									
									
									
								
							@@ -1,48 +0,0 @@
 | 
				
			|||||||
# coding=utf8
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import sleekxmpp
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
from optparse import OptionParser
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if sys.version_info < (3,0):
 | 
					 | 
				
			||||||
	reload(sys)
 | 
					 | 
				
			||||||
	sys.setdefaultencoding('utf8')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Example(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)
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def start(self, event):
 | 
					 | 
				
			||||||
		self.getRoster()
 | 
					 | 
				
			||||||
		self.sendPresence()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def message(self, msg):
 | 
					 | 
				
			||||||
		msg.reply("Thanks for sending\n%(body)s" % msg).send()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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")
 | 
					 | 
				
			||||||
	opts,args = optp.parse_args()
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
 | 
					 | 
				
			||||||
	xmpp = Example('user@gmail.com/sleekxmpp', 'password')
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0030')	
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0004')
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0060')
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0199')
 | 
					 | 
				
			||||||
	if xmpp.connect(('talk.google.com', 5222)):
 | 
					 | 
				
			||||||
		xmpp.process(threaded=False)
 | 
					 | 
				
			||||||
		print("done")
 | 
					 | 
				
			||||||
	else:
 | 
					 | 
				
			||||||
		print("Unable to connect.")
 | 
					 | 
				
			||||||
							
								
								
									
										204
									
								
								examples/adhoc_provider.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										204
									
								
								examples/adhoc_provider.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,204 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CommandBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple SleekXMPP bot that provides a basic
 | 
				
			||||||
 | 
					    adhoc command.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 add the command after session_start has fired
 | 
				
			||||||
 | 
					        # to ensure that the correct full JID is used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If using a component, may also pass jid keyword parameter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self['xep_0050'].add_command(node='greeting',
 | 
				
			||||||
 | 
					                                     name='Greeting',
 | 
				
			||||||
 | 
					                                     handler=self._handle_command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_command(self, iq, session):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Respond to the initial request for a command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            iq      -- The iq stanza containing the command request.
 | 
				
			||||||
 | 
					            session -- A dictionary of data relevant to the command
 | 
				
			||||||
 | 
					                       session. Additional, custom data may be saved
 | 
				
			||||||
 | 
					                       here to persist across handler callbacks.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        form = self['xep_0004'].makeForm('form', 'Greeting')
 | 
				
			||||||
 | 
					        form['instructions'] = 'Send a custom greeting to a JID'
 | 
				
			||||||
 | 
					        form.addField(var='greeting',
 | 
				
			||||||
 | 
					                      ftype='text-single',
 | 
				
			||||||
 | 
					                      label='Your greeting')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        session['payload'] = form
 | 
				
			||||||
 | 
					        session['next'] = self._handle_command_complete
 | 
				
			||||||
 | 
					        session['has_next'] = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Other useful session values:
 | 
				
			||||||
 | 
					        # session['to']                    -- The JID that received the
 | 
				
			||||||
 | 
					        #                                     command request.
 | 
				
			||||||
 | 
					        # session['from']                  -- The JID that sent the
 | 
				
			||||||
 | 
					        #                                     command request.
 | 
				
			||||||
 | 
					        # session['has_next'] = True       -- There are more steps to complete
 | 
				
			||||||
 | 
					        # session['allow_complete'] = True -- Allow user to finish immediately
 | 
				
			||||||
 | 
					        #                                     and possibly skip steps
 | 
				
			||||||
 | 
					        # session['cancel'] = handler      -- Assign a handler for if the user
 | 
				
			||||||
 | 
					        #                                     cancels the command.
 | 
				
			||||||
 | 
					        # session['notes'] = [             -- Add informative notes about the
 | 
				
			||||||
 | 
					        #   ('info', 'Info message'),         command's results.
 | 
				
			||||||
 | 
					        #   ('warning', 'Warning message'),
 | 
				
			||||||
 | 
					        #   ('error', 'Error message')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_command_complete(self, payload, session):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process a command result from the user.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            payload -- Either a single item, such as a form, or a list
 | 
				
			||||||
 | 
					                       of items or forms if more than one form was
 | 
				
			||||||
 | 
					                       provided to the user. The payload may be any
 | 
				
			||||||
 | 
					                       stanza, such as jabber:x:oob for out of band
 | 
				
			||||||
 | 
					                       data, or jabber:x:data for typical data forms.
 | 
				
			||||||
 | 
					            session -- A dictionary of data relevant to the command
 | 
				
			||||||
 | 
					                       session. Additional, custom data may be saved
 | 
				
			||||||
 | 
					                       here to persist across handler callbacks.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # In this case (as is typical), the payload is a form
 | 
				
			||||||
 | 
					        form = payload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        greeting = form['values']['greeting']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.send_message(mto=session['from'],
 | 
				
			||||||
 | 
					                          mbody="%s, World!" % greeting,
 | 
				
			||||||
 | 
					                          mtype='chat')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Having no return statement is the same as unsetting the 'payload'
 | 
				
			||||||
 | 
					        # and 'next' session values and returning the session.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Unless it is the final step, always return the session dictionary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        session['payload'] = None
 | 
				
			||||||
 | 
					        session['next'] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = CommandBot(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.")
 | 
				
			||||||
							
								
								
									
										210
									
								
								examples/adhoc_user.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										210
									
								
								examples/adhoc_user.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,210 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CommandUserBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple SleekXMPP bot that uses the adhoc command
 | 
				
			||||||
 | 
					    provided by the adhoc_provider.py example.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, password, other, greeting):
 | 
				
			||||||
 | 
					        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.command_provider = other
 | 
				
			||||||
 | 
					        self.greeting = greeting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The session_start event will be triggered when
 | 
				
			||||||
 | 
					        # the bot establishes its connection with the server
 | 
				
			||||||
 | 
					        # and the XML streams are ready for use. We want to
 | 
				
			||||||
 | 
					        # listen for this event so that we we can initialize
 | 
				
			||||||
 | 
					        # our roster.
 | 
				
			||||||
 | 
					        self.add_event_handler("session_start", self.start)
 | 
				
			||||||
 | 
					        self.add_event_handler("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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # We first create a session dictionary containing:
 | 
				
			||||||
 | 
					        #   'next'  -- the handler to execute on a successful response
 | 
				
			||||||
 | 
					        #   'error' -- the handler to execute if an error occurs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The session may also contain custom data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        session = {'greeting': self.greeting,
 | 
				
			||||||
 | 
					                   'next': self._command_start,
 | 
				
			||||||
 | 
					                   'error': self._command_error}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self['xep_0050'].start_command(jid=self.command_provider,
 | 
				
			||||||
 | 
					                                       node='greeting',
 | 
				
			||||||
 | 
					                                       session=session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def message(self, msg):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process incoming message stanzas.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            msg -- The received message stanza.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        logging.info(msg['body'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _command_start(self, iq, session):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process the initial command result.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            iq      -- The iq stanza containing the command result.
 | 
				
			||||||
 | 
					            session -- A dictionary of data relevant to the command
 | 
				
			||||||
 | 
					                       session. Additional, custom data may be saved
 | 
				
			||||||
 | 
					                       here to persist across handler callbacks.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The greeting command provides a form with a single field:
 | 
				
			||||||
 | 
					        # <x xmlns="jabber:x:data" type="form">
 | 
				
			||||||
 | 
					        #   <field var="greeting"
 | 
				
			||||||
 | 
					        #          type="text-single"
 | 
				
			||||||
 | 
					        #          label="Your greeting" />
 | 
				
			||||||
 | 
					        # </x>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        form = self['xep_0004'].makeForm(ftype='submit')
 | 
				
			||||||
 | 
					        form.addField(var='greeting',
 | 
				
			||||||
 | 
					                      value=session['greeting'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        session['payload'] = form
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # We don't need to process the next result.
 | 
				
			||||||
 | 
					        session['next'] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Other options include using:
 | 
				
			||||||
 | 
					        # continue_command() -- Continue to the next step in the workflow
 | 
				
			||||||
 | 
					        # cancel_command()   -- Stop command execution.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self['xep_0050'].complete_command(session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _command_error(self, iq, session):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process an error that occurs during command execution.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            iq      -- The iq stanza containing the error.
 | 
				
			||||||
 | 
					            session -- A dictionary of data relevant to the command
 | 
				
			||||||
 | 
					                       session. Additional, custom data may be saved
 | 
				
			||||||
 | 
					                       here to persist across handler callbacks.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        logging.error("COMMAND: %s %s" % (iq['error']['condition'],
 | 
				
			||||||
 | 
					                                          iq['error']['text']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Terminate the command's execution and clear its session.
 | 
				
			||||||
 | 
					        # The session will automatically be cleared if no error
 | 
				
			||||||
 | 
					        # handler is provided.
 | 
				
			||||||
 | 
					        self['xep_0050'].terminate_command(session)
 | 
				
			||||||
 | 
					        self.disconnect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    # Setup the command line arguments.
 | 
				
			||||||
 | 
					    optp = OptionParser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Output verbosity options.
 | 
				
			||||||
 | 
					    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.ERROR, default=logging.INFO)
 | 
				
			||||||
 | 
					    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.DEBUG, default=logging.INFO)
 | 
				
			||||||
 | 
					    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=5, default=logging.INFO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # JID and password options.
 | 
				
			||||||
 | 
					    optp.add_option("-j", "--jid", dest="jid",
 | 
				
			||||||
 | 
					                    help="JID to use")
 | 
				
			||||||
 | 
					    optp.add_option("-p", "--password", dest="password",
 | 
				
			||||||
 | 
					                    help="password to use")
 | 
				
			||||||
 | 
					    optp.add_option("-o", "--other", dest="other",
 | 
				
			||||||
 | 
					                    help="JID providing commands")
 | 
				
			||||||
 | 
					    optp.add_option("-g", "--greeting", dest="greeting",
 | 
				
			||||||
 | 
					                    help="Greeting")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 Commands: ")
 | 
				
			||||||
 | 
					    if opts.greeting is None:
 | 
				
			||||||
 | 
					        opts.greeting = raw_input("Greeting: ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup the CommandBot and register plugins. Note that while plugins may
 | 
				
			||||||
 | 
					    # have interdependencies, the order in which you register them does
 | 
				
			||||||
 | 
					    # not matter.
 | 
				
			||||||
 | 
					    xmpp = CommandUserBot(opts.jid, opts.password, opts.other, opts.greeting)
 | 
				
			||||||
 | 
					    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.")
 | 
				
			||||||
							
								
								
									
										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
 | 
				
			||||||
							
								
								
									
										199
									
								
								examples/disco_browser.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										199
									
								
								examples/disco_browser.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,199 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Disco(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A demonstration for using basic service discovery.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Send a disco#info and disco#items request to a JID/node combination,
 | 
				
			||||||
 | 
					    and print out the results.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    May also request only particular info categories such as just features,
 | 
				
			||||||
 | 
					    or just items.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, password, target_jid, target_node='', get=''):
 | 
				
			||||||
 | 
					        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Using service discovery requires the XEP-0030 plugin.
 | 
				
			||||||
 | 
					        self.register_plugin('xep_0030')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.get = get
 | 
				
			||||||
 | 
					        self.target_jid = target_jid
 | 
				
			||||||
 | 
					        self.target_node = target_node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Values to control which disco entities are reported
 | 
				
			||||||
 | 
					        self.info_types = ['', 'all', 'info', 'identities', 'features']
 | 
				
			||||||
 | 
					        self.identity_types = ['', 'all', 'info', 'identities']
 | 
				
			||||||
 | 
					        self.feature_types = ['', 'all', 'info', 'features']
 | 
				
			||||||
 | 
					        self.items_types = ['', 'all', 'items']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        In this case, we send disco#info and disco#items
 | 
				
			||||||
 | 
					        stanzas to the requested JID and print the results.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            event -- An empty dictionary. The session_start
 | 
				
			||||||
 | 
					                     event does not provide any additional
 | 
				
			||||||
 | 
					                     data.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.get_roster()
 | 
				
			||||||
 | 
					        self.send_presence()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.get in self.info_types:
 | 
				
			||||||
 | 
					            # By using block=True, the result stanza will be
 | 
				
			||||||
 | 
					            # returned. Execution will block until the reply is
 | 
				
			||||||
 | 
					            # received. Non-blocking options would be to listen
 | 
				
			||||||
 | 
					            # for the disco_info event, or passing a handler
 | 
				
			||||||
 | 
					            # function using the callback parameter.
 | 
				
			||||||
 | 
					            info = self['xep_0030'].get_info(jid=self.target_jid,
 | 
				
			||||||
 | 
					                                             node=self.target_node,
 | 
				
			||||||
 | 
					                                             block=True)
 | 
				
			||||||
 | 
					        if self.get in self.items_types:
 | 
				
			||||||
 | 
					            # The same applies from above. Listen for the
 | 
				
			||||||
 | 
					            # disco_items event or pass a callback function
 | 
				
			||||||
 | 
					            # if you need to process a non-blocking request.
 | 
				
			||||||
 | 
					            items = self['xep_0030'].get_items(jid=self.target_jid,
 | 
				
			||||||
 | 
					                                               node=self.target_node,
 | 
				
			||||||
 | 
					                                               block=True)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logging.error("Invalid disco request type.")
 | 
				
			||||||
 | 
					            self.disconnect()
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        header = 'XMPP Service Discovery: %s' % self.target_jid
 | 
				
			||||||
 | 
					        print(header)
 | 
				
			||||||
 | 
					        print('-' * len(header))
 | 
				
			||||||
 | 
					        if self.target_node != '':
 | 
				
			||||||
 | 
					            print('Node: %s' % self.target_node)
 | 
				
			||||||
 | 
					            print('-' * len(header))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.get in self.identity_types:
 | 
				
			||||||
 | 
					            print('Identities:')
 | 
				
			||||||
 | 
					            for identity in info['disco_info']['identities']:
 | 
				
			||||||
 | 
					                print('  - %s' % str(identity))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.get in self.feature_types:
 | 
				
			||||||
 | 
					            print('Features:')
 | 
				
			||||||
 | 
					            for feature in info['disco_info']['features']:
 | 
				
			||||||
 | 
					                print('  - %s' % feature)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.get in self.items_types:
 | 
				
			||||||
 | 
					            print('Items:')
 | 
				
			||||||
 | 
					            for item in items['disco_items']['items']:
 | 
				
			||||||
 | 
					                print('  - %s' % str(item))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.disconnect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    # Setup the command line arguments.
 | 
				
			||||||
 | 
					    optp = OptionParser()
 | 
				
			||||||
 | 
					    optp.version = '%%prog 0.1'
 | 
				
			||||||
 | 
					    optp.usage = "Usage: %%prog [options] %s <jid> [<node>]" % \
 | 
				
			||||||
 | 
					                             'all|info|items|identities|features'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 len(args) == 2:
 | 
				
			||||||
 | 
					        args = (args[0], args[1], '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if opts.jid is None:
 | 
				
			||||||
 | 
					        opts.jid = raw_input("Username: ")
 | 
				
			||||||
 | 
					    if opts.password is None:
 | 
				
			||||||
 | 
					        opts.password = getpass.getpass("Password: ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup the Disco browser.
 | 
				
			||||||
 | 
					    xmpp = Disco(opts.jid, opts.password, args[1], args[2], args[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # 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.")
 | 
				
			||||||
							
								
								
									
										144
									
								
								examples/echo_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										144
									
								
								examples/echo_client.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EchoBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple SleekXMPP bot that will echo messages it
 | 
				
			||||||
 | 
					    receives, along with a short thank you message.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, password):
 | 
				
			||||||
 | 
					        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The session_start event will be triggered when
 | 
				
			||||||
 | 
					        # the bot establishes its connection with the server
 | 
				
			||||||
 | 
					        # and the XML streams are ready for use. We want to
 | 
				
			||||||
 | 
					        # listen for this event so that we we can initialize
 | 
				
			||||||
 | 
					        # our roster.
 | 
				
			||||||
 | 
					        self.add_event_handler("session_start", self.start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The message event is triggered whenever a message
 | 
				
			||||||
 | 
					        # stanza is received. Be aware that that includes
 | 
				
			||||||
 | 
					        # MUC messages and error messages.
 | 
				
			||||||
 | 
					        self.add_event_handler("message", self.message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self, event):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process the session_start event.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Typical actions for the session_start event are
 | 
				
			||||||
 | 
					        requesting the roster and broadcasting an initial
 | 
				
			||||||
 | 
					        presence stanza.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            event -- An empty dictionary. The session_start
 | 
				
			||||||
 | 
					                     event does not provide any additional
 | 
				
			||||||
 | 
					                     data.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.send_presence()
 | 
				
			||||||
 | 
					        self.get_roster()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def message(self, msg):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process incoming message stanzas. Be aware that this also
 | 
				
			||||||
 | 
					        includes MUC messages and error messages. It is usually
 | 
				
			||||||
 | 
					        a good idea to check the messages's type before processing
 | 
				
			||||||
 | 
					        or sending replies.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            msg -- The received message stanza. See the documentation
 | 
				
			||||||
 | 
					                   for stanza objects and the Message stanza to see
 | 
				
			||||||
 | 
					                   how it may be used.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if msg['type'] in ('chat', 'normal'):
 | 
				
			||||||
 | 
					            msg.reply("Thanks for sending\n%(body)s" % msg).send()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    # Setup the command line arguments.
 | 
				
			||||||
 | 
					    optp = OptionParser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Output verbosity options.
 | 
				
			||||||
 | 
					    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.ERROR, default=logging.INFO)
 | 
				
			||||||
 | 
					    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.DEBUG, default=logging.INFO)
 | 
				
			||||||
 | 
					    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=5, default=logging.INFO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # JID and password options.
 | 
				
			||||||
 | 
					    optp.add_option("-j", "--jid", dest="jid",
 | 
				
			||||||
 | 
					                    help="JID to use")
 | 
				
			||||||
 | 
					    optp.add_option("-p", "--password", dest="password",
 | 
				
			||||||
 | 
					                    help="password to use")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    opts, args = optp.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup logging.
 | 
				
			||||||
 | 
					    logging.basicConfig(level=opts.loglevel,
 | 
				
			||||||
 | 
					                        format='%(levelname)-8s %(message)s')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if opts.jid is None:
 | 
				
			||||||
 | 
					        opts.jid = raw_input("Username: ")
 | 
				
			||||||
 | 
					    if opts.password is None:
 | 
				
			||||||
 | 
					        opts.password = getpass.getpass("Password: ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup the EchoBot and register plugins. Note that while plugins may
 | 
				
			||||||
 | 
					    # have interdependencies, the order in which you register them does
 | 
				
			||||||
 | 
					    # not matter.
 | 
				
			||||||
 | 
					    xmpp = EchoBot(opts.jid, opts.password)
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0004') # Data Forms
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0060') # PubSub
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you are working with an OpenFire server, you may need
 | 
				
			||||||
 | 
					    # to adjust the SSL version used:
 | 
				
			||||||
 | 
					    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you want to verify the SSL certificates offered by a server:
 | 
				
			||||||
 | 
					    # xmpp.ca_certs = "path/to/ca/cert"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
				
			||||||
 | 
					    if xmpp.connect():
 | 
				
			||||||
 | 
					        # If you do not have the dnspython library installed, you will need
 | 
				
			||||||
 | 
					        # to manually specify the name of the server if it does not match
 | 
				
			||||||
 | 
					        # the one in the JID. For example, to use Google Talk you would
 | 
				
			||||||
 | 
					        # need to use:
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # if xmpp.connect(('talk.google.com', 5222)):
 | 
				
			||||||
 | 
					        #     ...
 | 
				
			||||||
 | 
					        xmpp.process(block=True)
 | 
				
			||||||
 | 
					        print("Done")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print("Unable to connect.")
 | 
				
			||||||
							
								
								
									
										122
									
								
								examples/echo_component.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										122
									
								
								examples/echo_component.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					#!/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.componentxmpp import ComponentXMPP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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 EchoComponent(ComponentXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple SleekXMPP component that echoes messages.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, secret, server, port):
 | 
				
			||||||
 | 
					        ComponentXMPP.__init__(self, jid, secret, server, port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # You don't need a session_start handler, but that is
 | 
				
			||||||
 | 
					        # where you would broadcast initial presence.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Since a component may send messages from any number of JIDs,
 | 
				
			||||||
 | 
					        it is best to always include a from JID.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            msg -- The received message stanza. See the documentation
 | 
				
			||||||
 | 
					                   for stanza objects and the Message stanza to see
 | 
				
			||||||
 | 
					                   how it may be used.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # The reply method will use the messages 'to' JID as the
 | 
				
			||||||
 | 
					        # outgoing reply's 'from' JID.
 | 
				
			||||||
 | 
					        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")
 | 
				
			||||||
 | 
					    optp.add_option("-s", "--server", dest="server",
 | 
				
			||||||
 | 
					                    help="server to connect to")
 | 
				
			||||||
 | 
					    optp.add_option("-P", "--port", dest="port",
 | 
				
			||||||
 | 
					                    help="port to connect to")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    opts, args = optp.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if opts.jid is None:
 | 
				
			||||||
 | 
					        opts.jid = raw_input("Component JID: ")
 | 
				
			||||||
 | 
					    if opts.password is None:
 | 
				
			||||||
 | 
					        opts.password = getpass.getpass("Password: ")
 | 
				
			||||||
 | 
					    if opts.server is None:
 | 
				
			||||||
 | 
					        opts.server = raw_input("Server: ")
 | 
				
			||||||
 | 
					    if opts.port is None:
 | 
				
			||||||
 | 
					        opts.port = int(raw_input("Port: "))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup logging.
 | 
				
			||||||
 | 
					    logging.basicConfig(level=opts.loglevel,
 | 
				
			||||||
 | 
					                        format='%(levelname)-8s %(message)s')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup the EchoComponent and register plugins. Note that while plugins
 | 
				
			||||||
 | 
					    # may have interdependencies, the order in which you register them does
 | 
				
			||||||
 | 
					    # not matter.
 | 
				
			||||||
 | 
					    xmpp = EchoComponent(opts.jid, opts.password, opts.server, opts.port)
 | 
				
			||||||
 | 
					    xmpp.registerPlugin('xep_0030') # Service Discovery
 | 
				
			||||||
 | 
					    xmpp.registerPlugin('xep_0004') # Data Forms
 | 
				
			||||||
 | 
					    xmpp.registerPlugin('xep_0060') # PubSub
 | 
				
			||||||
 | 
					    xmpp.registerPlugin('xep_0199') # XMPP Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
				
			||||||
 | 
					    if xmpp.connect():
 | 
				
			||||||
 | 
					        xmpp.process(block=True)
 | 
				
			||||||
 | 
					        print("Done")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print("Unable to connect.")
 | 
				
			||||||
							
								
								
									
										193
									
								
								examples/muc.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										193
									
								
								examples/muc.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,193 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MUCBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple SleekXMPP bot that will greets those
 | 
				
			||||||
 | 
					    who enter the room, and acknowledge any messages
 | 
				
			||||||
 | 
					    that mentions the bot's nickname.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, password, room, nick):
 | 
				
			||||||
 | 
					        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.room = room
 | 
				
			||||||
 | 
					        self.nick = nick
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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 groupchat_message event is triggered whenever a message
 | 
				
			||||||
 | 
					        # stanza is received from any chat room. If you also also
 | 
				
			||||||
 | 
					        # register a handler for the 'message' event, MUC messages
 | 
				
			||||||
 | 
					        # will be processed by both handlers.
 | 
				
			||||||
 | 
					        self.add_event_handler("groupchat_message", self.muc_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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.
 | 
				
			||||||
 | 
					        self.add_event_handler("muc::%s::got_online" % self.room,
 | 
				
			||||||
 | 
					                               self.muc_online)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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.get_roster()
 | 
				
			||||||
 | 
					        self.send_presence()
 | 
				
			||||||
 | 
					        self.plugin['xep_0045'].joinMUC(self.room,
 | 
				
			||||||
 | 
					                                        self.nick,
 | 
				
			||||||
 | 
					                                        # If a room password is needed, use:
 | 
				
			||||||
 | 
					                                        # password=the_room_password,
 | 
				
			||||||
 | 
					                                        wait=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def muc_message(self, msg):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process incoming message stanzas from any chat room. Be aware
 | 
				
			||||||
 | 
					        that if you also have any handlers for the 'message' event,
 | 
				
			||||||
 | 
					        message stanzas may be processed by both handlers, so check
 | 
				
			||||||
 | 
					        the 'type' attribute when using a 'message' event handler.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Whenever the bot's nickname is mentioned, respond to
 | 
				
			||||||
 | 
					        the message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        IMPORTANT: Always check that a message is not from yourself,
 | 
				
			||||||
 | 
					                   otherwise you will create an infinite loop responding
 | 
				
			||||||
 | 
					                   to your own messages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This handler will reply to messages that mention
 | 
				
			||||||
 | 
					        the bot's nickname.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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['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')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def muc_online(self, presence):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process a presence stanza from a chat room. In this case,
 | 
				
			||||||
 | 
					        presences from users that have just come online are
 | 
				
			||||||
 | 
					        handled by sending a welcome message that includes
 | 
				
			||||||
 | 
					        the user's nickname and role in the room.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            presence -- The received presence stanza. See the
 | 
				
			||||||
 | 
					                        documentation for the Presence stanza
 | 
				
			||||||
 | 
					                        to see how else it may be used.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        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')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    # Setup the command line arguments.
 | 
				
			||||||
 | 
					    optp = OptionParser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Output verbosity options.
 | 
				
			||||||
 | 
					    optp.add_option('-q', '--quiet', help='set logging to ERROR',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.ERROR, default=logging.INFO)
 | 
				
			||||||
 | 
					    optp.add_option('-d', '--debug', help='set logging to DEBUG',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.DEBUG, default=logging.INFO)
 | 
				
			||||||
 | 
					    optp.add_option('-v', '--verbose', help='set logging to COMM',
 | 
				
			||||||
 | 
					                    action='store_const', dest='loglevel',
 | 
				
			||||||
 | 
					                    const=5, default=logging.INFO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # JID and password options.
 | 
				
			||||||
 | 
					    optp.add_option("-j", "--jid", dest="jid",
 | 
				
			||||||
 | 
					                    help="JID to use")
 | 
				
			||||||
 | 
					    optp.add_option("-p", "--password", dest="password",
 | 
				
			||||||
 | 
					                    help="password to use")
 | 
				
			||||||
 | 
					    optp.add_option("-r", "--room", dest="room",
 | 
				
			||||||
 | 
					                    help="MUC room to join")
 | 
				
			||||||
 | 
					    optp.add_option("-n", "--nick", dest="nick",
 | 
				
			||||||
 | 
					                    help="MUC nickname")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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.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
 | 
				
			||||||
 | 
					    # not matter.
 | 
				
			||||||
 | 
					    xmpp = MUCBot(opts.jid, opts.password, opts.room, opts.nick)
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0045') # Multi-User Chat
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # 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.")
 | 
				
			||||||
							
								
								
									
										141
									
								
								examples/ping.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										141
									
								
								examples/ping.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PingTest(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple SleekXMPP bot that will send a ping request
 | 
				
			||||||
 | 
					    to a given JID.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, password, pingjid):
 | 
				
			||||||
 | 
					        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
				
			||||||
 | 
					        if pingjid is None:
 | 
				
			||||||
 | 
					            pingjid = self.jid
 | 
				
			||||||
 | 
					        self.pingjid = pingjid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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()
 | 
				
			||||||
 | 
					        result = self['xep_0199'].send_ping(self.pingjid,
 | 
				
			||||||
 | 
					                                            timeout=10,
 | 
				
			||||||
 | 
					                                            errorfalse=True)
 | 
				
			||||||
 | 
					        logging.info("Pinging...")
 | 
				
			||||||
 | 
					        if result is False:
 | 
				
			||||||
 | 
					            logging.info("Couldn't ping.")
 | 
				
			||||||
 | 
					            self.disconnect()
 | 
				
			||||||
 | 
					            sys.exit(1)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logging.info("Success! RTT: %s", str(result))
 | 
				
			||||||
 | 
					            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)
 | 
				
			||||||
 | 
					    optp.add_option('-t', '--pingto', help='set jid to ping',
 | 
				
			||||||
 | 
					                    action='store', type='string', dest='pingjid',
 | 
				
			||||||
 | 
					                    default=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # JID and password options.
 | 
				
			||||||
 | 
					    optp.add_option("-j", "--jid", dest="jid",
 | 
				
			||||||
 | 
					                    help="JID to use")
 | 
				
			||||||
 | 
					    optp.add_option("-p", "--password", dest="password",
 | 
				
			||||||
 | 
					                    help="password to use")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 PingTest and register plugins. Note that while plugins may
 | 
				
			||||||
 | 
					    # have interdependencies, the order in which you register them does
 | 
				
			||||||
 | 
					    # not matter.
 | 
				
			||||||
 | 
					    xmpp = PingTest(opts.jid, opts.password, opts.pingjid)
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0030')  # Service Discovery
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0004')  # Data Forms
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0060')  # PubSub
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0199')  # XMPP Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you are working with an OpenFire server, you may need
 | 
				
			||||||
 | 
					    # to adjust the SSL version used:
 | 
				
			||||||
 | 
					    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you want to verify the SSL certificates offered by a server:
 | 
				
			||||||
 | 
					    # xmpp.ca_certs = "path/to/ca/cert"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
				
			||||||
 | 
					    if xmpp.connect():
 | 
				
			||||||
 | 
					        # If you do not have the dnspython library installed, you will need
 | 
				
			||||||
 | 
					        # to manually specify the name of the server if it does not match
 | 
				
			||||||
 | 
					        # the one in the JID. For example, to use Google Talk you would
 | 
				
			||||||
 | 
					        # need to use:
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # if xmpp.connect(('talk.google.com', 5222)):
 | 
				
			||||||
 | 
					        #     ...
 | 
				
			||||||
 | 
					        xmpp.process(block=True)
 | 
				
			||||||
 | 
					        print("Done")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print("Unable to connect.")
 | 
				
			||||||
							
								
								
									
										168
									
								
								examples/proxy_echo_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										168
									
								
								examples/proxy_echo_client.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,168 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EchoBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple SleekXMPP bot that will echo messages it
 | 
				
			||||||
 | 
					    receives, along with a short thank you message.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, password):
 | 
				
			||||||
 | 
					        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The session_start event will be triggered when
 | 
				
			||||||
 | 
					        # the bot establishes its connection with the server
 | 
				
			||||||
 | 
					        # and the XML streams are ready for use. We want to
 | 
				
			||||||
 | 
					        # listen for this event so that we we can initialize
 | 
				
			||||||
 | 
					        # our roster.
 | 
				
			||||||
 | 
					        self.add_event_handler("session_start", self.start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The message event is triggered whenever a message
 | 
				
			||||||
 | 
					        # stanza is received. Be aware that that includes
 | 
				
			||||||
 | 
					        # MUC messages and error messages.
 | 
				
			||||||
 | 
					        self.add_event_handler("message", self.message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        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")
 | 
				
			||||||
 | 
					    optp.add_option("--phost", dest="proxy_host",
 | 
				
			||||||
 | 
					                    help="Proxy hostname")
 | 
				
			||||||
 | 
					    optp.add_option("--pport", dest="proxy_port",
 | 
				
			||||||
 | 
					                    help="Proxy port")
 | 
				
			||||||
 | 
					    optp.add_option("--puser", dest="proxy_user",
 | 
				
			||||||
 | 
					                    help="Proxy username")
 | 
				
			||||||
 | 
					    optp.add_option("--ppass", dest="proxy_pass",
 | 
				
			||||||
 | 
					                    help="Proxy password")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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.proxy_host is None:
 | 
				
			||||||
 | 
					        opts.proxy_host = raw_input("Proxy host: ")
 | 
				
			||||||
 | 
					    if opts.proxy_port is None:
 | 
				
			||||||
 | 
					        opts.proxy_port = raw_input("Proxy port: ")
 | 
				
			||||||
 | 
					    if opts.proxy_user is None:
 | 
				
			||||||
 | 
					        opts.proxy_user = raw_input("Proxy username: ")
 | 
				
			||||||
 | 
					    if opts.proxy_pass is None and opts.proxy_user:
 | 
				
			||||||
 | 
					        opts.proxy_pass = getpass.getpass("Proxy password: ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup the EchoBot and register plugins. Note that while plugins may
 | 
				
			||||||
 | 
					    # have interdependencies, the order in which you register them does
 | 
				
			||||||
 | 
					    # not matter.
 | 
				
			||||||
 | 
					    xmpp = EchoBot(opts.jid, opts.password)
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0004') # Data Forms
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0060') # PubSub
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you are working with an OpenFire server, you may need
 | 
				
			||||||
 | 
					    # to adjust the SSL version used:
 | 
				
			||||||
 | 
					    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you want to verify the SSL certificates offered by a server:
 | 
				
			||||||
 | 
					    # xmpp.ca_certs = "path/to/ca/cert"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    xmpp.use_proxy = True
 | 
				
			||||||
 | 
					    xmpp.proxy_config = {
 | 
				
			||||||
 | 
					        'host': opts.proxy_host,
 | 
				
			||||||
 | 
					        'port': int(opts.proxy_port),
 | 
				
			||||||
 | 
					        'username': opts.proxy_user,
 | 
				
			||||||
 | 
					        'password': opts.proxy_pass}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # 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.")
 | 
				
			||||||
							
								
								
									
										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.")
 | 
				
			||||||
							
								
								
									
										172
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								examples/roster_browser.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2011  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					from sleekxmpp.exceptions import 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 RosterBrowser(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A basic script for dumping a client's roster to
 | 
				
			||||||
 | 
					    the command line.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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. We need threaded=True so that the
 | 
				
			||||||
 | 
					        # session_start handler doesn't block event processing
 | 
				
			||||||
 | 
					        # while we wait for presence stanzas to arrive.
 | 
				
			||||||
 | 
					        self.add_event_handler("session_start", self.start, threaded=True)
 | 
				
			||||||
 | 
					        self.add_event_handler("changed_status", self.wait_for_presences)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.received = set()
 | 
				
			||||||
 | 
					        self.presences_received = threading.Event()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self, event):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process the session_start event.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Typical actions for the session_start event are
 | 
				
			||||||
 | 
					        requesting the roster and broadcasting an initial
 | 
				
			||||||
 | 
					        presence stanza.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Arguments:
 | 
				
			||||||
 | 
					            event -- An empty dictionary. The session_start
 | 
				
			||||||
 | 
					                     event does not provide any additional
 | 
				
			||||||
 | 
					                     data.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.get_roster()
 | 
				
			||||||
 | 
					        except IqError as err:
 | 
				
			||||||
 | 
					            print('Error: %' % err.iq['error']['condition'])
 | 
				
			||||||
 | 
					        except IqTimeout:
 | 
				
			||||||
 | 
					            print('Error: Request timed out')
 | 
				
			||||||
 | 
					        self.send_presence()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print('Waiting for presence updates...\n')
 | 
				
			||||||
 | 
					        self.presences_received.wait(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print('Roster for %s' % self.boundjid.bare)
 | 
				
			||||||
 | 
					        groups = self.client_roster.groups()
 | 
				
			||||||
 | 
					        for group in groups:
 | 
				
			||||||
 | 
					            print('\n%s' % group)
 | 
				
			||||||
 | 
					            print('-' * 72)
 | 
				
			||||||
 | 
					            for jid in groups[group]:
 | 
				
			||||||
 | 
					                sub = self.client_roster[jid]['subscription']
 | 
				
			||||||
 | 
					                name = self.client_roster[jid]['name']
 | 
				
			||||||
 | 
					                if self.client_roster[jid]['name']:
 | 
				
			||||||
 | 
					                    print(' %s (%s) [%s]' % (name, jid, sub))
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    print(' %s [%s]' % (jid, sub))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                connections = self.client_roster.presence(jid)
 | 
				
			||||||
 | 
					                for res, pres in connections.items():
 | 
				
			||||||
 | 
					                    show = 'available'
 | 
				
			||||||
 | 
					                    if pres['show']:
 | 
				
			||||||
 | 
					                        show = pres['show']
 | 
				
			||||||
 | 
					                    print('   - %s (%s)' % (res, show))
 | 
				
			||||||
 | 
					                    if pres['status']:
 | 
				
			||||||
 | 
					                        print('       %s' % pres['status'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.disconnect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def wait_for_presences(self, pres):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Track how many roster entries have received presence updates.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.received.add(pres['from'].bare)
 | 
				
			||||||
 | 
					        if len(self.received) >= len(self.client_roster.keys()):
 | 
				
			||||||
 | 
					            self.presences_received.set()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.presences_received.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    # Setup the command line arguments.
 | 
				
			||||||
 | 
					    optp = OptionParser()
 | 
				
			||||||
 | 
					    optp.add_option('-q','--quiet', help='set logging to ERROR',
 | 
				
			||||||
 | 
					                    action='store_const',
 | 
				
			||||||
 | 
					                    dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.ERROR,
 | 
				
			||||||
 | 
					                    default=logging.ERROR)
 | 
				
			||||||
 | 
					    optp.add_option('-d','--debug', help='set logging to DEBUG',
 | 
				
			||||||
 | 
					                    action='store_const',
 | 
				
			||||||
 | 
					                    dest='loglevel',
 | 
				
			||||||
 | 
					                    const=logging.DEBUG,
 | 
				
			||||||
 | 
					                    default=logging.ERROR)
 | 
				
			||||||
 | 
					    optp.add_option('-v','--verbose', help='set logging to COMM',
 | 
				
			||||||
 | 
					                    action='store_const',
 | 
				
			||||||
 | 
					                    dest='loglevel',
 | 
				
			||||||
 | 
					                    const=5,
 | 
				
			||||||
 | 
					                    default=logging.ERROR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # JID and password options.
 | 
				
			||||||
 | 
					    optp.add_option("-j", "--jid", dest="jid",
 | 
				
			||||||
 | 
					                    help="JID to use")
 | 
				
			||||||
 | 
					    optp.add_option("-p", "--password", dest="password",
 | 
				
			||||||
 | 
					                    help="password to use")
 | 
				
			||||||
 | 
					    opts,args = optp.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup logging.
 | 
				
			||||||
 | 
					    logging.basicConfig(level=opts.loglevel,
 | 
				
			||||||
 | 
					                        format='%(levelname)-8s %(message)s')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if opts.jid is None:
 | 
				
			||||||
 | 
					        opts.jid = raw_input("Username: ")
 | 
				
			||||||
 | 
					    if opts.password is None:
 | 
				
			||||||
 | 
					        opts.password = getpass.getpass("Password: ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    xmpp = RosterBrowser(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)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print("Unable to connect.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										44
									
								
								examples/rpc_async.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								examples/rpc_async.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2011  Dann Martens
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
				
			||||||
 | 
					    ANY_ALL, Future
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Boomerang(Endpoint):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def FQN(self):
 | 
				
			||||||
 | 
					        return 'boomerang'
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					    @remote
 | 
				
			||||||
 | 
					    def throw(self):
 | 
				
			||||||
 | 
					        print "Duck!"
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session.new_handler(ANY_ALL, Boomerang)    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    callback = Future()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    boomerang.async(callback).throw()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    time.sleep(10)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    session.close()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
							
								
								
									
										53
									
								
								examples/rpc_client_side.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								examples/rpc_client_side.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2011  Dann Martens
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
				
			||||||
 | 
					    ANY_ALL
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Thermostat(Endpoint):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def FQN(self):
 | 
				
			||||||
 | 
					        return 'thermostat'
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def __init__(self, initial_temperature):
 | 
				
			||||||
 | 
					        self._temperature = initial_temperature
 | 
				
			||||||
 | 
					        self._event = threading.Event()        
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @remote
 | 
				
			||||||
 | 
					    def set_temperature(self, temperature):
 | 
				
			||||||
 | 
					        return NotImplemented
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @remote
 | 
				
			||||||
 | 
					    def get_temperature(self):
 | 
				
			||||||
 | 
					        return NotImplemented
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @remote(False)
 | 
				
			||||||
 | 
					    def release(self):
 | 
				
			||||||
 | 
					        return NotImplemented
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session = Remote.new_session('operator@xmpp.org/rpc', '*****')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    print("Current temperature is %s" % thermostat.get_temperature())
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    thermostat.set_temperature(20)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    time.sleep(10)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    session.close()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
							
								
								
									
										52
									
								
								examples/rpc_server_side.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								examples/rpc_server_side.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2011  Dann Martens
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
 | 
				
			||||||
 | 
					    ANY_ALL
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Thermostat(Endpoint):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def FQN(self):
 | 
				
			||||||
 | 
					        return 'thermostat'
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def __init__(self, initial_temperature):
 | 
				
			||||||
 | 
					        self._temperature = initial_temperature
 | 
				
			||||||
 | 
					        self._event = threading.Event()        
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @remote
 | 
				
			||||||
 | 
					    def set_temperature(self, temperature):
 | 
				
			||||||
 | 
					        print("Setting temperature to %s" % temperature)
 | 
				
			||||||
 | 
					        self._temperature = temperature
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @remote
 | 
				
			||||||
 | 
					    def get_temperature(self):
 | 
				
			||||||
 | 
					        return self._temperature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @remote(False)
 | 
				
			||||||
 | 
					    def release(self):
 | 
				
			||||||
 | 
					        self._event.set() 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def wait_for_release(self):
 | 
				
			||||||
 | 
					        self._event.wait()        
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    thermostat.wait_for_release()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    session.close()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
							
								
								
									
										143
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										143
									
								
								examples/send_client.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import getpass
 | 
				
			||||||
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sleekxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python versions before 3.0 do not use UTF-8 encoding
 | 
				
			||||||
 | 
					# by default. To ensure that Unicode is handled properly
 | 
				
			||||||
 | 
					# throughout SleekXMPP, we will set the default encoding
 | 
				
			||||||
 | 
					# ourselves to UTF-8.
 | 
				
			||||||
 | 
					if sys.version_info < (3, 0):
 | 
				
			||||||
 | 
					    reload(sys)
 | 
				
			||||||
 | 
					    sys.setdefaultencoding('utf8')
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    raw_input = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SendMsgBot(sleekxmpp.ClientXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A basic SleekXMPP bot that will log in, send a message,
 | 
				
			||||||
 | 
					    and then log out.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, jid, password, recipient, message):
 | 
				
			||||||
 | 
					        sleekxmpp.ClientXMPP.__init__(self, jid, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The message we wish to send, and the JID that
 | 
				
			||||||
 | 
					        # will receive it.
 | 
				
			||||||
 | 
					        self.recipient = recipient
 | 
				
			||||||
 | 
					        self.msg = message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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_message(mto=self.recipient,
 | 
				
			||||||
 | 
					                          mbody=self.msg,
 | 
				
			||||||
 | 
					                          mtype='chat')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Using wait=True ensures that the send queue will be
 | 
				
			||||||
 | 
					        # emptied before ending the session.
 | 
				
			||||||
 | 
					        self.disconnect(wait=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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("-t", "--to", dest="to",
 | 
				
			||||||
 | 
					                    help="JID to send the message to")
 | 
				
			||||||
 | 
					    optp.add_option("-m", "--message", dest="message",
 | 
				
			||||||
 | 
					                    help="message to send")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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.to is None:
 | 
				
			||||||
 | 
					        opts.to = raw_input("Send To: ")
 | 
				
			||||||
 | 
					    if opts.message is None:
 | 
				
			||||||
 | 
					        opts.message = raw_input("Message: ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup the EchoBot and register plugins. Note that while plugins may
 | 
				
			||||||
 | 
					    # have interdependencies, the order in which you register them does
 | 
				
			||||||
 | 
					    # not matter.
 | 
				
			||||||
 | 
					    xmpp = SendMsgBot(opts.jid, opts.password, opts.to, opts.message)
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
				
			||||||
 | 
					    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you are working with an OpenFire server, you may need
 | 
				
			||||||
 | 
					    # to adjust the SSL version used:
 | 
				
			||||||
 | 
					    # xmpp.ssl_version = ssl.PROTOCOL_SSLv3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If you want to verify the SSL certificates offered by a server:
 | 
				
			||||||
 | 
					    # xmpp.ca_certs = "path/to/ca/cert"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
				
			||||||
 | 
					    if xmpp.connect():
 | 
				
			||||||
 | 
					        # If you do not have the dnspython library installed, you will need
 | 
				
			||||||
 | 
					        # to manually specify the name of the server if it does not match
 | 
				
			||||||
 | 
					        # the one in the JID. For example, to use Google Talk you would
 | 
				
			||||||
 | 
					        # need to use:
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # if xmpp.connect(('talk.google.com', 5222)):
 | 
				
			||||||
 | 
					        #     ...
 | 
				
			||||||
 | 
					        xmpp.process(block=True)
 | 
				
			||||||
 | 
					        print("Done")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        print("Unable to connect.")
 | 
				
			||||||
							
								
								
									
										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.")
 | 
				
			||||||
							
								
								
									
										105
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										105
									
								
								setup.py
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,17 +1,22 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Copyright (C) 2007-2008 Nathanael C. Fritz
 | 
					# Copyright (C) 2007-2011 Nathanael C. Fritz
 | 
				
			||||||
# All Rights Reserved
 | 
					# All Rights Reserved
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# This software is licensed as described in the README file,
 | 
					# This software is licensed as described in the README.rst and LICENSE
 | 
				
			||||||
# which you should have received as part of this distribution.
 | 
					# file, which you should have received as part of this distribution.
 | 
				
			||||||
#
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# from ez_setup import use_setuptools
 | 
					 | 
				
			||||||
from distutils.core import setup
 | 
					 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					import codecs
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from setuptools import setup, Command
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    from distutils.core import setup, Command
 | 
				
			||||||
 | 
					# from ez_setup import use_setuptools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from testall import TestCommand
 | 
				
			||||||
 | 
					from sleekxmpp.version import __version__
 | 
				
			||||||
# if 'cygwin' in sys.platform.lower():
 | 
					# if 'cygwin' in sys.platform.lower():
 | 
				
			||||||
#     min_version = '0.6c6'
 | 
					#     min_version = '0.6c6'
 | 
				
			||||||
# else:
 | 
					# else:
 | 
				
			||||||
@@ -25,29 +30,76 @@ import sys
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
# from setuptools import setup, find_packages, Extension, Feature
 | 
					# from setuptools import setup, find_packages, Extension, Feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION          = '0.2.3.1'
 | 
					VERSION          = __version__
 | 
				
			||||||
DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
 | 
					DESCRIPTION      = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
 | 
				
			||||||
LONG_DESCRIPTION = """
 | 
					with codecs.open('README.rst', 'r', encoding='UTF-8') as readme:
 | 
				
			||||||
SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).
 | 
					    LONG_DESCRIPTION = ''.join(readme)
 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CLASSIFIERS      = [ 'Intended Audience :: Developers',
 | 
					CLASSIFIERS      = [ 'Intended Audience :: Developers',
 | 
				
			||||||
                     'License :: OSI Approved :: MIT',
 | 
					                     'License :: OSI Approved :: MIT License',
 | 
				
			||||||
                     'Programming Language :: Python',
 | 
					                     'Programming Language :: Python',
 | 
				
			||||||
 | 
					                     'Programming Language :: Python :: 2.6',
 | 
				
			||||||
 | 
					                     'Programming Language :: Python :: 2.7',
 | 
				
			||||||
 | 
					                     'Programming Language :: Python :: 3.1',
 | 
				
			||||||
 | 
					                     'Programming Language :: Python :: 3.2',
 | 
				
			||||||
                     'Topic :: Software Development :: Libraries :: Python Modules',
 | 
					                     'Topic :: Software Development :: Libraries :: Python Modules',
 | 
				
			||||||
                   ]
 | 
					                   ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
packages     = [ 'sleekxmpp',
 | 
					packages     = [ 'sleekxmpp',
 | 
				
			||||||
				 'sleekxmpp/plugins',
 | 
					                 'sleekxmpp/stanza',
 | 
				
			||||||
				 'sleekxmpp/stanza',
 | 
					                 'sleekxmpp/test',
 | 
				
			||||||
				 'sleekxmpp/xmlstream',
 | 
					                 'sleekxmpp/roster',
 | 
				
			||||||
				 'sleekxmpp/xmlstream/matcher',
 | 
					                 'sleekxmpp/xmlstream',
 | 
				
			||||||
				 'sleekxmpp/xmlstream/handler' ]
 | 
					                 'sleekxmpp/xmlstream/matcher',
 | 
				
			||||||
 | 
					                 'sleekxmpp/xmlstream/handler',
 | 
				
			||||||
if sys.version_info < (3, 0):
 | 
					                 'sleekxmpp/plugins',
 | 
				
			||||||
	packages.append('sleekxmpp/xmlstream/tostring26')
 | 
					                 'sleekxmpp/plugins/xep_0004',
 | 
				
			||||||
else:
 | 
					                 'sleekxmpp/plugins/xep_0004/stanza',
 | 
				
			||||||
	packages.append('sleekxmpp/xmlstream/tostring')
 | 
					                 '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',
 | 
				
			||||||
 | 
					                 'sleekxmpp/features/feature_mechanisms/stanza',
 | 
				
			||||||
 | 
					                 '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',
 | 
				
			||||||
 | 
					                 ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
setup(
 | 
					setup(
 | 
				
			||||||
    name             = "sleekxmpp",
 | 
					    name             = "sleekxmpp",
 | 
				
			||||||
@@ -56,10 +108,11 @@ setup(
 | 
				
			|||||||
    long_description = LONG_DESCRIPTION,
 | 
					    long_description = LONG_DESCRIPTION,
 | 
				
			||||||
    author       = 'Nathanael Fritz',
 | 
					    author       = 'Nathanael Fritz',
 | 
				
			||||||
    author_email = 'fritzy [at] netflint.net',
 | 
					    author_email = 'fritzy [at] netflint.net',
 | 
				
			||||||
    url          = 'http://code.google.com/p/sleekxmpp',
 | 
					    url          = 'http://github.com/fritzy/SleekXMPP',
 | 
				
			||||||
    license      = 'MIT',
 | 
					    license      = 'MIT',
 | 
				
			||||||
    platforms    = [ 'any' ],
 | 
					    platforms    = [ 'any' ],
 | 
				
			||||||
	packages	 = packages,
 | 
					    packages     = packages,
 | 
				
			||||||
    requires     = [ 'tlslite', 'pythondns' ],
 | 
					    requires     = [ 'dnspython', 'pyasn1', 'pyasn1_modules' ],
 | 
				
			||||||
    )
 | 
					    classifiers  = CLASSIFIERS,
 | 
				
			||||||
 | 
					    cmdclass     = {'test': TestCommand}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,275 +1,18 @@
 | 
				
			|||||||
#!/usr/bin/python2.5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
	SleekXMPP: The Sleek XMPP Library
 | 
					    SleekXMPP: The Sleek XMPP Library
 | 
				
			||||||
	Copyright (C) 2010  Nathanael C. Fritz
 | 
					    Copyright (C) 2010  Nathanael C. Fritz
 | 
				
			||||||
	This file is part of SleekXMPP.
 | 
					    This file is part of SleekXMPP.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	See the file license.txt for copying permission.
 | 
					    See the file LICENSE for copying permission.
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from __future__ import absolute_import, unicode_literals
 | 
					 | 
				
			||||||
from . basexmpp import basexmpp
 | 
					 | 
				
			||||||
from xml.etree import cElementTree as ET
 | 
					 | 
				
			||||||
from . xmlstream.xmlstream import XMLStream
 | 
					 | 
				
			||||||
from . xmlstream.xmlstream import RestartStream
 | 
					 | 
				
			||||||
from . xmlstream.matcher.xmlmask import MatchXMLMask
 | 
					 | 
				
			||||||
from . xmlstream.matcher.xpath import MatchXPath
 | 
					 | 
				
			||||||
from . xmlstream.matcher.many import MatchMany
 | 
					 | 
				
			||||||
from . xmlstream.handler.callback import Callback
 | 
					 | 
				
			||||||
from . xmlstream.stanzabase import StanzaBase
 | 
					 | 
				
			||||||
from . xmlstream import xmlstream as xmlstreammod
 | 
					 | 
				
			||||||
from . stanza.message import Message
 | 
					 | 
				
			||||||
from . stanza.iq import Iq
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
import random
 | 
					 | 
				
			||||||
import copy
 | 
					 | 
				
			||||||
from . import plugins
 | 
					 | 
				
			||||||
#from . import stanza
 | 
					 | 
				
			||||||
srvsupport = True
 | 
					 | 
				
			||||||
try:
 | 
					 | 
				
			||||||
	import dns.resolver
 | 
					 | 
				
			||||||
	import dns.rdatatype
 | 
					 | 
				
			||||||
except ImportError:
 | 
					 | 
				
			||||||
	srvsupport = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sleekxmpp.basexmpp import BaseXMPP
 | 
				
			||||||
 | 
					from sleekxmpp.clientxmpp import ClientXMPP
 | 
				
			||||||
 | 
					from sleekxmpp.componentxmpp import ComponentXMPP
 | 
				
			||||||
 | 
					from sleekxmpp.stanza import Message, Presence, Iq
 | 
				
			||||||
 | 
					from sleekxmpp.xmlstream.handler import *
 | 
				
			||||||
 | 
					from sleekxmpp.xmlstream import XMLStream, RestartStream
 | 
				
			||||||
 | 
					from sleekxmpp.xmlstream.matcher import *
 | 
				
			||||||
 | 
					from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sleekxmpp.version import __version__, __version_info__
 | 
				
			||||||
#class PresenceStanzaType(object):
 | 
					 | 
				
			||||||
#	
 | 
					 | 
				
			||||||
#	def fromXML(self, xml):
 | 
					 | 
				
			||||||
#		self.ptype = xml.get('type')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ClientXMPP(basexmpp, XMLStream):
 | 
					 | 
				
			||||||
	"""SleekXMPP's client class.  Use only for good, not evil."""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True):
 | 
					 | 
				
			||||||
		global srvsupport
 | 
					 | 
				
			||||||
		XMLStream.__init__(self)
 | 
					 | 
				
			||||||
		self.default_ns = 'jabber:client'
 | 
					 | 
				
			||||||
		basexmpp.__init__(self)
 | 
					 | 
				
			||||||
		self.plugin_config = plugin_config
 | 
					 | 
				
			||||||
		self.escape_quotes = escape_quotes
 | 
					 | 
				
			||||||
		self.set_jid(jid)
 | 
					 | 
				
			||||||
		self.server = None
 | 
					 | 
				
			||||||
		self.port = 5222 # not used if DNS SRV is used
 | 
					 | 
				
			||||||
		self.plugin_whitelist = plugin_whitelist
 | 
					 | 
				
			||||||
		self.auto_reconnect = True
 | 
					 | 
				
			||||||
		self.srvsupport = srvsupport
 | 
					 | 
				
			||||||
		self.password = password
 | 
					 | 
				
			||||||
		self.registered_features = []
 | 
					 | 
				
			||||||
		self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.domain,self.default_ns)
 | 
					 | 
				
			||||||
		self.stream_footer = "</stream:stream>"
 | 
					 | 
				
			||||||
		#self.map_namespace('http://etherx.jabber.org/streams', 'stream')
 | 
					 | 
				
			||||||
		#self.map_namespace('jabber:client', '')
 | 
					 | 
				
			||||||
		self.features = []
 | 
					 | 
				
			||||||
		#TODO: Use stream state here
 | 
					 | 
				
			||||||
		self.authenticated = False
 | 
					 | 
				
			||||||
		self.sessionstarted = False
 | 
					 | 
				
			||||||
		self.bound = False
 | 
					 | 
				
			||||||
		self.bindfail = False
 | 
					 | 
				
			||||||
		self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
 | 
					 | 
				
			||||||
		self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
 | 
					 | 
				
			||||||
		#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
 | 
					 | 
				
			||||||
		self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True)
 | 
					 | 
				
			||||||
		self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True)
 | 
					 | 
				
			||||||
		self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource)
 | 
					 | 
				
			||||||
		self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session)
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
 | 
					 | 
				
			||||||
		#self.register_plugins()
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def __getitem__(self, key):
 | 
					 | 
				
			||||||
		if key in self.plugin:
 | 
					 | 
				
			||||||
			return self.plugin[key]
 | 
					 | 
				
			||||||
		else:
 | 
					 | 
				
			||||||
			logging.warning("""Plugin "%s" is not loaded.""" % key)
 | 
					 | 
				
			||||||
			return False
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def get(self, key, default):
 | 
					 | 
				
			||||||
		return self.plugin.get(key, default)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def connect(self, host=None, port=None):
 | 
					 | 
				
			||||||
		"""Connect to the Jabber Server.  Attempts SRV lookup, and if it fails, uses
 | 
					 | 
				
			||||||
		the JID server."""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if self.state['connected']: return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if host:
 | 
					 | 
				
			||||||
			self.server = host
 | 
					 | 
				
			||||||
			if port is None: port = self.port
 | 
					 | 
				
			||||||
		else:
 | 
					 | 
				
			||||||
			if not self.srvsupport:
 | 
					 | 
				
			||||||
				logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org).  Continuing to attempt connection, using domain from JID.")
 | 
					 | 
				
			||||||
			else:
 | 
					 | 
				
			||||||
				logging.debug("Since no address is supplied, attempting SRV lookup.")
 | 
					 | 
				
			||||||
				try:
 | 
					 | 
				
			||||||
					answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.domain, 
 | 
					 | 
				
			||||||
					        dns.rdatatype.SRV )
 | 
					 | 
				
			||||||
				except dns.resolver.NXDOMAIN:
 | 
					 | 
				
			||||||
					logging.debug("No appropriate SRV record found.  Using JID server name.")
 | 
					 | 
				
			||||||
				else:
 | 
					 | 
				
			||||||
					# pick a random answer, weighted by priority
 | 
					 | 
				
			||||||
					# there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway 
 | 
					 | 
				
			||||||
					# suggestions are welcome
 | 
					 | 
				
			||||||
					addresses = {}
 | 
					 | 
				
			||||||
					intmax = 0
 | 
					 | 
				
			||||||
					priorities = []
 | 
					 | 
				
			||||||
					for answer in answers:
 | 
					 | 
				
			||||||
						intmax += answer.priority
 | 
					 | 
				
			||||||
						addresses[intmax] = (answer.target.to_text()[:-1], answer.port)
 | 
					 | 
				
			||||||
						priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort()
 | 
					 | 
				
			||||||
					picked = random.randint(0, intmax)
 | 
					 | 
				
			||||||
					for priority in priorities:
 | 
					 | 
				
			||||||
						if picked <= priority:
 | 
					 | 
				
			||||||
							(host,port) = addresses[priority]
 | 
					 | 
				
			||||||
							break
 | 
					 | 
				
			||||||
					# if SRV lookup was successful, we aren't using a particular server.
 | 
					 | 
				
			||||||
					self.server = None 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if not host:
 | 
					 | 
				
			||||||
			# if all else fails take server from JID.
 | 
					 | 
				
			||||||
			(host,port) = (self.domain, self.port)
 | 
					 | 
				
			||||||
			self.server = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		logging.debug('Attempting connection to %s:%d', host, port )
 | 
					 | 
				
			||||||
		#TODO option to not use TLS?
 | 
					 | 
				
			||||||
		result = XMLStream.connect(self, host, port, use_tls=True)
 | 
					 | 
				
			||||||
		if result:
 | 
					 | 
				
			||||||
			self.event("connected")
 | 
					 | 
				
			||||||
		else:
 | 
					 | 
				
			||||||
			logging.warning("Failed to connect")
 | 
					 | 
				
			||||||
			self.event("disconnected")
 | 
					 | 
				
			||||||
		return result
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	# overriding reconnect and disconnect so that we can get some events
 | 
					 | 
				
			||||||
	# should events be part of or required by xmlstream?  Maybe that would be cleaner
 | 
					 | 
				
			||||||
	def reconnect(self):
 | 
					 | 
				
			||||||
		self.disconnect(reconnect=True)
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def disconnect(self, reconnect=False):
 | 
					 | 
				
			||||||
		self.event("disconnected")
 | 
					 | 
				
			||||||
		self.authenticated = False
 | 
					 | 
				
			||||||
		self.sessionstarted = False
 | 
					 | 
				
			||||||
		XMLStream.disconnect(self, reconnect)
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def registerFeature(self, mask, pointer, breaker = False):
 | 
					 | 
				
			||||||
		"""Register a stream feature."""
 | 
					 | 
				
			||||||
		self.registered_features.append((MatchXMLMask(mask), pointer, breaker))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def updateRoster(self, jid, name=None, subscription=None, groups=[]):
 | 
					 | 
				
			||||||
		"""Add or change a roster item."""
 | 
					 | 
				
			||||||
		iq = self.Iq().setValues({'type': 'set'})
 | 
					 | 
				
			||||||
		iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
 | 
					 | 
				
			||||||
		#self.send(iq, self.Iq().setValues({'id': iq['id']}))
 | 
					 | 
				
			||||||
		r = iq.send()
 | 
					 | 
				
			||||||
		return r['type'] == 'result'
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def getRoster(self):
 | 
					 | 
				
			||||||
		"""Request the roster be sent."""
 | 
					 | 
				
			||||||
		iq = self.Iq().setValues({'type': 'get'}).enable('roster').send()
 | 
					 | 
				
			||||||
		self._handleRoster(iq, request=True)
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def _handleStreamFeatures(self, features):
 | 
					 | 
				
			||||||
		logging.debug('handling stream features')
 | 
					 | 
				
			||||||
		self.features = []
 | 
					 | 
				
			||||||
		for sub in features.xml:
 | 
					 | 
				
			||||||
			self.features.append(sub.tag)
 | 
					 | 
				
			||||||
		for subelement in features.xml:
 | 
					 | 
				
			||||||
			for feature in self.registered_features:
 | 
					 | 
				
			||||||
				if feature[0].match(subelement):
 | 
					 | 
				
			||||||
				#if self.maskcmp(subelement, feature[0], True):
 | 
					 | 
				
			||||||
					# This calls the feature handler & optionally breaks
 | 
					 | 
				
			||||||
					if feature[1](subelement) and feature[2]: #if breaker, don't continue
 | 
					 | 
				
			||||||
						return True
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def handler_starttls(self, xml):
 | 
					 | 
				
			||||||
		logging.debug( 'TLS start handler; SSL support: %s', self.ssl_support )
 | 
					 | 
				
			||||||
		if not self.authenticated and self.ssl_support:
 | 
					 | 
				
			||||||
			_stanza = "<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />"
 | 
					 | 
				
			||||||
			if not self.event_handlers.get(_stanza,None): # don't add handler > once
 | 
					 | 
				
			||||||
				self.add_handler( _stanza, self.handler_tls_start, instream=True )
 | 
					 | 
				
			||||||
			self.sendXML(xml)
 | 
					 | 
				
			||||||
			return True
 | 
					 | 
				
			||||||
		else:
 | 
					 | 
				
			||||||
			logging.warning("The module tlslite is required in to some servers, and has not been found.")
 | 
					 | 
				
			||||||
			return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def handler_tls_start(self, xml):
 | 
					 | 
				
			||||||
		logging.debug("Starting TLS")
 | 
					 | 
				
			||||||
		if self.startTLS():
 | 
					 | 
				
			||||||
			raise RestartStream()
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def handler_sasl_auth(self, xml):
 | 
					 | 
				
			||||||
		if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
 | 
					 | 
				
			||||||
			return False
 | 
					 | 
				
			||||||
		logging.debug("Starting SASL Auth")
 | 
					 | 
				
			||||||
		self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True)
 | 
					 | 
				
			||||||
		self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, instream=True)
 | 
					 | 
				
			||||||
		sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism')
 | 
					 | 
				
			||||||
		if len(sasl_mechs):
 | 
					 | 
				
			||||||
			for sasl_mech in sasl_mechs:
 | 
					 | 
				
			||||||
				self.features.append("sasl:%s" % sasl_mech.text)
 | 
					 | 
				
			||||||
			if 'sasl:PLAIN' in self.features:
 | 
					 | 
				
			||||||
				if sys.version_info < (3,0):
 | 
					 | 
				
			||||||
					self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8'))
 | 
					 | 
				
			||||||
				else:
 | 
					 | 
				
			||||||
					self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8'))
 | 
					 | 
				
			||||||
			else:
 | 
					 | 
				
			||||||
				logging.error("No appropriate login method.")
 | 
					 | 
				
			||||||
				self.disconnect()
 | 
					 | 
				
			||||||
				#if 'sasl:DIGEST-MD5' in self.features:
 | 
					 | 
				
			||||||
				#	self._auth_digestmd5()
 | 
					 | 
				
			||||||
		return True
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def handler_auth_success(self, xml):
 | 
					 | 
				
			||||||
		logging.debug("Authentication successful.")
 | 
					 | 
				
			||||||
		self.authenticated = True
 | 
					 | 
				
			||||||
		self.features = []
 | 
					 | 
				
			||||||
		raise RestartStream()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def handler_auth_fail(self, xml):
 | 
					 | 
				
			||||||
		logging.warning("Authentication failed.")
 | 
					 | 
				
			||||||
		self.disconnect()
 | 
					 | 
				
			||||||
		self.event("failed_auth")
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def handler_bind_resource(self, xml):
 | 
					 | 
				
			||||||
		logging.debug("Requesting resource: %s" % self.resource)
 | 
					 | 
				
			||||||
		iq = self.Iq(stype='set')
 | 
					 | 
				
			||||||
		res = ET.Element('resource')
 | 
					 | 
				
			||||||
		res.text = self.resource
 | 
					 | 
				
			||||||
		xml.append(res)
 | 
					 | 
				
			||||||
		iq.append(xml)
 | 
					 | 
				
			||||||
		response = iq.send()
 | 
					 | 
				
			||||||
		#response = self.send(iq, self.Iq(sid=iq['id']))
 | 
					 | 
				
			||||||
		self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
 | 
					 | 
				
			||||||
		self.bound = True
 | 
					 | 
				
			||||||
		logging.info("Node set to: %s" % self.fulljid)
 | 
					 | 
				
			||||||
		if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail:
 | 
					 | 
				
			||||||
			logging.debug("Established Session")
 | 
					 | 
				
			||||||
			self.sessionstarted = True
 | 
					 | 
				
			||||||
			self.event("session_start")
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def handler_start_session(self, xml):
 | 
					 | 
				
			||||||
		if self.authenticated and self.bound:
 | 
					 | 
				
			||||||
			iq = self.makeIqSet(xml)
 | 
					 | 
				
			||||||
			response = iq.send()
 | 
					 | 
				
			||||||
			logging.debug("Established Session")
 | 
					 | 
				
			||||||
			self.sessionstarted = True
 | 
					 | 
				
			||||||
			self.event("session_start")
 | 
					 | 
				
			||||||
		else:
 | 
					 | 
				
			||||||
			#bind probably hasn't happened yet
 | 
					 | 
				
			||||||
			self.bindfail = True
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def _handleRoster(self, iq, request=False):
 | 
					 | 
				
			||||||
		if iq['type'] == 'set' or (iq['type'] == 'result' and request):
 | 
					 | 
				
			||||||
			for jid in iq['roster']['items']:
 | 
					 | 
				
			||||||
				if not jid in self.roster:
 | 
					 | 
				
			||||||
					self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
 | 
					 | 
				
			||||||
				self.roster[jid].update(iq['roster']['items'][jid])
 | 
					 | 
				
			||||||
			if iq['type'] == 'set':
 | 
					 | 
				
			||||||
				self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster'))
 | 
					 | 
				
			||||||
		self.event("roster_update", iq)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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)
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										302
									
								
								sleekxmpp/clientxmpp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								sleekxmpp/clientxmpp.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,302 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					    sleekxmpp.clientxmpp
 | 
				
			||||||
 | 
					    ~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from sleekxmpp.stanza import StreamFeatures
 | 
				
			||||||
 | 
					from sleekxmpp.basexmpp import BaseXMPP
 | 
				
			||||||
 | 
					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:
 | 
				
			||||||
 | 
					    import dns.resolver
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    DNSPYTHON = False
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    DNSPYTHON = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ClientXMPP(BaseXMPP):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    SleekXMPP's client class. (Use only for good, not for evil.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Typical use pattern:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .. code-block:: python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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):
 | 
				
			||||||
 | 
					        BaseXMPP.__init__(self, jid, 'jabber:client')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.set_jid(jid)
 | 
				
			||||||
 | 
					        self.escape_quotes = escape_quotes
 | 
				
			||||||
 | 
					        self.plugin_config = plugin_config
 | 
				
			||||||
 | 
					        self.plugin_whitelist = plugin_whitelist
 | 
				
			||||||
 | 
					        self.default_port = 5222
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.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,
 | 
				
			||||||
 | 
					                "xmlns='%s'" % self.default_ns)
 | 
				
			||||||
 | 
					        self.stream_footer = "</stream:stream>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.features = set()
 | 
				
			||||||
 | 
					        self._stream_feature_handlers = {}
 | 
				
			||||||
 | 
					        self._stream_feature_order = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.dns_service = 'xmpp-client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #TODO: Use stream state here
 | 
				
			||||||
 | 
					        self.authenticated = False
 | 
				
			||||||
 | 
					        self.sessionstarted = False
 | 
				
			||||||
 | 
					        self.bound = False
 | 
				
			||||||
 | 
					        self.bindfail = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.add_event_handler('connected', self._handle_connected)
 | 
				
			||||||
 | 
					        self.add_event_handler('session_bind', self._handle_session_bind)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.register_stanza(StreamFeatures)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.register_handler(
 | 
				
			||||||
 | 
					                Callback('Stream Features',
 | 
				
			||||||
 | 
					                         MatchXPath('{%s}features' % self.stream_ns),
 | 
				
			||||||
 | 
					                         self._handle_stream_features))
 | 
				
			||||||
 | 
					        self.register_handler(
 | 
				
			||||||
 | 
					                Callback('Roster Update',
 | 
				
			||||||
 | 
					                         StanzaPath('iq@type=set/roster'),
 | 
				
			||||||
 | 
					                         self._handle_roster))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Setup default stream features
 | 
				
			||||||
 | 
					        self.register_plugin('feature_starttls')
 | 
				
			||||||
 | 
					        self.register_plugin('feature_bind')
 | 
				
			||||||
 | 
					        self.register_plugin('feature_session')
 | 
				
			||||||
 | 
					        self.register_plugin('feature_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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :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 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 register_feature(self, name, handler, restart=False, order=5000):
 | 
				
			||||||
 | 
					        """Register a stream feature handler.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :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))
 | 
				
			||||||
 | 
					        self._stream_feature_order.sort()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_roster(self, jid, name=None, subscription=None, groups=[],
 | 
				
			||||||
 | 
					                            block=True, timeout=None, callback=None):
 | 
				
			||||||
 | 
					        """Add or change a roster item.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :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.
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :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``.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        iq = self.Iq()
 | 
				
			||||||
 | 
					        iq['type'] = 'get'
 | 
				
			||||||
 | 
					        iq.enable('roster')
 | 
				
			||||||
 | 
					        if 'rosterver' in self.features:
 | 
				
			||||||
 | 
					            iq['roster']['ver'] = self.client_roster.version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not block and callback is None:
 | 
				
			||||||
 | 
					            callback = lambda resp: self._handle_roster(resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = iq.send(block, timeout, callback)
 | 
				
			||||||
 | 
					        self.event('roster_received', response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if block: 
 | 
				
			||||||
 | 
					            self._handle_roster(response)
 | 
				
			||||||
 | 
					            return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_connected(self, event=None):
 | 
				
			||||||
 | 
					        #TODO: Use stream state here
 | 
				
			||||||
 | 
					        self.authenticated = False
 | 
				
			||||||
 | 
					        self.sessionstarted = False
 | 
				
			||||||
 | 
					        self.bound = False
 | 
				
			||||||
 | 
					        self.bindfail = False
 | 
				
			||||||
 | 
					        self.features = set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_stream_features(self, features):
 | 
				
			||||||
 | 
					        """Process the received stream features.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param features: The features stanza.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        for order, name in self._stream_feature_order:
 | 
				
			||||||
 | 
					            if name in features['features']:
 | 
				
			||||||
 | 
					                handler, restart = self._stream_feature_handlers[name]
 | 
				
			||||||
 | 
					                if handler(features) and restart:
 | 
				
			||||||
 | 
					                    # Don't continue if the feature requires
 | 
				
			||||||
 | 
					                    # restarting the XML stream.
 | 
				
			||||||
 | 
					                    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_roster(self, iq):
 | 
				
			||||||
 | 
					        """Update the roster after receiving a roster stanza.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param iq: The roster stanza.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        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':
 | 
				
			||||||
 | 
					            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.
 | 
				
			||||||
 | 
					# Deprecated method names are re-mapped for backwards compatibility.
 | 
				
			||||||
 | 
					ClientXMPP.updateRoster = ClientXMPP.update_roster
 | 
				
			||||||
 | 
					ClientXMPP.delRosterItem = ClientXMPP.del_roster_item
 | 
				
			||||||
 | 
					ClientXMPP.getRoster = ClientXMPP.get_roster
 | 
				
			||||||
 | 
					ClientXMPP.registerFeature = ClientXMPP.register_feature
 | 
				
			||||||
@@ -1,41 +0,0 @@
 | 
				
			|||||||
import sleekxmpp.componentxmpp
 | 
					 | 
				
			||||||
import logging
 | 
					 | 
				
			||||||
from optparse import OptionParser
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Example(sleekxmpp.componentxmpp.ComponentXMPP):
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def __init__(self, jid, password):
 | 
					 | 
				
			||||||
		sleekxmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'vm1', 5230)
 | 
					 | 
				
			||||||
		self.add_event_handler("session_start", self.start)
 | 
					 | 
				
			||||||
		self.add_event_handler("message", self.message)
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	def start(self, event):
 | 
					 | 
				
			||||||
		#self.getRoster()
 | 
					 | 
				
			||||||
		#self.sendPresence(pto='admin@tigase.netflint.net/sarkozy')
 | 
					 | 
				
			||||||
		#self.sendPresence(pto='tigase.netflint.net')
 | 
					 | 
				
			||||||
		pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	def message(self, event):
 | 
					 | 
				
			||||||
		self.sendMessage("%s/%s" % (event['jid'], event['resource']), "Thanks for sending me, \"%s\"." % event['message'], mtype=event['type'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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")
 | 
					 | 
				
			||||||
	opts,args = optp.parse_args()
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
 | 
					 | 
				
			||||||
	xmpp = Example('component.vm1', 'secreteating')
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0004')
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0030')
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0060')
 | 
					 | 
				
			||||||
	xmpp.registerPlugin('xep_0199')
 | 
					 | 
				
			||||||
	if xmpp.connect():
 | 
					 | 
				
			||||||
		xmpp.process(threaded=False)
 | 
					 | 
				
			||||||
		print("done")
 | 
					 | 
				
			||||||
	else:
 | 
					 | 
				
			||||||
		print("Unable to connect.")
 | 
					 | 
				
			||||||
							
								
								
									
										216
									
								
								sleekxmpp/componentxmpp.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										216
									
								
								sleekxmpp/componentxmpp.py
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							@@ -1,88 +1,162 @@
 | 
				
			|||||||
#!/usr/bin/python2.6
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
    SleekXMPP: The Sleek XMPP Library
 | 
					    sleekxmpp.clientxmpp
 | 
				
			||||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
					    ~~~~~~~~~~~~~~~~~~~~
 | 
				
			||||||
    This file is part of SleekXMPP.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    See the file license.txt 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
 | 
					from __future__ import absolute_import
 | 
				
			||||||
from . basexmpp import basexmpp
 | 
					 | 
				
			||||||
from xml.etree import cElementTree as ET
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . xmlstream.xmlstream import XMLStream
 | 
					 | 
				
			||||||
from . xmlstream.xmlstream import RestartStream
 | 
					 | 
				
			||||||
from . xmlstream.matcher.xmlmask import MatchXMLMask
 | 
					 | 
				
			||||||
from . xmlstream.matcher.xpath import MatchXPath
 | 
					 | 
				
			||||||
from . xmlstream.matcher.many import MatchMany
 | 
					 | 
				
			||||||
from . xmlstream.handler.callback import Callback
 | 
					 | 
				
			||||||
from . xmlstream.stanzabase import StanzaBase
 | 
					 | 
				
			||||||
from . xmlstream import xmlstream as xmlstreammod
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import base64
 | 
					 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import random
 | 
					 | 
				
			||||||
import copy
 | 
					 | 
				
			||||||
from . import plugins
 | 
					 | 
				
			||||||
from . import stanza
 | 
					 | 
				
			||||||
import hashlib
 | 
					import hashlib
 | 
				
			||||||
srvsupport = True
 | 
					
 | 
				
			||||||
try:
 | 
					from sleekxmpp.basexmpp import BaseXMPP
 | 
				
			||||||
	import dns.resolver
 | 
					from sleekxmpp.xmlstream import XMLStream
 | 
				
			||||||
except ImportError:
 | 
					from sleekxmpp.xmlstream import ET
 | 
				
			||||||
	srvsupport = False
 | 
					from sleekxmpp.xmlstream.matcher import MatchXPath
 | 
				
			||||||
 | 
					from sleekxmpp.xmlstream.handler import Callback
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ComponentXMPP(basexmpp, XMLStream):
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
	"""SleekXMPP's client class.  Use only for good, not evil."""
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False):
 | 
					 | 
				
			||||||
		XMLStream.__init__(self)
 | 
					 | 
				
			||||||
		if use_jc_ns:
 | 
					 | 
				
			||||||
			self.default_ns = 'jabber:client'
 | 
					 | 
				
			||||||
		else:
 | 
					 | 
				
			||||||
			self.default_ns = 'jabber:component:accept'
 | 
					 | 
				
			||||||
		basexmpp.__init__(self)
 | 
					 | 
				
			||||||
		self.auto_authorize = None
 | 
					 | 
				
			||||||
		self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid
 | 
					 | 
				
			||||||
		self.stream_footer = "</stream:stream>"
 | 
					 | 
				
			||||||
		self.server_host = host
 | 
					 | 
				
			||||||
		self.server_port = port
 | 
					 | 
				
			||||||
		self.set_jid(jid)
 | 
					 | 
				
			||||||
		self.secret = secret
 | 
					 | 
				
			||||||
		self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def __getitem__(self, key):
 | 
					class ComponentXMPP(BaseXMPP):
 | 
				
			||||||
		if key in self.plugin:
 | 
					 | 
				
			||||||
			return self.plugin[key]
 | 
					 | 
				
			||||||
		else:
 | 
					 | 
				
			||||||
			logging.warning("""Plugin "%s" is not loaded.""" % key)
 | 
					 | 
				
			||||||
			return False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def get(self, key, default):
 | 
					    """
 | 
				
			||||||
		return self.plugin.get(key, default)
 | 
					    SleekXMPP's basic XMPP server component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def incoming_filter(self, xmlobj):
 | 
					    Use only for good, not for evil.
 | 
				
			||||||
		if xmlobj.tag.startswith('{jabber:client}'):
 | 
					 | 
				
			||||||
			xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
 | 
					 | 
				
			||||||
		for sub in xmlobj:
 | 
					 | 
				
			||||||
			self.incoming_filter(sub)
 | 
					 | 
				
			||||||
		return xmlobj
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def start_stream_handler(self, xml):
 | 
					    :param jid: The JID of the component.
 | 
				
			||||||
		sid = xml.get('id', '')
 | 
					    :param secret: The secret or password for the component.
 | 
				
			||||||
		handshake = ET.Element('{jabber:component:accept}handshake')
 | 
					    :param host: The server accepting the component.
 | 
				
			||||||
		if sys.version_info < (3,0):
 | 
					    :param port: The port used to connect to the server.
 | 
				
			||||||
			handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower()
 | 
					    :param plugin_config: A dictionary of plugin configurations.
 | 
				
			||||||
		else:
 | 
					    :param plugin_whitelist: A list of approved plugins that 
 | 
				
			||||||
			handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
 | 
					                    will be loaded when calling 
 | 
				
			||||||
		self.sendXML(handshake)
 | 
					                    :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 _handleHandshake(self, xml):
 | 
					    def __init__(self, jid, secret, host=None, port=None,
 | 
				
			||||||
		self.event("session_start")
 | 
					                 plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
 | 
				
			||||||
 | 
					        if use_jc_ns:
 | 
				
			||||||
 | 
					            default_ns = 'jabber:client'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            default_ns = 'jabber:component:accept'
 | 
				
			||||||
 | 
					        BaseXMPP.__init__(self, jid, default_ns)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	def connect(self):
 | 
					        self.auto_authorize = None
 | 
				
			||||||
		logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
 | 
					        self.stream_header = "<stream:stream %s %s to='%s'>" % (
 | 
				
			||||||
		return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)
 | 
					                'xmlns="jabber:component:accept"',
 | 
				
			||||||
 | 
					                'xmlns:stream="%s"' % self.stream_ns,
 | 
				
			||||||
 | 
					                jid)
 | 
				
			||||||
 | 
					        self.stream_footer = "</stream:stream>"
 | 
				
			||||||
 | 
					        self.server_host = host
 | 
				
			||||||
 | 
					        self.server_port = port
 | 
				
			||||||
 | 
					        self.secret = secret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.plugin_config = plugin_config
 | 
				
			||||||
 | 
					        self.plugin_whitelist = plugin_whitelist
 | 
				
			||||||
 | 
					        self.is_component = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.register_handler(
 | 
				
			||||||
 | 
					                Callback('Handshake',
 | 
				
			||||||
 | 
					                         MatchXPath('{jabber:component:accept}handshake'),
 | 
				
			||||||
 | 
					                         self._handle_handshake))
 | 
				
			||||||
 | 
					        self.add_event_handler('presence_probe',
 | 
				
			||||||
 | 
					                               self._handle_probe)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def connect(self, host=None, port=None, use_ssl=False, 
 | 
				
			||||||
 | 
					                      use_tls=True, reattempt=True):
 | 
				
			||||||
 | 
					        """Connect to the server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Setting ``reattempt`` to ``True`` will cause connection attempts to
 | 
				
			||||||
 | 
					        be made every second until a successful connection is established.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param host: The name of the desired server for the connection.
 | 
				
			||||||
 | 
					                     Defaults to :attr:`server_host`.
 | 
				
			||||||
 | 
					        :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.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if host is None:
 | 
				
			||||||
 | 
					            host = self.server_host
 | 
				
			||||||
 | 
					        if port is None:
 | 
				
			||||||
 | 
					            port = self.server_port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.server_name = self.boundjid.host
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log.debug("Connecting to %s:%s", host, port)
 | 
				
			||||||
 | 
					        return XMLStream.connect(self, host=host, port=port,
 | 
				
			||||||
 | 
					                                       use_ssl=use_ssl,
 | 
				
			||||||
 | 
					                                       use_tls=use_tls,
 | 
				
			||||||
 | 
					                                       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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param xml: The XML stanza to pre-process.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if xml.tag.startswith('{jabber:client}'):
 | 
				
			||||||
 | 
					            xml.tag = xml.tag.replace('jabber:client', self.default_ns)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The incoming_filter call is only made on top level stanza
 | 
				
			||||||
 | 
					        # elements. So we manually continue filtering on sub-elements.
 | 
				
			||||||
 | 
					        for sub in xml:
 | 
				
			||||||
 | 
					            self.incoming_filter(sub)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start_stream_handler(self, xml):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Once the streams are established, attempt to handshake
 | 
				
			||||||
 | 
					        with the server to be accepted as a component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param xml: The incoming stream's root element.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        BaseXMPP.start_stream_handler(self, xml)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Construct a hash of the stream ID and the component secret.
 | 
				
			||||||
 | 
					        sid = xml.get('id', '')
 | 
				
			||||||
 | 
					        pre_hash = '%s%s' % (sid, self.secret)
 | 
				
			||||||
 | 
					        if sys.version_info >= (3, 0):
 | 
				
			||||||
 | 
					            # Handle Unicode byte encoding in Python 3.
 | 
				
			||||||
 | 
					            pre_hash = bytes(pre_hash, 'utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        handshake = ET.Element('{jabber:component:accept}handshake')
 | 
				
			||||||
 | 
					        handshake.text = hashlib.sha1(pre_hash).hexdigest().lower()
 | 
				
			||||||
 | 
					        self.send_xml(handshake, now=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_handshake(self, xml):
 | 
				
			||||||
 | 
					        """The handshake has been accepted.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param xml: The reply handshake stanza.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.session_started_event.set()
 | 
				
			||||||
 | 
					        self.event("session_start")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_probe(self, presence):
 | 
				
			||||||
 | 
					        pto = presence['to'].bare
 | 
				
			||||||
 | 
					        pfrom = presence['from'].bare
 | 
				
			||||||
 | 
					        self.roster[pto][pfrom].handle_probe(presence)
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user