Compare commits
	
		
			1722 Commits
		
	
	
		
			sleek-1.0-
			...
			slix-1.2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 187c350805 | ||
|  | 96d1c26f90 | ||
|  | 46a90749f8 | ||
|  | 0c63a4bbda | ||
|  | e4696e0471 | ||
|  | 8217dc5239 | ||
|  | 2586abc0d3 | ||
|  | 28f84ab3d9 | ||
|  | 813b45aded | ||
|  | 3a9b45e4f2 | ||
|  | b8e091233e | ||
|  | 0edeefd977 | ||
|  | 6ba53cf1ff | ||
|  | d7758eb7f4 | ||
|  | 125336aeee | ||
|  | 7cd1cf32ae | ||
|  | d099e353a4 | ||
|  | 1e4a301c6e | ||
|  | f53b12d227 | ||
|  | e2562dcccf | ||
|  | 7b69ae3738 | ||
|  | ab6df235d7 | ||
|  | 52cd8f4b22 | ||
|  | e28318c271 | ||
|  | 39ee833c29 | ||
|  | 9019e2bc71 | ||
|  | 9208bf5bf1 | ||
|  | f0f1698e46 | ||
|  | eccd7f1c98 | ||
|  | 2587d82af8 | ||
|  | 7ea121b115 | ||
|  | bb81fbbdfc | ||
|  | 1a00a08b7d | ||
|  | 90ea2a3411 | ||
|  | 8fc6814b6d | ||
|  | ffced0ed9a | ||
|  | e7248d9af9 | ||
|  | 6b1a04f59d | ||
|  | 4905407092 | ||
|  | bd6ec10939 | ||
|  | e15e6735f1 | ||
|  | 67afd6a462 | ||
|  | 2e2b97c53b | ||
|  | a35df7fe1f | ||
|  | fbc8562779 | ||
|  | b549db959a | ||
|  | d5188ac68a | ||
|  | ada9444bf8 | ||
|  | acc52fd935 | ||
|  | 1100ff1feb | ||
|  | c17fc3a869 | ||
|  | 4dba697075 | ||
|  | e42d651d7e | ||
|  | 4305eddb4f | ||
|  | c2dc44cfd1 | ||
|  | 5fc14de32e | ||
|  | d245558fd5 | ||
|  | 9d45370e8a | ||
|  | cc1cc61d36 | ||
|  | c6740a4908 | ||
|  | 55114bcffe | ||
|  | 4fa5dedc47 | ||
|  | 5525ef2285 | ||
|  | a7ac969215 | ||
|  | 329cb5a9f8 | ||
|  | d9b47b33f5 | ||
|  | 3582ac9941 | ||
|  | 2a127a57a7 | ||
|  | 7059400020 | ||
|  | 0b14ef82d4 | ||
|  | 83953af53d | ||
|  | 110cf25c6d | ||
|  | f2bf6072ec | ||
|  | 5f9abe2e0e | ||
|  | ea65b672e7 | ||
|  | 93c705fb31 | ||
|  | 0724f623bb | ||
|  | 82e549c0e9 | ||
|  | 1aa15792b4 | ||
|  | ffb2b6bc04 | ||
|  | 27f98bf22c | ||
|  | 3978078710 | ||
|  | 00a0698720 | ||
|  | 4a24f58be2 | ||
|  | da14ce16ec | ||
|  | 18e5abb9dd | ||
|  | 1a75b76916 | ||
|  | 53b56899a0 | ||
|  | 804b23d390 | ||
|  | 04eaf52b1d | ||
|  | dc7fef1064 | ||
|  | 488c433555 | ||
|  | 9c5dd024b1 | ||
|  | 6e61adf3db | ||
|  | 041bd63864 | ||
|  | a366482551 | ||
|  | a721084f6e | ||
|  | 1b4187fa56 | ||
|  | cf7a60705e | ||
|  | 349b05b9b7 | ||
|  | 9fbacf377a | ||
|  | 2da9e35cbc | ||
|  | 8adc8fa2ba | ||
|  | 9efa909dfc | ||
|  | 7f21fdbe26 | ||
|  | f9c7fa92ea | ||
|  | e75a160d52 | ||
|  | 170bd51387 | ||
|  | abcec1e2d3 | ||
|  | eeab646bfa | ||
|  | 2c69144189 | ||
|  | f54ebec654 | ||
|  | 2ce931cb7a | ||
|  | 84eddd2ed2 | ||
|  | 2042e1a4d5 | ||
|  | be14f0cc52 | ||
|  | edd9199be8 | ||
|  | bb094cc649 | ||
|  | dbaa6ed952 | ||
|  | 8c94d894ab | ||
|  | ffc7eac4dc | ||
|  | 555fd6d926 | ||
|  | c024ac8f0b | ||
|  | f00177c0cf | ||
|  | d0ad25745a | ||
|  | 55be23a6da | ||
|  | 75ba283572 | ||
|  | f7164d35d2 | ||
|  | 4afbb0322b | ||
|  | 7bce1ecc8a | ||
|  | bbce16d526 | ||
|  | c29fc39ef1 | ||
|  | 8335c08782 | ||
|  | 224d7ae133 | ||
|  | 04bff00171 | ||
|  | f3e31baf04 | ||
|  | 9b25a7cf77 | ||
|  | 7a908ac07b | ||
|  | 92901637ec | ||
|  | 3590b663ed | ||
|  | a33bde9cc3 | ||
|  | ac50fdccfc | ||
|  | a0c6bf15e9 | ||
|  | a2852eb249 | ||
|  | f1e6d6b0a9 | ||
|  | 116a33ba51 | ||
|  | a8ac115310 | ||
|  | 1345b7c1d0 | ||
|  | d60a652259 | ||
|  | 61a7cecb31 | ||
|  | 192b7e0349 | ||
|  | 80b60fc048 | ||
|  | b8d7b9520c | ||
|  | 0305ce66b7 | ||
|  | 474405ab90 | ||
|  | 4415d3be1a | ||
|  | 058c530787 | ||
|  | 766d0dfd40 | ||
|  | ac31913a65 | ||
|  | d34ddf33db | ||
|  | eb4e09b0ca | ||
|  | ce085bf4f4 | ||
|  | 990113f8e7 | ||
|  | aa022204ee | ||
|  | c1f23b566b | ||
|  | 45f7cb8bda | ||
|  | bdb1f66ac9 | ||
|  | 842157a6cc | ||
|  | a63cc01482 | ||
|  | 1bbb6f3ff9 | ||
|  | 93894247a4 | ||
|  | 16bb5e2537 | ||
|  | d19a6e05b2 | ||
|  | 86e85f9835 | ||
|  | cc145d20b0 | ||
|  | 881d9040c4 | ||
|  | 1e77ea0944 | ||
|  | 140f0885b2 | ||
|  | 83f71a6610 | ||
|  | 271343a32d | ||
|  | 48857b0030 | ||
|  | 1fe7f5f4e6 | ||
|  | d5b1904ebb | ||
|  | b6b0e82dec | ||
|  | 632b7b4afe | ||
|  | 81b7b2c190 | ||
|  | 460de7d301 | ||
|  | 69022c6db7 | ||
|  | 0ef3fa2703 | ||
|  | 8da269de88 | ||
|  | 93ce318259 | ||
|  | 997928de91 | ||
|  | 83d00a5913 | ||
|  | bf5d7c83af | ||
|  | c66a4d4097 | ||
|  | e112e86475 | ||
|  | e034b31d6b | ||
|  | 18a4978456 | ||
|  | 17464b10a4 | ||
|  | 6fb3ecd414 | ||
|  | c214e4f037 | ||
|  | 2ee05d9616 | ||
|  | f795ac02e3 | ||
|  | 6e8235544c | ||
|  | 6e35948276 | ||
|  | 4da870fd19 | ||
|  | cd7ff685fb | ||
|  | 1e4944d47e | ||
|  | e68135f59f | ||
|  | 6408c5a747 | ||
|  | 115fe954ac | ||
|  | 3d243f7da5 | ||
|  | ea5615f236 | ||
|  | 69da1c1d7c | ||
|  | e85fa4203e | ||
|  | 506ca69917 | ||
|  | 8ac0ecdf40 | ||
|  | dbd8115557 | ||
|  | 74b4ea20bf | ||
|  | 11fbaa4241 | ||
|  | 8fd0d7c993 | ||
|  | 1450d36377 | ||
|  | 06358d0665 | ||
|  | 2b3b86e281 | ||
|  | 92e4bc752a | ||
|  | ffb2e05f21 | ||
|  | 1e2665df19 | ||
|  | 4d063e287e | ||
|  | 44f02fb3ab | ||
|  | f6b3a0c6cf | ||
|  | 8b36e918e8 | ||
|  | 9044807121 | ||
|  | 24264d3a07 | ||
|  | 8bc70264ef | ||
|  | 957c635fb7 | ||
|  | 4027927c6e | ||
|  | c16b862200 | ||
|  | a96f608469 | ||
|  | e1f25604ec | ||
|  | 0fe057b5c3 | ||
|  | be76dda21d | ||
|  | ecd124dd06 | ||
|  | 4a8951c4ee | ||
|  | 8afba7de85 | ||
|  | 1ce42d3a2f | ||
|  | 2f4d811db4 | ||
|  | 61127f521d | ||
|  | 62eefdbd6a | ||
|  | 225e07eb64 | ||
|  | 1207c81ab5 | ||
|  | 565da65ccd | ||
|  | 47fbd4cead | ||
|  | 1b9b4199e8 | ||
|  | b5930ca958 | ||
|  | 063e73c0d2 | ||
|  | 423974f90d | ||
|  | d261318e1a | ||
|  | d33cc00fe9 | ||
|  | 5fcf08a415 | ||
|  | 3c06568ed5 | ||
|  | 27582f6fd2 | ||
|  | e328ff4833 | ||
|  | 68e35e631a | ||
|  | ad8c76602b | ||
|  | b5c98ba99e | ||
|  | 711f8dc6af | ||
|  | 5b41fb98de | ||
|  | 6da625dbdb | ||
|  | e862c47b8b | ||
|  | 4a8fe56470 | ||
|  | 7c3e61950d | ||
|  | 61f89eef2e | ||
|  | 06de587ed2 | ||
|  | 49beb3ac08 | ||
|  | 403462fdb8 | ||
|  | f22d8e67b4 | ||
|  | e1c944d723 | ||
|  | 83442b9849 | ||
|  | edd6ffeb01 | ||
|  | 5a8881000c | ||
|  | 70839368c1 | ||
|  | 9d8a2a1a7a | ||
|  | 17174016ec | ||
|  | ed37174a2b | ||
|  | 8660148960 | ||
|  | 67ca2dd0f4 | ||
|  | 882f984b26 | ||
|  | 6175cbcd99 | ||
|  | 476d76a533 | ||
|  | df68bb4896 | ||
|  | 815e647c97 | ||
|  | ad70ffba59 | ||
|  | 0e95015410 | ||
|  | 35f33f1614 | ||
|  | c9f8ddff65 | ||
|  | f5ae98aaf1 | ||
|  | b92dac72f3 | ||
|  | 073e85381a | ||
|  | afc939708f | ||
|  | aabec8b993 | ||
|  | e5e2fbb16b | ||
|  | 3dd379cdf1 | ||
|  | a20582aba4 | ||
|  | 09cdbf1b76 | ||
|  | ca306e7cec | ||
|  | 1bf34f7fe6 | ||
|  | 4144d60017 | ||
|  | 7265682a4d | ||
|  | 08c62a6bf1 | ||
|  | d61f1cd035 | ||
|  | 1063feb33b | ||
|  | 79f3c1ac8f | ||
|  | cdb9a6ff7e | ||
|  | a59148dfeb | ||
|  | a5c03b763a | ||
|  | 3670d82f1c | ||
|  | 07e46837d9 | ||
|  | fa21e262c7 | ||
|  | 93934c7992 | ||
|  | 73edd42774 | ||
|  | ab03ad54aa | ||
|  | 2e571ac950 | ||
|  | 6c15d65107 | ||
|  | e5af0597a6 | ||
|  | 74117453b5 | ||
|  | 5611b30022 | ||
|  | ede9dcd18f | ||
|  | d3b56a5d94 | ||
|  | f5d4334963 | ||
|  | 5c769632e8 | ||
|  | 373505f483 | ||
|  | a2cad40f91 | ||
|  | 4328762076 | ||
|  | c2f6f07776 | ||
|  | 5ab77c7452 | ||
|  | e94a73553d | ||
|  | 577fd71472 | ||
|  | e5582694c0 | ||
|  | 768136e493 | ||
|  | 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 | ||
|  | ef1c4368d0 | ||
|  | 4b7ec4a32a | ||
|  | 2229ad8d8e | ||
|  | 61aff9f49a | ||
|  | 67235c4214 | ||
|  | 48def71d0c | ||
|  | a00eee1bbe | ||
|  | 12e8bb6ddc | ||
|  | c8c20fff71 | ||
|  | 75a18b5ffe | ||
|  | 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 | ||
|  | ea3d39b50e | ||
|  | 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 | ||
|  | d8d9e8df16 | ||
|  | d1e12cd46f | ||
|  | adf6d49fd1 | ||
|  | 0826a44d4b | ||
|  | ce145b04ac | ||
|  | 29d775e675 | ||
|  | 251a47db8c | ||
|  | e4b3a191d6 | ||
|  | 393d702e77 | ||
|  | 4efd41f1ba | ||
|  | 58aa944a5e | ||
|  | a3597d6deb | ||
|  | 8fada4d015 | ||
|  | 5d11ab269d | ||
|  | dd41a85efc | ||
|  | 6d59f55fd4 | ||
|  | 7a60e4b458 | ||
|  | 937dce8e65 | ||
|  | e2d18170b0 | ||
|  | e219c0f976 | ||
|  | 5c1562f36b | ||
|  | 823c13707d | ||
|  | 8eb59072b4 | ||
|  | 4266ee0fa4 | ||
|  | 3a62908703 | ||
|  | 20d053807d | ||
|  | 8aa4396e44 | ||
|  | 8f9100c762 | ||
|  | 788a5b73f9 | ||
|  | 1469323350 | ||
|  | 83a73ac9b7 | ||
|  | 5ed27bf5f6 | ||
|  | a81162edd2 | ||
|  | 8080b4cae2 | ||
|  | 1735c194cd | ||
|  | 6997b2fbf8 | ||
|  | b81ab97900 | ||
|  | 384e1a92b7 | ||
|  | ccb0eeefbd | ||
|  | 62bdaab7c7 | ||
|  | ec9aed5b75 | ||
|  | 9851a2a057 | ||
|  | bb2bc64d15 | ||
|  | 7152d93dd0 | ||
|  | baa1eaf73a | ||
|  | 4c7da3899e | ||
|  | 4bb226147a | ||
|  | 4d3593ac86 | ||
|  | c49a8e9114 | ||
|  | 6b274a2543 | ||
|  | a269be485f | ||
|  | d3bd9cd31d | ||
|  | 6a07e7cbe3 | ||
|  | e694e4a791 | ||
|  | e2de82ac8d | ||
|  | e3b14bc5a9 | ||
|  | 9f1648328f | ||
|  | 8e9b3d0760 | ||
|  | b1439df72a | ||
|  | 8e46aa7054 | ||
|  | 5399fdd3a9 | ||
|  | 8a22597180 | ||
|  | 016aac69f6 | ||
|  | e919906c8c | ||
|  | 46dc6eac88 | ||
|  | ea8c40c7b6 | ||
|  | b9bf30e095 | ||
|  | 1d891858b6 | ||
|  | b60c51ef13 | ||
|  | f02b0564e0 | ||
|  | ae5ce17035 | ||
|  | 6b05938573 | ||
|  | 2e1befc8c6 | ||
|  | 87ccd804ff | ||
|  | d7ba7cc72a | ||
|  | f125c11a81 | ||
|  | 77601f7262 | ||
|  | d94811d81d | ||
|  | 6d45971411 | ||
|  | 84e2589f22 | ||
|  | bf2f2782b7 | ||
|  | d9be51b2ef | ||
|  | a3d111be12 | ||
|  | 4916a12b6f | ||
|  | d6f2e51b05 | ||
|  | feb7f892ea | ||
|  | a420771665 | ||
|  | 694673b9bd | ||
|  | 393259c24b | ||
|  | f2449009d1 | ||
|  | 833f95b53a | ||
|  | 306bdd8021 | ||
|  | 756c4c032f | ||
|  | 4b1fadde4b | ||
|  | a9685a00b3 | ||
|  | e1360ae049 | ||
|  | 86a6b40fd8 | ||
|  | 7ef6abb2a3 | ||
|  | af45b51f4f | ||
|  | 566ec8a5f9 | ||
|  | b048f8d733 | ||
|  | dbf6780345 | ||
|  | 450c313340 | ||
|  | dca8516cec | ||
|  | f2c99798a6 | ||
|  | f65f88325b | ||
|  | 996ca52471 | ||
|  | 6244857746 | ||
|  | 5635265203 | ||
|  | 42c8f6ae87 | ||
|  | c2161ca56b | ||
|  | 45ccb31356 | ||
|  | 1a81b2f464 | ||
|  | 77251452c1 | ||
|  | 4df3aa569b | ||
|  | 2e2e16e281 | ||
|  | d5b3a52827 | ||
|  | e4f3b777f9 | ||
|  | a278f79bdb | ||
|  | d709f8db65 | ||
|  | 75584d7ad7 | ||
|  | e0f9025e7c | ||
|  | 9004e8bbf2 | ||
|  | 8b5511c7ec | ||
|  | 34f6195ca5 | ||
|  | 70af52d74c | ||
|  | ca2b4a188a | ||
|  | 0d32638379 | ||
|  | c4b1212c44 | ||
|  | 3463bf46c6 | ||
|  | 13a01beb07 | ||
|  | 145f577bde | ||
|  | 30da68f47b | ||
|  | 72ead3d598 | ||
|  | 4b71fba64c | ||
|  | 606c369173 | ||
|  | 1ed06bebcd | ||
|  | aa1996eba6 | ||
|  | 683f717cf7 | ||
|  | 3c871920b1 | ||
|  | de6170a13d | ||
|  | 8dbe6f6546 | ||
|  | 5313338c3a | ||
|  | 1a270dc05c | ||
|  | bd9bf3f1c7 | ||
|  | cd800d636a | ||
|  | 40642b2cd1 | ||
|  | 35ef8f9090 | ||
|  | 38dc35840e | ||
|  | b4004cd4d6 | ||
|  | 0c8a8314b2 | ||
|  | 4e757c2b56 | ||
|  | c3be6ea0b2 | ||
|  | da332365d4 | ||
|  | f7e7bf601e | ||
|  | 6f4c2f22f3 | ||
|  | 493df57035 | ||
|  | 897a9ac333 | ||
|  | acc2d071ac | ||
|  | d3b1f8c476 | ||
|  | f1db2fc156 | ||
|  | 2004ddd678 | ||
|  | cb85d4a529 | ||
|  | ead3af3135 | ||
|  | a2891d7608 | ||
|  | d7dea0c6cc | ||
|  | 65931bb384 | ||
|  | 632827f213 | ||
|  | b71550cec7 | ||
|  | b68e7bed40 | ||
|  | 4be6482ff3 | ||
|  | a21178007f | ||
|  | 2e6c27f665 | ||
|  | 0a3a7b5a70 | ||
|  | 3a12cdbd13 | ||
|  | 7d93d1824b | ||
|  | ba0d699d83 | ||
|  | c6ac40c476 | ||
|  | a5d53b3349 | ||
|  | 4487a90623 | ||
|  | 05da8cc3d1 | ||
|  | fe3f8dde4b | ||
|  | 23e499998f | ||
|  | acdf9e2d22 | ||
|  | 2076d506b4 | ||
|  | 68ce47c905 | ||
|  | 7c7fa0f008 | ||
|  | a8e3657487 | ||
|  | 13a2f719f4 | ||
|  | 2908751020 | ||
|  | c156a4f723 | ||
|  | 8b29431cde | ||
|  | 4b145958fa | ||
|  | e08b0054b2 | ||
|  | 596e135a03 | ||
|  | e55e213c78 | ||
|  | 8749f5e09b | ||
|  | b3353183f3 | ||
|  | 3657bf6636 | ||
|  | f97f6e5985 | ||
|  | 34c374a1e1 | ||
|  | 506eccf84d | ||
|  | 982bf3b2ec | ||
|  | 53a5026301 | ||
|  | 0aee445e69 | ||
|  | adade2e5ec | ||
|  | cbc42c29fb | ||
|  | 874c51d74d | ||
|  | f9ac95ddb7 | ||
|  | 0ea014fe41 | ||
|  | 62b190d0ff | ||
|  | 4b57b8131f | ||
|  | 988a90a176 | ||
|  | 67775fb8bd | ||
|  | e81683beee | ||
|  | d9c25ee65c | ||
|  | 1ebc7f4d4b | ||
|  | 2c5b77ae2e | ||
|  | d8aae88526 | ||
|  | 2f4bdfee1b | ||
|  | c16913c999 | ||
|  | f4451fe6b7 | ||
|  | 8d4e77aba6 | ||
|  | f474d378ef | ||
|  | defc252c7d | ||
|  | 19bd1e0485 | ||
|  | 5f2fc67c40 | ||
|  | 8ead33fc3b | ||
|  | ab25301953 | ||
|  | 291b118aca | ||
|  | 12b61365ad | ||
|  | db7fb10e95 | ||
|  | 9214dc6610 | ||
|  | 58b95e4ae4 | ||
|  | 60d3afe6b6 | ||
|  | afeb8f3f7c | ||
|  | cdbc0570ca | ||
|  | e648f08bad | ||
|  | 7ba6d5e02d | ||
|  | ea48bb5ac5 | ||
|  | 6ee8a2980c | ||
|  | debf909359 | ||
|  | b8114b25ed | ||
|  | 0d4d84b2fa | ||
|  | d2aca3e7e0 | ||
|  | 26aca2b789 | ||
|  | 5424ede413 | ||
|  | 20112f8e16 | ||
|  | 4260a754e5 | ||
|  | ce30f72738 | ||
|  | 69d430dd75 | ||
|  | 673545c7e4 | ||
|  | 45991e47ee | ||
|  | b8f40eb843 | ||
|  | b73a859031 | ||
|  | 9dbf246f0b | ||
|  | 4fb77ac878 | ||
|  | d0c506f930 | ||
|  | 7351fe1a02 | ||
|  | 38c2f51f83 | ||
|  | 1bf34caa5b | ||
|  | 5769935720 | ||
|  | 0214db7545 | ||
|  | ffc6f031d9 | ||
|  | 9e248bb852 | ||
|  | 973890e2c9 | ||
|  | 9c08e56ed0 | ||
|  | 4f69a03bb1 | ||
|  | c2c18acd6a | ||
|  | 12ead36f96 | ||
|  | 41a642e06c | ||
|  | c6ed4b8a1d | ||
|  | 65aa6573df | ||
|  | b888610525 | ||
|  | 6d68706326 | 
							
								
								
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,15 @@ | |||||||
| *.pyc | *.py[co] | ||||||
| build/ | build/ | ||||||
|  | dist/ | ||||||
|  | MANIFEST | ||||||
|  | docs/_build/ | ||||||
|  | *.swp | ||||||
|  | .tox/ | ||||||
|  | .coverage | ||||||
|  | slixmpp.egg-info/ | ||||||
|  | .ropeproject/ | ||||||
|  | 4913 | ||||||
|  | *~ | ||||||
|  | .baboon/ | ||||||
|  | .DS_STORE | ||||||
|  | .idea/ | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | language: python | ||||||
|  | python: | ||||||
|  |   - "2.6" | ||||||
|  |   - "2.7" | ||||||
|  |   - "3.2" | ||||||
|  |   - "3.3" | ||||||
|  |   - "3.4" | ||||||
|  | install: | ||||||
|  |   - "pip install ." | ||||||
|  | script: testall.py | ||||||
							
								
								
									
										14
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								CONTRIBUTING.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | Contributing to the Slixmpp project | ||||||
|  | =================================== | ||||||
|  |  | ||||||
|  | To contribute, the preferred way is to commit your changes on some | ||||||
|  | publicly-available git repository (on a fork `on github | ||||||
|  | <https://github.com/poezio/slixmpp>`_ or on your own repository) and to | ||||||
|  | notify the developers with either: | ||||||
|  |  - a ticket `on the bug tracker <https://dev.poez.io/new>`_ | ||||||
|  |  - a pull request on github | ||||||
|  |  - a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_ | ||||||
|  |  | ||||||
|  | Even though Slixmpp’s github repository is just a read-only mirror, we can | ||||||
|  | still be notified of the pull requests and fetch your mirror manually to | ||||||
|  | integrate your changes. | ||||||
							
								
								
									
										5
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,5 +1,6 @@ | |||||||
| Pre-requisites: | Pre-requisites: | ||||||
| - Python 3.1 or 2.6 | - Python 3.4 | ||||||
|  | - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) | ||||||
|  |  | ||||||
| Install: | Install: | ||||||
| > python3 setup.py install | > python3 setup.py install | ||||||
| @@ -9,4 +10,4 @@ Root install: | |||||||
|  |  | ||||||
| To test: | To test: | ||||||
| > cd examples | > cd examples | ||||||
| > python echo_client.py -v -j [USER@example.com] -p [PASSWORD] | > python3 echo_client.py -d -j [USER@example.com] -p [PASSWORD] | ||||||
|   | |||||||
							
								
								
									
										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. | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								MANIFEST.in
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | include README.rst | ||||||
|  | include LICENSE | ||||||
|  | include run_tests.py | ||||||
|  | include slixmpp/stringprep.pyx | ||||||
|  | recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png | ||||||
|  | recursive-include examples *.py | ||||||
|  | recursive-include tests *.py | ||||||
							
								
								
									
										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. |  | ||||||
							
								
								
									
										139
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								README.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | |||||||
|  | Slixmpp | ||||||
|  | ######### | ||||||
|  |  | ||||||
|  | Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of | ||||||
|  | SleekXMPP. | ||||||
|  |  | ||||||
|  | Slixmpp's goals is to only rewrite the core of the library (the low level | ||||||
|  | socket handling, the timers, the events dispatching) in order to remove all | ||||||
|  | threads. | ||||||
|  |  | ||||||
|  | Building | ||||||
|  | -------- | ||||||
|  |  | ||||||
|  | Slixmpp can make use of cython to improve performance on critical modules. | ||||||
|  | To do that, **cython3** is necessary along with **libidn** headers. | ||||||
|  | Otherwise, no compilation is needed. Building is done by running setup.py:: | ||||||
|  |  | ||||||
|  |     python3 setup.py build_ext --inplace | ||||||
|  |  | ||||||
|  | Documentation and Testing | ||||||
|  | ------------------------- | ||||||
|  | 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 Slixmpp:: | ||||||
|  |  | ||||||
|  |     python run_tests.py | ||||||
|  |  | ||||||
|  |  | ||||||
|  | The Slixmpp Boilerplate | ||||||
|  | ------------------------- | ||||||
|  | Projects using Slixmpp tend to follow a basic pattern for setting up client/component | ||||||
|  | connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp | ||||||
|  | based project. See the documetation or examples directory for more detailed archetypes for | ||||||
|  | Slixmpp projects:: | ||||||
|  |  | ||||||
|  |     import logging | ||||||
|  |  | ||||||
|  |     from slixmpp import ClientXMPP | ||||||
|  |     from slixmpp.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(forever=True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Slixmpp Credits | ||||||
|  | --------------- | ||||||
|  |  | ||||||
|  | **Maintainers:** | ||||||
|  |     - Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_), | ||||||
|  |     - Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_), | ||||||
|  |  | ||||||
|  | **Contributors:** | ||||||
|  |     - Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_) | ||||||
|  |     - Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_) | ||||||
|  |     - Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_) | ||||||
|  |     - Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_) | ||||||
|  |     - Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_) | ||||||
|  |     - Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_) | ||||||
|  |  | ||||||
|  | Credits (SleekXMPP) | ||||||
|  | ------------------- | ||||||
|  |  | ||||||
|  | **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.server |  | ||||||
| 		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/Slixmpp.qhcp" | ||||||
|  | 	@echo "To view the help file:" | ||||||
|  | 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Slixmpp.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/Slixmpp" | ||||||
|  | 	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Slixmpp" | ||||||
|  | 	@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:: slixmpp.basexmpp | ||||||
|  |  | ||||||
|  | .. autoclass:: BaseXMPP | ||||||
|  |     :members: | ||||||
							
								
								
									
										8
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/clientxmpp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | ========== | ||||||
|  | ClientXMPP | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.clientxmpp | ||||||
|  |  | ||||||
|  | .. autoclass:: ClientXMPP | ||||||
|  |     :members: | ||||||
							
								
								
									
										8
									
								
								docs/api/componentxmpp.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/componentxmpp.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | ============= | ||||||
|  | ComponentXMPP | ||||||
|  | ============= | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.componentxmpp | ||||||
|  |  | ||||||
|  | .. autoclass:: ComponentXMPP | ||||||
|  |     :members: | ||||||
							
								
								
									
										14
									
								
								docs/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								docs/api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | Exceptions | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.exceptions | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. autoexception:: XMPPError | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: IqError | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | .. autoexception:: IqTimeout | ||||||
|  |     :members: | ||||||
							
								
								
									
										8
									
								
								docs/api/stanza/iq.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/iq.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | IQ Stanza | ||||||
|  | ========= | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.stanza | ||||||
|  |  | ||||||
|  | .. autoclass:: Iq | ||||||
|  |     :members: | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								docs/api/stanza/message.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								docs/api/stanza/message.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | Message Stanza | ||||||
|  | ============== | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.stanza | ||||||
|  |  | ||||||
|  | .. autoclass:: Message | ||||||
|  |     :members: | ||||||
							
								
								
									
										8
									
								
								docs/api/stanza/presence.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/presence.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | Presence Stanza | ||||||
|  | =============== | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.stanza | ||||||
|  |  | ||||||
|  | .. autoclass:: Presence | ||||||
|  |     :members: | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								docs/api/stanza/rootstanza.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/stanza/rootstanza.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | Root Stanza | ||||||
|  | =========== | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.stanza.rootstanza | ||||||
|  |  | ||||||
|  | .. autoclass:: RootStanza | ||||||
|  |     :members: | ||||||
|  |  | ||||||
							
								
								
									
										28
									
								
								docs/api/xmlstream/handler.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								docs/api/xmlstream/handler.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | Stanza Handlers | ||||||
|  | =============== | ||||||
|  |  | ||||||
|  | The Basic Handler | ||||||
|  | ----------------- | ||||||
|  | .. module:: slixmpp.xmlstream.handler.base | ||||||
|  |  | ||||||
|  | .. autoclass:: BaseHandler | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | Callback | ||||||
|  | -------- | ||||||
|  | .. module:: slixmpp.xmlstream.handler | ||||||
|  |  | ||||||
|  | .. autoclass:: Callback | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | CoroutineCallback | ||||||
|  | ----------------- | ||||||
|  |  | ||||||
|  | .. autoclass:: CoroutineCallback | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  | 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:: slixmpp.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:: slixmpp.xmlstream.matcher.base | ||||||
|  |  | ||||||
|  | .. autoclass:: MatcherBase | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ID Matching | ||||||
|  | ----------- | ||||||
|  | .. module:: slixmpp.xmlstream.matcher.id | ||||||
|  |  | ||||||
|  | .. autoclass:: MatcherId | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Stanza Path Matching | ||||||
|  | -------------------- | ||||||
|  | .. module:: slixmpp.xmlstream.matcher.stanzapath | ||||||
|  |  | ||||||
|  | .. autoclass:: StanzaPath | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | XPath | ||||||
|  | ----- | ||||||
|  | .. module:: slixmpp.xmlstream.matcher.xpath | ||||||
|  |  | ||||||
|  | .. autoclass:: MatchXPath | ||||||
|  |     :members: | ||||||
|  |  | ||||||
|  |  | ||||||
|  | XMLMask | ||||||
|  | ------- | ||||||
|  | .. module:: slixmpp.xmlstream.matcher.xmlmask | ||||||
|  |  | ||||||
|  | .. autoclass:: MatchXMLMask | ||||||
|  |     :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:: slixmpp.xmlstream.stanzabase | ||||||
|  |  | ||||||
|  | The :mod:`~slixmpp.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:`~slixmpp.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 Slixmpp 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 <substanza>`, are accessible through a | ||||||
|  | special ``'substanzas'`` interface. This option is useful for stanzas which | ||||||
|  | may contain more than one of the same kind of element. When there is | ||||||
|  | only one child element, the plugin method is more useful. For plugins, | ||||||
|  | a parent stanza object delegates one of its XML child elements to the | ||||||
|  | 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="Slixmpp 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="Slixmpp Bot" /> | ||||||
|  |     </query>' | ||||||
|  |  | ||||||
|  | We can then drill down through the plugin object's interfaces as desired:: | ||||||
|  |  | ||||||
|  |     >>> iq['disco_info']['identities'] | ||||||
|  |     [('client', 'bot', 'Slixmpp 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: | ||||||
							
								
								
									
										47
									
								
								docs/api/xmlstream/tostring.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								docs/api/xmlstream/tostring.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | .. module:: slixmpp.xmlstream.tostring | ||||||
|  |  | ||||||
|  | .. _tostring: | ||||||
|  |  | ||||||
|  | XML Serialization | ||||||
|  | ================= | ||||||
|  |  | ||||||
|  | Since the XML layer of Slixmpp 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 slixmpp.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:`~slixmpp.stanza.message.Message` instance and dump | ||||||
|  | it to the terminal, the ``jabber:client`` namespace will appear. | ||||||
|  |  | ||||||
|  | .. autofunction:: slixmpp.xmlstream.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 | ||||||
							
								
								
									
										8
									
								
								docs/api/xmlstream/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/api/xmlstream/xmlstream.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | ========== | ||||||
|  | XML Stream | ||||||
|  | ========== | ||||||
|  |  | ||||||
|  | .. module:: slixmpp.xmlstream.xmlstream | ||||||
|  |  | ||||||
|  | .. autoclass:: XMLStream | ||||||
|  |     :members: | ||||||
							
								
								
									
										150
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								docs/architecture.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | .. index:: XMLStream, BaseXMPP, ClientXMPP, ComponentXMPP | ||||||
|  |  | ||||||
|  | Slixmpp Architecture | ||||||
|  | ====================== | ||||||
|  |  | ||||||
|  | The core of Slixmpp 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:`~slixmpp.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 event loop | ||||||
|  | ~~~~~~~~~~~~~~ | ||||||
|  | :class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the | ||||||
|  | :class:`asyncio.BaseProtocol` class, and therefore do not have to handle | ||||||
|  | reads and writes directly, but receive data through | ||||||
|  | :meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write | ||||||
|  | data in the socket transport. | ||||||
|  |  | ||||||
|  | Upon receiving data, :term:`stream handlers <stream handler>` are run | ||||||
|  | immediately, except if they are coroutines, in which case they are | ||||||
|  | scheduled using :meth:`asyncio.async`. | ||||||
|  |  | ||||||
|  | :term:`Event handlers <event handler>` (which are called inside | ||||||
|  | :term:`stream handlers <stream handler>`) work the same way. | ||||||
|  |  | ||||||
|  | How XML Text is Turned into Action | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  | 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> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #. **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:`~slixmpp.stanza.Message` | ||||||
|  |    :term:`stanza object` because the namespaced element name | ||||||
|  |    ``{jabber:client}message`` is associated with the class | ||||||
|  |    :class:`~slixmpp.stanza.Message`. | ||||||
|  |  | ||||||
|  | #. **Match stanza objects to callbacks.** | ||||||
|  |  | ||||||
|  |    These objects are then compared against the stored patterns associated | ||||||
|  |    with the registered callback handlers. | ||||||
|  |  | ||||||
|  |    Each handler matching our :term:`stanza object` is then added to a list. | ||||||
|  |  | ||||||
|  | #. **Processing callbacks** | ||||||
|  |  | ||||||
|  |    Every handler in the list is then called with the :term:`stanza object` | ||||||
|  |    as a parameter; if the handler is a | ||||||
|  |    :class:`~slixmpp.xmlstream.handler.CoroutineCallback` | ||||||
|  |    then it will be scheduled in the event loop using :meth:`asyncio.async` | ||||||
|  |    instead of run. | ||||||
|  |  | ||||||
|  | #. **Raise Custom Events** | ||||||
|  |  | ||||||
|  |    Since a :term:`stream handler` shouldn't block, if extensive processing | ||||||
|  |    for a stanza is required (such as needing to send and receive an | ||||||
|  |    :class:`~slixmpp.stanza.Iq` stanza), then custom events must be used. | ||||||
|  |    These events are not explicitly tied to the incoming XML stream and may | ||||||
|  |    be raised at any time. | ||||||
|  |  | ||||||
|  |    In contrast to :term:`stream handlers <stream handler>`, these functions | ||||||
|  |    are referred to as :term:`event handlers <event handler>`. | ||||||
|  |  | ||||||
|  |    The code for :meth:`BaseXMPP._handle_message` follows this pattern, and | ||||||
|  |    raises a ``'message'`` event | ||||||
|  |  | ||||||
|  |    .. code-block:: python | ||||||
|  |  | ||||||
|  |         self.event('message', msg) | ||||||
|  |  | ||||||
|  | #. **Process Custom Events** | ||||||
|  |  | ||||||
|  |    The :term:`event handlers <event handler>` are then executed, passing | ||||||
|  |    the stanza as the only argument. | ||||||
|  |  | ||||||
|  |    .. note:: | ||||||
|  |        Events may be raised without needing :term:`stanza objects <stanza object>`. | ||||||
|  |        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:: | ||||||
|  |  | ||||||
|  |        reply = msg.reply() | ||||||
|  |        reply['body'] = "Hey! This is awesome!" | ||||||
|  |        reply.send() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .. index:: BaseXMPP, XMLStream | ||||||
|  |  | ||||||
|  | Raising XMPP Awareness: BaseXMPP | ||||||
|  | -------------------------------- | ||||||
|  | While :class:`~slixmpp.xmlstream.xmlstream.XMLStream` attempts to shy away | ||||||
|  | from anything too XMPP specific, :class:`~slixmpp.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:`~slixmpp.basexmpp.BaseXMPP`. | ||||||
|  |  | ||||||
|  | .. index:: ClientXMPP, BaseXMPP | ||||||
|  |  | ||||||
|  | ClientXMPP | ||||||
|  | ---------- | ||||||
|  | :class:`~slixmpp.clientxmpp.ClientXMPP` extends | ||||||
|  | :class:`~slixmpp.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:`~slixmpp.componentxmpp.ComponentXMPP` is only a thin layer on top of | ||||||
|  | :class:`~slixmpp.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 -*- | ||||||
|  | # | ||||||
|  | # Slixmpp 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'Slixmpp' | ||||||
|  | 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.1' | ||||||
|  | # The full version, including alpha/beta/rc tags. | ||||||
|  | release = '1.1' | ||||||
|  |  | ||||||
|  | # 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 = 'slixmpp' | ||||||
|  |  | ||||||
|  | # 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 = 'Slixmppdoc' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # -- 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', 'Slixmpp.tex', u'Slixmpp 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', 'slixmpp', u'Slixmpp Documentation', | ||||||
|  |      [u'Nathan Fritz, Lance Stout'], 1) | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | intersphinx_mapping = {'python': ('http://docs.python.org/3.4', 'python-objects.inv')} | ||||||
							
								
								
									
										682
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										682
									
								
								docs/create_plugin.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,682 @@ | |||||||
|  | .. _create-plugin: | ||||||
|  |  | ||||||
|  | Creating a Slixmpp Plugin | ||||||
|  | =========================== | ||||||
|  |  | ||||||
|  | One of the goals of Slixmpp is to provide support for every draft or final | ||||||
|  | XMPP extension (`XEP <http://xmpp.org/extensions/>`_). To do this, Slixmpp 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 Slixmpp | ||||||
|  | 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 Slixmpp | ||||||
|  | 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:`BasePlugin <slixmpp.plugins.base.BasePlugin`, | ||||||
|  | and must include a ``plugin_init`` method. While the | ||||||
|  | plugins distributed with Slixmpp must be placed in the plugins directory | ||||||
|  | ``slixmpp/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 | ||||||
|  | ``Slixmpp/slixmpp/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 Slixmpp Plugin | ||||||
|  |  | ||||||
|  |     This is a minimal implementation of XEP-0077 to serve | ||||||
|  |     as a tutorial for creating Slixmpp plugins. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     from slixmpp.plugins.base import BasePlugin | ||||||
|  |  | ||||||
|  |     class xep_0077(BasePlugin): | ||||||
|  |         """ | ||||||
|  |         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 | ||||||
|  | ``slixmpp/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 ``BasePlugin.post_init(self)`` | ||||||
|  | which will mark that ``post_init`` has been called for the plugin. Once the | ||||||
|  | Slixmpp 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'``. Slixmpp | ||||||
|  | does not automatically load plugin dependencies for you. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     def post_init(self): | ||||||
|  |         BasePlugin.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 Slixmpp. | ||||||
|  |  | ||||||
|  | * ``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 slixmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin | ||||||
|  |     from slixmpp 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 | ||||||
|  | ``slixmpp.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().set_payload(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().set_payload(iq['register'].xml) | ||||||
|  |         iq.error() | ||||||
|  |         iq['error']['code'] = code | ||||||
|  |         iq['error']['type'] = error_type | ||||||
|  |         iq['error']['condition'] = name | ||||||
|  |         iq['error']['text'] = text | ||||||
|  |         iq.send() | ||||||
|  |  | ||||||
|  | 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().set_payload(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 slixmpp.componentxmpp | ||||||
|  |  | ||||||
|  |     class Example(slixmpp.componentxmpp.ComponentXMPP): | ||||||
|  |  | ||||||
|  |         def __init__(self, jid, password): | ||||||
|  |             slixmpp.componentxmpp.ComponentXMPP.__init__(self, jid, password, 'localhost', 8888) | ||||||
|  |  | ||||||
|  |             self.register_plugin('xep_0030') | ||||||
|  |             self.register_plugin('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.send_message(iq['from'], msg, mfrom=self.fulljid) | ||||||
|  |  | ||||||
|  |         def unreg(self, iq): | ||||||
|  |             msg = "Bye! %s" % iq['register']['username'] | ||||||
|  |             self.send_message(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 Slixmpp Plugin | ||||||
|  |  | ||||||
|  |     This is a minimal implementation of XEP-0077 to serve | ||||||
|  |     as a tutorial for creating Slixmpp plugins. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     from slixmpp.plugins.base import BasePlugin | ||||||
|  |     from slixmpp.xmlstream.handler.callback import Callback | ||||||
|  |     from slixmpp.xmlstream.matcher.xpath import MatchXPath | ||||||
|  |     from slixmpp.xmlstream import ElementBase, ET, JID, register_stanza_plugin | ||||||
|  |     from slixmpp 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(BasePlugin): | ||||||
|  |         """ | ||||||
|  |         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): | ||||||
|  |             BasePlugin.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) | ||||||
|  |                     reply = iq.reply() | ||||||
|  |                     reply.set_payload(iq['register'].xml) | ||||||
|  |                     reply.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) | ||||||
|  |  | ||||||
|  |             reply = iq.reply() | ||||||
|  |             reply.set_payload(reg.xml) | ||||||
|  |             reply.send() | ||||||
|  |  | ||||||
|  |         def _sendError(self, iq, code, error_type, name, text=''): | ||||||
|  |             reply = iq.reply() | ||||||
|  |             reply.set_payload(iq['register'].xml) | ||||||
|  |             reply.error() | ||||||
|  |             reply['error']['code'] = code | ||||||
|  |             reply['error']['type'] = error_type | ||||||
|  |             reply['error']['condition'] = name | ||||||
|  |             reply['error']['text'] = text | ||||||
|  |             reply.send() | ||||||
							
								
								
									
										47
									
								
								docs/differences.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								docs/differences.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | .. _differences: | ||||||
|  |  | ||||||
|  | Differences from SleekXMPP | ||||||
|  | ========================== | ||||||
|  |  | ||||||
|  | **Python 3.4+ only** | ||||||
|  |     slixmpp will only work on python 3.4 and above. | ||||||
|  |  | ||||||
|  | **Stanza copies** | ||||||
|  |     The same stanza object is given through all the handlers; a handler that | ||||||
|  |     edits the stanza object should make its own copy. | ||||||
|  |  | ||||||
|  | **Replies** | ||||||
|  |     Because stanzas are not copied anymore, | ||||||
|  |     :meth:`Stanza.reply() <.StanzaBase.reply>` calls | ||||||
|  |     (for :class:`IQs <.Iq>`, :class:`Messages <.Message>`, etc) | ||||||
|  |     now return a new object instead of editing the stanza object | ||||||
|  |     in-place. | ||||||
|  |  | ||||||
|  | **Block and threaded arguments** | ||||||
|  |     All the functions that had a ``threaded=`` or ``block=`` argument | ||||||
|  |     do not have it anymore. Also, :meth:`.Iq.send` **does not block | ||||||
|  |     anymore**. | ||||||
|  |  | ||||||
|  | **Coroutine facilities** | ||||||
|  |     **See** :ref:`using_asyncio` | ||||||
|  |  | ||||||
|  |     If an event handler is a coroutine, it will be called asynchronously | ||||||
|  |     in the event loop instead of inside the event caller. | ||||||
|  |  | ||||||
|  |     A CoroutineCallback class has been added to create coroutine stream | ||||||
|  |     handlers, which will be also handled in the event loop. | ||||||
|  |  | ||||||
|  |     The :class:`~.slixmpp.stanza.Iq` object’s :meth:`~.slixmpp.stanza.Iq.send` | ||||||
|  |     method now **always** return a :class:`~.asyncio.Future` which result will be set | ||||||
|  |     to the IQ reply when it is received, or to ``None`` if the IQ is not of | ||||||
|  |     type ``get`` or ``set``. | ||||||
|  |  | ||||||
|  |     Many plugins (WIP) calls which retrieve information also return the same | ||||||
|  |     future. | ||||||
|  |  | ||||||
|  | **Architectural differences** | ||||||
|  |     slixmpp does not have an event queue anymore, and instead processes | ||||||
|  |     handlers directly after receiving the XML stanza. | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |     If you find something that doesn’t work but should, please report it. | ||||||
							
								
								
									
										282
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								docs/event_index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,282 @@ | |||||||
|  | Event Index | ||||||
|  | =========== | ||||||
|  |  | ||||||
|  | .. glossary:: | ||||||
|  |     :sorted: | ||||||
|  |  | ||||||
|  |     connected | ||||||
|  |         - **Data:** ``{}`` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.xmlstream.XMLstream` | ||||||
|  |  | ||||||
|  |         Signal that a connection can not be established after number of attempts. | ||||||
|  |  | ||||||
|  |     changed_status | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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 Slixmpp 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:`~slixmpp.plugins.xep_0030.stanza.DiscoInfo` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` | ||||||
|  |  | ||||||
|  |         Triggered whenever a ``disco#info`` result stanza is received. | ||||||
|  |  | ||||||
|  |     disco_items | ||||||
|  |         - **Data:** :py:class:`~slixmpp.plugins.xep_0030.stanza.DiscoItems` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.plugins.xep_0030.disco.xep_0030` | ||||||
|  |  | ||||||
|  |         Triggered whenever a ``disco#items`` result stanza is received. | ||||||
|  |  | ||||||
|  |     disconnected | ||||||
|  |         - **Data:** ``{}`` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.xmlstream.XMLstream` | ||||||
|  |  | ||||||
|  |         Signal that the connection with the XMPP server has been lost. | ||||||
|  |  | ||||||
|  |     entity_time | ||||||
|  |         - **Data:** | ||||||
|  |         - **Source:** | ||||||
|  |  | ||||||
|  |     failed_auth | ||||||
|  |         - **Data:** ``{}`` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.ClientXMPP`, :py:class:`~slixmpp.plugins.xep_0078.xep_0078` | ||||||
|  |  | ||||||
|  |         Signal that the server has rejected the provided login credentials. | ||||||
|  |  | ||||||
|  |     gmail_notify | ||||||
|  |         - **Data:** ``{}`` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Iq` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Message` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.plugins.xep_0249.direct` | ||||||
|  |  | ||||||
|  |     groupchat_message | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Message` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.plugins.xep_0045.xep_0045` | ||||||
|  |  | ||||||
|  |         Triggered whenever a message is received from a multi-user chat room. | ||||||
|  |  | ||||||
|  |     groupchat_presence | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Message` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Message` | ||||||
|  |         - **Source:** :py:class:`BaseXMPP <slixmpp.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_error | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Message` | ||||||
|  |         - **Source:** :py:class:`BaseXMPP <slixmpp.BaseXMPP>` | ||||||
|  |  | ||||||
|  |         Makes the contents of message stanzas available whenever one is received. | ||||||
|  |         Only handler messages with an ``error`` type. | ||||||
|  |  | ||||||
|  |     message_form | ||||||
|  |         - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004` | ||||||
|  |  | ||||||
|  |         Currently the same as :term:`message_xform`. | ||||||
|  |  | ||||||
|  |     message_xform | ||||||
|  |         - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``available``' is received. | ||||||
|  |  | ||||||
|  |     presence_error | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``error``' is received. | ||||||
|  |  | ||||||
|  |     presence_form | ||||||
|  |         - **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``probe``' is received. | ||||||
|  |  | ||||||
|  |     presence_subscribe | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``subscribe``' is received. | ||||||
|  |  | ||||||
|  |     presence_subscribed | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``subscribed``' is received. | ||||||
|  |  | ||||||
|  |     presence_unavailable | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``unavailable``' is received. | ||||||
|  |  | ||||||
|  |     presence_unsubscribe | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``unsubscribe``' is received. | ||||||
|  |  | ||||||
|  |     presence_unsubscribed | ||||||
|  |         - **Data:** :py:class:`~slixmpp.Presence` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
|  |  | ||||||
|  |         A presence stanza with a type of '``unsubscribed``' is received. | ||||||
|  |  | ||||||
|  |     roster_update | ||||||
|  |         - **Data:** :py:class:`~slixmpp.stanza.Roster` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.ClientXMPP` | ||||||
|  |  | ||||||
|  |         An IQ result containing roster entries is received. | ||||||
|  |  | ||||||
|  |     sent_presence | ||||||
|  |         - **Data:** ``{}`` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.roster.multi.Roster` | ||||||
|  |  | ||||||
|  |         Signal that an initial presence stanza has been written to the XML stream. | ||||||
|  |  | ||||||
|  |     session_end | ||||||
|  |         - **Data:** ``{}`` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.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 | ||||||
|  |         implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ | ||||||
|  |         distinguish between the two events. | ||||||
|  |  | ||||||
|  |         Plugins that maintain session-based state should clear themselves when | ||||||
|  |         this event is fired. | ||||||
|  |  | ||||||
|  |     session_start | ||||||
|  |         - **Data:** ``{}`` | ||||||
|  |         - **Source:** :py:class:`ClientXMPP <slixmpp.ClientXMPP>`, | ||||||
|  |           :py:class:`ComponentXMPP <slixmpp.ComponentXMPP>` | ||||||
|  |           :py:class:`XEP-0078 <slixmpp.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:`~slixmpp.xmlstream.XMLstream` | ||||||
|  |  | ||||||
|  |     stream_error | ||||||
|  |         - **Data:** :py:class:`~slixmpp.stanza.StreamError` | ||||||
|  |         - **Source:** :py:class:`~slixmpp.BaseXMPP` | ||||||
							
								
								
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/features.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | How to Use Stream Features | ||||||
|  | ========================== | ||||||
							
								
								
									
										67
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								docs/getting_started/component.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | .. _echocomponent: | ||||||
|  |  | ||||||
|  | ================================= | ||||||
|  | Create and Run a Server Component | ||||||
|  | ================================= | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     If you have any issues working through this quickstart guide | ||||||
|  |     join the chat room at `slixmpp@muc.poez.io | ||||||
|  |     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||||
|  |  | ||||||
|  | If you have not yet installed Slixmpp, do so now by either checking out a version | ||||||
|  | with `Git <http://git.poez.io/slixmpp>`_. | ||||||
|  |  | ||||||
|  | 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 slixmpp.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.slixmpp.com', '******', 'slixmpp.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:`~slixmpp.basexmpp.BaseXMPP.send_message()` and | ||||||
|  | :meth:`~slixmpp.basexmpp.BaseXMPP.send_presence()` accept the parameters | ||||||
|  | ``mfrom`` and ``pfrom``, respectively. For any method that uses | ||||||
|  | :class:`~slixmpp.stanza.iq.Iq` stanzas, ``ifrom`` may be used. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Final Product | ||||||
|  | ------------- | ||||||
|  |  | ||||||
|  | .. include:: ../../examples/echo_component.py | ||||||
|  |     :literal: | ||||||
							
								
								
									
										349
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								docs/getting_started/echobot.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,349 @@ | |||||||
|  | .. _echobot: | ||||||
|  |  | ||||||
|  | =============================== | ||||||
|  | Slixmpp Quickstart - Echo Bot | ||||||
|  | =============================== | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     If you have any issues working through this quickstart guide | ||||||
|  |     join the chat room at `slixmpp@muc.poez.io | ||||||
|  |     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||||
|  |  | ||||||
|  | If you have not yet installed Slixmpp, do so now by either checking out a version | ||||||
|  | with `Git <http://git.poez.io/slixmpp>`_. | ||||||
|  |  | ||||||
|  | As a basic starting project, we will create an echo bot which will reply to any | ||||||
|  | messages sent to it. We will also go through adding some basic command line configuration | ||||||
|  | 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 asyncio | ||||||
|  |     import logging | ||||||
|  |     import getpass | ||||||
|  |     from optparse import OptionParser | ||||||
|  |  | ||||||
|  |     import slixmpp | ||||||
|  |  | ||||||
|  |     '''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''' | ||||||
|  |  | ||||||
|  | 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. | ||||||
|  |  | ||||||
|  | Slixmpp comes with a :class:`ClientXMPP <slixmpp.clientxmpp.ClientXMPP>` class | ||||||
|  | which we can extend to add our message echoing feature. :class:`ClientXMPP <slixmpp.clientxmpp.ClientXMPP>` | ||||||
|  | requires the parameters ``jid`` and ``password``, so we will let our ``EchoBot`` class accept those | ||||||
|  | as well. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     class EchoBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |         def __init__(self, jid, password): | ||||||
|  |             super().__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().__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 <slixmpp.basexmpp.BaseXMPP.send_presence>`. | ||||||
|  | Calling :meth:`send_presence <slixmpp.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 <slixmpp.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 <slixmpp.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 | ||||||
|  | <slixmpp.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 | ||||||
|  | <slixmpp.exceptions.IQTimeout>` is raised. Similarly, an :class:`IQError <slixmpp.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().__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 <slixmpp.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. Slixmpp thus leaves out the ``from`` attribute when replying | ||||||
|  |     using a client connection. | ||||||
|  |  | ||||||
|  | Command Line Arguments and Logging | ||||||
|  | ---------------------------------- | ||||||
|  |  | ||||||
|  | While this isn't part of Slixmpp 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 Slixmpp 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 | ||||||
|  | ``aiodns`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method | ||||||
|  | will perform a DNS query to find the appropriate server to connect to for the | ||||||
|  | given JID. If you do not have ``aiodns``, then Slixmpp will attempt to | ||||||
|  | connect to the hostname used by the JID, unless an address tuple is supplied | ||||||
|  | to :meth:`slixmpp.clientxmpp.ClientXMPP`. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     if __name__ == '__main__': | ||||||
|  |  | ||||||
|  |         # .. option parsing & echo bot configuration | ||||||
|  |  | ||||||
|  |         if xmpp.connect(): | ||||||
|  |             xmpp.process(block=True) | ||||||
|  |         else: | ||||||
|  |             print('Unable to connect') | ||||||
|  |  | ||||||
|  | To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process` | ||||||
|  | which will start the event handling, send queue, and XML reader threads. It will also call | ||||||
|  | the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By | ||||||
|  | passing ``block=True`` to :meth:`slixmpp.basexmpp.BaseXMPP.process` we are running the | ||||||
|  | main processing loop in the main thread of execution. The :meth:`slixmpp.basexmpp.BaseXMPP.process` | ||||||
|  | call will not return until after Slixmpp 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:`slixmpp.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 Slixmpp 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 Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/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:`~slixmpp.stanza.message.Message` and | ||||||
|  | :class:`~slixmpp.stanza.presence.Presence` stanzas which only use | ||||||
|  | text data for basic usage, :class:`~slixmpp.stanza.iq.Iq` stanzas | ||||||
|  | require using XML payloads, and generally entail creating a new | ||||||
|  | Slixmpp plugin to provide the necessary convenience methods to | ||||||
|  | make working with them easier. | ||||||
|  |  | ||||||
|  | Basic Use | ||||||
|  | --------- | ||||||
|  |  | ||||||
|  | XMPP's use of :class:`~slixmpp.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 | ||||||
|  | ~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | Slixmpp provides built-in support for creating basic :class:`~slixmpp.stanza.iq.Iq` | ||||||
|  | stanzas this way. The relevant methods are: | ||||||
|  |  | ||||||
|  | * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq` | ||||||
|  | * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_get` | ||||||
|  | * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_set` | ||||||
|  | * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_result` | ||||||
|  | * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_error` | ||||||
|  | * :meth:`~slixmpp.basexmpp.BaseXMPP.make_iq_query` | ||||||
|  |  | ||||||
|  | These methods all follow the same pattern: create or modify an existing | ||||||
|  | :class:`~slixmpp.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:`~slixmpp.stanza.iq.Iq` stanza is created, sending it | ||||||
|  | over the wire is done using its :meth:`~slixmpp.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:`~slixmpp.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:`~slixmpp.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:`~slixmpp.stanza.iq.Iq.send()`: :exc:`~slixmpp.exceptions.IqError` | ||||||
|  | and :exc:`~slixmpp.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:`~slixmpp.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 Slixmpp requires building at least a | ||||||
|  | rudimentary Slixmpp plugin to create a :term:`stanza object` for | ||||||
|  | interfacting with the :class:`~slixmpp.stanza.iq.Iq` payload. | ||||||
|  |  | ||||||
|  | .. seealso:: | ||||||
|  |  | ||||||
|  |     * :ref:`create-plugin` | ||||||
|  |     * :ref:`work-with-stanzas` | ||||||
|  |     * :ref:`using-handlers-matchers` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | The typical way to respond to :class:`~slixmpp.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:`~slixmpp.xmlstream.stanzabase.ElementBase.plugin_attrib` value | ||||||
|  | of ``custom_xep``. | ||||||
|  |  | ||||||
|  | There are two types of incoming :class:`~slixmpp.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:`~slixmpp.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') | ||||||
							
								
								
									
										200
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								docs/getting_started/muc.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | |||||||
|  | .. _mucbot: | ||||||
|  |  | ||||||
|  | ========================= | ||||||
|  | Mulit-User Chat (MUC) Bot | ||||||
|  | ========================= | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     If you have any issues working through this quickstart guide | ||||||
|  |     join the chat room at `slixmpp@muc.poez.io | ||||||
|  |     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||||
|  |  | ||||||
|  | If you have not yet installed Slixmpp, do so now by either checking out a version | ||||||
|  | from `Git <http://git.poez.io/slixmpp>`_. | ||||||
|  |  | ||||||
|  | Now that you've got the basic gist of using Slixmpp by following the | ||||||
|  | echobot example (:ref:`echobot`), we can use one of the bundled plugins | ||||||
|  | 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 <slixmpp.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 slixmpp | ||||||
|  |  | ||||||
|  |     class MUCBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |         def __init__(self, jid, password, room, nick): | ||||||
|  |             slixmpp.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'].join_muc(self.room, | ||||||
|  |                                          self.nick, | ||||||
|  |                                          wait=True) | ||||||
|  |  | ||||||
|  | Note that as in :ref:`echobot`, we need to include send an initial presence and request | ||||||
|  | the roster. Next, we want to join the group chat, so we call the | ||||||
|  | ``join_muc`` method of the MUC plugin. | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     The :attr:`plugin <slixmpp.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): | ||||||
|  |         slixmpp.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): | ||||||
|  |         slixmpp.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 | ||||||
|  | ============================= | ||||||
							
								
								
									
										40
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								docs/getting_started/proxy.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | .. _proxy: | ||||||
|  |  | ||||||
|  | ========================= | ||||||
|  | Enable HTTP Proxy Support | ||||||
|  | ========================= | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     If you have any issues working through this quickstart guide | ||||||
|  |     join the chat room at `slixmpp@muc.poez.io | ||||||
|  |     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||||
|  |  | ||||||
|  | In some instances, you may wish to route XMPP traffic through | ||||||
|  | an HTTP proxy, probably to get around restrictive firewalls. | ||||||
|  | Slixmpp provides support for basic HTTP proxying with DIGEST | ||||||
|  | authentication. | ||||||
|  |  | ||||||
|  | Enabling proxy support is done in two steps. The first is to instruct Slixmpp | ||||||
|  | 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 | ||||||
|  | ============================== | ||||||
							
								
								
									
										92
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								docs/getting_started/sendlogout.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | Sign in, Send a Message, and Disconnect | ||||||
|  | ======================================= | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |  | ||||||
|  |     If you have any issues working through this quickstart guide | ||||||
|  |     join the chat room at `slixmpp@muc.poez.io | ||||||
|  |     <xmpp:slixmpp@muc.poez.io?join>`_. | ||||||
|  |  | ||||||
|  | A common use case for Slixmpp is to send one-off messages from | ||||||
|  | time to time. For example, one use case could be sending out a notice when | ||||||
|  | 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 <slixmpp.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 slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     class SendMsgBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |         def __init__(self, jid, password, recipient, msg): | ||||||
|  |             super().__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 <slixmpp.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 <slixmpp.xmlstream.XMLStream.disconnect>`. | ||||||
|  | Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call | ||||||
|  | :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible | ||||||
|  | for the client to disconnect before the send queue is processed and the message is actually | ||||||
|  | sent on the wire. To ensure that our message is processed, we use | ||||||
|  | :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`. | ||||||
|  |  | ||||||
|  | .. 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) <slixmpp.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: | ||||||
							
								
								
									
										34
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								docs/glossary.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | .. _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 :class:`Matcher <.MatcherBase>` object, and | ||||||
|  |         which provides additional semantics. For example, the | ||||||
|  |         :class:`.Waiter` handler wrapper blocks thread execution until a | ||||||
|  |         matching stanza is received. | ||||||
|  |  | ||||||
|  |     event handler | ||||||
|  |         A callback function that responds to events raised by | ||||||
|  |         :meth:`.XMLStream.event`. | ||||||
|  |  | ||||||
|  |     stanza object | ||||||
|  |         Informally may refer both to classes which extend :class:`.ElementBase` | ||||||
|  |         or :class:`.StanzaBase`, and to objects of such classes. | ||||||
|  |  | ||||||
|  |         A stanza object is a wrapper for an XML object which exposes :class:`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` | ||||||
							
								
								
									
										200
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								docs/guide_xep_0030.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | |||||||
|  | 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 Slixmpp.) Slixmpp 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 ``restore_defaults()``. | ||||||
|  | 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='Slixmpp', | ||||||
|  |                                   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``. | ||||||
|  |  | ||||||
|  | Performing Disco Queries | ||||||
|  | ------------------------ | ||||||
|  | The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs | ||||||
|  | and their nodes for disco information. Since these methods are wrappers for | ||||||
|  | sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()`` | ||||||
|  | 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 = yield from self['xep_0030'].get_info(jid='foo@example.com', | ||||||
|  |                                                 node='bar', | ||||||
|  |                                                 ifrom='baz@mycomponent.example.com', | ||||||
|  |                                                 timeout=30) | ||||||
|  |  | ||||||
|  |     items = self['xep_0030'].get_info(jid='foo@example.com', | ||||||
|  |                                       node='bar', | ||||||
|  |                                       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 | ||||||
|  | -------------------------- | ||||||
							
								
								
									
										236
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								docs/index.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | |||||||
|  | Slixmpp | ||||||
|  | ######### | ||||||
|  |  | ||||||
|  | .. sidebar:: Get the Code | ||||||
|  |  | ||||||
|  |     The latest source code for Slixmpp may be found on the `Git repo | ||||||
|  |     <http://git.poez.io/slixmpp>`_. :: | ||||||
|  |  | ||||||
|  |         git clone git://git.poez.io/slixmpp | ||||||
|  |  | ||||||
|  |     An XMPP chat room is available for discussing and getting help with slixmpp. | ||||||
|  |  | ||||||
|  |     **Chat** | ||||||
|  |         `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_ | ||||||
|  |  | ||||||
|  |     **Reporting bugs** | ||||||
|  |         You can report bugs at http://dev.louiz.org/projects/slixmpp/issues. | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |     slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_ | ||||||
|  |     which goal is to use asyncio instead of threads to handle networking. See | ||||||
|  |     :ref:`differences`. | ||||||
|  |  | ||||||
|  | Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.4+, | ||||||
|  |  | ||||||
|  | Slixmpp's design goals and philosphy are: | ||||||
|  |  | ||||||
|  | **Low number of dependencies** | ||||||
|  |     Installing and using Slixmpp 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 Slixmpp 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, Slixmpp 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 Slixmpp Bot: | ||||||
|  | -------------------------------- | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     import asyncio | ||||||
|  |     import logging | ||||||
|  |  | ||||||
|  |     from slixmpp import ClientXMPP | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     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') | ||||||
|  |  | ||||||
|  |         def session_start(self, event): | ||||||
|  |             self.send_presence() | ||||||
|  |             self.get_roster() | ||||||
|  |  | ||||||
|  |             # Most get_*/set_* methods from plugins use Iq stanzas, which | ||||||
|  |             # are sent asynchronously. You can almost always provide a | ||||||
|  |             # callback that will be executed when the reply is received. | ||||||
|  |  | ||||||
|  |         def message(self, msg): | ||||||
|  |             if msg['type'] in ('chat', 'normal'): | ||||||
|  |                 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() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | To read if you come from SleekXMPP | ||||||
|  | ---------------------------------- | ||||||
|  |  | ||||||
|  | .. toctree:: | ||||||
|  |     :maxdepth: 1 | ||||||
|  |  | ||||||
|  |     differences | ||||||
|  |     using_asyncio | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  |  | ||||||
|  |     xeps | ||||||
|  |     xmpp_tdg | ||||||
|  |     howto/stanzas | ||||||
|  |     create_plugin | ||||||
|  |     features | ||||||
|  |     sasl | ||||||
|  |     handlersmatchers | ||||||
|  |  | ||||||
|  | Plugin Guides | ||||||
|  | ~~~~~~~~~~~~~ | ||||||
|  | .. toctree:: | ||||||
|  |     :maxdepth: 1 | ||||||
|  |  | ||||||
|  |     guide_xep_0030 | ||||||
|  |  | ||||||
|  | Slixmpp 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/tostring | ||||||
|  |  | ||||||
|  | Core Stanzas | ||||||
|  | ~~~~~~~~~~~~ | ||||||
|  | .. toctree:: | ||||||
|  |     :maxdepth: 2 | ||||||
|  |  | ||||||
|  |     api/stanza/rootstanza | ||||||
|  |     api/stanza/message | ||||||
|  |     api/stanza/presence | ||||||
|  |     api/stanza/iq | ||||||
|  |  | ||||||
|  | Plugins | ||||||
|  | ~~~~~~~ | ||||||
|  | .. toctree:: | ||||||
|  |     :maxdepth: 2 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Additional Info | ||||||
|  | --------------- | ||||||
|  | .. toctree:: | ||||||
|  |     :hidden: | ||||||
|  |  | ||||||
|  |     glossary | ||||||
|  |     license | ||||||
|  |  | ||||||
|  | * :ref:`license` | ||||||
|  | * :ref:`glossary` | ||||||
|  | * :ref:`genindex` | ||||||
|  | * :ref:`modindex` | ||||||
|  | * :ref:`search` | ||||||
|  |  | ||||||
|  | SleekXMPP Credits | ||||||
|  | ----------------- | ||||||
|  |  | ||||||
|  | .. note:: | ||||||
|  |     Those people made SleekXMPP, so you should not bother them if | ||||||
|  |     you have an issue with slixmpp. But it’s still fair to credit | ||||||
|  |     them for their work. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | **Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_ | ||||||
|  |      `fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_, | ||||||
|  |      `@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\Slixmpp.qhcp | ||||||
|  | 	echo.To view the help file: | ||||||
|  | 	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Slixmpp.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 | ||||||
|  | ============================= | ||||||
							
								
								
									
										148
									
								
								docs/using_asyncio.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								docs/using_asyncio.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | |||||||
|  | .. _using_asyncio: | ||||||
|  |  | ||||||
|  | ============= | ||||||
|  | Using asyncio | ||||||
|  | ============= | ||||||
|  |  | ||||||
|  | Block on IQ sending | ||||||
|  | ~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | :meth:`.Iq.send` now returns a :class:`~.Future` so you can easily block with: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     result = yield from iq.send() | ||||||
|  |  | ||||||
|  | .. warning:: | ||||||
|  |  | ||||||
|  |     If the reply is an IQ with an ``error`` type, this will raise an | ||||||
|  |     :class:`.IqError`, and if it timeouts, it will raise an | ||||||
|  |     :class:`.IqTimeout`. Don't forget to catch it. | ||||||
|  |  | ||||||
|  | You can still use callbacks instead. | ||||||
|  |  | ||||||
|  | XEP plugin integration | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | The same changes from the SleekXMPP API apply, so you can do: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     iq_info = yield from self.xmpp['xep_0030'].get_info(jid) | ||||||
|  |  | ||||||
|  | But the following will only return a Future: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     iq_info = self.xmpp['xep_0030'].get_info(jid) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Callbacks, Event Handlers, and Stream Handlers | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | IQ callbacks and :term:`Event Handlers <event handler>` can be coroutine | ||||||
|  | functions; in this case, they will be scheduled in the event loop using | ||||||
|  | :meth:`.asyncio.async` and not ran immediately. | ||||||
|  |  | ||||||
|  | A :class:`.CoroutineCallback` class has been added as well for | ||||||
|  | :term:`Stream Handlers <stream handler>`, which will use | ||||||
|  | :meth:`.asyncio.async` to schedule the callback. | ||||||
|  |  | ||||||
|  | Running the event loop | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | :meth:`.XMLStream.process` is only a thin wrapper on top of | ||||||
|  | ``loop.run_forever()`` (if ``timeout`` is provided then it will | ||||||
|  | only run for this amount of time, and if ``forever`` is False it will | ||||||
|  | run until disconnection). | ||||||
|  |  | ||||||
|  | Therefore you can handle the event loop in any way you like | ||||||
|  | instead of using ``process()``. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Examples | ||||||
|  | ~~~~~~~~ | ||||||
|  |  | ||||||
|  | Blocking until the session is established | ||||||
|  | ----------------------------------------- | ||||||
|  |  | ||||||
|  | This code blocks until the XMPP session is fully established, which | ||||||
|  | can be useful to make sure external events aren’t triggering XMPP | ||||||
|  | callbacks while everything is not ready. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     import asyncio, slixmpp | ||||||
|  |  | ||||||
|  |     client = slixmpp.ClientXMPP('jid@example', 'password') | ||||||
|  |     client.connected_event = asyncio.Event() | ||||||
|  |     callback = lambda _: client.connected_event.set() | ||||||
|  |     client.add_event_handler('session_start', callback) | ||||||
|  |     client.connect() | ||||||
|  |     loop.run_until_complete(event.wait()) | ||||||
|  |     # do some other stuff before running the event loop, e.g. | ||||||
|  |     # loop.run_until_complete(httpserver.init()) | ||||||
|  |     client.process() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Use with other asyncio-based libraries | ||||||
|  | -------------------------------------- | ||||||
|  |  | ||||||
|  | This code interfaces with aiohttp to retrieve two pages asynchronously | ||||||
|  | when the session is established, and then send the HTML content inside | ||||||
|  | a simple <message>. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     import asyncio, aiohttp, slixmpp | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def get_pythonorg(event): | ||||||
|  |         req = yield from aiohttp.request('get', 'http://www.python.org') | ||||||
|  |         text = yield from req.text | ||||||
|  |         client.send_message(mto='jid2@example', mbody=text) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def get_asyncioorg(event): | ||||||
|  |         req = yield from aiohttp.request('get', 'http://www.asyncio.org') | ||||||
|  |         text = yield from req.text | ||||||
|  |         client.send_message(mto='jid3@example', mbody=text) | ||||||
|  |  | ||||||
|  |     client = slixmpp.ClientXMPP('jid@example', 'password') | ||||||
|  |     client.add_event_handler('session_start', get_pythonorg) | ||||||
|  |     client.add_event_handler('session_start', get_asyncioorg) | ||||||
|  |     client.connect() | ||||||
|  |     client.process() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Blocking Iq | ||||||
|  | ----------- | ||||||
|  |  | ||||||
|  | This client checks (via XEP-0092) the software used by every entity it | ||||||
|  | receives a message from. After this, it sends a message to a specific | ||||||
|  | JID indicating its findings. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     import asyncio, slixmpp | ||||||
|  |  | ||||||
|  |     class ExampleClient(slixmpp.ClientXMPP): | ||||||
|  |         def __init__(self, *args, **kwargs): | ||||||
|  |             slixmpp.ClientXMPP.__init__(self, *args, **kwargs) | ||||||
|  |             self.register_plugin('xep_0092') | ||||||
|  |             self.add_event_handler('message', self.on_message) | ||||||
|  |  | ||||||
|  |         @asyncio.coroutine | ||||||
|  |         def on_message(self, event): | ||||||
|  |             # You should probably handle IqError and IqTimeout exceptions here | ||||||
|  |             # but this is an example. | ||||||
|  |             version = yield from self['xep_0092'].get_version(message['from']) | ||||||
|  |             text = "%s sent me a message, he runs %s" % (message['from'], | ||||||
|  |                                                          version['software_version']['name']) | ||||||
|  |             self.send_message(mto='master@example.tld', mbody=text) | ||||||
|  |  | ||||||
|  |     client = ExampleClient('jid@example', 'password') | ||||||
|  |     client.connect() | ||||||
|  |     client.process() | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										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* | ||||||
|  | ====================================== | ||||||
|  |  | ||||||
|  | Slixmpp 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 | ||||||
|  | Slixmpp release, is available at http://github.com/legastero/xmpp-tdg. | ||||||
|  |  | ||||||
|  | However, since publication, Slixmpp 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 Slixmpp. | ||||||
|  | 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.send_message(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 Slixmpp 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 = {slixmpp.stanza.presence.Presence: "status", | ||||||
|  |                     slixmpp.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.send_message(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; Slixmpp 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 ``make_presence`` 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.send_message``. | ||||||
|  |  | ||||||
|  | Updated Code | ||||||
|  | ~~~~~~~~~~~~ | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |   def handleXMPPPresenceProbe(self, event) : | ||||||
|  |     self.xmpp.send_presence(pto = event["from"]) | ||||||
|  |  | ||||||
|  |   def handleXMPPPresenceSubscription(self, subscription) : | ||||||
|  |     if subscription["type"] == "subscribe" : | ||||||
|  |       userJID = subscription["from"].jid | ||||||
|  |       self.xmpp.send_presence_subscription(pto=userJID, ptype="subscribed") | ||||||
|  |       self.xmpp.send_presence(pto = userJID) | ||||||
|  |       self.xmpp.send_presence_subscription(pto=userJID, ptype="subscribe") | ||||||
|  |  | ||||||
|  |   def handleMessageAddedToBackend(self, message) : | ||||||
|  |     body = message.user + ": " + message.text | ||||||
|  |     for subscriberJID in self.backend.getSubscriberJIDs(message.user) : | ||||||
|  |       self.xmpp.send_message(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 Slixmpp 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 | ||||||
|  | Slixmpp. 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.send_presence_subscription( | ||||||
|  |           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>`_ | ||||||
							
								
								
									
										182
									
								
								examples/IoT_TestDevice.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										182
									
								
								examples/IoT_TestDevice.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,182 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick 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 Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from os.path import basename, join as pjoin | ||||||
|  | from argparse import ArgumentParser | ||||||
|  | from urllib import urlopen | ||||||
|  | from getpass import getpass | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.plugins.xep_0323.device import Device | ||||||
|  |  | ||||||
|  | #from slixmpp.exceptions import IqError, IqTimeout | ||||||
|  |  | ||||||
|  | class IoT_TestDevice(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple IoT device that can act as server or client | ||||||
|  |     """ | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         slixmpp.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 | ||||||
|  |  | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |     parser.add_argument("-t", "--pingto", help="set jid to ping", | ||||||
|  |                         action="store", type="string", dest="pingjid", | ||||||
|  |                         default=None) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     # IoT test | ||||||
|  |     parser.add_argument("-c", "--sensorjid", dest="sensorjid", | ||||||
|  |                         help="Another device to call for data on", default=None) | ||||||
|  |     parser.add_argument("-n", "--nodeid", dest="nodeid", | ||||||
|  |                         help="I am a device get ready to be called", default=None) | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |      # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     xmpp = IoT_TestDevice(args.jid,args.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 args.nodeid: | ||||||
|  |  | ||||||
|  |         # xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn', | ||||||
|  |         # node=args.nodeid, | ||||||
|  |         # jid=xmpp.boundjid.full) | ||||||
|  |  | ||||||
|  |         myDevice = TheDevice(args.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=args.nodeid, device=myDevice, commTimeout=10); | ||||||
|  |         xmpp.beClientOrServer(server=True) | ||||||
|  |         while not(xmpp.testForRelease()): | ||||||
|  |             xmpp.connect() | ||||||
|  |             xmpp.process(block=True) | ||||||
|  |             logging.debug("lost connection") | ||||||
|  |     if args.sensorjid: | ||||||
|  |         logging.debug("will try to call another device for data") | ||||||
|  |         xmpp.beClientOrServer(server=False,clientJID=args.sensorjid) | ||||||
|  |         xmpp.connect() | ||||||
|  |         xmpp.process(block=True) | ||||||
|  |         logging.debug("ready ending") | ||||||
|  |  | ||||||
|  |     else: | ||||||
|  |        print("noopp didn't happen") | ||||||
|  |  | ||||||
							
								
								
									
										173
									
								
								examples/adhoc_provider.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										173
									
								
								examples/adhoc_provider.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,173 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CommandBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that provides a basic | ||||||
|  |     adhoc command. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |     def start(self, event): | ||||||
|  |         """ | ||||||
|  |         Process the session_start event. | ||||||
|  |  | ||||||
|  |         Typical actions for the session_start event are | ||||||
|  |         requesting the roster and broadcasting an initial | ||||||
|  |         presence stanza. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             event -- An empty dictionary. The session_start | ||||||
|  |                      event does not provide any additional | ||||||
|  |                      data. | ||||||
|  |         """ | ||||||
|  |         self.send_presence() | ||||||
|  |         self.get_roster() | ||||||
|  |  | ||||||
|  |         # We add the command after session_start has fired | ||||||
|  |         # to ensure that the correct full JID is used. | ||||||
|  |  | ||||||
|  |         # If using a component, may also pass jid keyword parameter. | ||||||
|  |  | ||||||
|  |         self['xep_0050'].add_command(node='greeting', | ||||||
|  |                                      name='Greeting', | ||||||
|  |                                      handler=self._handle_command) | ||||||
|  |  | ||||||
|  |     def _handle_command(self, iq, session): | ||||||
|  |         """ | ||||||
|  |         Respond to the initial request for a command. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             iq      -- The iq stanza containing the command request. | ||||||
|  |             session -- A dictionary of data relevant to the command | ||||||
|  |                        session. Additional, custom data may be saved | ||||||
|  |                        here to persist across handler callbacks. | ||||||
|  |         """ | ||||||
|  |         form = self['xep_0004'].make_form('form', 'Greeting') | ||||||
|  |         form['instructions'] = 'Send a custom greeting to a JID' | ||||||
|  |         form.addField(var='greeting', | ||||||
|  |                       ftype='text-single', | ||||||
|  |                       label='Your greeting') | ||||||
|  |  | ||||||
|  |         session['payload'] = form | ||||||
|  |         session['next'] = self._handle_command_complete | ||||||
|  |         session['has_next'] = False | ||||||
|  |  | ||||||
|  |         # Other useful session values: | ||||||
|  |         # session['to']                    -- The JID that received the | ||||||
|  |         #                                     command request. | ||||||
|  |         # session['from']                  -- The JID that sent the | ||||||
|  |         #                                     command request. | ||||||
|  |         # session['has_next'] = True       -- There are more steps to complete | ||||||
|  |         # session['allow_complete'] = True -- Allow user to finish immediately | ||||||
|  |         #                                     and possibly skip steps | ||||||
|  |         # session['cancel'] = handler      -- Assign a handler for if the user | ||||||
|  |         #                                     cancels the command. | ||||||
|  |         # session['notes'] = [             -- Add informative notes about the | ||||||
|  |         #   ('info', 'Info message'),         command's results. | ||||||
|  |         #   ('warning', 'Warning message'), | ||||||
|  |         #   ('error', 'Error message')] | ||||||
|  |  | ||||||
|  |         return session | ||||||
|  |  | ||||||
|  |     def _handle_command_complete(self, payload, session): | ||||||
|  |         """ | ||||||
|  |         Process a command result from the user. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             payload -- Either a single item, such as a form, or a list | ||||||
|  |                        of items or forms if more than one form was | ||||||
|  |                        provided to the user. The payload may be any | ||||||
|  |                        stanza, such as jabber:x:oob for out of band | ||||||
|  |                        data, or jabber:x:data for typical data forms. | ||||||
|  |             session -- A dictionary of data relevant to the command | ||||||
|  |                        session. Additional, custom data may be saved | ||||||
|  |                        here to persist across handler callbacks. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         # In this case (as is typical), the payload is a form | ||||||
|  |         form = payload | ||||||
|  |  | ||||||
|  |         greeting = form['values']['greeting'] | ||||||
|  |  | ||||||
|  |         self.send_message(mto=session['from'], | ||||||
|  |                           mbody="%s, World!" % greeting, | ||||||
|  |                           mtype='chat') | ||||||
|  |  | ||||||
|  |         # Having no return statement is the same as unsetting the 'payload' | ||||||
|  |         # and 'next' session values and returning the session. | ||||||
|  |  | ||||||
|  |         # Unless it is the final step, always return the session dictionary. | ||||||
|  |  | ||||||
|  |         session['payload'] = None | ||||||
|  |         session['next'] = None | ||||||
|  |  | ||||||
|  |         return session | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     # Setup the CommandBot and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = CommandBot(args.jid, args.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}) | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										179
									
								
								examples/adhoc_user.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										179
									
								
								examples/adhoc_user.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,179 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CommandUserBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that uses the adhoc command | ||||||
|  |     provided by the adhoc_provider.py example. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, other, greeting): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.command_provider = other | ||||||
|  |         self.greeting = greeting | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |         self.add_event_handler("message", self.message) | ||||||
|  |  | ||||||
|  |     def start(self, event): | ||||||
|  |         """ | ||||||
|  |         Process the session_start event. | ||||||
|  |  | ||||||
|  |         Typical actions for the session_start event are | ||||||
|  |         requesting the roster and broadcasting an initial | ||||||
|  |         presence stanza. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             event -- An empty dictionary. The session_start | ||||||
|  |                      event does not provide any additional | ||||||
|  |                      data. | ||||||
|  |         """ | ||||||
|  |         self.send_presence() | ||||||
|  |         self.get_roster() | ||||||
|  |  | ||||||
|  |         # We first create a session dictionary containing: | ||||||
|  |         #   'next'  -- the handler to execute on a successful response | ||||||
|  |         #   'error' -- the handler to execute if an error occurs | ||||||
|  |  | ||||||
|  |         # The session may also contain custom data. | ||||||
|  |  | ||||||
|  |         session = {'greeting': self.greeting, | ||||||
|  |                    'next': self._command_start, | ||||||
|  |                    'error': self._command_error} | ||||||
|  |  | ||||||
|  |         self['xep_0050'].start_command(jid=self.command_provider, | ||||||
|  |                                        node='greeting', | ||||||
|  |                                        session=session) | ||||||
|  |  | ||||||
|  |     def message(self, msg): | ||||||
|  |         """ | ||||||
|  |         Process incoming message stanzas. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             msg -- The received message stanza. | ||||||
|  |         """ | ||||||
|  |         logging.info(msg['body']) | ||||||
|  |  | ||||||
|  |     def _command_start(self, iq, session): | ||||||
|  |         """ | ||||||
|  |         Process the initial command result. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             iq      -- The iq stanza containing the command result. | ||||||
|  |             session -- A dictionary of data relevant to the command | ||||||
|  |                        session. Additional, custom data may be saved | ||||||
|  |                        here to persist across handler callbacks. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         # The greeting command provides a form with a single field: | ||||||
|  |         # <x xmlns="jabber:x:data" type="form"> | ||||||
|  |         #   <field var="greeting" | ||||||
|  |         #          type="text-single" | ||||||
|  |         #          label="Your greeting" /> | ||||||
|  |         # </x> | ||||||
|  |  | ||||||
|  |         form = self['xep_0004'].make_form(ftype='submit') | ||||||
|  |         form.addField(var='greeting', | ||||||
|  |                       value=session['greeting']) | ||||||
|  |  | ||||||
|  |         session['payload'] = form | ||||||
|  |  | ||||||
|  |         # We don't need to process the next result. | ||||||
|  |         session['next'] = None | ||||||
|  |  | ||||||
|  |         # Other options include using: | ||||||
|  |         # continue_command() -- Continue to the next step in the workflow | ||||||
|  |         # cancel_command()   -- Stop command execution. | ||||||
|  |  | ||||||
|  |         self['xep_0050'].complete_command(session) | ||||||
|  |  | ||||||
|  |     def _command_error(self, iq, session): | ||||||
|  |         """ | ||||||
|  |         Process an error that occurs during command execution. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             iq      -- The iq stanza containing the error. | ||||||
|  |             session -- A dictionary of data relevant to the command | ||||||
|  |                        session. Additional, custom data may be saved | ||||||
|  |                        here to persist across handler callbacks. | ||||||
|  |         """ | ||||||
|  |         logging.error("COMMAND: %s %s" % (iq['error']['condition'], | ||||||
|  |                                           iq['error']['text'])) | ||||||
|  |  | ||||||
|  |         # Terminate the command's execution and clear its session. | ||||||
|  |         # The session will automatically be cleared if no error | ||||||
|  |         # handler is provided. | ||||||
|  |         self['xep_0050'].terminate_command(session) | ||||||
|  |         self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("-o", "--other", dest="other", | ||||||
|  |                         help="JID providing commands") | ||||||
|  |     parser.add_argument("-g", "--greeting", dest="greeting", | ||||||
|  |                         help="Greeting") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.other is None: | ||||||
|  |         args.other = input("JID Providing Commands: ") | ||||||
|  |     if args.greeting is None: | ||||||
|  |         args.greeting = input("Greeting: ") | ||||||
|  |  | ||||||
|  |     # Setup the CommandBot and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = CommandUserBot(args.jid, args.password, args.other, args.greeting) | ||||||
|  |     xmpp.register_plugin('xep_0030') # Service Discovery | ||||||
|  |     xmpp.register_plugin('xep_0004') # Data Forms | ||||||
|  |     xmpp.register_plugin('xep_0050') # Adhoc Commands | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										147
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										147
									
								
								examples/admin_commands.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,147 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AdminCommands(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that uses admin commands to | ||||||
|  |     add a new user to a server. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, command): | ||||||
|  |         slixmpp.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] = 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] = 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. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("-c", "--command", dest="command", | ||||||
|  |                         help="admin command to use") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.command is None: | ||||||
|  |         args.command = 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(args.jid, args.password, args.command) | ||||||
|  |     xmpp.register_plugin('xep_0133') # Service Administration | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
| @@ -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.") |  | ||||||
							
								
								
									
										100
									
								
								examples/confirm_answer.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										100
									
								
								examples/confirm_answer.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2015 Emmanuel Gil Peyrot | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.exceptions import XMPPError | ||||||
|  | from slixmpp import asyncio | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AnswerConfirm(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A basic client demonstrating how to confirm or deny an HTTP request. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, trusted): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.add_event_handler("http_confirm", self.confirm) | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |     def start(self, *args): | ||||||
|  |         self.make_presence().send() | ||||||
|  |  | ||||||
|  |     def prompt(self, stanza): | ||||||
|  |         confirm = stanza['confirm'] | ||||||
|  |         print('Received confirm request %s from %s to access %s using ' | ||||||
|  |                  'method %s' % ( | ||||||
|  |                      confirm['id'], stanza['from'], confirm['url'], | ||||||
|  |                      confirm['method']) | ||||||
|  |                 ) | ||||||
|  |         result = input("Do you accept (y/N)? ") | ||||||
|  |         return 'y' == result.lower() | ||||||
|  |  | ||||||
|  |     def confirm(self, stanza): | ||||||
|  |         if self.prompt(stanza): | ||||||
|  |             reply = stanza.reply() | ||||||
|  |         else: | ||||||
|  |             reply = stanza.reply() | ||||||
|  |             reply.enable('error') | ||||||
|  |             reply['error']['type'] = 'auth' | ||||||
|  |             reply['error']['code'] = '401' | ||||||
|  |             reply['error']['condition'] = 'not-authorized' | ||||||
|  |         reply.append(stanza['confirm']) | ||||||
|  |         reply.send() | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.ERROR, | ||||||
|  |                         default=logging.INFO) | ||||||
|  |     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, | ||||||
|  |                         default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     # Other options. | ||||||
|  |     parser.add_argument("-t", "--trusted", nargs='*', | ||||||
|  |                         help="List of trusted JIDs") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     xmpp = AnswerConfirm(args.jid, args.password, args.trusted) | ||||||
|  |     xmpp.register_plugin('xep_0070') | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										125
									
								
								examples/confirm_ask.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										125
									
								
								examples/confirm_ask.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,125 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2015 Emmanuel Gil Peyrot | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.exceptions import XMPPError, IqError | ||||||
|  | from slixmpp import asyncio | ||||||
|  |  | ||||||
|  | log = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AskConfirm(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A basic client asking an entity if they confirm the access to an HTTP URL. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, recipient, id, url, method): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.recipient = recipient | ||||||
|  |         self.id = id | ||||||
|  |         self.url = url | ||||||
|  |         self.method = method | ||||||
|  |  | ||||||
|  |         # Will be used to set the proper exit code. | ||||||
|  |         self.confirmed = asyncio.Future() | ||||||
|  |  | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |         self.add_event_handler("message", self.start) | ||||||
|  |         self.add_event_handler("http_confirm_message", self.confirm) | ||||||
|  |  | ||||||
|  |     def confirm(self, message): | ||||||
|  |         print(message) | ||||||
|  |         if message['confirm']['id'] == self.id: | ||||||
|  |             if message['type'] == 'error': | ||||||
|  |                 self.confirmed.set_result(False) | ||||||
|  |             else: | ||||||
|  |                 self.confirmed.set_result(True) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def start(self, event): | ||||||
|  |         log.info('Sending confirm request %s to %s who wants to access %s using ' | ||||||
|  |                  'method %s...' % (self.id, self.recipient, self.url, self.method)) | ||||||
|  |         try: | ||||||
|  |             confirmed = yield from self['xep_0070'].ask_confirm(self.recipient, | ||||||
|  |                                                                 id=self.id, | ||||||
|  |                                                                 url=self.url, | ||||||
|  |                                                                 method=self.method, | ||||||
|  |                                                                 message='Plz say yes or no for {method} {url} ({id}).') | ||||||
|  |             if isinstance(confirmed, slixmpp.Message): | ||||||
|  |                 confirmed = yield from self.confirmed | ||||||
|  |             else: | ||||||
|  |                 confirmed = True | ||||||
|  |         except IqError: | ||||||
|  |             confirmed = False | ||||||
|  |         if confirmed: | ||||||
|  |             print('Confirmed') | ||||||
|  |         else: | ||||||
|  |             print('Denied') | ||||||
|  |         self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.ERROR, | ||||||
|  |                         default=logging.INFO) | ||||||
|  |     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, | ||||||
|  |                         default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     # Other options. | ||||||
|  |     parser.add_argument("-r", "--recipient", required=True, | ||||||
|  |                         help="Recipient JID") | ||||||
|  |     parser.add_argument("-i", "--id", required=True, | ||||||
|  |                         help="id TODO") | ||||||
|  |     parser.add_argument("-u", "--url", required=True, | ||||||
|  |                         help="URL the user tried to access") | ||||||
|  |     parser.add_argument("-m", "--method", required=True, | ||||||
|  |                         help="HTTP method used") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id, | ||||||
|  |                       args.url, args.method) | ||||||
|  |     xmpp.register_plugin('xep_0070') | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process(forever=False) | ||||||
|  |     sys.exit(0 if xmpp.confirmed else 1) | ||||||
							
								
								
									
										137
									
								
								examples/custom_stanzas/custom_stanza_provider.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										137
									
								
								examples/custom_stanzas/custom_stanza_provider.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  | from slixmpp import ClientXMPP, Iq | ||||||
|  | from slixmpp.exceptions import IqError, IqTimeout, XMPPError | ||||||
|  | from slixmpp.xmlstream import register_stanza_plugin | ||||||
|  | from slixmpp.xmlstream.handler import Callback | ||||||
|  | from slixmpp.xmlstream.matcher import StanzaPath | ||||||
|  | from stanza import Action | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ActionBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that receives a custom stanza | ||||||
|  |     from another client. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |         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) | ||||||
|  |  | ||||||
|  |         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. | ||||||
|  |         """ | ||||||
|  |         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. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     # Setup the CommandBot and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = ActionBot(args.jid, args.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}) | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										142
									
								
								examples/custom_stanzas/custom_stanza_user.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										142
									
								
								examples/custom_stanzas/custom_stanza_user.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,142 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp import Iq | ||||||
|  | from slixmpp.exceptions import XMPPError | ||||||
|  | from slixmpp.xmlstream import register_stanza_plugin | ||||||
|  |  | ||||||
|  | from stanza import Action | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ActionUserBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that sends a custom action stanza | ||||||
|  |     to another client. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, other): | ||||||
|  |         slixmpp.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) | ||||||
|  |         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) | ||||||
|  |  | ||||||
|  |                 self.disconnect() | ||||||
|  |         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. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("-o", "--other", dest="other", | ||||||
|  |                         help="JID providing custom stanza") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.other is None: | ||||||
|  |         args.other = 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(args.jid, args.password, args.other) | ||||||
|  |     xmpp.register_plugin('xep_0030') # Service Discovery | ||||||
|  |     xmpp.register_plugin('xep_0004') # Data Forms | ||||||
|  |     xmpp.register_plugin('xep_0050') # Adhoc Commands | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										56
									
								
								examples/custom_stanzas/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								examples/custom_stanzas/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | from slixmpp.xmlstream import ElementBase | ||||||
|  |  | ||||||
|  | class Action(ElementBase): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A stanza class for XML content of the form: | ||||||
|  |  | ||||||
|  |     <action xmlns="slixmpp: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 = 'slixmpp: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="slixmpp:custom:actions"> | ||||||
|  |     #:       <method>foo</method> | ||||||
|  |     #:     </action> | ||||||
|  |     sub_interfaces = interfaces | ||||||
							
								
								
									
										161
									
								
								examples/disco_browser.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										161
									
								
								examples/disco_browser.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,161 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.exceptions import IqError, IqTimeout | ||||||
|  | from slixmpp.xmlstream.asyncio import asyncio | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Disco(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A demonstration for using basic service discovery. | ||||||
|  |  | ||||||
|  |     Send a disco#info and disco#items request to a JID/node combination, | ||||||
|  |     and print out the results. | ||||||
|  |  | ||||||
|  |     May also request only particular info categories such as just features, | ||||||
|  |     or just items. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, target_jid, target_node='', get=''): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         # Using service discovery requires the XEP-0030 plugin. | ||||||
|  |         self.register_plugin('xep_0030') | ||||||
|  |  | ||||||
|  |         self.get = get | ||||||
|  |         self.target_jid = target_jid | ||||||
|  |         self.target_node = target_node | ||||||
|  |  | ||||||
|  |         # Values to control which disco entities are reported | ||||||
|  |         self.info_types = ['', 'all', 'info', 'identities', 'features'] | ||||||
|  |         self.identity_types = ['', 'all', 'info', 'identities'] | ||||||
|  |         self.feature_types = ['', 'all', 'info', 'features'] | ||||||
|  |         self.items_types = ['', 'all', 'items'] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def start(self, event): | ||||||
|  |         """ | ||||||
|  |         Process the session_start event. | ||||||
|  |  | ||||||
|  |         Typical actions for the session_start event are | ||||||
|  |         requesting the roster and broadcasting an initial | ||||||
|  |         presence stanza. | ||||||
|  |  | ||||||
|  |         In this case, we send disco#info and disco#items | ||||||
|  |         stanzas to the requested JID and print the results. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             event -- An empty dictionary. The session_start | ||||||
|  |                      event does not provide any additional | ||||||
|  |                      data. | ||||||
|  |         """ | ||||||
|  |         self.get_roster() | ||||||
|  |         self.send_presence() | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             if self.get in self.info_types: | ||||||
|  |                 # function using the callback parameter. | ||||||
|  |                 info = yield from self['xep_0030'].get_info(jid=self.target_jid, | ||||||
|  |                                                             node=self.target_node) | ||||||
|  |             if self.get in self.items_types: | ||||||
|  |                 # The same applies from above. Listen for the | ||||||
|  |                 # disco_items event or pass a callback function | ||||||
|  |                 # if you need to process a non-blocking request. | ||||||
|  |                 items = yield from self['xep_0030'].get_items(jid=self.target_jid, | ||||||
|  |                                                               node=self.target_node) | ||||||
|  |             if self.get not in self.info_types and self.get not in self.items_types: | ||||||
|  |                 logging.error("Invalid disco request type.") | ||||||
|  |                 return | ||||||
|  |         except IqError as e: | ||||||
|  |             logging.error("Entity returned an error: %s" % e.iq['error']['condition']) | ||||||
|  |         except IqTimeout: | ||||||
|  |             logging.error("No response received.") | ||||||
|  |         else: | ||||||
|  |             header = 'XMPP Service Discovery: %s' % self.target_jid | ||||||
|  |             print(header) | ||||||
|  |             print('-' * len(header)) | ||||||
|  |             if self.target_node != '': | ||||||
|  |                 print('Node: %s' % self.target_node) | ||||||
|  |                 print('-' * len(header)) | ||||||
|  |  | ||||||
|  |             if self.get in self.identity_types: | ||||||
|  |                 print('Identities:') | ||||||
|  |                 for identity in info['disco_info']['identities']: | ||||||
|  |                     print('  - %s' % str(identity)) | ||||||
|  |  | ||||||
|  |             if self.get in self.feature_types: | ||||||
|  |                 print('Features:') | ||||||
|  |                 for feature in info['disco_info']['features']: | ||||||
|  |                     print('  - %s' % feature) | ||||||
|  |  | ||||||
|  |             if self.get in self.items_types: | ||||||
|  |                 print('Items:') | ||||||
|  |                 for item in items['disco_items']['items']: | ||||||
|  |                     print('  - %s' % str(item)) | ||||||
|  |         finally: | ||||||
|  |             self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser(description=Disco.__doc__) | ||||||
|  |  | ||||||
|  |     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.ERROR, | ||||||
|  |                         default=logging.ERROR) | ||||||
|  |     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, | ||||||
|  |                         default=logging.ERROR) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("query", choices=["all", "info", "items", "identities", "features"]) | ||||||
|  |     parser.add_argument("target_jid") | ||||||
|  |     parser.add_argument("node", nargs='?') | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     # Setup the Disco browser. | ||||||
|  |     xmpp = Disco(args.jid, args.password, args.target_jid, args.node, args.query) | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process(forever=False) | ||||||
							
								
								
									
										165
									
								
								examples/download_avatars.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										165
									
								
								examples/download_avatars.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,165 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2012  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.exceptions import XMPPError | ||||||
|  | from slixmpp import asyncio | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FILE_TYPES = { | ||||||
|  |     'image/png': 'png', | ||||||
|  |     'image/gif': 'gif', | ||||||
|  |     'image/jpeg': 'jpg' | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AvatarDownloader(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A basic script for downloading the avatars for a user's contacts. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |         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 = asyncio.Event() | ||||||
|  |         self.roster_received = asyncio.Event() | ||||||
|  |  | ||||||
|  |     def roster_received_cb(self, event): | ||||||
|  |         self.roster_received.set() | ||||||
|  |         self.presences_received.clear() | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     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(callback=self.roster_received_cb) | ||||||
|  |  | ||||||
|  |         print('Waiting for presence updates...\n') | ||||||
|  |         yield from self.roster_received.wait() | ||||||
|  |         print('Roster received') | ||||||
|  |         yield from self.presences_received.wait() | ||||||
|  |         self.disconnect() | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def on_vcard_avatar(self, pres): | ||||||
|  |         print("Received vCard avatar update from %s" % pres['from'].bare) | ||||||
|  |         try: | ||||||
|  |             result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True, | ||||||
|  |                                                            timeout=5) | ||||||
|  |         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, 'wb+') as img: | ||||||
|  |             img.write(avatar['BINVAL']) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     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 = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'], | ||||||
|  |                                                                          timeout=5) | ||||||
|  |                 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, 'wb+') 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) | ||||||
|  |         print((len(self.received), len(self.client_roster.keys()))) | ||||||
|  |         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. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.ERROR, | ||||||
|  |                         default=logging.ERROR) | ||||||
|  |     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, | ||||||
|  |                         default=logging.ERROR) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     xmpp = AvatarDownloader(args.jid, args.password) | ||||||
|  |     xmpp.register_plugin('xep_0054') | ||||||
|  |     xmpp.register_plugin('xep_0153') | ||||||
|  |     xmpp.register_plugin('xep_0084') | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
| @@ -1,44 +1,35 @@ | |||||||
| #!/usr/bin/env python | #!/usr/bin/env python3 | ||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
| """ | """ | ||||||
|     SleekXMPP: The Sleek XMPP Library |     Slixmpp: The Slick XMPP Library | ||||||
|     Copyright (C) 2010  Nathanael C. Fritz |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|     This file is part of SleekXMPP. |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|     See the file LICENSE for copying permission. |     See the file LICENSE for copying permission. | ||||||
| """ | """ | ||||||
|  |  | ||||||
| import sys |  | ||||||
| import logging | import logging | ||||||
| import time | from getpass import getpass | ||||||
| from optparse import OptionParser | from argparse import ArgumentParser | ||||||
|  |  | ||||||
| import sleekxmpp | import slixmpp | ||||||
|  |  | ||||||
| # 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 EchoBot(sleekxmpp.ClientXMPP): | class EchoBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|     """ |     """ | ||||||
|     A simple SleekXMPP bot that will echo messages it |     A simple Slixmpp bot that will echo messages it | ||||||
|     receives, along with a short thank you message. |     receives, along with a short thank you message. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, jid, password): |     def __init__(self, jid, password): | ||||||
|         sleekxmpp.ClientXMPP.__init__(self, jid, password) |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|         # 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) | ||||||
|  |  | ||||||
| @@ -52,7 +43,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: | ||||||
| @@ -60,8 +51,8 @@ class EchoBot(sleekxmpp.ClientXMPP): | |||||||
|                      event does not provide any additional |                      event does not provide any additional | ||||||
|                      data. |                      data. | ||||||
|         """ |         """ | ||||||
|         self.getRoster() |         self.send_presence() | ||||||
|         self.sendPresence() |         self.get_roster() | ||||||
|  |  | ||||||
|     def message(self, msg): |     def message(self, msg): | ||||||
|         """ |         """ | ||||||
| @@ -75,55 +66,48 @@ 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__': | ||||||
|     # Setup the command line arguments. |     # Setup the command line arguments. | ||||||
|     optp = OptionParser() |     parser = ArgumentParser(description=EchoBot.__doc__) | ||||||
|  |  | ||||||
|     # Output verbosity options. |     # Output verbosity options. | ||||||
|     optp.add_option('-q', '--quiet', help='set logging to ERROR', |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|                     action='store_const', dest='loglevel', |                         action="store_const", dest="loglevel", | ||||||
|                     const=logging.ERROR, default=logging.INFO) |                         const=logging.ERROR, default=logging.INFO) | ||||||
|     optp.add_option('-d', '--debug', help='set logging to DEBUG', |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|                     action='store_const', dest='loglevel', |                         action="store_const", dest="loglevel", | ||||||
|                     const=logging.DEBUG, default=logging.INFO) |                         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. |     # JID and password options. | ||||||
|     optp.add_option("-j", "--jid", dest="jid", |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|                     help="JID to use") |                         help="JID to use") | ||||||
|     optp.add_option("-p", "--password", dest="password", |     parser.add_argument("-p", "--password", dest="password", | ||||||
|                     help="password to use") |                         help="password to use") | ||||||
|  |  | ||||||
|     opts, args = optp.parse_args() |     args = parser.parse_args() | ||||||
|  |  | ||||||
|     # Setup logging. |     # Setup logging. | ||||||
|     logging.basicConfig(level=opts.loglevel, |     logging.basicConfig(level=args.loglevel, | ||||||
|                         format='%(levelname)-8s %(message)s') |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|     # Setup the EchoBot and register plugins. Note that while plugins may |     # Setup the EchoBot 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 | ||||||
|     # not matter. |     # not matter. | ||||||
|     xmpp = EchoBot(opts.jid, opts.password) |     xmpp = EchoBot(args.jid, args.password) | ||||||
|     xmpp.registerPlugin('xep_0030') # Service Discovery |     xmpp.register_plugin('xep_0030') # Service Discovery | ||||||
|     xmpp.registerPlugin('xep_0004') # Data Forms |     xmpp.register_plugin('xep_0004') # Data Forms | ||||||
|     xmpp.registerPlugin('xep_0060') # PubSub |     xmpp.register_plugin('xep_0060') # PubSub | ||||||
|     xmpp.registerPlugin('xep_0199') # XMPP Ping |     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||||
|  |  | ||||||
|     # Connect to the XMPP server and start processing XMPP stanzas. |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|     if xmpp.connect(): |     xmpp.connect() | ||||||
|         # If you do not have the pydns library installed, you will need |     xmpp.process() | ||||||
|         # to manually specify the name of the server if it does not match |  | ||||||
|         # the one in the JID. For example, to use Google Talk you would |  | ||||||
|         # need to use: |  | ||||||
|         # |  | ||||||
|         # if xmpp.connect(('talk.google.com', 5222)): |  | ||||||
|         #     ... |  | ||||||
|         xmpp.process(threaded=False) |  | ||||||
|         print("Done") |  | ||||||
|     else: |  | ||||||
|         print("Unable to connect.") |  | ||||||
|   | |||||||
							
								
								
									
										105
									
								
								examples/echo_component.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										105
									
								
								examples/echo_component.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,105 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.componentxmpp import ComponentXMPP | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class EchoComponent(ComponentXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp 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. | ||||||
|  |     parser = ArgumentParser(description=EchoComponent.__doc__) | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("-s", "--server", dest="server", | ||||||
|  |                         help="server to connect to") | ||||||
|  |     parser.add_argument("-P", "--port", dest="port", | ||||||
|  |                         help="port to connect to") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Component JID: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.server is None: | ||||||
|  |         args.server = input("Server: ") | ||||||
|  |     if args.port is None: | ||||||
|  |         args.port = int(input("Port: ")) | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.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(args.jid, args.password, args.server, args.port) | ||||||
|  |     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 | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										133
									
								
								examples/gtalk_custom_domain.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										133
									
								
								examples/gtalk_custom_domain.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  | import ssl | ||||||
|  | from slixmpp.xmlstream import cert | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GTalkBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A demonstration of using Slixmpp with accounts from a Google Apps | ||||||
|  |     account with a custom domain, because it requires custom certificate | ||||||
|  |     validation. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |         # The message event is triggered whenever a message | ||||||
|  |         # stanza is received. Be aware that that includes | ||||||
|  |         # MUC messages and error messages. | ||||||
|  |         self.add_event_handler("message", self.message) | ||||||
|  |  | ||||||
|  |         # 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. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     # Setup the GTalkBot and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = GTalkBot(args.jid, args.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 | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										97
									
								
								examples/http_over_xmpp.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								examples/http_over_xmpp.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Implementation of HTTP over XMPP transport | ||||||
|  |     http://xmpp.org/extensions/xep-0332.html | ||||||
|  |     Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com | ||||||
|  |     This file is part of slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from slixmpp import ClientXMPP | ||||||
|  |  | ||||||
|  | from optparse import OptionParser | ||||||
|  | import logging | ||||||
|  | import getpass | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class HTTPOverXMPPClient(ClientXMPP): | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         ClientXMPP.__init__(self, jid, password) | ||||||
|  |         self.register_plugin('xep_0332')    # HTTP over XMPP Transport | ||||||
|  |         self.add_event_handler( | ||||||
|  |             'session_start', self.session_start, threaded=True | ||||||
|  |         ) | ||||||
|  |         self.add_event_handler('http_request', self.http_request_received) | ||||||
|  |         self.add_event_handler('http_response', self.http_response_received) | ||||||
|  |  | ||||||
|  |     def http_request_received(self, iq): | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     def http_response_received(self, iq): | ||||||
|  |         print('HTTP Response Received : %s' % iq) | ||||||
|  |         print('From    : %s' %  iq['from']) | ||||||
|  |         print('To      : %s' % iq['to']) | ||||||
|  |         print('Type    : %s' % iq['type']) | ||||||
|  |         print('Headers : %s' % iq['resp']['headers']) | ||||||
|  |         print('Code    : %s' % iq['resp']['code']) | ||||||
|  |         print('Message : %s' % iq['resp']['message']) | ||||||
|  |         print('Data    : %s' % iq['resp']['data']) | ||||||
|  |  | ||||||
|  |     def session_start(self, event): | ||||||
|  |         # TODO: Fill in the blanks | ||||||
|  |         self['xep_0332'].send_request( | ||||||
|  |             to='?', method='?', resource='?', headers={} | ||||||
|  |         ) | ||||||
|  |         self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |  | ||||||
|  |     # | ||||||
|  |     # NOTE: To run this example, fill up the blanks in session_start() and | ||||||
|  |     #       use the following command. | ||||||
|  |     # | ||||||
|  |     # ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v] | ||||||
|  |     # | ||||||
|  |  | ||||||
|  |     parser = OptionParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_option( | ||||||
|  |         '-v', '--verbose', help='set logging to DEBUG', action='store_const', | ||||||
|  |         dest='loglevel', const=logging.DEBUG, default=logging.ERROR | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_option('-J', '--jid', dest='jid', help='JID') | ||||||
|  |     parser.add_option('-P', '--password', dest='password', help='Password') | ||||||
|  |  | ||||||
|  |     # XMPP server ip and port options. | ||||||
|  |     parser.add_option( | ||||||
|  |         '-i', '--ipaddr', dest='ipaddr', | ||||||
|  |         help='IP Address of the XMPP server', default=None | ||||||
|  |     ) | ||||||
|  |     parser.add_option( | ||||||
|  |         '-p', '--port', dest='port', | ||||||
|  |         help='Port of the XMPP server', default=None | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     opts, args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=opts.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if opts.jid is None: | ||||||
|  |         opts.jid = input('Username: ') | ||||||
|  |     if opts.password is None: | ||||||
|  |         opts.password = getpass.getpass('Password: ') | ||||||
|  |  | ||||||
|  |     xmpp = HTTPOverXMPPClient(opts.jid, opts.password) | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
|  |  | ||||||
							
								
								
									
										112
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										112
									
								
								examples/ibb_transfer/ibb_receiver.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,112 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IBBReceiver(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A basic example of creating and using an in-band bytestream. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, filename): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.file = open(filename, 'wb') | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |         self.add_event_handler("ibb_stream_start", self.stream_opened) | ||||||
|  |         self.add_event_handler("ibb_stream_data", self.stream_data) | ||||||
|  |         self.add_event_handler("ibb_stream_end", self.stream_closed) | ||||||
|  |  | ||||||
|  |     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 stream_opened(self, stream): | ||||||
|  |         print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid)) | ||||||
|  |  | ||||||
|  |     def stream_data(self, stream): | ||||||
|  |         self.file.write(stream.read()) | ||||||
|  |  | ||||||
|  |     def stream_closed(self, stream): | ||||||
|  |         print('Stream closed: %s from %s' % (stream.sid, stream.peer_jid)) | ||||||
|  |         self.file.close() | ||||||
|  |         self.disconnect() | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("-o", "--out", dest="filename", | ||||||
|  |                         help="file to save to") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.filename is None: | ||||||
|  |         args.filename = input("File path: ") | ||||||
|  |  | ||||||
|  |     # Setup the IBBReceiver and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = IBBReceiver(args.jid, args.password, args.filename) | ||||||
|  |     xmpp.register_plugin('xep_0030') # Service Discovery | ||||||
|  |     xmpp.register_plugin('xep_0047', { | ||||||
|  |         'auto_accept': True | ||||||
|  |     }) # In-band Bytestreams | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process(forever=False) | ||||||
							
								
								
									
										125
									
								
								examples/ibb_transfer/ibb_sender.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										125
									
								
								examples/ibb_transfer/ibb_sender.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,125 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.exceptions import IqError, IqTimeout | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IBBSender(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A basic example of creating and using an in-band bytestream. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, receiver, filename, use_messages=False): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.receiver = receiver | ||||||
|  |  | ||||||
|  |         self.file = open(filename, 'rb') | ||||||
|  |         self.use_messages = use_messages | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # 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) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     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() | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             # Open the IBB stream in which to write to. | ||||||
|  |             stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages) | ||||||
|  |  | ||||||
|  |             # If you want to send in-memory bytes, use stream.sendall() instead. | ||||||
|  |             yield from stream.sendfile(self.file, timeout=10) | ||||||
|  |  | ||||||
|  |             # And finally close the stream. | ||||||
|  |             yield from stream.close(timeout=10) | ||||||
|  |         except (IqError, IqTimeout): | ||||||
|  |             print('File transfer errored') | ||||||
|  |         else: | ||||||
|  |             print('File transfer finished') | ||||||
|  |         finally: | ||||||
|  |             self.file.close() | ||||||
|  |             self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("-r", "--receiver", dest="receiver", | ||||||
|  |                         help="JID of the receiver") | ||||||
|  |     parser.add_argument("-f", "--file", dest="filename", | ||||||
|  |                         help="file to send") | ||||||
|  |     parser.add_argument("-m", "--use-messages", action="store_true", | ||||||
|  |                         help="use messages instead of iqs for file transfer") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.receiver is None: | ||||||
|  |         args.receiver = input("Receiver: ") | ||||||
|  |     if args.filename is None: | ||||||
|  |         args.filename = input("File path: ") | ||||||
|  |  | ||||||
|  |     # Setup the IBBSender and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename, args.use_messages) | ||||||
|  |     xmpp.register_plugin('xep_0030') # Service Discovery | ||||||
|  |     xmpp.register_plugin('xep_0047') # In-band Bytestreams | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process(forever=False) | ||||||
							
								
								
									
										107
									
								
								examples/migrate_roster.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										107
									
								
								examples/migrate_roster.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Setup the command line arguments. | ||||||
|  | parser = ArgumentParser() | ||||||
|  |  | ||||||
|  | # Output verbosity options. | ||||||
|  | parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                     action="store_const", dest="loglevel", | ||||||
|  |                     const=logging.ERROR, default=logging.INFO) | ||||||
|  | parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                     action="store_const", dest="loglevel", | ||||||
|  |                     const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  | # JID and password options. | ||||||
|  | parser.add_argument("--oldjid", dest="old_jid", | ||||||
|  |                     help="JID of the old account") | ||||||
|  | parser.add_argument("--oldpassword", dest="old_password", | ||||||
|  |                     help="password of the old account") | ||||||
|  |  | ||||||
|  | parser.add_argument("--newjid", dest="new_jid", | ||||||
|  |                     help="JID of the old account") | ||||||
|  | parser.add_argument("--newpassword", dest="new_password", | ||||||
|  |                     help="password of the old account") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | args = parser.parse_args() | ||||||
|  |  | ||||||
|  | # Setup logging. | ||||||
|  | logging.basicConfig(level=args.loglevel, | ||||||
|  |                     format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  | if args.old_jid is None: | ||||||
|  |     args.old_jid = input("Old JID: ") | ||||||
|  | if args.old_password is None: | ||||||
|  |     args.old_password = getpass("Old Password: ") | ||||||
|  |  | ||||||
|  | if args.new_jid is None: | ||||||
|  |     args.new_jid = input("New JID: ") | ||||||
|  | if args.new_password is None: | ||||||
|  |     args.new_password = getpass("New Password: ") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | old_xmpp = slixmpp.ClientXMPP(args.old_jid, args.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 = slixmpp.ClientXMPP(args.new_jid, args.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) | ||||||
|  |  | ||||||
|  | new_xmpp.connect() | ||||||
|  | new_xmpp.process() | ||||||
							
								
								
									
										169
									
								
								examples/muc.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										169
									
								
								examples/muc.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,169 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MUCBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that will greets those | ||||||
|  |     who enter the room, and acknowledge any messages | ||||||
|  |     that mentions the bot's nickname. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, room, nick): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         self.room = room | ||||||
|  |         self.nick = nick | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |         # The groupchat_message event is triggered whenever a message | ||||||
|  |         # stanza is received from any chat room. If you also also | ||||||
|  |         # register a handler for the 'message' event, MUC messages | ||||||
|  |         # will be processed by both handlers. | ||||||
|  |         self.add_event_handler("groupchat_message", self.muc_message) | ||||||
|  |  | ||||||
|  |         # The groupchat_presence event is triggered whenever a | ||||||
|  |         # presence stanza is received from any chat room, including | ||||||
|  |         # any presences you send yourself. To limit event handling | ||||||
|  |         # to a single room, use the events muc::room@server::presence, | ||||||
|  |         # muc::room@server::got_online, or muc::room@server::got_offline. | ||||||
|  |         self.add_event_handler("muc::%s::got_online" % self.room, | ||||||
|  |                                self.muc_online) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def start(self, event): | ||||||
|  |         """ | ||||||
|  |         Process the session_start event. | ||||||
|  |  | ||||||
|  |         Typical actions for the session_start event are | ||||||
|  |         requesting the roster and broadcasting an initial | ||||||
|  |         presence stanza. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             event -- An empty dictionary. The session_start | ||||||
|  |                      event does not provide any additional | ||||||
|  |                      data. | ||||||
|  |         """ | ||||||
|  |         self.get_roster() | ||||||
|  |         self.send_presence() | ||||||
|  |         self.plugin['xep_0045'].join_muc(self.room, | ||||||
|  |                                          self.nick, | ||||||
|  |                                          # If a room password is needed, use: | ||||||
|  |                                          # password=the_room_password, | ||||||
|  |                                          wait=True) | ||||||
|  |  | ||||||
|  |     def muc_message(self, msg): | ||||||
|  |         """ | ||||||
|  |         Process incoming message stanzas from any chat room. Be aware | ||||||
|  |         that if you also have any handlers for the 'message' event, | ||||||
|  |         message stanzas may be processed by both handlers, so check | ||||||
|  |         the 'type' attribute when using a 'message' event handler. | ||||||
|  |  | ||||||
|  |         Whenever the bot's nickname is mentioned, respond to | ||||||
|  |         the message. | ||||||
|  |  | ||||||
|  |         IMPORTANT: Always check that a message is not from yourself, | ||||||
|  |                    otherwise you will create an infinite loop responding | ||||||
|  |                    to your own messages. | ||||||
|  |  | ||||||
|  |         This handler will reply to messages that mention | ||||||
|  |         the bot's nickname. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             msg -- The received message stanza. See the documentation | ||||||
|  |                    for stanza objects and the Message stanza to see | ||||||
|  |                    how it may be used. | ||||||
|  |         """ | ||||||
|  |         if msg['mucnick'] != self.nick and self.nick in msg['body']: | ||||||
|  |             self.send_message(mto=msg['from'].bare, | ||||||
|  |                               mbody="I heard that, %s." % msg['mucnick'], | ||||||
|  |                               mtype='groupchat') | ||||||
|  |  | ||||||
|  |     def muc_online(self, presence): | ||||||
|  |         """ | ||||||
|  |         Process a presence stanza from a chat room. In this case, | ||||||
|  |         presences from users that have just come online are | ||||||
|  |         handled by sending a welcome message that includes | ||||||
|  |         the user's nickname and role in the room. | ||||||
|  |  | ||||||
|  |         Arguments: | ||||||
|  |             presence -- The received presence stanza. See the | ||||||
|  |                         documentation for the Presence stanza | ||||||
|  |                         to see how else it may be used. | ||||||
|  |         """ | ||||||
|  |         if presence['muc']['nick'] != self.nick: | ||||||
|  |             self.send_message(mto=presence['from'].bare, | ||||||
|  |                               mbody="Hello, %s %s" % (presence['muc']['role'], | ||||||
|  |                                                       presence['muc']['nick']), | ||||||
|  |                               mtype='groupchat') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("-r", "--room", dest="room", | ||||||
|  |                         help="MUC room to join") | ||||||
|  |     parser.add_argument("-n", "--nick", dest="nick", | ||||||
|  |                         help="MUC nickname") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.room is None: | ||||||
|  |         args.room = input("MUC room: ") | ||||||
|  |     if args.nick is None: | ||||||
|  |         args.nick = input("MUC nickname: ") | ||||||
|  |  | ||||||
|  |     # Setup the MUCBot and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = MUCBot(args.jid, args.password, args.room, args.nick) | ||||||
|  |     xmpp.register_plugin('xep_0030') # Service Discovery | ||||||
|  |     xmpp.register_plugin('xep_0045') # Multi-User Chat | ||||||
|  |     xmpp.register_plugin('xep_0199') # XMPP Ping | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										114
									
								
								examples/ping.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										114
									
								
								examples/ping.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  | from slixmpp.exceptions import IqError, IqTimeout | ||||||
|  | from slixmpp import asyncio | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PingTest(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that will send a ping request | ||||||
|  |     to a given JID. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, pingjid): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |         if pingjid is None: | ||||||
|  |             pingjid = self.boundjid.bare | ||||||
|  |         self.pingjid = pingjid | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     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() | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             rtt = yield from self['xep_0199'].ping(self.pingjid, | ||||||
|  |                                                    timeout=10) | ||||||
|  |             logging.info("Success! RTT: %s", rtt) | ||||||
|  |         except IqError as e: | ||||||
|  |             logging.info("Error pinging %s: %s", | ||||||
|  |                     self.pingjid, | ||||||
|  |                     e.iq['error']['condition']) | ||||||
|  |         except IqTimeout: | ||||||
|  |             logging.info("No response from %s", self.pingjid) | ||||||
|  |         finally: | ||||||
|  |             self.disconnect() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |     parser.add_argument("-t", "--pingto", help="set jid to ping", | ||||||
|  |                         dest="pingjid", default=None) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     # Setup the PingTest and register plugins. Note that while plugins may | ||||||
|  |     # have interdependencies, the order in which you register them does | ||||||
|  |     # not matter. | ||||||
|  |     xmpp = PingTest(args.jid, args.password, args.pingjid) | ||||||
|  |     xmpp.register_plugin('xep_0030')  # Service Discovery | ||||||
|  |     xmpp.register_plugin('xep_0004')  # Data Forms | ||||||
|  |     xmpp.register_plugin('xep_0060')  # PubSub | ||||||
|  |     xmpp.register_plugin('xep_0199')  # XMPP Ping | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										135
									
								
								examples/proxy_echo_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										135
									
								
								examples/proxy_echo_client.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |     Slixmpp: The Slick XMPP Library | ||||||
|  |     Copyright (C) 2010  Nathanael C. Fritz | ||||||
|  |     This file is part of Slixmpp. | ||||||
|  |  | ||||||
|  |     See the file LICENSE for copying permission. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import slixmpp | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class EchoBot(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     """ | ||||||
|  |     A simple Slixmpp bot that will echo messages it | ||||||
|  |     receives, along with a short thank you message. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password): | ||||||
|  |         slixmpp.ClientXMPP.__init__(self, jid, password) | ||||||
|  |  | ||||||
|  |         # The session_start event will be triggered when | ||||||
|  |         # the bot establishes its connection with the server | ||||||
|  |         # and the XML streams are ready for use. We want to | ||||||
|  |         # listen for this event so that we we can initialize | ||||||
|  |         # our roster. | ||||||
|  |         self.add_event_handler("session_start", self.start) | ||||||
|  |  | ||||||
|  |         # The message event is triggered whenever a message | ||||||
|  |         # stanza is received. Be aware that that includes | ||||||
|  |         # MUC messages and error messages. | ||||||
|  |         self.add_event_handler("message", self.message) | ||||||
|  |  | ||||||
|  |     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. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |  | ||||||
|  |     # Output verbosity options. | ||||||
|  |     parser.add_argument("-q", "--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.ERROR, default=logging.INFO) | ||||||
|  |     parser.add_argument("-d", "--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |     parser.add_argument("--phost", dest="proxy_host", | ||||||
|  |                         help="Proxy hostname") | ||||||
|  |     parser.add_argument("--pport", dest="proxy_port", | ||||||
|  |                         help="Proxy port") | ||||||
|  |     parser.add_argument("--puser", dest="proxy_user", | ||||||
|  |                         help="Proxy username") | ||||||
|  |     parser.add_argument("--ppass", dest="proxy_pass", | ||||||
|  |                         help="Proxy password") | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |     if args.proxy_host is None: | ||||||
|  |         args.proxy_host = input("Proxy host: ") | ||||||
|  |     if args.proxy_port is None: | ||||||
|  |         args.proxy_port = input("Proxy port: ") | ||||||
|  |     if args.proxy_user is None: | ||||||
|  |         args.proxy_user = input("Proxy username: ") | ||||||
|  |     if args.proxy_pass is None and args.proxy_user: | ||||||
|  |         args.proxy_pass = 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(args.jid, args.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 | ||||||
|  |  | ||||||
|  |     xmpp.use_proxy = True | ||||||
|  |     xmpp.proxy_config = { | ||||||
|  |         'host': args.proxy_host, | ||||||
|  |         'port': int(args.proxy_port), | ||||||
|  |         'username': args.proxy_user, | ||||||
|  |         'password': args.proxy_pass} | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process() | ||||||
							
								
								
									
										166
									
								
								examples/pubsub_client.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										166
									
								
								examples/pubsub_client.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,166 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  | from getpass import getpass | ||||||
|  | from argparse import ArgumentParser | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  | import slixmpp | ||||||
|  | from slixmpp.exceptions import XMPPError | ||||||
|  | from slixmpp.xmlstream import ET, tostring | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PubsubClient(slixmpp.ClientXMPP): | ||||||
|  |  | ||||||
|  |     def __init__(self, jid, password, server, | ||||||
|  |                        node=None, action='nodes', data=''): | ||||||
|  |         super().__init__(jid, password) | ||||||
|  |  | ||||||
|  |         self.register_plugin('xep_0030') | ||||||
|  |         self.register_plugin('xep_0059') | ||||||
|  |         self.register_plugin('xep_0060') | ||||||
|  |  | ||||||
|  |         self.actions = ['nodes', 'create', 'delete', | ||||||
|  |                         '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) | ||||||
|  |  | ||||||
|  |     @asyncio.coroutine | ||||||
|  |     def start(self, event): | ||||||
|  |         self.get_roster() | ||||||
|  |         self.send_presence() | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             yield from getattr(self, self.action)() | ||||||
|  |         except: | ||||||
|  |             logging.error('Could not execute: %s', self.action) | ||||||
|  |         self.disconnect() | ||||||
|  |  | ||||||
|  |     def nodes(self): | ||||||
|  |         try: | ||||||
|  |             result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node) | ||||||
|  |             for item in result['disco_items']['items']: | ||||||
|  |                 logging.info('  - %s', str(item)) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not retrieve node list: %s', error.format()) | ||||||
|  |  | ||||||
|  |     def create(self): | ||||||
|  |         try: | ||||||
|  |             yield from self['xep_0060'].create_node(self.pubsub_server, self.node) | ||||||
|  |             logging.info('Created node %s', self.node) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not create node %s: %s', self.node, error.format()) | ||||||
|  |  | ||||||
|  |     def delete(self): | ||||||
|  |         try: | ||||||
|  |             yield from self['xep_0060'].delete_node(self.pubsub_server, self.node) | ||||||
|  |             logging.info('Deleted node %s', self.node) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not delete node %s: %s', self.node, error.format()) | ||||||
|  |  | ||||||
|  |     def publish(self): | ||||||
|  |         payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data) | ||||||
|  |         try: | ||||||
|  |             result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload) | ||||||
|  |             logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id']) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not publish to %s: %s', self.node, error.format()) | ||||||
|  |  | ||||||
|  |     def get(self): | ||||||
|  |         try: | ||||||
|  |             result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data) | ||||||
|  |             for item in result['pubsub']['items']['substanzas']: | ||||||
|  |                 logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload'])) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format()) | ||||||
|  |  | ||||||
|  |     def retract(self): | ||||||
|  |         try: | ||||||
|  |             yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data) | ||||||
|  |             logging.info('Retracted item %s from node %s', self.data, self.node) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format()) | ||||||
|  |  | ||||||
|  |     def purge(self): | ||||||
|  |         try: | ||||||
|  |             yield from self['xep_0060'].purge(self.pubsub_server, self.node) | ||||||
|  |             logging.info('Purged all items from node %s', self.node) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not purge items from node %s: %s', self.node, error.format()) | ||||||
|  |  | ||||||
|  |     def subscribe(self): | ||||||
|  |         try: | ||||||
|  |             iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node) | ||||||
|  |             subscription = iq['pubsub']['subscription'] | ||||||
|  |             logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node']) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format()) | ||||||
|  |  | ||||||
|  |     def unsubscribe(self): | ||||||
|  |         try: | ||||||
|  |             yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node) | ||||||
|  |             logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node) | ||||||
|  |         except XMPPError as error: | ||||||
|  |             logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     # Setup the command line arguments. | ||||||
|  |     parser = ArgumentParser() | ||||||
|  |     parser.version = '%%prog 0.1' | ||||||
|  |     parser.usage = "Usage: %%prog [options] <jid> " + \ | ||||||
|  |                              'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \ | ||||||
|  |                              ' [<node> <data>]' | ||||||
|  |  | ||||||
|  |     parser.add_argument("-q","--quiet", help="set logging to ERROR", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.ERROR, | ||||||
|  |                         default=logging.INFO) | ||||||
|  |     parser.add_argument("-d","--debug", help="set logging to DEBUG", | ||||||
|  |                         action="store_const", | ||||||
|  |                         dest="loglevel", | ||||||
|  |                         const=logging.DEBUG, | ||||||
|  |                         default=logging.INFO) | ||||||
|  |  | ||||||
|  |     # JID and password options. | ||||||
|  |     parser.add_argument("-j", "--jid", dest="jid", | ||||||
|  |                         help="JID to use") | ||||||
|  |     parser.add_argument("-p", "--password", dest="password", | ||||||
|  |                         help="password to use") | ||||||
|  |  | ||||||
|  |     parser.add_argument("server") | ||||||
|  |     parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"]) | ||||||
|  |     parser.add_argument("node", nargs='?') | ||||||
|  |     parser.add_argument("data", nargs='?') | ||||||
|  |  | ||||||
|  |     args = parser.parse_args() | ||||||
|  |  | ||||||
|  |     # Setup logging. | ||||||
|  |     logging.basicConfig(level=args.loglevel, | ||||||
|  |                         format='%(levelname)-8s %(message)s') | ||||||
|  |  | ||||||
|  |     if args.jid is None: | ||||||
|  |         args.jid = input("Username: ") | ||||||
|  |     if args.password is None: | ||||||
|  |         args.password = getpass("Password: ") | ||||||
|  |  | ||||||
|  |     # Setup the Pubsub client | ||||||
|  |     xmpp = PubsubClient(args.jid, args.password, | ||||||
|  |                         server=args.server, | ||||||
|  |                         node=args.node, | ||||||
|  |                         action=args.action, | ||||||
|  |                         data=args.data) | ||||||
|  |  | ||||||
|  |     # Connect to the XMPP server and start processing XMPP stanzas. | ||||||
|  |     xmpp.connect() | ||||||
|  |     xmpp.process(forever=False) | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user