Compare commits
1204 Commits
sleek-1.0-
...
1.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
753cb3580e | ||
|
|
60b050b82a | ||
|
|
ad91a8cd5e | ||
|
|
02f79fc94b | ||
|
|
230a73fad2 | ||
|
|
d94dd486fe | ||
|
|
6ecc39b816 | ||
|
|
9c240df9db | ||
|
|
a918bf3a95 | ||
|
|
9434ae267f | ||
|
|
94187d215a | ||
|
|
ef2f5d2978 | ||
|
|
62671e0f56 | ||
|
|
93869f77a0 | ||
|
|
8282d135cc | ||
|
|
9acc78c81d | ||
|
|
3642469630 | ||
|
|
34cd20339c | ||
|
|
7548f44047 | ||
|
|
7cf55ef695 | ||
|
|
543250da13 | ||
|
|
69e55d7316 | ||
|
|
158411e918 | ||
|
|
3f873002c4 | ||
|
|
818f4e5973 | ||
|
|
c8d6e512d2 | ||
|
|
a2423b8499 | ||
|
|
49acdac776 | ||
|
|
7e1587faa2 | ||
|
|
84a6ed8e80 | ||
|
|
654420e351 | ||
|
|
651915f31c | ||
|
|
d9db1b84fe | ||
|
|
bd03f071c6 | ||
|
|
eb6ac68d5c | ||
|
|
848e6ebd83 | ||
|
|
f76524fc9f | ||
|
|
b95532b68b | ||
|
|
d002d4c06f | ||
|
|
7c03cc622c | ||
|
|
cebfd84416 | ||
|
|
12995e280e | ||
|
|
4ae6d44efc | ||
|
|
01e1878900 | ||
|
|
df9ad82336 | ||
|
|
c183fd5e35 | ||
|
|
820d07f309 | ||
|
|
f4e3c04bbf | ||
|
|
540d6e9dbb | ||
|
|
79a3a2befd | ||
|
|
08a0fd5420 | ||
|
|
92d6bc6875 | ||
|
|
fb5d20c4f8 | ||
|
|
65e3122f52 | ||
|
|
be874e3c70 | ||
|
|
beae845281 | ||
|
|
6f64dac262 | ||
|
|
cd2d25cf87 | ||
|
|
b8b2f37e7b | ||
|
|
00152358de | ||
|
|
a2784be4d6 | ||
|
|
ad7a57103d | ||
|
|
19b24b276d | ||
|
|
23750357e2 | ||
|
|
07284f380f | ||
|
|
e60401278f | ||
|
|
24c474a9ec | ||
|
|
8fd3781ef5 | ||
|
|
c85f2494a8 | ||
|
|
6c2fa7a382 | ||
|
|
45689fd879 | ||
|
|
45a2cfb01b | ||
|
|
c4bb6c900c | ||
|
|
f7c042fc77 | ||
|
|
b20dc9fe2b | ||
|
|
a030e05993 | ||
|
|
648b03f811 | ||
|
|
e57e321d33 | ||
|
|
b6e53c7b1b | ||
|
|
1c3bfd949b | ||
|
|
6401c9aaaa | ||
|
|
c02adbb8e1 | ||
|
|
88e64dbfae | ||
|
|
afd48b9e08 | ||
|
|
db0ab9a0b3 | ||
|
|
556e4bd74d | ||
|
|
d439c4f215 | ||
|
|
a9f2e1482c | ||
|
|
2c26fb0d76 | ||
|
|
18dde97c8c | ||
|
|
85bc6f5301 | ||
|
|
8f364b9a95 | ||
|
|
ee6c5632ac | ||
|
|
cc81a0e8da | ||
|
|
262652992d | ||
|
|
eb63825dfd | ||
|
|
c49017c6f1 | ||
|
|
7d08bd3142 | ||
|
|
f12c241dca | ||
|
|
cedc9dd175 | ||
|
|
669e708b70 | ||
|
|
e76a483931 | ||
|
|
c0437d2de8 | ||
|
|
37a8043202 | ||
|
|
f4c69d4045 | ||
|
|
a3606d9e4d | ||
|
|
805f1c0e39 | ||
|
|
7430a8ca40 | ||
|
|
1776e2edcc | ||
|
|
baf9aaf26c | ||
|
|
4864b07e13 | ||
|
|
13c919773e | ||
|
|
ed3a4fb8d4 | ||
|
|
df3e826d0a | ||
|
|
a9e7d489b8 | ||
|
|
da6b549f8b | ||
|
|
76e07a9089 | ||
|
|
4a590d1497 | ||
|
|
82e1508d6f | ||
|
|
400f08db9d | ||
|
|
e48b650caa | ||
|
|
d9f595283a | ||
|
|
85fd14f47f | ||
|
|
b7adaafb3e | ||
|
|
d0bba87cdd | ||
|
|
2cc75d4bbd | ||
|
|
24bd591faa | ||
|
|
2e9ccd0623 | ||
|
|
7b49c82210 | ||
|
|
d3284f1604 | ||
|
|
3279697128 | ||
|
|
60cfab995f | ||
|
|
8ec18bdb2c | ||
|
|
3c3cd65235 | ||
|
|
7ac75de19d | ||
|
|
fae39e1ab4 | ||
|
|
3732139fc3 | ||
|
|
0a2737dc77 | ||
|
|
481971928c | ||
|
|
020197718f | ||
|
|
a0c77c04a5 | ||
|
|
620ee9719f | ||
|
|
c0d02d9935 | ||
|
|
01356d23e5 | ||
|
|
8b73c2bcff | ||
|
|
5a771dbe2f | ||
|
|
9ba5b644cf | ||
|
|
f76f0c3787 | ||
|
|
01abd6a705 | ||
|
|
44e2b5d945 | ||
|
|
82bbe5d1a6 | ||
|
|
a1d71d31e8 | ||
|
|
766e0b685d | ||
|
|
58f5e4702b | ||
|
|
d9906756cf | ||
|
|
9a45ebd98b | ||
|
|
7f9ff9d0e7 | ||
|
|
8c763fcf43 | ||
|
|
6dd4456b11 | ||
|
|
c30c47d291 | ||
|
|
d8c9662302 | ||
|
|
ec5e819b16 | ||
|
|
55e50ad979 | ||
|
|
99ecb166d3 | ||
|
|
cdeae7e72f | ||
|
|
fbf79755d7 | ||
|
|
78bd21b7cf | ||
|
|
88c7c29954 | ||
|
|
d4dde89ea6 | ||
|
|
774bf35fab | ||
|
|
1a2db7fb11 | ||
|
|
da3223ac92 | ||
|
|
b0fed5a48d | ||
|
|
43132dab85 | ||
|
|
badd327360 | ||
|
|
9a6bfc6614 | ||
|
|
79914fb56b | ||
|
|
75a792eb6f | ||
|
|
23f112602c | ||
|
|
639a3aa832 | ||
|
|
79a8c5ceae | ||
|
|
97a2f4449d | ||
|
|
7f42d15175 | ||
|
|
ef9c8e910c | ||
|
|
a1b33da9ca | ||
|
|
1741059cf6 | ||
|
|
1f137735e1 | ||
|
|
a186972f09 | ||
|
|
751628401e | ||
|
|
403b1802ec | ||
|
|
9165cbf7f6 | ||
|
|
bad405bea9 | ||
|
|
4f9a95b011 | ||
|
|
903e641457 | ||
|
|
f34b9399cc | ||
|
|
7d0d96f940 | ||
|
|
27196a21ae | ||
|
|
ea0381fa09 | ||
|
|
3423589ba1 | ||
|
|
1f9286d39e | ||
|
|
93b8e66b5d | ||
|
|
a1716de683 | ||
|
|
ccf7916257 | ||
|
|
d86adfa1b1 | ||
|
|
648f3f978a | ||
|
|
5e4b8bd67c | ||
|
|
64ef690432 | ||
|
|
41991b5982 | ||
|
|
01da222d67 | ||
|
|
518eee05c2 | ||
|
|
1dbfa29a1e | ||
|
|
6bac4741f6 | ||
|
|
a0266dac6f | ||
|
|
ce977a7809 | ||
|
|
8644a83ed9 | ||
|
|
7b45245b1d | ||
|
|
f04f4e4a1a | ||
|
|
b07f1b3bd3 | ||
|
|
0e7486d7b4 | ||
|
|
6c0afb87b9 | ||
|
|
e5750b368e | ||
|
|
ef76f923ad | ||
|
|
2c04ae084c | ||
|
|
91dc58d967 | ||
|
|
0e2abe74d5 | ||
|
|
fea444925e | ||
|
|
0998429b07 | ||
|
|
597eb1779c | ||
|
|
9ae3a7dbff | ||
|
|
3519e845a3 | ||
|
|
29c049612a | ||
|
|
ed48185732 | ||
|
|
f431bbfca2 | ||
|
|
8b29900be4 | ||
|
|
6f8a4f8354 | ||
|
|
def34f0e42 | ||
|
|
e25a49f804 | ||
|
|
b820351f64 | ||
|
|
0eb009496e | ||
|
|
2c2498b658 | ||
|
|
a1d988fed5 | ||
|
|
b0c50b7a59 | ||
|
|
1a2b404076 | ||
|
|
2d066c34fd | ||
|
|
7a1ed64985 | ||
|
|
1b449585f7 | ||
|
|
032d41dbb8 | ||
|
|
3a7569e3ea | ||
|
|
d444930494 | ||
|
|
6045a6bfb3 | ||
|
|
f3f543b31e | ||
|
|
0fea4262ea | ||
|
|
4b7ec4a32a | ||
|
|
2229ad8d8e | ||
|
|
61aff9f49a | ||
|
|
67235c4214 | ||
|
|
a00eee1bbe | ||
|
|
12e8bb6ddc | ||
|
|
06a690a259 | ||
|
|
52feabbe76 | ||
|
|
14c9e9a9cc | ||
|
|
a22ca228cc | ||
|
|
d0666a5eb6 | ||
|
|
931d49560a | ||
|
|
2a4e435228 | ||
|
|
3655827ef2 | ||
|
|
c5046b9c91 | ||
|
|
4598031dd2 | ||
|
|
12e0e1a16b | ||
|
|
5e9266ba90 | ||
|
|
0d448b8221 | ||
|
|
e6c95f0a2a | ||
|
|
63b58edda1 | ||
|
|
af9632519c | ||
|
|
d367fb938d | ||
|
|
77f2a339e1 | ||
|
|
4190027a78 | ||
|
|
ef48a8c4d9 | ||
|
|
829b225053 | ||
|
|
747a6e94e6 | ||
|
|
cebc798e72 | ||
|
|
7c485c6a8b | ||
|
|
e2e8c4b5dc | ||
|
|
675c0112ac | ||
|
|
4dd2c15775 | ||
|
|
9f6decdbc1 | ||
|
|
fc07e23ff8 | ||
|
|
4ea328b9f2 | ||
|
|
84a2fc382b | ||
|
|
098714b3c4 | ||
|
|
cf2c94d974 | ||
|
|
657102e938 | ||
|
|
44e7585bf8 | ||
|
|
94488fa2ea | ||
|
|
a2c60a4911 | ||
|
|
ee9c4abd08 | ||
|
|
b5b1c932c7 | ||
|
|
b8f04983e1 | ||
|
|
90807dd973 | ||
|
|
ef974114ea | ||
|
|
f6e1fecdf8 | ||
|
|
94e8b2becf | ||
|
|
a6ca6701a0 | ||
|
|
c4edb9724b | ||
|
|
b5c669bdff | ||
|
|
e449dce65c | ||
|
|
73ce9a5ecc | ||
|
|
671f680bb3 | ||
|
|
dfff19ffbf | ||
|
|
a4abdf9fa6 | ||
|
|
6c57bb0553 | ||
|
|
d385b9e708 | ||
|
|
c2ae1ee891 | ||
|
|
67147570e9 | ||
|
|
fb3e6b7e35 | ||
|
|
cf28d4586d | ||
|
|
f65eb5eeea | ||
|
|
26fa9bd87e | ||
|
|
0016d9a638 | ||
|
|
a88b9737ff | ||
|
|
df9ac58d05 | ||
|
|
357406d801 | ||
|
|
19a78f63f4 | ||
|
|
c7ec6a72cd | ||
|
|
e68b07dbce | ||
|
|
e20610ab80 | ||
|
|
1ca0c46333 | ||
|
|
e510875f64 | ||
|
|
f52a10b061 | ||
|
|
7d382a2bfd | ||
|
|
09bec1c4fe | ||
|
|
ff28b0a005 | ||
|
|
8a03bd72ae | ||
|
|
a249f8736a | ||
|
|
f0e1fc5aad | ||
|
|
f09adf0014 | ||
|
|
c6ac64ed2d | ||
|
|
04dc68f5f6 | ||
|
|
92be051450 | ||
|
|
5c25208fb5 | ||
|
|
779c258e27 | ||
|
|
962dfad216 | ||
|
|
f7a710e55b | ||
|
|
814a50e36f | ||
|
|
230465b946 | ||
|
|
d11a67702e | ||
|
|
4e12e228cb | ||
|
|
4a94aeba49 | ||
|
|
14aa831169 | ||
|
|
295d23ccf3 | ||
|
|
75d904ed01 | ||
|
|
aebcf6ff82 | ||
|
|
8c2ece3bca | ||
|
|
80a90a6221 | ||
|
|
f81d5e4bd6 | ||
|
|
2324c90232 | ||
|
|
2f65fdbc76 | ||
|
|
59ff08174f | ||
|
|
2f4149c7d0 | ||
|
|
b84e359770 | ||
|
|
fb4275648c | ||
|
|
475ccfa8dc | ||
|
|
267c24c8ef | ||
|
|
06a9d9fc30 | ||
|
|
1383ca19b5 | ||
|
|
4c3ff2abab | ||
|
|
7c6ef18e4f | ||
|
|
f8856467d5 | ||
|
|
3bd84b8d27 | ||
|
|
bc8b5774ac | ||
|
|
8009b0485e | ||
|
|
8742a56b3e | ||
|
|
a792bcdafe | ||
|
|
167d1ce97b | ||
|
|
695cd95657 | ||
|
|
44ce01a70b | ||
|
|
e4b4c67637 | ||
|
|
422e77ae40 | ||
|
|
5ae6c8f8fa | ||
|
|
54656b331a | ||
|
|
9047b627a4 | ||
|
|
6645a3be40 | ||
|
|
c2189b4ecd | ||
|
|
e3fab66dfb | ||
|
|
5867f08bf1 | ||
|
|
a06fa2de67 | ||
|
|
c9b2cf6043 | ||
|
|
35396d2977 | ||
|
|
3bff743d9f | ||
|
|
5a878f829b | ||
|
|
16ec0f151a | ||
|
|
26dc6e90ea | ||
|
|
94c749fd5a | ||
|
|
7b80ed0807 | ||
|
|
98b7e8b10a | ||
|
|
c42f1ad4c7 | ||
|
|
9d8de7fc15 | ||
|
|
70883086b7 | ||
|
|
9a08dfc7d4 | ||
|
|
3e43b36a9d | ||
|
|
352ee2f2fd | ||
|
|
78aa5c3dfa | ||
|
|
613323b5fb | ||
|
|
6c4b01db8a | ||
|
|
d06897a635 | ||
|
|
1600bb0aaf | ||
|
|
b5c9c98a8b | ||
|
|
e4e18a416f | ||
|
|
01cc0e6def | ||
|
|
a3ec1af205 | ||
|
|
d571d691a7 | ||
|
|
2e580304f9 | ||
|
|
fb221a8dc0 | ||
|
|
459e1ed345 | ||
|
|
6680c244f5 | ||
|
|
06423964ec | ||
|
|
5492e9028d | ||
|
|
474390fa00 | ||
|
|
81d3723084 | ||
|
|
32e798967e | ||
|
|
060c9ab679 | ||
|
|
acd9c32a9f | ||
|
|
b8581b0278 | ||
|
|
917faecdcb | ||
|
|
78f0325398 | ||
|
|
f6edaa56a6 | ||
|
|
51fee28bf4 | ||
|
|
e8a3e92ceb | ||
|
|
5df3839b7a | ||
|
|
8dcb441f44 | ||
|
|
a347cf625a | ||
|
|
46f49c7a12 | ||
|
|
99701c947e | ||
|
|
1baae1b81e | ||
|
|
7d20f0e9a6 | ||
|
|
fbad22a1cd | ||
|
|
5af2f62c04 | ||
|
|
4a4a03858e | ||
|
|
1efe049959 | ||
|
|
2393148908 | ||
|
|
6819b57353 | ||
|
|
88b5e60807 | ||
|
|
c7594b3ef0 | ||
|
|
b210870f48 | ||
|
|
a26a8bd79c | ||
|
|
9307a6915f | ||
|
|
5d6019a962 | ||
|
|
85ef2d8d0b | ||
|
|
c2c7cc032b | ||
|
|
e4911e9391 | ||
|
|
b11e1ee92d | ||
|
|
5027d00c10 | ||
|
|
69ddeceb49 | ||
|
|
82698672bb | ||
|
|
9cec284947 | ||
|
|
dc501d1902 | ||
|
|
100e504b7f | ||
|
|
eb5df1aa37 | ||
|
|
8a745c5e81 | ||
|
|
bf0a157c5d | ||
|
|
f49818be06 | ||
|
|
1ad171dfe5 | ||
|
|
2a78570d65 | ||
|
|
546066d677 | ||
|
|
7a112f2523 | ||
|
|
3234596974 | ||
|
|
e86444e5fb | ||
|
|
5820d49cd4 | ||
|
|
36c11ad9de | ||
|
|
019a4b20ae | ||
|
|
433ee08687 | ||
|
|
7858d969d8 | ||
|
|
8119551049 | ||
|
|
061489f03a | ||
|
|
d92aa05b5c | ||
|
|
f7a74d960e | ||
|
|
95a0e51b41 | ||
|
|
110e45e187 | ||
|
|
534aaf2b2a | ||
|
|
4cc20fdd05 | ||
|
|
f3fae192a8 | ||
|
|
7d59a8a0ad | ||
|
|
8da387a38a | ||
|
|
ff6fc44215 | ||
|
|
62391a895a | ||
|
|
9bcdd7d18f | ||
|
|
5c4f7bfe8b | ||
|
|
0b7f134021 | ||
|
|
378a42889f | ||
|
|
f824950552 | ||
|
|
3d2d11f169 | ||
|
|
181aea737d | ||
|
|
1ab66e5767 | ||
|
|
aab2682f9a | ||
|
|
55d332bcc8 | ||
|
|
ee702f4071 | ||
|
|
a08c2161a7 | ||
|
|
f89df6e70c | ||
|
|
0e36a01354 | ||
|
|
c39ad7dfbb | ||
|
|
b92ae706e9 | ||
|
|
250d28e870 | ||
|
|
19f65c8510 | ||
|
|
f70b49882f | ||
|
|
a7b092a305 | ||
|
|
daa73a3f3c | ||
|
|
6997261c6b | ||
|
|
0b51afe87a | ||
|
|
6cfb5cb14c | ||
|
|
8567d6034f | ||
|
|
e06368f8cd | ||
|
|
4b37a4706f | ||
|
|
7b1564947d | ||
|
|
2b298766c9 | ||
|
|
10664d723b | ||
|
|
c012208a8f | ||
|
|
0953896d2d | ||
|
|
cf9e89d0ae | ||
|
|
48dd01b0bb | ||
|
|
7247efe055 | ||
|
|
8def3758e4 | ||
|
|
1851ab6f5f | ||
|
|
289b052338 | ||
|
|
26147f5ae0 | ||
|
|
ae01f1071a | ||
|
|
dcdf5dcd09 | ||
|
|
c59a6d0f51 | ||
|
|
2cd936318d | ||
|
|
2f38857681 | ||
|
|
39505ae1ff | ||
|
|
44ee0633f2 | ||
|
|
b52d2768b0 | ||
|
|
cf24b870b1 | ||
|
|
69cffce7dc | ||
|
|
a14979375b | ||
|
|
40ef4a16b1 | ||
|
|
f5652a667b | ||
|
|
3b2c865a58 | ||
|
|
db0e683d01 | ||
|
|
e29a9e0394 | ||
|
|
edf65f4f52 | ||
|
|
98677fd602 | ||
|
|
61a4f76c8d | ||
|
|
856a826eea | ||
|
|
387ef513d6 | ||
|
|
2858dbf57f | ||
|
|
350a2b8bbc | ||
|
|
c9093c9972 | ||
|
|
d1ad31696e | ||
|
|
f49311ef9e | ||
|
|
678e529efc | ||
|
|
6ddb430fef | ||
|
|
74d1f88146 | ||
|
|
7842c55da3 | ||
|
|
f5beac2afa | ||
|
|
8a23f28dfa | ||
|
|
9c4886e746 | ||
|
|
e0bcd5d722 | ||
|
|
ba854e7d85 | ||
|
|
4ded34ebc9 | ||
|
|
e918a86028 | ||
|
|
24234bf718 | ||
|
|
ec99339140 | ||
|
|
03dedfc871 | ||
|
|
9e86a7b357 | ||
|
|
6a32417957 | ||
|
|
97a7be7dfa | ||
|
|
fa86f956ef | ||
|
|
a9acff5294 | ||
|
|
ad5b61de50 | ||
|
|
f53b815855 | ||
|
|
bf8a9dc20d | ||
|
|
08716c35fd | ||
|
|
fd81bab906 | ||
|
|
1cf55c14b0 | ||
|
|
8b47159788 | ||
|
|
2eeaf4d80c | ||
|
|
4d89d26a1c | ||
|
|
0cc14cee4d | ||
|
|
a20a9c505d | ||
|
|
913738444e | ||
|
|
8ee30179ea | ||
|
|
cb2469322b | ||
|
|
94aa6673ca | ||
|
|
4b2b2d16b8 | ||
|
|
4cd5d3b3b5 | ||
|
|
e48e50c6ff | ||
|
|
01189376e2 | ||
|
|
60195cf2dc | ||
|
|
15ef273141 | ||
|
|
eed6da538a | ||
|
|
d3e8993e22 | ||
|
|
8a8926c5e8 | ||
|
|
f9d0ee824b | ||
|
|
af099737ab | ||
|
|
9ffde5ab37 | ||
|
|
272ddf9f01 | ||
|
|
259c84e99a | ||
|
|
7391288668 | ||
|
|
7734aee7ad | ||
|
|
9f855b9679 | ||
|
|
aedbecd673 | ||
|
|
83c5a4cd2f | ||
|
|
9c61c2882f | ||
|
|
e0dd9c3618 | ||
|
|
4921c44d0a | ||
|
|
3161f104c7 | ||
|
|
898f5f4b51 | ||
|
|
3ee3fdca91 | ||
|
|
488f7ed886 | ||
|
|
51e5aee830 | ||
|
|
af13bea2b8 | ||
|
|
cdf0b353db | ||
|
|
48504ed5e2 | ||
|
|
4d4d1e0ee5 | ||
|
|
c1d36cad46 | ||
|
|
aad2eb31fc | ||
|
|
1bd7824f24 | ||
|
|
912463ed6a | ||
|
|
dda2473d35 | ||
|
|
94923ae898 | ||
|
|
f1fde07eb9 | ||
|
|
a1ddd88208 | ||
|
|
ee6a9b981a | ||
|
|
9879c7af59 | ||
|
|
fa4c52e499 | ||
|
|
d5484808a7 | ||
|
|
1c83391948 | ||
|
|
59d1b8e131 | ||
|
|
859822ff05 | ||
|
|
3acc7d0914 | ||
|
|
b077ef9150 | ||
|
|
e2ce5ae222 | ||
|
|
73cabcb6ae | ||
|
|
fbdf2bed49 | ||
|
|
33d01fb694 | ||
|
|
ab2e43d052 | ||
|
|
0c24fbdb06 | ||
|
|
eb25998e72 | ||
|
|
eafd2aee93 | ||
|
|
a6f3d740a2 | ||
|
|
19a6f61b44 | ||
|
|
58e0f1e6c3 | ||
|
|
96ff2d43c0 | ||
|
|
1b00b7e8df | ||
|
|
7284ceb90c | ||
|
|
24ec448b7f | ||
|
|
ed5a2f400d | ||
|
|
9596616b42 | ||
|
|
8d38fb511b | ||
|
|
5a2cbbb731 | ||
|
|
32d6f85649 | ||
|
|
a2b47e5749 | ||
|
|
14d4062f4a | ||
|
|
67972c5e84 | ||
|
|
3467ac18cc | ||
|
|
cabf27424f | ||
|
|
162e955bd6 | ||
|
|
57d761b8a2 | ||
|
|
8b2023225c | ||
|
|
f8f2b541db | ||
|
|
9d645ad5cd | ||
|
|
610d366bdb | ||
|
|
64c46562d3 | ||
|
|
87d6ade06d | ||
|
|
4a009515c1 | ||
|
|
6497857495 | ||
|
|
5a324c01de | ||
|
|
17279de4a3 | ||
|
|
34a7a62c35 | ||
|
|
2305cc61fd | ||
|
|
2f677c98f8 | ||
|
|
3fda053606 | ||
|
|
6d855ec06c | ||
|
|
23cc62fe7c | ||
|
|
26ea67d211 | ||
|
|
d43cd9fa54 | ||
|
|
6f337b5425 | ||
|
|
d104a5fe75 | ||
|
|
cdd69c6842 | ||
|
|
4a3a9067d4 | ||
|
|
1aecb2293a | ||
|
|
ad8fd91b7a | ||
|
|
1f5a3a4445 | ||
|
|
be363e0b46 | ||
|
|
a104cd6dae | ||
|
|
e287282782 | ||
|
|
8b06d10415 | ||
|
|
1a153487c3 | ||
|
|
01b2499915 | ||
|
|
9f43d31bf5 | ||
|
|
a318beded4 | ||
|
|
5f4b528e6b | ||
|
|
f759b0ada1 | ||
|
|
7d89fa27a8 | ||
|
|
10ec92f7c6 | ||
|
|
58d2f317a0 | ||
|
|
34b094561f | ||
|
|
91155444c0 | ||
|
|
7f71ac7e0a | ||
|
|
e5fc59a4c6 | ||
|
|
549a9ab472 | ||
|
|
09720dcf42 | ||
|
|
ec044affd4 | ||
|
|
af39945009 | ||
|
|
78a50d0237 | ||
|
|
861d279b08 | ||
|
|
eb1a32fc90 | ||
|
|
4610a6615c | ||
|
|
4cb8a8d389 | ||
|
|
a71823dc04 | ||
|
|
d41ada6b66 | ||
|
|
fdfe2cd64f | ||
|
|
7b51c6f5cc | ||
|
|
be7f07ad12 | ||
|
|
830db11b41 | ||
|
|
53bcd33e1d | ||
|
|
e3d596c9fa | ||
|
|
ecd6ad6930 | ||
|
|
c36073b40e | ||
|
|
afe0d16797 | ||
|
|
977fcc0632 | ||
|
|
94b57d232d | ||
|
|
7cdedb2ec0 | ||
|
|
676324805e | ||
|
|
7d74a7b027 | ||
|
|
9d5eb864d1 | ||
|
|
86a482e032 | ||
|
|
c43c7be86c | ||
|
|
c58462f154 | ||
|
|
31d3e3b2b6 | ||
|
|
fb2582e53b | ||
|
|
d807613117 | ||
|
|
6d922d00c3 | ||
|
|
61ea84093b | ||
|
|
e76d6a481f | ||
|
|
c1357717d9 | ||
|
|
ca5145c210 | ||
|
|
1a272fd276 | ||
|
|
952260b423 | ||
|
|
caa967105c | ||
|
|
d565e4be20 | ||
|
|
85dd005abc | ||
|
|
021c57205f | ||
|
|
261a501afc | ||
|
|
9a38a101d2 | ||
|
|
4665c5cf1a | ||
|
|
bd52a5e6c1 | ||
|
|
f98e5a03de | ||
|
|
2217c69757 | ||
|
|
5a4df56836 | ||
|
|
3ab7c8bcc3 | ||
|
|
8f25acd0f3 | ||
|
|
999f1932cc | ||
|
|
69940a8ab9 | ||
|
|
13158e3cdf | ||
|
|
f06589c913 | ||
|
|
2735b680b9 | ||
|
|
5f1d4ce433 | ||
|
|
25f87607aa | ||
|
|
f81fb6af44 | ||
|
|
bb0a5186d6 | ||
|
|
baad907422 | ||
|
|
1022fc0060 | ||
|
|
3a22d798f8 | ||
|
|
71ea430c62 | ||
|
|
0d2125e737 | ||
|
|
02f4006153 | ||
|
|
b25668b5b7 | ||
|
|
bb3080e829 | ||
|
|
bd85e95398 | ||
|
|
22cc194ed8 | ||
|
|
79b71228c1 | ||
|
|
fd515d807c | ||
|
|
4f4c121d9b | ||
|
|
72e1ab47fc | ||
|
|
3575084640 | ||
|
|
1e01903072 | ||
|
|
3672856ab4 | ||
|
|
86d8736dcc | ||
|
|
2923f56561 | ||
|
|
4274f49ada | ||
|
|
a4b27ff031 | ||
|
|
f49b6fa79f | ||
|
|
7b854a190e | ||
|
|
947d1ffbb3 | ||
|
|
de35848500 | ||
|
|
1ae219025a | ||
|
|
e8b2dd6698 | ||
|
|
c0074f95b1 | ||
|
|
a79ce1c35e | ||
|
|
1eb69f7075 | ||
|
|
a86935a42f | ||
|
|
1674bd753e | ||
|
|
6b9a55e62d | ||
|
|
c578ddeb1a | ||
|
|
8ef7188dae | ||
|
|
738ec92b8e | ||
|
|
be9e26b4a3 | ||
|
|
b345c227b2 | ||
|
|
c7e95c8dec | ||
|
|
3a4e3d3f51 | ||
|
|
8fd2efa2fa | ||
|
|
97378998a5 | ||
|
|
79f1aa0e1b | ||
|
|
fb5a6a7d71 | ||
|
|
7d1c5f4a2b | ||
|
|
6b6995bb0b | ||
|
|
27c658922e | ||
|
|
1b0fd76b45 | ||
|
|
35954cdc90 | ||
|
|
46e93bea09 | ||
|
|
cbc6a0296b | ||
|
|
cc63bef179 | ||
|
|
cbcfa156c4 | ||
|
|
fa912aeb84 | ||
|
|
4a12e1059a | ||
|
|
9a5e2ae768 | ||
|
|
f9cd051209 | ||
|
|
e0545bf0bc | ||
|
|
03bc38f7e3 | ||
|
|
4e23a4e08e | ||
|
|
d817d64c65 | ||
|
|
8a29ec67ac | ||
|
|
6722b0224a | ||
|
|
8eb225bdec | ||
|
|
a7df76a275 | ||
|
|
efae8f3369 | ||
|
|
a11e6c0b77 | ||
|
|
1bb0b38868 | ||
|
|
8cafa8578f | ||
|
|
b74ea47650 | ||
|
|
2dc230a68b | ||
|
|
4df1641689 | ||
|
|
5ef0b96d5c | ||
|
|
d979b5f2b9 | ||
|
|
1a61bdb302 | ||
|
|
e8545dd2bc | ||
|
|
2f2ebb37e4 | ||
|
|
522f0dac16 | ||
|
|
cd5ae944ec | ||
|
|
42a86fe0d4 | ||
|
|
e928b9c434 | ||
|
|
fb55d9e9d1 | ||
|
|
74e7e5a291 | ||
|
|
6c58b8cc4b | ||
|
|
2b3d11a7a5 | ||
|
|
9950208d06 | ||
|
|
a67e16d1b7 | ||
|
|
c98a22e065 | ||
|
|
8f9d1bcfe0 | ||
|
|
a7a2fd1d5b | ||
|
|
d496417deb | ||
|
|
f6e30edbc4 | ||
|
|
45ed68006f | ||
|
|
dcb0d8b00e | ||
|
|
116bb6e1b9 | ||
|
|
9c6dde5d22 | ||
|
|
cb635dcd5a | ||
|
|
eff3330e75 | ||
|
|
fc8a13df5a | ||
|
|
85e9042db6 | ||
|
|
62e6d6fb4c | ||
|
|
16c72e8efd | ||
|
|
efe1b9f5a9 | ||
|
|
65dbddb6b6 | ||
|
|
2a67a31120 | ||
|
|
a720c3348b | ||
|
|
79ac60b6e8 | ||
|
|
e01c2d222a | ||
|
|
8922e2050a | ||
|
|
a85891c611 | ||
|
|
2586fdffda | ||
|
|
c9dc9ec11e | ||
|
|
b9332142c9 | ||
|
|
b7b53362e1 | ||
|
|
68cf66a5fe | ||
|
|
4eb7eeb40f | ||
|
|
a1d64fa215 | ||
|
|
5f44c0e678 | ||
|
|
b87c4d786d | ||
|
|
329b0df3f6 | ||
|
|
6906c15e8e | ||
|
|
ff5421cefc | ||
|
|
4498e992a2 | ||
|
|
2d610dfdc8 | ||
|
|
2b0a05ee32 | ||
|
|
bc2d0ee9a8 | ||
|
|
862a2a1440 | ||
|
|
fba60ffff1 | ||
|
|
d1a945a305 | ||
|
|
685b9ab102 | ||
|
|
24f27c0fe3 | ||
|
|
3019c82d8a | ||
|
|
f9d0b55ca3 | ||
|
|
b54cc97e4c | ||
|
|
e3b9d5abbf | ||
|
|
2332970cf2 | ||
|
|
48af3d3322 | ||
|
|
429c94d6a9 | ||
|
|
deb52ad350 | ||
|
|
6f3cc77bb5 | ||
|
|
1baf139ca4 | ||
|
|
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 | ||
|
|
d1e12cd46f | ||
|
|
adf6d49fd1 | ||
|
|
0826a44d4b | ||
|
|
ce145b04ac | ||
|
|
29d775e675 | ||
|
|
251a47db8c | ||
|
|
e4b3a191d6 | ||
|
|
393d702e77 | ||
|
|
4efd41f1ba | ||
|
|
a3597d6deb | ||
|
|
8fada4d015 | ||
|
|
5d11ab269d | ||
|
|
6d59f55fd4 | ||
|
|
7a60e4b458 | ||
|
|
937dce8e65 | ||
|
|
5c1562f36b | ||
|
|
823c13707d | ||
|
|
8eb59072b4 | ||
|
|
20d053807d | ||
|
|
8aa4396e44 | ||
|
|
8f9100c762 | ||
|
|
788a5b73f9 | ||
|
|
83a73ac9b7 | ||
|
|
5ed27bf5f6 | ||
|
|
ccb0eeefbd | ||
|
|
62bdaab7c7 | ||
|
|
9851a2a057 | ||
|
|
bb2bc64d15 | ||
|
|
baa1eaf73a | ||
|
|
4c7da3899e | ||
|
|
4d3593ac86 | ||
|
|
c49a8e9114 | ||
|
|
a269be485f | ||
|
|
d3bd9cd31d | ||
|
|
e694e4a791 | ||
|
|
e2de82ac8d | ||
|
|
e3b14bc5a9 | ||
|
|
b1439df72a | ||
|
|
8e46aa7054 | ||
|
|
8a22597180 | ||
|
|
e919906c8c | ||
|
|
46dc6eac88 | ||
|
|
ea8c40c7b6 | ||
|
|
b9bf30e095 | ||
|
|
b60c51ef13 | ||
|
|
ae5ce17035 | ||
|
|
6b05938573 | ||
|
|
f125c11a81 | ||
|
|
77601f7262 | ||
|
|
bf2f2782b7 | ||
|
|
d9be51b2ef | ||
|
|
694673b9bd | ||
|
|
393259c24b | ||
|
|
306bdd8021 | ||
|
|
756c4c032f | ||
|
|
a9685a00b3 | ||
|
|
e1360ae049 | ||
|
|
af45b51f4f | ||
|
|
566ec8a5f9 | ||
|
|
b048f8d733 | ||
|
|
dca8516cec | ||
|
|
f2c99798a6 | ||
|
|
f65f88325b | ||
|
|
42c8f6ae87 | ||
|
|
c2161ca56b | ||
|
|
d5b3a52827 | ||
|
|
e4f3b777f9 | ||
|
|
a278f79bdb | ||
|
|
606c369173 | ||
|
|
3c871920b1 | ||
|
|
de6170a13d | ||
|
|
1a270dc05c | ||
|
|
bd9bf3f1c7 | ||
|
|
65931bb384 | ||
|
|
a5d53b3349 | ||
|
|
4487a90623 | ||
|
|
05da8cc3d1 | ||
|
|
23e499998f | ||
|
|
c156a4f723 | ||
|
|
3657bf6636 | ||
|
|
adade2e5ec | ||
|
|
c16913c999 | ||
|
|
12b61365ad | ||
|
|
9214dc6610 | ||
|
|
58b95e4ae4 | ||
|
|
debf909359 | ||
|
|
0d4d84b2fa | ||
|
|
d2aca3e7e0 | ||
|
|
26aca2b789 | ||
|
|
5424ede413 | ||
|
|
20112f8e16 | ||
|
|
4260a754e5 | ||
|
|
ce30f72738 | ||
|
|
69d430dd75 | ||
|
|
673545c7e4 | ||
|
|
4f69a03bb1 | ||
|
|
c2c18acd6a | ||
|
|
12ead36f96 | ||
|
|
41a642e06c | ||
|
|
c6ed4b8a1d | ||
|
|
65aa6573df |
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,2 +1,14 @@
|
|||||||
*.pyc
|
*.py[co]
|
||||||
build/
|
build/
|
||||||
|
dist/
|
||||||
|
MANIFEST
|
||||||
|
docs/_build/
|
||||||
|
*.swp
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
sleekxmpp.egg-info/
|
||||||
|
.ropeproject/
|
||||||
|
4913
|
||||||
|
*~
|
||||||
|
.baboon/
|
||||||
|
.DS_STORE
|
||||||
|
|||||||
175
LICENSE
175
LICENSE
@@ -17,3 +17,178 @@ 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 2004-2013 David Alan Cridland
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
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.
|
||||||
|
|
||||||
|
socksipy: A Python SOCKS client module.
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Copyright 2006 Dan-Haim. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||||
|
to endorse or promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||||
|
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||||
|
|||||||
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
|
||||||
47
README
47
README
@@ -1,47 +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
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
We try to keep requirements to a minimum, but we suggest that you install http://dnspython.org although it isn't strictly required.
|
|
||||||
If you do not install this library, you may need to specify the server/port for services that use SRV records (like GTalk).
|
|
||||||
"sudo pip install dnspython" on a *nix system with pip installed.
|
|
||||||
|
|
||||||
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.
|
|
||||||
179
README.rst
Normal file
179
README.rst
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
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.3.0 <http://github.com/fritzy/SleekXMPP/zipball/1.3.0>`_
|
||||||
|
|
||||||
|
**Develop Releases**
|
||||||
|
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
||||||
|
|
||||||
|
|
||||||
|
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,350 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import sleekxmpp
|
|
||||||
from optparse import OptionParser
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
import Queue
|
|
||||||
import thread
|
|
||||||
|
|
||||||
|
|
||||||
class testps(sleekxmpp.ClientXMPP):
|
|
||||||
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], nodenum=0, pshost=None):
|
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password, ssl, plugin_config, plugin_whitelist)
|
|
||||||
self.registerPlugin('xep_0004')
|
|
||||||
self.registerPlugin('xep_0030')
|
|
||||||
self.registerPlugin('xep_0060')
|
|
||||||
self.registerPlugin('xep_0092')
|
|
||||||
self.add_handler("<message xmlns='jabber:client'><event xmlns='http://jabber.org/protocol/pubsub#event' /></message>", self.pubsubEventHandler, name='Pubsub Event', threaded=True)
|
|
||||||
self.add_event_handler("session_start", self.start, threaded=True)
|
|
||||||
self.add_handler("<iq type='error' />", self.handleError, name='Iq Error')
|
|
||||||
self.events = Queue.Queue()
|
|
||||||
self.default_config = None
|
|
||||||
self.ps = self.plugin['xep_0060']
|
|
||||||
self.node = "pstestnode_%s"
|
|
||||||
self.pshost = pshost
|
|
||||||
if pshost is None:
|
|
||||||
self.pshost = self.boundjid.host
|
|
||||||
self.nodenum = int(nodenum)
|
|
||||||
self.leafnode = self.nodenum + 1
|
|
||||||
self.collectnode = self.nodenum + 2
|
|
||||||
self.lasterror = ''
|
|
||||||
self.sprintchars = 0
|
|
||||||
self.defaultconfig = None
|
|
||||||
self.tests = ['test_defaultConfig', 'test_createDefaultNode', 'test_getNodes', 'test_deleteNode', 'test_createWithConfig', 'test_reconfigureNode', 'test_subscribeToNode', 'test_addItem', 'test_updateItem', 'test_deleteItem', 'test_unsubscribeNode', 'test_createCollection', 'test_subscribeCollection', 'test_addNodeCollection', 'test_deleteNodeCollection', 'test_addCollectionNode', 'test_deleteCollectionNode', 'test_unsubscribeNodeCollection', 'test_deleteCollection']
|
|
||||||
self.passed = 0
|
|
||||||
self.width = 120
|
|
||||||
|
|
||||||
def start(self, event):
|
|
||||||
#TODO: make this configurable
|
|
||||||
self.getRoster()
|
|
||||||
self.sendPresence(ppriority=20)
|
|
||||||
thread.start_new(self.test_all, tuple())
|
|
||||||
|
|
||||||
def sprint(self, msg, end=False, color=False):
|
|
||||||
length = len(msg)
|
|
||||||
if color:
|
|
||||||
if color == "red":
|
|
||||||
color = "1;31"
|
|
||||||
elif color == "green":
|
|
||||||
color = "0;32"
|
|
||||||
msg = "%s%s%s" % ("\033[%sm" % color, msg, "\033[0m")
|
|
||||||
if not end:
|
|
||||||
sys.stdout.write(msg)
|
|
||||||
self.sprintchars += length
|
|
||||||
else:
|
|
||||||
self.sprint("%s%s" % ("." * (self.width - self.sprintchars - length), msg))
|
|
||||||
print('')
|
|
||||||
self.sprintchars = 0
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
def pubsubEventHandler(self, xml):
|
|
||||||
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}item'):
|
|
||||||
self.events.put(item.get('id', '__unknown__'))
|
|
||||||
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}items/{http://jabber.org/protocol/pubsub#event}retract'):
|
|
||||||
self.events.put(item.get('id', '__unknown__'))
|
|
||||||
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}disassociate'):
|
|
||||||
self.events.put(item.get('node', '__unknown__'))
|
|
||||||
for item in xml.findall('{http://jabber.org/protocol/pubsub#event}event/{http://jabber.org/protocol/pubsub#event}collection/{http://jabber.org/protocol/pubsub#event}associate'):
|
|
||||||
self.events.put(item.get('node', '__unknown__'))
|
|
||||||
|
|
||||||
def handleError(self, xml):
|
|
||||||
error = xml.find('{jabber:client}error')
|
|
||||||
self.lasterror = error.getchildren()[0].tag.split('}')[-1]
|
|
||||||
|
|
||||||
def test_all(self):
|
|
||||||
print("Running Publish-Subscribe Tests")
|
|
||||||
version = self.plugin['xep_0092'].getVersion(self.pshost)
|
|
||||||
if version:
|
|
||||||
print("%s %s on %s" % (version.get('name', 'Unknown Server'), version.get('version', 'v?'), version.get('os', 'Unknown OS')))
|
|
||||||
print("=" * self.width)
|
|
||||||
for test in self.tests:
|
|
||||||
testfunc = getattr(self, test)
|
|
||||||
self.sprint("%s" % testfunc.__doc__)
|
|
||||||
if testfunc():
|
|
||||||
self.sprint("Passed", True, "green")
|
|
||||||
self.passed += 1
|
|
||||||
else:
|
|
||||||
if not self.lasterror:
|
|
||||||
self.lasterror = 'No response'
|
|
||||||
self.sprint("Failed (%s)" % self.lasterror, True, "red")
|
|
||||||
self.lasterror = ''
|
|
||||||
print("=" * self.width)
|
|
||||||
self.sprint("Cleaning up...")
|
|
||||||
#self.ps.deleteNode(self.pshost, self.node % self.nodenum)
|
|
||||||
self.ps.deleteNode(self.pshost, self.node % self.leafnode)
|
|
||||||
#self.ps.deleteNode(self.pshost, self.node % self.collectnode)
|
|
||||||
self.sprint("Done", True, "green")
|
|
||||||
self.disconnect()
|
|
||||||
self.sprint("%s" % self.passed, False, "green")
|
|
||||||
self.sprint("/%s Passed -- " % len(self.tests))
|
|
||||||
if len(self.tests) - self.passed:
|
|
||||||
self.sprint("%s" % (len(self.tests) - self.passed), False, "red")
|
|
||||||
else:
|
|
||||||
self.sprint("%s" % (len(self.tests) - self.passed), False, "green")
|
|
||||||
self.sprint(" Failed Tests")
|
|
||||||
print
|
|
||||||
#print "%s/%s Passed -- %s Failed Tests" % (self.passed, len(self.tests), len(self.tests) - self.passed)
|
|
||||||
|
|
||||||
def test_defaultConfig(self):
|
|
||||||
"Retreiving default configuration"
|
|
||||||
result = self.ps.getNodeConfig(self.pshost)
|
|
||||||
if result is False or result is None:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.defaultconfig = result
|
|
||||||
try:
|
|
||||||
self.defaultconfig.field['pubsub#access_model'].setValue('open')
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
self.defaultconfig.field['pubsub#notify_retract'].setValue(True)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return True
|
|
||||||
|
|
||||||
def test_createDefaultNode(self):
|
|
||||||
"Creating default node"
|
|
||||||
return self.ps.create_node(self.pshost, self.node % self.nodenum)
|
|
||||||
|
|
||||||
def test_getNodes(self):
|
|
||||||
"Getting list of nodes"
|
|
||||||
self.ps.getNodes(self.pshost)
|
|
||||||
self.ps.getItems(self.pshost, 'blog')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def test_deleteNode(self):
|
|
||||||
"Deleting node"
|
|
||||||
return self.ps.deleteNode(self.pshost, self.node % self.nodenum)
|
|
||||||
|
|
||||||
def test_createWithConfig(self):
|
|
||||||
"Creating node with config"
|
|
||||||
if self.defaultconfig is None:
|
|
||||||
self.lasterror = "No Avail Config"
|
|
||||||
return False
|
|
||||||
return self.ps.create_node(self.pshost, self.node % self.leafnode, self.defaultconfig)
|
|
||||||
|
|
||||||
def test_reconfigureNode(self):
|
|
||||||
"Retrieving node config and reconfiguring"
|
|
||||||
nconfig = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
|
|
||||||
if nconfig == False:
|
|
||||||
return False
|
|
||||||
return self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, nconfig)
|
|
||||||
|
|
||||||
def test_subscribeToNode(self):
|
|
||||||
"Subscribing to node"
|
|
||||||
return self.ps.subscribe(self.pshost, self.node % self.leafnode)
|
|
||||||
|
|
||||||
def test_addItem(self):
|
|
||||||
"Adding item, waiting for notification"
|
|
||||||
item = ET.Element('test')
|
|
||||||
result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),))
|
|
||||||
if result == False:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
event = self.events.get(True, 10)
|
|
||||||
except Queue.Empty:
|
|
||||||
return False
|
|
||||||
if event == 'test_node1':
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_updateItem(self):
|
|
||||||
"Updating item, waiting for notification"
|
|
||||||
item = ET.Element('test')
|
|
||||||
item.attrib['crap'] = 'yup, right here'
|
|
||||||
result = self.ps.setItem(self.pshost, self.node % self.leafnode, (('test_node1', item),))
|
|
||||||
if result == False:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
event = self.events.get(True, 10)
|
|
||||||
except Queue.Empty:
|
|
||||||
return False
|
|
||||||
if event == 'test_node1':
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_deleteItem(self):
|
|
||||||
"Deleting item, waiting for notification"
|
|
||||||
result = self.ps.deleteItem(self.pshost, self.node % self.leafnode, 'test_node1')
|
|
||||||
if result == False:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
event = self.events.get(True, 10)
|
|
||||||
except Queue.Empty:
|
|
||||||
self.lasterror = "No Notification"
|
|
||||||
return False
|
|
||||||
if event == 'test_node1':
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_unsubscribeNode(self):
|
|
||||||
"Unsubscribing from node"
|
|
||||||
return self.ps.unsubscribe(self.pshost, self.node % self.leafnode)
|
|
||||||
|
|
||||||
def test_createCollection(self):
|
|
||||||
"Creating collection node"
|
|
||||||
return self.ps.create_node(self.pshost, self.node % self.collectnode, self.defaultconfig, True)
|
|
||||||
|
|
||||||
def test_subscribeCollection(self):
|
|
||||||
"Subscribing to collection node"
|
|
||||||
return self.ps.subscribe(self.pshost, self.node % self.collectnode)
|
|
||||||
|
|
||||||
def test_addNodeCollection(self):
|
|
||||||
"Assigning node to collection, waiting for notification"
|
|
||||||
config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#collection'].setValue(self.node % self.collectnode)
|
|
||||||
except KeyError:
|
|
||||||
self.sprint("...Missing Field...", False, "red")
|
|
||||||
config.addField('pubsub#collection', value=self.node % self.collectnode)
|
|
||||||
if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config):
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
event = self.events.get(True, 10)
|
|
||||||
except Queue.Empty:
|
|
||||||
self.lasterror = "No Notification"
|
|
||||||
return False
|
|
||||||
if event == self.node % self.leafnode:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_deleteNodeCollection(self):
|
|
||||||
"Removing node assignment to collection, waiting for notification"
|
|
||||||
config = self.ps.getNodeConfig(self.pshost, self.node % self.leafnode)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#collection'].delValue(self.node % self.collectnode)
|
|
||||||
except KeyError:
|
|
||||||
self.sprint("...Missing Field...", False, "red")
|
|
||||||
config.addField('pubsub#collection', value='')
|
|
||||||
if not self.ps.setNodeConfig(self.pshost, self.node % self.leafnode, config):
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
event = self.events.get(True, 10)
|
|
||||||
except Queue.Empty:
|
|
||||||
self.lasterror = "No Notification"
|
|
||||||
return False
|
|
||||||
if event == self.node % self.leafnode:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_addCollectionNode(self):
|
|
||||||
"Assigning node from collection, waiting for notification"
|
|
||||||
config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#children'].setValue(self.node % self.leafnode)
|
|
||||||
except KeyError:
|
|
||||||
self.sprint("...Missing Field...", False, "red")
|
|
||||||
config.addField('pubsub#children', value=self.node % self.leafnode)
|
|
||||||
if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config):
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
event = self.events.get(True, 10)
|
|
||||||
except Queue.Empty:
|
|
||||||
self.lasterror = "No Notification"
|
|
||||||
return False
|
|
||||||
if event == self.node % self.leafnode:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_deleteCollectionNode(self):
|
|
||||||
"Removing node from collection, waiting for notification"
|
|
||||||
config = self.ps.getNodeConfig(self.pshost, self.node % self.collectnode)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#children'].delValue(self.node % self.leafnode)
|
|
||||||
except KeyError:
|
|
||||||
self.sprint("...Missing Field...", False, "red")
|
|
||||||
config.addField('pubsub#children', value='')
|
|
||||||
if not self.ps.setNodeConfig(self.pshost, self.node % self.collectnode, config):
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
event = self.events.get(True, 10)
|
|
||||||
except Queue.Empty:
|
|
||||||
self.lasterror = "No Notification"
|
|
||||||
return False
|
|
||||||
if event == self.node % self.leafnode:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def test_unsubscribeNodeCollection(self):
|
|
||||||
"Unsubscribing from collection"
|
|
||||||
return self.ps.unsubscribe(self.pshost, self.node % self.collectnode)
|
|
||||||
|
|
||||||
def test_deleteCollection(self):
|
|
||||||
"Deleting collection"
|
|
||||||
return self.ps.deleteNode(self.pshost, self.node % self.collectnode)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
#parse command line arguements
|
|
||||||
optp = OptionParser()
|
|
||||||
optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO)
|
|
||||||
optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
|
|
||||||
optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO)
|
|
||||||
optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
|
|
||||||
optp.add_option("-n","--nodenum", dest="nodenum", default="1", help="set node number to use")
|
|
||||||
optp.add_option("-p","--pubsub", dest="pubsub", default="1", help="set pubsub host to use")
|
|
||||||
opts,args = optp.parse_args()
|
|
||||||
|
|
||||||
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
#load xml config
|
|
||||||
logging.info("Loading config file: %s" % opts.configfile)
|
|
||||||
config = ET.parse(os.path.expanduser(opts.configfile)).find('auth')
|
|
||||||
|
|
||||||
#init
|
|
||||||
logging.info("Logging in as %s" % config.attrib['jid'])
|
|
||||||
|
|
||||||
|
|
||||||
plugin_config = {}
|
|
||||||
plugin_config['xep_0092'] = {'name': 'SleekXMPP Example', 'version': '0.1-dev'}
|
|
||||||
plugin_config['xep_0199'] = {'keepalive': True, 'timeout': 30, 'frequency': 300}
|
|
||||||
|
|
||||||
con = testps(config.attrib['jid'], config.attrib['pass'], plugin_config=plugin_config, plugin_whitelist=[], nodenum=opts.nodenum, pshost=opts.pubsub)
|
|
||||||
if not config.get('server', None):
|
|
||||||
# we don't know the server, but the lib can probably figure it out
|
|
||||||
con.connect()
|
|
||||||
else:
|
|
||||||
con.connect((config.attrib['server'], 5222))
|
|
||||||
con.process(threaded=False)
|
|
||||||
print("")
|
|
||||||
1
docs/.gitignore
vendored
Normal file
1
docs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
_build/*
|
||||||
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.register_handler(
|
||||||
|
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.register_handler(
|
||||||
|
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()
|
||||||
275
docs/event_index.rst
Normal file
275
docs/event_index.rst
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
Event Index
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
:sorted:
|
||||||
|
|
||||||
|
connected
|
||||||
|
- **Data:** ``{}``
|
||||||
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
|
|
||||||
|
Signal that a connection has been made with the XMPP server, but a session
|
||||||
|
has not yet been established.
|
||||||
|
|
||||||
|
connection_failed
|
||||||
|
- **Data:** ``{}`` or ``Failure Stanza`` if available
|
||||||
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
|
|
||||||
|
Signal that a connection can not be established after number of attempts.
|
||||||
|
|
||||||
|
changed_status
|
||||||
|
- **Data:** :py:class:`~sleekxmpp.Presence`
|
||||||
|
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
|
||||||
|
|
||||||
|
Triggered when a presence stanza is received from a JID with a show type
|
||||||
|
different than the last presence stanza from the same JID.
|
||||||
|
|
||||||
|
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.xmlstream.XMLstream`
|
||||||
|
|
||||||
|
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.roster.item.RosterItem`
|
||||||
|
|
||||||
|
If a presence stanza is received from a JID which was previously marked as
|
||||||
|
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
|
||||||
|
or '``xa``', then this event is triggered as well.
|
||||||
|
|
||||||
|
got_offline
|
||||||
|
- **Data:** :py:class:`~sleekxmpp.Presence`
|
||||||
|
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
muc::[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:`~sleekxmpp.roster.multi.Roster`
|
||||||
|
|
||||||
|
Signal that an initial presence stanza has been written to the XML stream.
|
||||||
|
|
||||||
|
session_end
|
||||||
|
- **Data:** ``{}``
|
||||||
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
|
|
||||||
|
Signal that a connection to the XMPP server has been lost and the current
|
||||||
|
stream session has ended. Currently equivalent to :term:`disconnected`, but
|
||||||
|
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):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
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>`_
|
||||||
203
examples/IoT_TestDevice.py
Executable file
203
examples/IoT_TestDevice.py
Executable file
@@ -0,0 +1,203 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Implementation of xeps for Internet of Things
|
||||||
|
http://wiki.xmpp.org/web/Tech_pages/IoT_systems
|
||||||
|
Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
# This can be used when you are in a test environment and need to make paths right
|
||||||
|
sys.path=['/Users/jocke/Dropbox/06_dev/SleekXMPP']+sys.path
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import unittest
|
||||||
|
import distutils.core
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from glob import glob
|
||||||
|
from os.path import splitext, basename, join as pjoin
|
||||||
|
from optparse import OptionParser
|
||||||
|
from urllib import urlopen
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0323.device import Device
|
||||||
|
|
||||||
|
#from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
class IoT_TestDevice(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple IoT device that can act as server or client
|
||||||
|
"""
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
self.add_event_handler("session_start", self.session_start)
|
||||||
|
self.add_event_handler("message", self.message)
|
||||||
|
self.device=None
|
||||||
|
self.releaseMe=False
|
||||||
|
self.beServer=True
|
||||||
|
self.clientJID=None
|
||||||
|
|
||||||
|
def datacallback(self,from_jid,result,nodeId=None,timestamp=None,fields=None,error_msg=None):
|
||||||
|
"""
|
||||||
|
This method will be called when you ask another IoT device for data with the xep_0323
|
||||||
|
se script below for the registration of the callback
|
||||||
|
"""
|
||||||
|
logging.debug("we got data %s from %s",str(result),from_jid)
|
||||||
|
|
||||||
|
def beClientOrServer(self,server=True,clientJID=None ):
|
||||||
|
if server:
|
||||||
|
self.beServer=True
|
||||||
|
self.clientJID=None
|
||||||
|
else:
|
||||||
|
self.beServer=False
|
||||||
|
self.clientJID=clientJID
|
||||||
|
|
||||||
|
def testForRelease(self):
|
||||||
|
# todo thread safe
|
||||||
|
return self.releaseMe
|
||||||
|
|
||||||
|
def doReleaseMe(self):
|
||||||
|
# todo thread safe
|
||||||
|
self.releaseMe=True
|
||||||
|
|
||||||
|
def addDevice(self, device):
|
||||||
|
self.device=device
|
||||||
|
|
||||||
|
def session_start(self, event):
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
# tell your preffered friend that you are alive
|
||||||
|
self.send_message(mto='jocke@jabber.sust.se', mbody=self.boundjid.bare +' is now online use xep_323 stanza to talk to me')
|
||||||
|
|
||||||
|
if not(self.beServer):
|
||||||
|
session=self['xep_0323'].request_data(self.boundjid.full,self.clientJID,self.datacallback)
|
||||||
|
|
||||||
|
def message(self, msg):
|
||||||
|
if msg['type'] in ('chat', 'normal'):
|
||||||
|
logging.debug("got normal chat message" + str(msg))
|
||||||
|
ip=urlopen('http://icanhazip.com').read()
|
||||||
|
msg.reply("Hi I am " + self.boundjid.full + " and I am on IP " + ip).send()
|
||||||
|
else:
|
||||||
|
logging.debug("got unknown message type %s", str(msg['type']))
|
||||||
|
|
||||||
|
class TheDevice(Device):
|
||||||
|
"""
|
||||||
|
This is the actual device object that you will use to get information from your real hardware
|
||||||
|
You will be called in the refresh method when someone is requesting information from you
|
||||||
|
"""
|
||||||
|
def __init__(self,nodeId):
|
||||||
|
Device.__init__(self,nodeId)
|
||||||
|
self.counter=0
|
||||||
|
|
||||||
|
def refresh(self,fields):
|
||||||
|
"""
|
||||||
|
the implementation of the refresh method
|
||||||
|
"""
|
||||||
|
self._set_momentary_timestamp(self._get_timestamp())
|
||||||
|
self.counter+=self.counter
|
||||||
|
self._add_field_momentary_data(self, "Temperature", self.counter)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
# Setup the command line arguments.
|
||||||
|
#
|
||||||
|
# This script can act both as
|
||||||
|
# "server" an IoT device that can provide sensorinformation
|
||||||
|
# python IoT_TestDevice.py -j "serverjid@yourdomain.com" -p "password" -n "TestIoT" --debug
|
||||||
|
#
|
||||||
|
# "client" an IoT device or other party that would like to get data from another device
|
||||||
|
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
optp.add_option('-t', '--pingto', help='set jid to ping',
|
||||||
|
action='store', type='string', dest='pingjid',
|
||||||
|
default=None)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
# IoT test
|
||||||
|
optp.add_option("-c", "--sensorjid", dest="sensorjid",
|
||||||
|
help="Another device to call for data on", default=None)
|
||||||
|
optp.add_option("-n", "--nodeid", dest="nodeid",
|
||||||
|
help="I am a device get ready to be called", default=None)
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
|
||||||
|
xmpp = IoT_TestDevice(opts.jid,opts.password)
|
||||||
|
xmpp.register_plugin('xep_0030')
|
||||||
|
#xmpp['xep_0030'].add_feature(feature='urn:xmpp:iot:sensordata',
|
||||||
|
# node=None,
|
||||||
|
# jid=None)
|
||||||
|
xmpp.register_plugin('xep_0323')
|
||||||
|
xmpp.register_plugin('xep_0325')
|
||||||
|
|
||||||
|
if opts.nodeid:
|
||||||
|
|
||||||
|
# xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn',
|
||||||
|
# node=opts.nodeid,
|
||||||
|
# jid=xmpp.boundjid.full)
|
||||||
|
|
||||||
|
myDevice = TheDevice(opts.nodeid);
|
||||||
|
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
|
||||||
|
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
|
||||||
|
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
|
||||||
|
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
|
||||||
|
|
||||||
|
xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10);
|
||||||
|
xmpp.beClientOrServer(server=True)
|
||||||
|
while not(xmpp.testForRelease()):
|
||||||
|
xmpp.connect()
|
||||||
|
xmpp.process(block=True)
|
||||||
|
logging.debug("lost connection")
|
||||||
|
if opts.sensorjid:
|
||||||
|
logging.debug("will try to call another device for data")
|
||||||
|
xmpp.beClientOrServer(server=False,clientJID=opts.sensorjid)
|
||||||
|
xmpp.connect()
|
||||||
|
xmpp.process(block=True)
|
||||||
|
logging.debug("ready ending")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print "noopp didn't happen"
|
||||||
|
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import getpass
|
import getpass
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
@@ -22,8 +21,10 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
class CommandBot(sleekxmpp.ClientXMPP):
|
class CommandBot(sleekxmpp.ClientXMPP):
|
||||||
@@ -39,7 +40,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
|
|||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
# and the XML streams are ready for use. We want to
|
# and the XML streams are ready for use. We want to
|
||||||
# listen for this event so that we we can intialize
|
# listen for this event so that we we can initialize
|
||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
|
|||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
Typical actions for the session_start event are
|
||||||
requesting the roster and broadcasting an intial
|
requesting the roster and broadcasting an initial
|
||||||
presence stanza.
|
presence stanza.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -70,7 +71,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
|
|||||||
|
|
||||||
def _handle_command(self, iq, session):
|
def _handle_command(self, iq, session):
|
||||||
"""
|
"""
|
||||||
Respond to the intial request for a command.
|
Respond to the initial request for a command.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
iq -- The iq stanza containing the command request.
|
iq -- The iq stanza containing the command request.
|
||||||
@@ -79,6 +80,7 @@ class CommandBot(sleekxmpp.ClientXMPP):
|
|||||||
here to persist across handler callbacks.
|
here to persist across handler callbacks.
|
||||||
"""
|
"""
|
||||||
form = self['xep_0004'].makeForm('form', 'Greeting')
|
form = self['xep_0004'].makeForm('form', 'Greeting')
|
||||||
|
form['instructions'] = 'Send a custom greeting to a JID'
|
||||||
form.addField(var='greeting',
|
form.addField(var='greeting',
|
||||||
ftype='text-single',
|
ftype='text-single',
|
||||||
label='Your greeting')
|
label='Your greeting')
|
||||||
@@ -123,8 +125,10 @@ class CommandBot(sleekxmpp.ClientXMPP):
|
|||||||
form = payload
|
form = payload
|
||||||
|
|
||||||
greeting = form['values']['greeting']
|
greeting = form['values']['greeting']
|
||||||
|
|
||||||
self.send_message(mto=session['from'],
|
self.send_message(mto=session['from'],
|
||||||
mbody="%s, World!" % greeting)
|
mbody="%s, World!" % greeting,
|
||||||
|
mtype='chat')
|
||||||
|
|
||||||
# Having no return statement is the same as unsetting the 'payload'
|
# Having no return statement is the same as unsetting the 'payload'
|
||||||
# and 'next' session values and returning the session.
|
# and 'next' session values and returning the session.
|
||||||
@@ -176,6 +180,7 @@ if __name__ == '__main__':
|
|||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
xmpp.register_plugin('xep_0004') # Data Forms
|
xmpp.register_plugin('xep_0004') # Data Forms
|
||||||
xmpp.register_plugin('xep_0050') # Adhoc Commands
|
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
|
# If you are working with an OpenFire server, you may need
|
||||||
# to adjust the SSL version used:
|
# to adjust the SSL version used:
|
||||||
@@ -186,14 +191,14 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
if xmpp.connect():
|
if xmpp.connect():
|
||||||
# If you do not have the pydns library installed, you will need
|
# If you do not have the dnspython library installed, you will need
|
||||||
# to manually specify the name of the server if it does not match
|
# 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
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
# need to use:
|
# need to use:
|
||||||
#
|
#
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
# ...
|
# ...
|
||||||
xmpp.process(threaded=False)
|
xmpp.process(block=True)
|
||||||
print("Done")
|
print("Done")
|
||||||
else:
|
else:
|
||||||
print("Unable to connect.")
|
print("Unable to connect.")
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import getpass
|
import getpass
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
@@ -22,8 +21,10 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
class CommandUserBot(sleekxmpp.ClientXMPP):
|
class CommandUserBot(sleekxmpp.ClientXMPP):
|
||||||
@@ -42,7 +43,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP):
|
|||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
# and the XML streams are ready for use. We want to
|
# and the XML streams are ready for use. We want to
|
||||||
# listen for this event so that we we can intialize
|
# listen for this event so that we we can initialize
|
||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
self.add_event_handler("message", self.message)
|
self.add_event_handler("message", self.message)
|
||||||
@@ -52,7 +53,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP):
|
|||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
Typical actions for the session_start event are
|
||||||
requesting the roster and broadcasting an intial
|
requesting the roster and broadcasting an initial
|
||||||
presence stanza.
|
presence stanza.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -136,6 +137,7 @@ class CommandUserBot(sleekxmpp.ClientXMPP):
|
|||||||
# The session will automatically be cleared if no error
|
# The session will automatically be cleared if no error
|
||||||
# handler is provided.
|
# handler is provided.
|
||||||
self['xep_0050'].terminate_command(session)
|
self['xep_0050'].terminate_command(session)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -176,7 +178,7 @@ if __name__ == '__main__':
|
|||||||
if opts.other is None:
|
if opts.other is None:
|
||||||
opts.other = raw_input("JID Providing Commands: ")
|
opts.other = raw_input("JID Providing Commands: ")
|
||||||
if opts.greeting is None:
|
if opts.greeting is None:
|
||||||
opts.other = raw_input("Greeting: ")
|
opts.greeting = raw_input("Greeting: ")
|
||||||
|
|
||||||
# Setup the CommandBot and register plugins. Note that while plugins may
|
# Setup the CommandBot and register plugins. Note that while plugins may
|
||||||
# have interdependencies, the order in which you register them does
|
# have interdependencies, the order in which you register them does
|
||||||
@@ -195,14 +197,14 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
if xmpp.connect():
|
if xmpp.connect():
|
||||||
# If you do not have the pydns library installed, you will need
|
# If you do not have the dnspython library installed, you will need
|
||||||
# to manually specify the name of the server if it does not match
|
# 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
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
# need to use:
|
# need to use:
|
||||||
#
|
#
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
# ...
|
# ...
|
||||||
xmpp.process(threaded=False)
|
xmpp.process(block=True)
|
||||||
print("Done")
|
print("Done")
|
||||||
else:
|
else:
|
||||||
print("Unable to connect.")
|
print("Unable to connect.")
|
||||||
|
|||||||
178
examples/admin_commands.py
Executable file
178
examples/admin_commands.py
Executable file
@@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class AdminCommands(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple SleekXMPP bot that uses admin commands to
|
||||||
|
add a new user to a server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password, command):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
self.command = command
|
||||||
|
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
def command_success(iq, session):
|
||||||
|
print('Command completed')
|
||||||
|
if iq['command']['form']:
|
||||||
|
for var, field in iq['command']['form']['fields'].items():
|
||||||
|
print('%s: %s' % (var, field['value']))
|
||||||
|
if iq['command']['notes']:
|
||||||
|
print('Command Notes:')
|
||||||
|
for note in iq['command']['notes']:
|
||||||
|
print('%s: %s' % note)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
def command_error(iq, session):
|
||||||
|
print('Error completing command')
|
||||||
|
print('%s: %s' % (iq['error']['condition'],
|
||||||
|
iq['error']['text']))
|
||||||
|
self['xep_0050'].terminate_command(session)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
def process_form(iq, session):
|
||||||
|
form = iq['command']['form']
|
||||||
|
answers = {}
|
||||||
|
for var, field in form['fields'].items():
|
||||||
|
if var != 'FORM_TYPE':
|
||||||
|
if field['type'] == 'boolean':
|
||||||
|
answers[var] = raw_input('%s (y/n): ' % field['label'])
|
||||||
|
if answers[var].lower() in ('1', 'true', 'y', 'yes'):
|
||||||
|
answers[var] = '1'
|
||||||
|
else:
|
||||||
|
answers[var] = '0'
|
||||||
|
else:
|
||||||
|
answers[var] = raw_input('%s: ' % field['label'])
|
||||||
|
else:
|
||||||
|
answers['FORM_TYPE'] = field['value']
|
||||||
|
form['type'] = 'submit'
|
||||||
|
form['values'] = answers
|
||||||
|
|
||||||
|
session['next'] = command_success
|
||||||
|
session['payload'] = form
|
||||||
|
|
||||||
|
self['xep_0050'].complete_command(session)
|
||||||
|
|
||||||
|
session = {'next': process_form,
|
||||||
|
'error': command_error}
|
||||||
|
|
||||||
|
command = self.command.replace('-', '_')
|
||||||
|
handler = getattr(self['xep_0133'], command, None)
|
||||||
|
|
||||||
|
if handler:
|
||||||
|
handler(session={
|
||||||
|
'next': process_form,
|
||||||
|
'error': command_error
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
print('Invalid command name: %s' % self.command)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
optp.add_option("-c", "--command", dest="command",
|
||||||
|
help="admin command to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
if opts.command is None:
|
||||||
|
opts.command = raw_input("Admin command: ")
|
||||||
|
|
||||||
|
# Setup the CommandBot and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = AdminCommands(opts.jid, opts.password, opts.command)
|
||||||
|
xmpp.register_plugin('xep_0133') # Service Administration
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<config xmlns="sleekxmpp:config">
|
|
||||||
<jid>component.localhost</jid>
|
|
||||||
<secret>ssshh</secret>
|
|
||||||
<server>localhost</server>
|
|
||||||
<port>8888</port>
|
|
||||||
|
|
||||||
<query xmlns="jabber:iq:roster">
|
|
||||||
<item jid="user@example.com" subscription="both" />
|
|
||||||
</query>
|
|
||||||
</config>
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
#!/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 time
|
|
||||||
from optparse import OptionParser
|
|
||||||
|
|
||||||
import sleekxmpp
|
|
||||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
|
||||||
from sleekxmpp.stanza.roster import Roster
|
|
||||||
from sleekxmpp.xmlstream import ElementBase
|
|
||||||
from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin
|
|
||||||
|
|
||||||
# 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')
|
|
||||||
|
|
||||||
|
|
||||||
class Config(ElementBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
In order to make loading and manipulating an XML config
|
|
||||||
file easier, we will create a custom stanza object for
|
|
||||||
our config XML file contents. See the documentation
|
|
||||||
on stanza objects for more information on how to create
|
|
||||||
and use stanza objects and stanza plugins.
|
|
||||||
|
|
||||||
We will reuse the IQ roster query stanza to store roster
|
|
||||||
information since it already exists.
|
|
||||||
|
|
||||||
Example config XML:
|
|
||||||
<config xmlns="sleekxmpp:config">
|
|
||||||
<jid>component.localhost</jid>
|
|
||||||
<secret>ssshh</secret>
|
|
||||||
<server>localhost</server>
|
|
||||||
<port>8888</port>
|
|
||||||
|
|
||||||
<query xmlns="jabber:iq:roster">
|
|
||||||
<item jid="user@example.com" subscription="both" />
|
|
||||||
</query>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = "config"
|
|
||||||
namespace = "sleekxmpp:config"
|
|
||||||
interfaces = set(('jid', 'secret', 'server', 'port'))
|
|
||||||
sub_interfaces = interfaces
|
|
||||||
|
|
||||||
|
|
||||||
registerStanzaPlugin(Config, Roster)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigComponent(ComponentXMPP):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A simple SleekXMPP component that uses an external XML
|
|
||||||
file to store its configuration data. To make testing
|
|
||||||
that the component works, it will also echo messages sent
|
|
||||||
to it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, config):
|
|
||||||
"""
|
|
||||||
Create a ConfigComponent.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
config -- The XML contents of the config file.
|
|
||||||
config_file -- The XML config file object itself.
|
|
||||||
"""
|
|
||||||
ComponentXMPP.__init__(self, config['jid'],
|
|
||||||
config['secret'],
|
|
||||||
config['server'],
|
|
||||||
config['port'])
|
|
||||||
|
|
||||||
# Store the roster information.
|
|
||||||
self.roster = config['roster']['items']
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
|
||||||
# the component 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
|
|
||||||
# broadcast any needed initial presence stanzas.
|
|
||||||
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.
|
|
||||||
|
|
||||||
The typical action for the session_start event in a component
|
|
||||||
is to broadcast presence stanzas to all subscribers to the
|
|
||||||
component. Note that the component does not have a roster
|
|
||||||
provided by the XMPP server. In this case, we have possibly
|
|
||||||
saved a roster in the component's configuration file.
|
|
||||||
|
|
||||||
Since the component may use any number of JIDs, you should
|
|
||||||
also include the JID that is sending the presence.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
event -- An empty dictionary. The session_start
|
|
||||||
event does not provide any additional
|
|
||||||
data.
|
|
||||||
"""
|
|
||||||
for jid in self.roster:
|
|
||||||
if self.roster[jid]['subscription'] != 'none':
|
|
||||||
self.sendPresence(pfrom=self.jid, pto=jid)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Component name and secret options.
|
|
||||||
optp.add_option("-c", "--config", help="path to config file",
|
|
||||||
dest="config", default="config.xml")
|
|
||||||
|
|
||||||
opts, args = optp.parse_args()
|
|
||||||
|
|
||||||
# Setup logging.
|
|
||||||
logging.basicConfig(level=opts.loglevel,
|
|
||||||
format='%(levelname)-8s %(message)s')
|
|
||||||
|
|
||||||
# Load configuration data.
|
|
||||||
config_file = open(opts.config, 'r+')
|
|
||||||
config_data = "\n".join([line for line in config_file])
|
|
||||||
config = Config(xml=ET.fromstring(config_data))
|
|
||||||
config_file.close()
|
|
||||||
|
|
||||||
# Setup the ConfigComponent and register plugins. Note that while plugins
|
|
||||||
# may have interdependencies, the order in which you register them does
|
|
||||||
# not matter.
|
|
||||||
xmpp = ConfigComponent(config)
|
|
||||||
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(threaded=False)
|
|
||||||
print("Done")
|
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
173
examples/custom_stanzas/custom_stanza_provider.py
Executable file
173
examples/custom_stanzas/custom_stanza_provider.py
Executable file
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
from sleekxmpp import ClientXMPP, Iq
|
||||||
|
from sleekxmpp.exceptions import IqError, IqTimeout, XMPPError
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from stanza import Action
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class ActionBot(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple SleekXMPP bot that receives a custom stanza
|
||||||
|
from another client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use. We want to
|
||||||
|
# listen for this event so that we we can initialize
|
||||||
|
# our roster.
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
self.register_handler(
|
||||||
|
Callback('Some custom iq',
|
||||||
|
StanzaPath('iq@type=set/action'),
|
||||||
|
self._handle_action))
|
||||||
|
|
||||||
|
self.add_event_handler('custom_action',
|
||||||
|
self._handle_action_event,
|
||||||
|
threaded=True)
|
||||||
|
|
||||||
|
register_stanza_plugin(Iq, Action)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
def _handle_action(self, iq):
|
||||||
|
"""
|
||||||
|
Raise an event for the stanza so that it can be processed in its
|
||||||
|
own thread without blocking the main stanza processing loop.
|
||||||
|
"""
|
||||||
|
self.event('custom_action', iq)
|
||||||
|
|
||||||
|
def _handle_action_event(self, iq):
|
||||||
|
"""
|
||||||
|
Respond to the custom action event.
|
||||||
|
|
||||||
|
Since one of the actions is to disconnect, this
|
||||||
|
event handler needs to be run in threaded mode, by
|
||||||
|
using `threaded=True` in the `add_event_handler` call.
|
||||||
|
"""
|
||||||
|
method = iq['action']['method']
|
||||||
|
param = iq['action']['param']
|
||||||
|
|
||||||
|
if method == 'is_prime' and param == '2':
|
||||||
|
print("got message: %s" % iq)
|
||||||
|
iq.reply()
|
||||||
|
iq['action']['status'] = 'done'
|
||||||
|
iq.send()
|
||||||
|
elif method == 'bye':
|
||||||
|
print("got message: %s" % iq)
|
||||||
|
self.disconnect()
|
||||||
|
else:
|
||||||
|
print("got message: %s" % iq)
|
||||||
|
iq.reply()
|
||||||
|
iq['action']['status'] = 'error'
|
||||||
|
iq.send()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
# Setup the CommandBot and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = ActionBot(opts.jid, opts.password)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0004') # Data Forms
|
||||||
|
xmpp.register_plugin('xep_0050') # Adhoc Commands
|
||||||
|
xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency':15})
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
175
examples/custom_stanzas/custom_stanza_user.py
Executable file
175
examples/custom_stanzas/custom_stanza_user.py
Executable file
@@ -0,0 +1,175 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp import Iq
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
|
||||||
|
from stanza import Action
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class ActionUserBot(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple SleekXMPP bot that sends a custom action stanza
|
||||||
|
to another client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password, other):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
self.action_provider = other
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use. We want to
|
||||||
|
# listen for this event so that we we can initialize
|
||||||
|
# our roster.
|
||||||
|
self.add_event_handler("session_start", self.start, threaded=True)
|
||||||
|
self.add_event_handler("message", self.message)
|
||||||
|
|
||||||
|
register_stanza_plugin(Iq, Action)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
self.send_custom_iq()
|
||||||
|
|
||||||
|
def send_custom_iq(self):
|
||||||
|
"""Create and send two custom actions.
|
||||||
|
|
||||||
|
If the first action was successful, then send
|
||||||
|
a shutdown command and then disconnect.
|
||||||
|
"""
|
||||||
|
iq = self.Iq()
|
||||||
|
iq['to'] = self.action_provider
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['action']['method'] = 'is_prime'
|
||||||
|
iq['action']['param'] = '2'
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = iq.send()
|
||||||
|
if resp['action']['status'] == 'done':
|
||||||
|
#sending bye
|
||||||
|
iq2 = self.Iq()
|
||||||
|
iq2['to'] = self.action_provider
|
||||||
|
iq2['type'] = 'set'
|
||||||
|
iq2['action']['method'] = 'bye'
|
||||||
|
iq2.send(block=False)
|
||||||
|
|
||||||
|
# The wait=True delays the disconnect until the queue
|
||||||
|
# of stanzas to be sent becomes empty.
|
||||||
|
self.disconnect(wait=True)
|
||||||
|
except XMPPError:
|
||||||
|
print('There was an error sending the custom action.')
|
||||||
|
|
||||||
|
def message(self, msg):
|
||||||
|
"""
|
||||||
|
Process incoming message stanzas.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
msg -- The received message stanza.
|
||||||
|
"""
|
||||||
|
logging.info(msg['body'])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
optp.add_option("-o", "--other", dest="other",
|
||||||
|
help="JID providing custom stanza")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
if opts.other is None:
|
||||||
|
opts.other = raw_input("JID Providing custom stanza: ")
|
||||||
|
|
||||||
|
# Setup the CommandBot and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = ActionUserBot(opts.jid, opts.password, opts.other)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0004') # Data Forms
|
||||||
|
xmpp.register_plugin('xep_0050') # Adhoc Commands
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
56
examples/custom_stanzas/stanza.py
Normal file
56
examples/custom_stanzas/stanza.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
class Action(ElementBase):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A stanza class for XML content of the form:
|
||||||
|
|
||||||
|
<action xmlns="sleekxmpp:custom:actions">
|
||||||
|
<method>X</method>
|
||||||
|
<param>X</param>
|
||||||
|
<status>X</status>
|
||||||
|
</action>
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The `name` field refers to the basic XML tag name of the
|
||||||
|
#: stanza. Here, the tag name will be 'action'.
|
||||||
|
name = 'action'
|
||||||
|
|
||||||
|
#: The namespace of the main XML tag.
|
||||||
|
namespace = 'sleekxmpp:custom:actions'
|
||||||
|
|
||||||
|
#: The `plugin_attrib` value is the name that can be used
|
||||||
|
#: with a parent stanza to access this stanza. For example
|
||||||
|
#: from an Iq stanza object, accessing:
|
||||||
|
#:
|
||||||
|
#: iq['action']
|
||||||
|
#:
|
||||||
|
#: would reference an Action object, and will even create
|
||||||
|
#: an Action object and append it to the Iq stanza if
|
||||||
|
#: one doesn't already exist.
|
||||||
|
plugin_attrib = 'action'
|
||||||
|
|
||||||
|
#: Stanza objects expose dictionary-like interfaces for
|
||||||
|
#: accessing and manipulating substanzas and other values.
|
||||||
|
#: The set of interfaces defined here are the names of
|
||||||
|
#: these dictionary-like interfaces provided by this stanza
|
||||||
|
#: type. For example, an Action stanza object can use:
|
||||||
|
#:
|
||||||
|
#: action['method'] = 'foo'
|
||||||
|
#: print(action['param'])
|
||||||
|
#: del action['status']
|
||||||
|
#:
|
||||||
|
#: to set, get, or remove its values.
|
||||||
|
interfaces = set(('method', 'param', 'status'))
|
||||||
|
|
||||||
|
#: By default, values in the `interfaces` set are mapped to
|
||||||
|
#: attribute values. This can be changed such that an interface
|
||||||
|
#: maps to a subelement's text value by adding interfaces to
|
||||||
|
#: the sub_interfaces set. For example, here all interfaces
|
||||||
|
#: are marked as sub_interfaces, and so the XML produced will
|
||||||
|
#: look like:
|
||||||
|
#:
|
||||||
|
#: <action xmlns="sleekxmpp:custom:actions">
|
||||||
|
#: <method>foo</method>
|
||||||
|
#: </action>
|
||||||
|
sub_interfaces = interfaces
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import logging
|
import logging
|
||||||
import getpass
|
import getpass
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
import sleekxmpp
|
import sleekxmpp
|
||||||
|
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
|
||||||
# Python versions before 3.0 do not use UTF-8 encoding
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
@@ -23,8 +23,10 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
class Disco(sleekxmpp.ClientXMPP):
|
class Disco(sleekxmpp.ClientXMPP):
|
||||||
@@ -59,16 +61,16 @@ class Disco(sleekxmpp.ClientXMPP):
|
|||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
# and the XML streams are ready for use. We want to
|
# and the XML streams are ready for use. We want to
|
||||||
# listen for this event so that we we can intialize
|
# listen for this event so that we we can initialize
|
||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start, threaded=True)
|
||||||
|
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
Typical actions for the session_start event are
|
||||||
requesting the roster and broadcasting an intial
|
requesting the roster and broadcasting an initial
|
||||||
presence stanza.
|
presence stanza.
|
||||||
|
|
||||||
In this case, we send disco#info and disco#items
|
In this case, we send disco#info and disco#items
|
||||||
@@ -82,50 +84,54 @@ class Disco(sleekxmpp.ClientXMPP):
|
|||||||
self.get_roster()
|
self.get_roster()
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
|
|
||||||
if self.get in self.info_types:
|
try:
|
||||||
# By using block=True, the result stanza will be
|
if self.get in self.info_types:
|
||||||
# returned. Execution will block until the reply is
|
# By using block=True, the result stanza will be
|
||||||
# received. Non-blocking options would be to listen
|
# returned. Execution will block until the reply is
|
||||||
# for the disco_info event, or passing a handler
|
# received. Non-blocking options would be to listen
|
||||||
# function using the callback parameter.
|
# for the disco_info event, or passing a handler
|
||||||
info = self['xep_0030'].get_info(jid=self.target_jid,
|
# function using the callback parameter.
|
||||||
node=self.target_node,
|
info = self['xep_0030'].get_info(jid=self.target_jid,
|
||||||
block=True)
|
node=self.target_node,
|
||||||
if self.get in self.items_types:
|
block=True)
|
||||||
# The same applies from above. Listen for the
|
elif self.get in self.items_types:
|
||||||
# disco_items event or pass a callback function
|
# The same applies from above. Listen for the
|
||||||
# if you need to process a non-blocking request.
|
# disco_items event or pass a callback function
|
||||||
items = self['xep_0030'].get_items(jid=self.target_jid,
|
# if you need to process a non-blocking request.
|
||||||
node=self.target_node,
|
items = self['xep_0030'].get_items(jid=self.target_jid,
|
||||||
block=True)
|
node=self.target_node,
|
||||||
|
block=True)
|
||||||
|
else:
|
||||||
|
logging.error("Invalid disco request type.")
|
||||||
|
return
|
||||||
|
except IqError as e:
|
||||||
|
logging.error("Entity returned an error: %s" % e.iq['error']['condition'])
|
||||||
|
except IqTimeout:
|
||||||
|
logging.error("No response received.")
|
||||||
else:
|
else:
|
||||||
logging.error("Invalid disco request type.")
|
header = 'XMPP Service Discovery: %s' % self.target_jid
|
||||||
self.disconnect()
|
print(header)
|
||||||
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))
|
print('-' * len(header))
|
||||||
|
if self.target_node != '':
|
||||||
|
print('Node: %s' % self.target_node)
|
||||||
|
print('-' * len(header))
|
||||||
|
|
||||||
if self.get in self.identity_types:
|
if self.get in self.identity_types:
|
||||||
print('Identities:')
|
print('Identities:')
|
||||||
for identity in info['disco_info']['identities']:
|
for identity in info['disco_info']['identities']:
|
||||||
print(' - %s' % str(identity))
|
print(' - %s' % str(identity))
|
||||||
|
|
||||||
if self.get in self.feature_types:
|
if self.get in self.feature_types:
|
||||||
print('Features:')
|
print('Features:')
|
||||||
for feature in info['disco_info']['features']:
|
for feature in info['disco_info']['features']:
|
||||||
print(' - %s' % feature)
|
print(' - %s' % feature)
|
||||||
|
|
||||||
if self.get in self.items_types:
|
if self.get in self.items_types:
|
||||||
print('Items:')
|
print('Items:')
|
||||||
for item in items['disco_items']['items']:
|
for item in items['disco_items']['items']:
|
||||||
print(' - %s' % str(item))
|
print(' - %s' % str(item))
|
||||||
|
finally:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -186,13 +192,13 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
if xmpp.connect():
|
if xmpp.connect():
|
||||||
# If you do not have the pydns library installed, you will need
|
# If you do not have the dnspython library installed, you will need
|
||||||
# to manually specify the name of the server if it does not match
|
# 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
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
# need to use:
|
# need to use:
|
||||||
#
|
#
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
# ...
|
# ...
|
||||||
xmpp.process(threaded=False)
|
xmpp.process(block=True)
|
||||||
else:
|
else:
|
||||||
print("Unable to connect.")
|
print("Unable to connect.")
|
||||||
|
|||||||
184
examples/download_avatars.py
Executable file
184
examples/download_avatars.py
Executable file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
import threading
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
FILE_TYPES = {
|
||||||
|
'image/png': 'png',
|
||||||
|
'image/gif': 'gif',
|
||||||
|
'image/jpeg': 'jpg'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarDownloader(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A basic script for downloading the avatars for a user's contacts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
self.add_event_handler("session_start", self.start, threaded=True)
|
||||||
|
self.add_event_handler("changed_status", self.wait_for_presences)
|
||||||
|
|
||||||
|
self.add_event_handler('vcard_avatar_update', self.on_vcard_avatar)
|
||||||
|
self.add_event_handler('avatar_metadata_publish', self.on_avatar)
|
||||||
|
|
||||||
|
self.received = set()
|
||||||
|
self.presences_received = threading.Event()
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
print('Waiting for presence updates...\n')
|
||||||
|
self.presences_received.wait(15)
|
||||||
|
self.disconnect(wait=True)
|
||||||
|
|
||||||
|
def on_vcard_avatar(self, pres):
|
||||||
|
print("Received vCard avatar update from %s" % pres['from'].bare)
|
||||||
|
try:
|
||||||
|
result = self['xep_0054'].get_vcard(pres['from'], cached=True)
|
||||||
|
except XMPPError:
|
||||||
|
print("Error retrieving avatar for %s" % pres['from'])
|
||||||
|
return
|
||||||
|
avatar = result['vcard_temp']['PHOTO']
|
||||||
|
|
||||||
|
filetype = FILE_TYPES.get(avatar['TYPE'], 'png')
|
||||||
|
filename = 'vcard_avatar_%s_%s.%s' % (
|
||||||
|
pres['from'].bare,
|
||||||
|
pres['vcard_temp_update']['photo'],
|
||||||
|
filetype)
|
||||||
|
with open(filename, 'w+') as img:
|
||||||
|
img.write(avatar['BINVAL'])
|
||||||
|
|
||||||
|
def on_avatar(self, msg):
|
||||||
|
print("Received avatar update from %s" % msg['from'])
|
||||||
|
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
||||||
|
for info in metadata['items']:
|
||||||
|
if not info['url']:
|
||||||
|
try:
|
||||||
|
result = self['xep_0084'].retrieve_avatar(msg['from'], info['id'])
|
||||||
|
except XMPPError:
|
||||||
|
print("Error retrieving avatar for %s" % msg['from'])
|
||||||
|
return
|
||||||
|
|
||||||
|
avatar = result['pubsub']['items']['item']['avatar_data']
|
||||||
|
|
||||||
|
filetype = FILE_TYPES.get(metadata['type'], 'png')
|
||||||
|
filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype)
|
||||||
|
with open(filename, 'w+') as img:
|
||||||
|
img.write(avatar['value'])
|
||||||
|
else:
|
||||||
|
# We could retrieve the avatar via HTTP, etc here instead.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def wait_for_presences(self, pres):
|
||||||
|
"""
|
||||||
|
Wait to receive updates from all roster contacts.
|
||||||
|
"""
|
||||||
|
self.received.add(pres['from'].bare)
|
||||||
|
if len(self.received) >= len(self.client_roster.keys()):
|
||||||
|
self.presences_received.set()
|
||||||
|
else:
|
||||||
|
self.presences_received.clear()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
optp.add_option('-q','--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=logging.ERROR,
|
||||||
|
default=logging.ERROR)
|
||||||
|
optp.add_option('-d','--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=logging.DEBUG,
|
||||||
|
default=logging.ERROR)
|
||||||
|
optp.add_option('-v','--verbose', help='set logging to COMM',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=5,
|
||||||
|
default=logging.ERROR)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
opts,args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
xmpp = AvatarDownloader(opts.jid, opts.password)
|
||||||
|
xmpp.register_plugin('xep_0054')
|
||||||
|
xmpp.register_plugin('xep_0153')
|
||||||
|
xmpp.register_plugin('xep_0084')
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import getpass
|
import getpass
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
@@ -22,8 +21,10 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
class EchoBot(sleekxmpp.ClientXMPP):
|
class EchoBot(sleekxmpp.ClientXMPP):
|
||||||
@@ -39,7 +40,7 @@ class EchoBot(sleekxmpp.ClientXMPP):
|
|||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
# and the XML streams are ready for use. We want to
|
# and the XML streams are ready for use. We want to
|
||||||
# listen for this event so that we we can intialize
|
# listen for this event so that we we can initialize
|
||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ class EchoBot(sleekxmpp.ClientXMPP):
|
|||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
Typical actions for the session_start event are
|
||||||
requesting the roster and broadcasting an intial
|
requesting the roster and broadcasting an initial
|
||||||
presence stanza.
|
presence stanza.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -76,7 +77,8 @@ class EchoBot(sleekxmpp.ClientXMPP):
|
|||||||
for stanza objects and the Message stanza to see
|
for stanza objects and the Message stanza to see
|
||||||
how it may be used.
|
how it may be used.
|
||||||
"""
|
"""
|
||||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
if msg['type'] in ('chat', 'normal'):
|
||||||
|
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -120,6 +122,19 @@ if __name__ == '__main__':
|
|||||||
xmpp.register_plugin('xep_0060') # PubSub
|
xmpp.register_plugin('xep_0060') # PubSub
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
xmpp.register_plugin('xep_0199') # XMPP Ping
|
||||||
|
|
||||||
|
# If you are connecting to Facebook and wish to use the
|
||||||
|
# X-FACEBOOK-PLATFORM authentication mechanism, you will need
|
||||||
|
# your API key and an access token. Then you'll set:
|
||||||
|
# xmpp.credentials['api_key'] = 'THE_API_KEY'
|
||||||
|
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
|
||||||
|
|
||||||
|
# If you are connecting to MSN, then you will need an
|
||||||
|
# access token, and it does not matter what JID you
|
||||||
|
# specify other than that the domain is 'messenger.live.com',
|
||||||
|
# so '_@messenger.live.com' will work. You can specify
|
||||||
|
# the access token as so:
|
||||||
|
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
# If you are working with an OpenFire server, you may need
|
||||||
# to adjust the SSL version used:
|
# to adjust the SSL version used:
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
@@ -129,14 +144,14 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
if xmpp.connect():
|
if xmpp.connect():
|
||||||
# If you do not have the pydns library installed, you will need
|
# If you do not have the dnspython library installed, you will need
|
||||||
# to manually specify the name of the server if it does not match
|
# 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
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
# need to use:
|
# need to use:
|
||||||
#
|
#
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
# ...
|
# ...
|
||||||
xmpp.process(threaded=False)
|
xmpp.process(block=True)
|
||||||
print("Done")
|
print("Done")
|
||||||
else:
|
else:
|
||||||
print("Unable to connect.")
|
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):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
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.")
|
||||||
165
examples/gtalk_custom_domain.py
Executable file
165
examples/gtalk_custom_domain.py
Executable file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
import ssl
|
||||||
|
from sleekxmpp.xmlstream import cert
|
||||||
|
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class GTalkBot(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A demonstration of using SleekXMPP with accounts from a Google Apps
|
||||||
|
account with a custom domain, because it requires custom certificate
|
||||||
|
validation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use. We want to
|
||||||
|
# listen for this event so that we we can initialize
|
||||||
|
# our roster.
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
# The message event is triggered whenever a message
|
||||||
|
# stanza is received. Be aware that that includes
|
||||||
|
# MUC messages and error messages.
|
||||||
|
self.add_event_handler("message", self.message)
|
||||||
|
|
||||||
|
# Using a Google Apps custom domain, the certificate
|
||||||
|
# does not contain the custom domain, just the GTalk
|
||||||
|
# server name. So we will need to process invalid
|
||||||
|
# certifcates ourselves and check that it really
|
||||||
|
# is from Google.
|
||||||
|
self.add_event_handler("ssl_invalid_cert", self.invalid_cert)
|
||||||
|
|
||||||
|
def invalid_cert(self, pem_cert):
|
||||||
|
der_cert = ssl.PEM_cert_to_DER_cert(pem_cert)
|
||||||
|
try:
|
||||||
|
cert.verify('talk.google.com', der_cert)
|
||||||
|
logging.debug("CERT: Found GTalk certificate")
|
||||||
|
except cert.CertificateError as err:
|
||||||
|
log.error(err.message)
|
||||||
|
self.disconnect(send_close=False)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
def message(self, msg):
|
||||||
|
"""
|
||||||
|
Process incoming message stanzas. Be aware that this also
|
||||||
|
includes MUC messages and error messages. It is usually
|
||||||
|
a good idea to check the messages's type before processing
|
||||||
|
or sending replies.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
msg -- The received message stanza. See the documentation
|
||||||
|
for stanza objects and the Message stanza to see
|
||||||
|
how it may be used.
|
||||||
|
"""
|
||||||
|
if msg['type'] in ('chat', 'normal'):
|
||||||
|
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
# Setup the GTalkBot and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = GTalkBot(opts.jid, opts.password)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0004') # Data Forms
|
||||||
|
xmpp.register_plugin('xep_0060') # PubSub
|
||||||
|
xmpp.register_plugin('xep_0199') # XMPP Ping
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
147
examples/ibb_transfer/ibb_receiver.py
Executable file
147
examples/ibb_transfer/ibb_receiver.py
Executable file
@@ -0,0 +1,147 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class IBBReceiver(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A basic example of creating and using an in-band bytestream.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
self.register_plugin('xep_0030') # Service Discovery
|
||||||
|
self.register_plugin('xep_0047', {
|
||||||
|
'auto_accept': True
|
||||||
|
}) # In-band Bytestreams
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use. We want to
|
||||||
|
# listen for this event so that we we can initialize
|
||||||
|
# our roster.
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
self.add_event_handler("ibb_stream_start", self.stream_opened, threaded=True)
|
||||||
|
self.add_event_handler("ibb_stream_data", self.stream_data)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
def accept_stream(self, iq):
|
||||||
|
"""
|
||||||
|
Check that it is ok to accept a stream request.
|
||||||
|
|
||||||
|
Controlling stream acceptance can be done via either:
|
||||||
|
- setting 'auto_accept' to False in the plugin
|
||||||
|
configuration. The default is True.
|
||||||
|
- setting 'accept_stream' to a function which accepts
|
||||||
|
an Iq stanza as its argument, like this one.
|
||||||
|
|
||||||
|
The accept_stream function will be used if it exists, and the
|
||||||
|
auto_accept value will be used otherwise.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stream_opened(self, stream):
|
||||||
|
print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid))
|
||||||
|
|
||||||
|
# You could run a loop reading from the stream using stream.recv(),
|
||||||
|
# or use the ibb_stream_data event.
|
||||||
|
|
||||||
|
def stream_data(self, event):
|
||||||
|
print(event['data'])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
xmpp = IBBReceiver(opts.jid, opts.password)
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
145
examples/ibb_transfer/ibb_sender.py
Executable file
145
examples/ibb_transfer/ibb_sender.py
Executable file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class IBBSender(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A basic example of creating and using an in-band bytestream.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password, receiver, filename):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
self.receiver = receiver
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use. We want to
|
||||||
|
# listen for this event so that we we can initialize
|
||||||
|
# our roster.
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
# For the purpose of demonstration, we'll set a very small block
|
||||||
|
# size. The default block size is 4096. We'll also use a window
|
||||||
|
# allowing sending multiple blocks at a time; in this case, three
|
||||||
|
# block transfers may be in progress at any time.
|
||||||
|
stream = self['xep_0047'].open_stream(self.receiver)
|
||||||
|
|
||||||
|
with open(self.filename) as f:
|
||||||
|
data = f.read()
|
||||||
|
stream.sendall(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
optp.add_option("-r", "--receiver", dest="receiver",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-f", "--file", dest="filename",
|
||||||
|
help="JID to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
if opts.receiver is None:
|
||||||
|
opts.receiver = raw_input("Receiver: ")
|
||||||
|
if opts.filename is None:
|
||||||
|
opts.filename = raw_input("File path: ")
|
||||||
|
|
||||||
|
# Setup the EchoBot and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = IBBSender(opts.jid, opts.password, opts.receiver, opts.filename)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0004') # Data Forms
|
||||||
|
xmpp.register_plugin('xep_0047') # In-band Bytestreams
|
||||||
|
xmpp.register_plugin('xep_0060') # PubSub
|
||||||
|
xmpp.register_plugin('xep_0199') # XMPP Ping
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
120
examples/migrate_roster.py
Executable file
120
examples/migrate_roster.py
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("--oldjid", dest="old_jid",
|
||||||
|
help="JID of the old account")
|
||||||
|
optp.add_option("--oldpassword", dest="old_password",
|
||||||
|
help="password of the old account")
|
||||||
|
|
||||||
|
optp.add_option("--newjid", dest="new_jid",
|
||||||
|
help="JID of the old account")
|
||||||
|
optp.add_option("--newpassword", dest="new_password",
|
||||||
|
help="password of the old account")
|
||||||
|
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.old_jid is None:
|
||||||
|
opts.old_jid = raw_input("Old JID: ")
|
||||||
|
if opts.old_password is None:
|
||||||
|
opts.old_password = getpass.getpass("Old Password: ")
|
||||||
|
|
||||||
|
if opts.new_jid is None:
|
||||||
|
opts.new_jid = raw_input("New JID: ")
|
||||||
|
if opts.new_password is None:
|
||||||
|
opts.new_password = getpass.getpass("New Password: ")
|
||||||
|
|
||||||
|
|
||||||
|
old_xmpp = sleekxmpp.ClientXMPP(opts.old_jid, opts.old_password)
|
||||||
|
|
||||||
|
# If you are connecting to Facebook and wish to use the
|
||||||
|
# X-FACEBOOK-PLATFORM authentication mechanism, you will need
|
||||||
|
# your API key and an access token. Then you'll set:
|
||||||
|
# xmpp.credentials['api_key'] = 'THE_API_KEY'
|
||||||
|
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
|
||||||
|
|
||||||
|
# If you are connecting to MSN, then you will need an
|
||||||
|
# access token, and it does not matter what JID you
|
||||||
|
# specify other than that the domain is 'messenger.live.com',
|
||||||
|
# so '_@messenger.live.com' will work. You can specify
|
||||||
|
# the access token as so:
|
||||||
|
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
roster = []
|
||||||
|
|
||||||
|
def on_session(event):
|
||||||
|
roster.append(old_xmpp.get_roster())
|
||||||
|
old_xmpp.disconnect()
|
||||||
|
old_xmpp.add_event_handler('session_start', on_session)
|
||||||
|
|
||||||
|
if old_xmpp.connect():
|
||||||
|
old_xmpp.process(block=True)
|
||||||
|
|
||||||
|
if not roster:
|
||||||
|
print('No roster to migrate')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
new_xmpp = sleekxmpp.ClientXMPP(opts.new_jid, opts.new_password)
|
||||||
|
def on_session2(event):
|
||||||
|
new_xmpp.get_roster()
|
||||||
|
new_xmpp.send_presence()
|
||||||
|
|
||||||
|
logging.info(roster[0])
|
||||||
|
data = roster[0]['roster']['items']
|
||||||
|
logging.info(data)
|
||||||
|
|
||||||
|
for jid, item in data.items():
|
||||||
|
if item['subscription'] != 'none':
|
||||||
|
new_xmpp.send_presence(ptype='subscribe', pto=jid)
|
||||||
|
new_xmpp.update_roster(jid,
|
||||||
|
name = item['name'],
|
||||||
|
groups = item['groups'])
|
||||||
|
new_xmpp.disconnect()
|
||||||
|
new_xmpp.add_event_handler('session_start', on_session2)
|
||||||
|
|
||||||
|
if new_xmpp.connect():
|
||||||
|
new_xmpp.process(block=True)
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import time
|
import getpass
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
import sleekxmpp
|
import sleekxmpp
|
||||||
@@ -21,8 +21,10 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
class MUCBot(sleekxmpp.ClientXMPP):
|
class MUCBot(sleekxmpp.ClientXMPP):
|
||||||
@@ -42,7 +44,7 @@ class MUCBot(sleekxmpp.ClientXMPP):
|
|||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
# and the XML streams are ready for use. We want to
|
# and the XML streams are ready for use. We want to
|
||||||
# listen for this event so that we we can intialize
|
# listen for this event so that we we can initialize
|
||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
@@ -59,14 +61,14 @@ class MUCBot(sleekxmpp.ClientXMPP):
|
|||||||
# muc::room@server::got_online, or muc::room@server::got_offline.
|
# muc::room@server::got_online, or muc::room@server::got_offline.
|
||||||
self.add_event_handler("muc::%s::got_online" % self.room,
|
self.add_event_handler("muc::%s::got_online" % self.room,
|
||||||
self.muc_online)
|
self.muc_online)
|
||||||
|
|
||||||
|
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
Typical actions for the session_start event are
|
||||||
requesting the roster and broadcasting an intial
|
requesting the roster and broadcasting an initial
|
||||||
presence stanza.
|
presence stanza.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -74,17 +76,17 @@ class MUCBot(sleekxmpp.ClientXMPP):
|
|||||||
event does not provide any additional
|
event does not provide any additional
|
||||||
data.
|
data.
|
||||||
"""
|
"""
|
||||||
self.getRoster()
|
self.get_roster()
|
||||||
self.sendPresence()
|
self.send_presence()
|
||||||
self.plugin['xep_0045'].joinMUC(self.room,
|
self.plugin['xep_0045'].joinMUC(self.room,
|
||||||
self.nick,
|
self.nick,
|
||||||
# If a room password is needed, use:
|
# If a room password is needed, use:
|
||||||
# password=the_room_password,
|
# password=the_room_password,
|
||||||
wait=True)
|
wait=True)
|
||||||
|
|
||||||
def muc_message(self, msg):
|
def muc_message(self, msg):
|
||||||
"""
|
"""
|
||||||
Process incoming message stanzas from any chat room. Be aware
|
Process incoming message stanzas from any chat room. Be aware
|
||||||
that if you also have any handlers for the 'message' event,
|
that if you also have any handlers for the 'message' event,
|
||||||
message stanzas may be processed by both handlers, so check
|
message stanzas may be processed by both handlers, so check
|
||||||
the 'type' attribute when using a 'message' event handler.
|
the 'type' attribute when using a 'message' event handler.
|
||||||
@@ -96,7 +98,7 @@ class MUCBot(sleekxmpp.ClientXMPP):
|
|||||||
otherwise you will create an infinite loop responding
|
otherwise you will create an infinite loop responding
|
||||||
to your own messages.
|
to your own messages.
|
||||||
|
|
||||||
This handler will reply to messages that mention
|
This handler will reply to messages that mention
|
||||||
the bot's nickname.
|
the bot's nickname.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -112,12 +114,12 @@ class MUCBot(sleekxmpp.ClientXMPP):
|
|||||||
def muc_online(self, presence):
|
def muc_online(self, presence):
|
||||||
"""
|
"""
|
||||||
Process a presence stanza from a chat room. In this case,
|
Process a presence stanza from a chat room. In this case,
|
||||||
presences from users that have just come online are
|
presences from users that have just come online are
|
||||||
handled by sending a welcome message that includes
|
handled by sending a welcome message that includes
|
||||||
the user's nickname and role in the room.
|
the user's nickname and role in the room.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
presence -- The received presence stanza. See the
|
presence -- The received presence stanza. See the
|
||||||
documentation for the Presence stanza
|
documentation for the Presence stanza
|
||||||
to see how else it may be used.
|
to see how else it may be used.
|
||||||
"""
|
"""
|
||||||
@@ -159,9 +161,14 @@ if __name__ == '__main__':
|
|||||||
logging.basicConfig(level=opts.loglevel,
|
logging.basicConfig(level=opts.loglevel,
|
||||||
format='%(levelname)-8s %(message)s')
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
if None in [opts.jid, opts.password, opts.room, opts.nick]:
|
if opts.jid is None:
|
||||||
optp.print_help()
|
opts.jid = raw_input("Username: ")
|
||||||
sys.exit(1)
|
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
|
# Setup the MUCBot and register plugins. Note that while plugins may
|
||||||
# have interdependencies, the order in which you register them does
|
# have interdependencies, the order in which you register them does
|
||||||
@@ -173,14 +180,14 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
if xmpp.connect():
|
if xmpp.connect():
|
||||||
# If you do not have the pydns library installed, you will need
|
# If you do not have the dnspython library installed, you will need
|
||||||
# to manually specify the name of the server if it does not match
|
# 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
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
# need to use:
|
# need to use:
|
||||||
#
|
#
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
# ...
|
# ...
|
||||||
xmpp.process(threaded=False)
|
xmpp.process(block=True)
|
||||||
print("Done")
|
print("Done")
|
||||||
else:
|
else:
|
||||||
print("Unable to connect.")
|
print("Unable to connect.")
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
import getpass
|
import getpass
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
@@ -22,8 +21,10 @@ import sleekxmpp
|
|||||||
# throughout SleekXMPP, we will set the default encoding
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
# ourselves to UTF-8.
|
# ourselves to UTF-8.
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
reload(sys)
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
sys.setdefaultencoding('utf8')
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
class PingTest(sleekxmpp.ClientXMPP):
|
class PingTest(sleekxmpp.ClientXMPP):
|
||||||
@@ -36,22 +37,22 @@ class PingTest(sleekxmpp.ClientXMPP):
|
|||||||
def __init__(self, jid, password, pingjid):
|
def __init__(self, jid, password, pingjid):
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
if pingjid is None:
|
if pingjid is None:
|
||||||
pingjid = self.jid
|
pingjid = self.boundjid.bare
|
||||||
self.pingjid = pingjid
|
self.pingjid = pingjid
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
# and the XML streams are ready for use. We want to
|
# and the XML streams are ready for use. We want to
|
||||||
# listen for this event so that we we can intialize
|
# listen for this event so that we we can initialize
|
||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start, threaded=True)
|
||||||
|
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
Typical actions for the session_start event are
|
Typical actions for the session_start event are
|
||||||
requesting the roster and broadcasting an intial
|
requesting the roster and broadcasting an initial
|
||||||
presence stanza.
|
presence stanza.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -61,16 +62,18 @@ class PingTest(sleekxmpp.ClientXMPP):
|
|||||||
"""
|
"""
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
result = self['xep_0199'].send_ping(self.pingjid,
|
|
||||||
timeout=10,
|
try:
|
||||||
errorfalse=True)
|
rtt = self['xep_0199'].ping(self.pingjid,
|
||||||
logging.info("Pinging...")
|
timeout=10)
|
||||||
if result is False:
|
logging.info("Success! RTT: %s", rtt)
|
||||||
logging.info("Couldn't ping.")
|
except IqError as e:
|
||||||
self.disconnect()
|
logging.info("Error pinging %s: %s",
|
||||||
sys.exit(1)
|
self.pingjid,
|
||||||
else:
|
e.iq['error']['condition'])
|
||||||
logging.info("Success! RTT: %s" % str(result))
|
except IqTimeout:
|
||||||
|
logging.info("No response from %s", self.pingjid)
|
||||||
|
finally:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
@@ -127,14 +130,14 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
if xmpp.connect():
|
if xmpp.connect():
|
||||||
# If you do not have the pydns library installed, you will need
|
# If you do not have the dnspython library installed, you will need
|
||||||
# to manually specify the name of the server if it does not match
|
# 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
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
# need to use:
|
# need to use:
|
||||||
#
|
#
|
||||||
# if xmpp.connect(('talk.google.com', 5222)):
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
# ...
|
# ...
|
||||||
xmpp.process(threaded=False)
|
xmpp.process(block=True)
|
||||||
print("Done")
|
print("Done")
|
||||||
else:
|
else:
|
||||||
print("Unable to connect.")
|
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):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
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.")
|
||||||
201
examples/pubsub_client.py
Executable file
201
examples/pubsub_client.py
Executable file
@@ -0,0 +1,201 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.xmlstream import ET, tostring
|
||||||
|
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class PubsubClient(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
def __init__(self, jid, password, server,
|
||||||
|
node=None, action='list', data=''):
|
||||||
|
super(PubsubClient, self).__init__(jid, password)
|
||||||
|
|
||||||
|
self.register_plugin('xep_0030')
|
||||||
|
self.register_plugin('xep_0059')
|
||||||
|
self.register_plugin('xep_0060')
|
||||||
|
|
||||||
|
self.actions = ['nodes', 'create', 'delete',
|
||||||
|
'publish', 'get', 'retract',
|
||||||
|
'purge', 'subscribe', 'unsubscribe']
|
||||||
|
|
||||||
|
self.action = action
|
||||||
|
self.node = node
|
||||||
|
self.data = data
|
||||||
|
self.pubsub_server = server
|
||||||
|
|
||||||
|
self.add_event_handler('session_start', self.start, threaded=True)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
self.get_roster()
|
||||||
|
self.send_presence()
|
||||||
|
|
||||||
|
try:
|
||||||
|
getattr(self, self.action)()
|
||||||
|
except:
|
||||||
|
logging.error('Could not execute: %s' % self.action)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
def nodes(self):
|
||||||
|
try:
|
||||||
|
result = self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||||
|
for item in result['disco_items']['items']:
|
||||||
|
print(' - %s' % str(item))
|
||||||
|
except:
|
||||||
|
logging.error('Could not retrieve node list.')
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
try:
|
||||||
|
self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||||
|
except:
|
||||||
|
logging.error('Could not create node: %s' % self.node)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
try:
|
||||||
|
self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||||
|
print('Deleted node: %s' % self.node)
|
||||||
|
except:
|
||||||
|
logging.error('Could not delete node: %s' % self.node)
|
||||||
|
|
||||||
|
def publish(self):
|
||||||
|
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
||||||
|
try:
|
||||||
|
result = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||||
|
id = result['pubsub']['publish']['item']['id']
|
||||||
|
print('Published at item id: %s' % id)
|
||||||
|
except:
|
||||||
|
logging.error('Could not publish to: %s' % self.node)
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
try:
|
||||||
|
result = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||||
|
for item in result['pubsub']['items']['substanzas']:
|
||||||
|
print('Retrieved item %s: %s' % (item['id'], tostring(item['payload'])))
|
||||||
|
except:
|
||||||
|
logging.error('Could not retrieve item %s from node %s' % (self.data, self.node))
|
||||||
|
|
||||||
|
def retract(self):
|
||||||
|
try:
|
||||||
|
result = self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||||
|
print('Retracted item %s from node %s' % (self.data, self.node))
|
||||||
|
except:
|
||||||
|
logging.error('Could not retract item %s from node %s' % (self.data, self.node))
|
||||||
|
|
||||||
|
def purge(self):
|
||||||
|
try:
|
||||||
|
result = self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||||
|
print('Purged all items from node %s' % self.node)
|
||||||
|
except:
|
||||||
|
logging.error('Could not purge items from node %s' % self.node)
|
||||||
|
|
||||||
|
def subscribe(self):
|
||||||
|
try:
|
||||||
|
result = self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||||
|
print('Subscribed %s to node %s' % (self.boundjid.bare, self.node))
|
||||||
|
except:
|
||||||
|
logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node))
|
||||||
|
|
||||||
|
def unsubscribe(self):
|
||||||
|
try:
|
||||||
|
result = self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||||
|
print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node))
|
||||||
|
except:
|
||||||
|
logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
optp.version = '%%prog 0.1'
|
||||||
|
optp.usage = "Usage: %%prog [options] <jid> " + \
|
||||||
|
'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \
|
||||||
|
' [<node> <data>]'
|
||||||
|
|
||||||
|
optp.add_option('-q','--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=logging.ERROR,
|
||||||
|
default=logging.ERROR)
|
||||||
|
optp.add_option('-d','--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=logging.DEBUG,
|
||||||
|
default=logging.ERROR)
|
||||||
|
optp.add_option('-v','--verbose', help='set logging to COMM',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=5,
|
||||||
|
default=logging.ERROR)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
opts,args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if len(args) < 2:
|
||||||
|
optp.print_help()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
if len(args) == 2:
|
||||||
|
args = (args[0], args[1], '', '', '')
|
||||||
|
elif len(args) == 3:
|
||||||
|
args = (args[0], args[1], args[2], '', '')
|
||||||
|
elif len(args) == 4:
|
||||||
|
args = (args[0], args[1], args[2], args[3], '')
|
||||||
|
|
||||||
|
|
||||||
|
# Setup the Pubsub client
|
||||||
|
xmpp = PubsubClient(opts.jid, opts.password,
|
||||||
|
server=args[0],
|
||||||
|
node=args[2],
|
||||||
|
action=args[1],
|
||||||
|
data=args[3])
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
154
examples/pubsub_events.py
Executable file
154
examples/pubsub_events.py
Executable file
@@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.xmlstream import ET, tostring
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class PubsubEvents(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
super(PubsubEvents, self).__init__(jid, password)
|
||||||
|
|
||||||
|
self.register_plugin('xep_0030')
|
||||||
|
self.register_plugin('xep_0059')
|
||||||
|
self.register_plugin('xep_0060')
|
||||||
|
|
||||||
|
self.add_event_handler('session_start', self.start)
|
||||||
|
|
||||||
|
# Some services may require configuration to allow
|
||||||
|
# sending delete, configuration, or subscription events.
|
||||||
|
self.add_event_handler('pubsub_publish', self._publish)
|
||||||
|
self.add_event_handler('pubsub_retract', self._retract)
|
||||||
|
self.add_event_handler('pubsub_purge', self._purge)
|
||||||
|
self.add_event_handler('pubsub_delete', self._delete)
|
||||||
|
self.add_event_handler('pubsub_config', self._config)
|
||||||
|
self.add_event_handler('pubsub_subscription', self._subscription)
|
||||||
|
|
||||||
|
# Want to use nicer, more specific pubsub event names?
|
||||||
|
# self['xep_0060'].map_node_event('node_name', 'event_prefix')
|
||||||
|
# self.add_event_handler('event_prefix_publish', handler)
|
||||||
|
# self.add_event_handler('event_prefix_retract', handler)
|
||||||
|
# self.add_event_handler('event_prefix_purge', handler)
|
||||||
|
# self.add_event_handler('event_prefix_delete', handler)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
self.get_roster()
|
||||||
|
self.send_presence()
|
||||||
|
|
||||||
|
def _publish(self, msg):
|
||||||
|
"""Handle receiving a publish item event."""
|
||||||
|
print('Published item %s to %s:' % (
|
||||||
|
msg['pubsub_event']['items']['item']['id'],
|
||||||
|
msg['pubsub_event']['items']['node']))
|
||||||
|
data = msg['pubsub_event']['items']['item']['payload']
|
||||||
|
if data is not None:
|
||||||
|
print(tostring(data))
|
||||||
|
else:
|
||||||
|
print('No item content')
|
||||||
|
|
||||||
|
def _retract(self, msg):
|
||||||
|
"""Handle receiving a retract item event."""
|
||||||
|
print('Retracted item %s from %s' % (
|
||||||
|
msg['pubsub_event']['items']['retract']['id'],
|
||||||
|
msg['pubsub_event']['items']['node']))
|
||||||
|
|
||||||
|
def _purge(self, msg):
|
||||||
|
"""Handle receiving a node purge event."""
|
||||||
|
print('Purged all items from %s' % (
|
||||||
|
msg['pubsub_event']['purge']['node']))
|
||||||
|
|
||||||
|
def _delete(self, msg):
|
||||||
|
"""Handle receiving a node deletion event."""
|
||||||
|
print('Deleted node %s' % (
|
||||||
|
msg['pubsub_event']['delete']['node']))
|
||||||
|
|
||||||
|
def _config(self, msg):
|
||||||
|
"""Handle receiving a node configuration event."""
|
||||||
|
print('Configured node %s:' % (
|
||||||
|
msg['pubsub_event']['configuration']['node']))
|
||||||
|
print(msg['pubsub_event']['configuration']['form'])
|
||||||
|
|
||||||
|
def _subscription(self, msg):
|
||||||
|
"""Handle receiving a node subscription event."""
|
||||||
|
print('Subscription change for node %s:' % (
|
||||||
|
msg['pubsub_event']['subscription']['node']))
|
||||||
|
print(msg['pubsub_event']['subscription'])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
logging.info("Run this in conjunction with the pubsub_client.py " + \
|
||||||
|
"example to watch events happen as you give commands.")
|
||||||
|
|
||||||
|
# Setup the PubsubEvents listener
|
||||||
|
xmpp = PubsubEvents(opts.jid, opts.password)
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
179
examples/register_account.py
Executable file
179
examples/register_account.py
Executable file
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterBot(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A basic bot that will attempt to register an account
|
||||||
|
with an XMPP server.
|
||||||
|
|
||||||
|
NOTE: This follows the very basic registration workflow
|
||||||
|
from XEP-0077. More advanced server registration
|
||||||
|
workflows will need to check for data forms, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use. We want to
|
||||||
|
# listen for this event so that we we can initialize
|
||||||
|
# our roster.
|
||||||
|
self.add_event_handler("session_start", self.start, threaded=True)
|
||||||
|
|
||||||
|
# The register event provides an Iq result stanza with
|
||||||
|
# a registration form from the server. This may include
|
||||||
|
# the basic registration fields, a data form, an
|
||||||
|
# out-of-band URL, or any combination. For more advanced
|
||||||
|
# cases, you will need to examine the fields provided
|
||||||
|
# and respond accordingly. SleekXMPP provides plugins
|
||||||
|
# for data forms and OOB links that will make that easier.
|
||||||
|
self.add_event_handler("register", self.register, threaded=True)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
# We're only concerned about registering, so nothing more to do here.
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
def register(self, iq):
|
||||||
|
"""
|
||||||
|
Fill out and submit a registration form.
|
||||||
|
|
||||||
|
The form may be composed of basic registration fields, a data form,
|
||||||
|
an out-of-band link, or any combination thereof. Data forms and OOB
|
||||||
|
links can be checked for as so:
|
||||||
|
|
||||||
|
if iq.match('iq/register/form'):
|
||||||
|
# do stuff with data form
|
||||||
|
# iq['register']['form']['fields']
|
||||||
|
if iq.match('iq/register/oob'):
|
||||||
|
# do stuff with OOB URL
|
||||||
|
# iq['register']['oob']['url']
|
||||||
|
|
||||||
|
To get the list of basic registration fields, you can use:
|
||||||
|
iq['register']['fields']
|
||||||
|
"""
|
||||||
|
resp = self.Iq()
|
||||||
|
resp['type'] = 'set'
|
||||||
|
resp['register']['username'] = self.boundjid.user
|
||||||
|
resp['register']['password'] = self.password
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp.send(now=True)
|
||||||
|
logging.info("Account created for %s!" % self.boundjid)
|
||||||
|
except IqError as e:
|
||||||
|
logging.error("Could not register account: %s" %
|
||||||
|
e.iq['error']['text'])
|
||||||
|
self.disconnect()
|
||||||
|
except IqTimeout:
|
||||||
|
logging.error("No response from server.")
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
# Setup the RegisterBot and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = RegisterBot(opts.jid, opts.password)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0004') # Data forms
|
||||||
|
xmpp.register_plugin('xep_0066') # Out-of-band Data
|
||||||
|
xmpp.register_plugin('xep_0077') # In-band Registration
|
||||||
|
|
||||||
|
# Some servers don't advertise support for inband registration, even
|
||||||
|
# though they allow it. If this applies to your server, use:
|
||||||
|
xmpp['xep_0077'].force_registration = True
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# 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
Executable file
172
examples/roster_browser.py
Executable 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):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
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.")
|
||||||
|
|
||||||
29
examples/rpc_async.py
Normal file → Executable file
29
examples/rpc_async.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2011 Dann Martens
|
Copyright (C) 2011 Dann Martens
|
||||||
@@ -11,34 +14,34 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
class Boomerang(Endpoint):
|
class Boomerang(Endpoint):
|
||||||
|
|
||||||
def FQN(self):
|
def FQN(self):
|
||||||
return 'boomerang'
|
return 'boomerang'
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def throw(self):
|
def throw(self):
|
||||||
print "Duck!"
|
print "Duck!"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
|
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
|
||||||
|
|
||||||
session.new_handler(ANY_ALL, Boomerang)
|
session.new_handler(ANY_ALL, Boomerang)
|
||||||
|
|
||||||
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
|
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
|
||||||
|
|
||||||
callback = Future()
|
callback = Future()
|
||||||
|
|
||||||
boomerang.async(callback).throw()
|
boomerang.async(callback).throw()
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
31
examples/rpc_client_side.py
Normal file → Executable file
31
examples/rpc_client_side.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2011 Dann Martens
|
Copyright (C) 2011 Dann Martens
|
||||||
@@ -12,18 +15,18 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
class Thermostat(Endpoint):
|
class Thermostat(Endpoint):
|
||||||
|
|
||||||
def FQN(self):
|
def FQN(self):
|
||||||
return 'thermostat'
|
return 'thermostat'
|
||||||
|
|
||||||
def __init(self, initial_temperature):
|
def __init__(self, initial_temperature):
|
||||||
self._temperature = initial_temperature
|
self._temperature = initial_temperature
|
||||||
self._event = threading.Event()
|
self._event = threading.Event()
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def set_temperature(self, temperature):
|
def set_temperature(self, temperature):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def get_temperature(self):
|
def get_temperature(self):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
@@ -31,23 +34,23 @@ class Thermostat(Endpoint):
|
|||||||
@remote(False)
|
@remote(False)
|
||||||
def release(self):
|
def release(self):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
session = Remote.new_session('operator@xmpp.org/rpc', '*****')
|
session = Remote.new_session('operator@xmpp.org/rpc', '*****')
|
||||||
|
|
||||||
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
|
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
|
||||||
|
|
||||||
print("Current temperature is %s" % thermostat.get_temperature())
|
print("Current temperature is %s" % thermostat.get_temperature())
|
||||||
|
|
||||||
thermostat.set_temperature(20)
|
thermostat.set_temperature(20)
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
33
examples/rpc_server_side.py
Normal file → Executable file
33
examples/rpc_server_side.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2011 Dann Martens
|
Copyright (C) 2011 Dann Martens
|
||||||
@@ -11,42 +14,42 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
class Thermostat(Endpoint):
|
class Thermostat(Endpoint):
|
||||||
|
|
||||||
def FQN(self):
|
def FQN(self):
|
||||||
return 'thermostat'
|
return 'thermostat'
|
||||||
|
|
||||||
def __init(self, initial_temperature):
|
def __init__(self, initial_temperature):
|
||||||
self._temperature = initial_temperature
|
self._temperature = initial_temperature
|
||||||
self._event = threading.Event()
|
self._event = threading.Event()
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def set_temperature(self, temperature):
|
def set_temperature(self, temperature):
|
||||||
print("Setting temperature to %s" % temperature)
|
print("Setting temperature to %s" % temperature)
|
||||||
self._temperature = temperature
|
self._temperature = temperature
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def get_temperature(self):
|
def get_temperature(self):
|
||||||
return self._temperature
|
return self._temperature
|
||||||
|
|
||||||
@remote(False)
|
@remote(False)
|
||||||
def release(self):
|
def release(self):
|
||||||
self._event.set()
|
self._event.set()
|
||||||
|
|
||||||
def wait_for_release(self):
|
def wait_for_release(self):
|
||||||
self._event.wait()
|
self._event.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
|
session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
|
||||||
|
|
||||||
thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
|
thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
|
||||||
|
|
||||||
thermostat.wait_for_release()
|
thermostat.wait_for_release()
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
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):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
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.")
|
||||||
174
examples/set_avatar.py
Executable file
174
examples/set_avatar.py
Executable file
@@ -0,0 +1,174 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import imghdr
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
import threading
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarSetter(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A basic script for downloading the avatars for a user's contacts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password, filepath):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
self.add_event_handler("session_start", self.start, threaded=True)
|
||||||
|
|
||||||
|
self.filepath = filepath
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
avatar_file = None
|
||||||
|
try:
|
||||||
|
avatar_file = open(os.path.expanduser(self.filepath))
|
||||||
|
except IOError:
|
||||||
|
print('Could not find file: %s' % self.filepath)
|
||||||
|
return self.disconnect()
|
||||||
|
|
||||||
|
avatar = avatar_file.read()
|
||||||
|
|
||||||
|
avatar_type = 'image/%s' % imghdr.what('', avatar)
|
||||||
|
avatar_id = self['xep_0084'].generate_id(avatar)
|
||||||
|
avatar_bytes = len(avatar)
|
||||||
|
|
||||||
|
avatar_file.close()
|
||||||
|
|
||||||
|
used_xep84 = False
|
||||||
|
try:
|
||||||
|
print('Publish XEP-0084 avatar data')
|
||||||
|
self['xep_0084'].publish_avatar(avatar)
|
||||||
|
used_xep84 = True
|
||||||
|
except XMPPError:
|
||||||
|
print('Could not publish XEP-0084 avatar')
|
||||||
|
|
||||||
|
try:
|
||||||
|
print('Update vCard with avatar')
|
||||||
|
self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||||
|
except XMPPError:
|
||||||
|
print('Could not set vCard avatar')
|
||||||
|
|
||||||
|
if used_xep84:
|
||||||
|
try:
|
||||||
|
print('Advertise XEP-0084 avatar metadata')
|
||||||
|
self['xep_0084'].publish_avatar_metadata([
|
||||||
|
{'id': avatar_id,
|
||||||
|
'type': avatar_type,
|
||||||
|
'bytes': avatar_bytes}
|
||||||
|
# We could advertise multiple avatars to provide
|
||||||
|
# options in image type, source (HTTP vs pubsub),
|
||||||
|
# size, etc.
|
||||||
|
# {'id': ....}
|
||||||
|
])
|
||||||
|
except XMPPError:
|
||||||
|
print('Could not publish XEP-0084 metadata')
|
||||||
|
|
||||||
|
print('Wait for presence updates to propagate...')
|
||||||
|
self.schedule('end', 5, self.disconnect, kwargs={'wait': True})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
optp.add_option('-q','--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=logging.ERROR,
|
||||||
|
default=logging.ERROR)
|
||||||
|
optp.add_option('-d','--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=logging.DEBUG,
|
||||||
|
default=logging.ERROR)
|
||||||
|
optp.add_option('-v','--verbose', help='set logging to COMM',
|
||||||
|
action='store_const',
|
||||||
|
dest='loglevel',
|
||||||
|
const=5,
|
||||||
|
default=logging.ERROR)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
optp.add_option("-f", "--file", dest="filepath",
|
||||||
|
help="path to the avatar file")
|
||||||
|
opts,args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
if opts.filepath is None:
|
||||||
|
opts.filepath = raw_input("Avatar file location: ")
|
||||||
|
|
||||||
|
xmpp = AvatarSetter(opts.jid, opts.password, opts.filepath)
|
||||||
|
xmpp.register_plugin('xep_0054')
|
||||||
|
xmpp.register_plugin('xep_0153')
|
||||||
|
xmpp.register_plugin('xep_0084')
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
if xmpp.connect():
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
247
examples/thirdparty_auth.py
Executable file
247
examples/thirdparty_auth.py
Executable file
@@ -0,0 +1,247 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
try:
|
||||||
|
from httplib import HTTPSConnection
|
||||||
|
from urllib import urlencode
|
||||||
|
except ImportError:
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
from http.client import HTTPSConnection
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.xmlstream import JID
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
class ThirdPartyAuthBot(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple SleekXMPP bot that will echo messages it
|
||||||
|
receives, along with a short thank you message.
|
||||||
|
|
||||||
|
This version uses a thirdpary service for authentication,
|
||||||
|
such as Facebook or Google.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
# The X-GOOGLE-TOKEN mech is ranked lower than PLAIN
|
||||||
|
# due to Google only allowing a single SASL attempt per
|
||||||
|
# connection. So PLAIN will be used for TLS connections,
|
||||||
|
# and X-GOOGLE-TOKEN for non-TLS connections. To use
|
||||||
|
# X-GOOGLE-TOKEN with a TLS connection, explicitly select
|
||||||
|
# it using:
|
||||||
|
#
|
||||||
|
# sleekxmpp.ClientXMPP.__init__(self, jid, password,
|
||||||
|
# sasl_mech="X-GOOGLE-TOKEN")
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use. We want to
|
||||||
|
# listen for this event so that we we can initialize
|
||||||
|
# our roster.
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
# The message event is triggered whenever a message
|
||||||
|
# stanza is received. Be aware that that includes
|
||||||
|
# MUC messages and error messages.
|
||||||
|
self.add_event_handler("message", self.message)
|
||||||
|
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
|
||||||
|
def message(self, msg):
|
||||||
|
"""
|
||||||
|
Process incoming message stanzas. Be aware that this also
|
||||||
|
includes MUC messages and error messages. It is usually
|
||||||
|
a good idea to check the messages's type before processing
|
||||||
|
or sending replies.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
msg -- The received message stanza. See the documentation
|
||||||
|
for stanza objects and the Message stanza to see
|
||||||
|
how it may be used.
|
||||||
|
"""
|
||||||
|
if msg['type'] in ('chat', 'normal'):
|
||||||
|
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
|
||||||
|
access_token = None
|
||||||
|
|
||||||
|
# Since documentation on how to work with Google tokens
|
||||||
|
# can be difficult to find, we'll demo a basic version
|
||||||
|
# here. Note that responses could refer to a Captcha
|
||||||
|
# URL that would require a browser.
|
||||||
|
|
||||||
|
# Using Facebook or MSN's custom authentication requires
|
||||||
|
# a browser, but the process is the same once a token
|
||||||
|
# has been retrieved.
|
||||||
|
|
||||||
|
# Request an access token from Google:
|
||||||
|
try:
|
||||||
|
conn = HTTPSConnection('www.google.com')
|
||||||
|
except:
|
||||||
|
print('Could not connect to Google')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
params = urlencode({
|
||||||
|
'accountType': 'GOOGLE',
|
||||||
|
'service': 'mail',
|
||||||
|
'Email': JID(opts.jid).bare,
|
||||||
|
'Passwd': opts.password
|
||||||
|
})
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
conn.request('POST', '/accounts/ClientLogin', params, headers)
|
||||||
|
resp = conn.getresponse().read()
|
||||||
|
data = {}
|
||||||
|
for line in resp.split():
|
||||||
|
k, v = line.split(b'=', 1)
|
||||||
|
data[k] = v
|
||||||
|
except Exception as e:
|
||||||
|
print('Could not retrieve login data')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if b'SID' not in data:
|
||||||
|
print('Required data not found')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
params = urlencode({
|
||||||
|
'SID': data[b'SID'],
|
||||||
|
'LSID': data[b'LSID'],
|
||||||
|
'service': 'mail'
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
conn.request('POST', '/accounts/IssueAuthToken', params, headers)
|
||||||
|
resp = conn.getresponse()
|
||||||
|
data = resp.read().split()
|
||||||
|
except:
|
||||||
|
print('Could not retrieve auth data')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
print('Could not retrieve token')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
access_token = data[0]
|
||||||
|
|
||||||
|
|
||||||
|
# Setup the ThirdPartyAuthBot and register plugins. Note that while plugins
|
||||||
|
# may have interdependencies, the order in which you register them does not
|
||||||
|
# matter.
|
||||||
|
|
||||||
|
# If using MSN, the JID should be "user@messenger.live.com", which will
|
||||||
|
# be overridden on session bind.
|
||||||
|
|
||||||
|
# We're using an access token instead of a password, so we'll use `''` as
|
||||||
|
# a password argument filler.
|
||||||
|
|
||||||
|
xmpp = ThirdPartyAuthBot(opts.jid, '')
|
||||||
|
xmpp.credentials['access_token'] = access_token
|
||||||
|
|
||||||
|
# The credentials dictionary is used to provide additional authentication
|
||||||
|
# information beyond just a password.
|
||||||
|
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0004') # Data Forms
|
||||||
|
xmpp.register_plugin('xep_0060') # PubSub
|
||||||
|
|
||||||
|
# MSN will kill connections that have been inactive for even
|
||||||
|
# short periods of time. So use pings to keep the session alive;
|
||||||
|
# whitespace keepalives do not work.
|
||||||
|
xmpp.register_plugin('xep_0199', {'keepalive': True, 'frequency': 60})
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
# Google only allows one SASL attempt per connection, so in order to
|
||||||
|
# enable the X-GOOGLE-TOKEN mechanism, we'll disable TLS.
|
||||||
|
if xmpp.connect(use_tls=False):
|
||||||
|
# If you do not have the dnspython library installed, you will need
|
||||||
|
# to manually specify the name of the server if it does not match
|
||||||
|
# the one in the JID. For example, to use Google Talk you would
|
||||||
|
# need to use:
|
||||||
|
#
|
||||||
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
|
# ...
|
||||||
|
xmpp.process(block=True)
|
||||||
|
print("Done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
125
examples/user_location.py
Executable file
125
examples/user_location.py
Executable 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.")
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user