Compare commits
454 Commits
sleek-merg
...
slix-1.5.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7bd224182 | ||
|
|
bac1e9b44a | ||
|
|
b62f0e90c1 | ||
|
|
9ace053992 | ||
|
|
c7cd2fcf33 | ||
|
|
e57289358f | ||
|
|
4aa35c11ab | ||
|
|
d6f7d32bbe | ||
|
|
0dd32be7f5 | ||
|
|
bf69698af1 | ||
|
|
aa732b3c94 | ||
|
|
d076cef023 | ||
|
|
f884b67b8b | ||
|
|
0d3116dbdf | ||
|
|
f1ab9ab964 | ||
|
|
e520ab1f5e | ||
|
|
3dcb96d9d8 | ||
|
|
0a7a4c3abe | ||
|
|
a4bbc404ed | ||
|
|
c3fbc6cb80 | ||
|
|
355d789061 | ||
|
|
47ed67c04e | ||
|
|
34567f450a | ||
|
|
9126bd8392 | ||
|
|
02202f7cd8 | ||
|
|
2add94f5b0 | ||
|
|
5fc757f200 | ||
|
|
98108d0445 | ||
|
|
76f4fb49d6 | ||
|
|
5be46a5e68 | ||
|
|
ab9040c30e | ||
|
|
a16e2a0f6c | ||
|
|
842aa3be8f | ||
|
|
6c28b49e7f | ||
|
|
621255027d | ||
|
|
efe316dc8c | ||
|
|
e9a87a0b77 | ||
|
|
85c9967b9c | ||
|
|
deb6d4f176 | ||
|
|
7218bb4499 | ||
|
|
d85efec7a2 | ||
|
|
115c234527 | ||
|
|
a0f5cb6e09 | ||
|
|
110bbf8afc | ||
|
|
d97efa0bd8 | ||
|
|
672f1b28f6 | ||
|
|
27d3ae958b | ||
|
|
a32794ec35 | ||
|
|
aa11ba463e | ||
|
|
a83c00e933 | ||
|
|
31f6ef6814 | ||
|
|
9b3874b5df | ||
|
|
0139fb291e | ||
|
|
e58988484a | ||
|
|
5d5e5cda19 | ||
|
|
11f707987d | ||
|
|
db13794e0f | ||
|
|
37bc1bb9b3 | ||
|
|
9be30e5291 | ||
|
|
9fe20a4056 | ||
|
|
3253d34c0a | ||
|
|
fef575ee1a | ||
|
|
540ff89427 | ||
|
|
dd8ac8fc87 | ||
|
|
2249d878d1 | ||
|
|
89fa9dc1dd | ||
|
|
d7729e8683 | ||
|
|
d618f55dea | ||
|
|
b0e688eb35 | ||
|
|
0e7176483b | ||
|
|
f35569a2c1 | ||
|
|
bec6f7c8f3 | ||
|
|
027ce2434d | ||
|
|
d57fbb57a2 | ||
|
|
85cd7a9166 | ||
|
|
d50d996c68 | ||
|
|
371ad20ca7 | ||
|
|
5f49df6b56 | ||
|
|
b50bfb2f34 | ||
|
|
b29bb30eb7 | ||
|
|
4435c81d77 | ||
|
|
2638ba2744 | ||
|
|
dbc9758311 | ||
|
|
47968963b1 | ||
|
|
4e8800f954 | ||
|
|
40053518aa | ||
|
|
1ee0f72ead | ||
|
|
4bb81228ae | ||
|
|
60a7a5b8df | ||
|
|
946674f424 | ||
|
|
412a9169bd | ||
|
|
72b355de8c | ||
|
|
af246dcfe1 | ||
|
|
9612e518fb | ||
|
|
fde8264191 | ||
|
|
1cdc656208 | ||
|
|
0042108a67 | ||
|
|
704161a285 | ||
|
|
6b1b58a339 | ||
|
|
4f96e5fa75 | ||
|
|
bcb90a653e | ||
|
|
7e435b703d | ||
|
|
2dda6b80d4 | ||
|
|
5629e44710 | ||
|
|
6a06881d8b | ||
|
|
2b666eb1de | ||
|
|
400e7a3903 | ||
|
|
fbab3ad214 | ||
|
|
628b357b06 | ||
|
|
88260cc240 | ||
|
|
e9f2f503b8 | ||
|
|
696a72247b | ||
|
|
05d76e4b1d | ||
|
|
d52d4fbbbe | ||
|
|
e53c0fcb30 | ||
|
|
97d68c5196 | ||
|
|
b42fafabb4 | ||
|
|
3a44ec8f15 | ||
|
|
93f385562f | ||
|
|
9cab02438b | ||
|
|
74ed50e626 | ||
|
|
9d378c611c | ||
|
|
d85d8f4479 | ||
|
|
fb75f7cda9 | ||
|
|
41419a2161 | ||
|
|
7cd73b594e | ||
|
|
15c6b775ff | ||
|
|
4b482477e2 | ||
|
|
f7e4caadfe | ||
|
|
5f25b0b6a0 | ||
|
|
d228bc42ea | ||
|
|
ecdc44a601 | ||
|
|
33370e42f1 | ||
|
|
4699861925 | ||
|
|
2d228bdb56 | ||
|
|
570e653ac2 | ||
|
|
282a481059 | ||
|
|
f386db380b | ||
|
|
7b87d98fff | ||
|
|
8779d40602 | ||
|
|
f0b21c42d5 | ||
|
|
e241d4e3c7 | ||
|
|
bd22a41a78 | ||
|
|
a29a29227a | ||
|
|
d4d542b741 | ||
|
|
dc4936a6d3 | ||
|
|
897610d819 | ||
|
|
d33366badd | ||
|
|
809c500002 | ||
|
|
dda4e18b81 | ||
|
|
8c09d932c8 | ||
|
|
31f5e84671 | ||
|
|
ad0dc33df9 | ||
|
|
7c3b3827b4 | ||
|
|
9f6fa65139 | ||
|
|
35fa33e3c2 | ||
|
|
86a2f280d2 | ||
|
|
490f15b8fc | ||
|
|
62661ee04f | ||
|
|
37d1f2a6b0 | ||
|
|
20107ad516 | ||
|
|
7738a01311 | ||
|
|
a9abed6151 | ||
|
|
0f690d4005 | ||
|
|
59d4420739 | ||
|
|
a88f317bbf | ||
|
|
2fc2a88970 | ||
|
|
c55e9279ac | ||
|
|
3502480384 | ||
|
|
caae713dd6 | ||
|
|
df0198abfe | ||
|
|
c20f4bf5fa | ||
|
|
9740e93aeb | ||
|
|
e7872aaa29 | ||
|
|
037706552c | ||
|
|
b881c6729b | ||
|
|
66909aafb3 | ||
|
|
cdfb5d56fc | ||
|
|
d146ce9fb6 | ||
|
|
cb59d60034 | ||
|
|
1d9fe3553e | ||
|
|
fe66c022ad | ||
|
|
92ea131721 | ||
|
|
dd7f67d10d | ||
|
|
c1562b76b2 | ||
|
|
32839f5252 | ||
|
|
80b7cf6ff8 | ||
|
|
128cc2eeb4 | ||
|
|
037912ee89 | ||
|
|
769bc6d3bf | ||
|
|
084d6cb5d9 | ||
|
|
5184713356 | ||
|
|
2f1225bad3 | ||
|
|
841f5a5a5b | ||
|
|
0c6de5e972 | ||
|
|
81dc61c55c | ||
|
|
bd63b1ce70 | ||
|
|
29faf114a7 | ||
|
|
94ea8151d4 | ||
|
|
66500ef5fb | ||
|
|
979396bb1e | ||
|
|
e177726387 | ||
|
|
20e88fda50 | ||
|
|
f252be9b6d | ||
|
|
ee98159586 | ||
|
|
c6443af29a | ||
|
|
d73f56a7af | ||
|
|
7c7f4308c5 | ||
|
|
eab8c265f4 | ||
|
|
80b9cd43b1 | ||
|
|
af1f9e08ad | ||
|
|
e3fd0af9c8 | ||
|
|
27e23672c1 | ||
|
|
b38e229359 | ||
|
|
9a563f1425 | ||
|
|
8b6f5953a7 | ||
|
|
2d2a80c73d | ||
|
|
4dfdd5d8e3 | ||
|
|
1994ed3025 | ||
|
|
aaa45846d3 | ||
|
|
d7ffcb54eb | ||
|
|
c33749e57a | ||
|
|
e4107d8b4d | ||
|
|
da5cb72d3a | ||
|
|
c372bd5168 | ||
|
|
cabf623131 | ||
|
|
ffc240d5b6 | ||
|
|
cc4522d9cd | ||
|
|
5bf69dca76 | ||
|
|
59dad12820 | ||
|
|
007c836296 | ||
|
|
3721bf9f6b | ||
|
|
802949eba8 | ||
|
|
24f35e433f | ||
|
|
22664ee7b8 | ||
|
|
6476cfcde5 | ||
|
|
5bb347e884 | ||
|
|
eb1251b919 | ||
|
|
820144c40c | ||
|
|
6034df0a78 | ||
|
|
df4012e66d | ||
|
|
c372f3071a | ||
|
|
829c8b27b6 | ||
|
|
fb3ac78bf9 | ||
|
|
ffd9436e5c | ||
|
|
bbb1344d79 | ||
|
|
457785b286 | ||
|
|
4847f834bd | ||
|
|
53191ff1cf | ||
|
|
ffdb6ffd69 | ||
|
|
7560db856b | ||
|
|
63d245ac48 | ||
|
|
7ddd37be29 | ||
|
|
a4d3a4a25e | ||
|
|
58bd07628b | ||
|
|
3569038493 | ||
|
|
20c4ff823a | ||
|
|
8a7448a5a1 | ||
|
|
d23d8f901e | ||
|
|
391f12eeab | ||
|
|
d008988843 | ||
|
|
dcacc7d7d5 | ||
|
|
c4285961df | ||
|
|
1038f656eb | ||
|
|
8b06aa1146 | ||
|
|
3c7236fe73 | ||
|
|
36824379c3 | ||
|
|
a0a37c19ff | ||
|
|
1b5fe57a5e | ||
|
|
5da31db0c7 | ||
|
|
f8cea760b6 | ||
|
|
5ef01ecdd1 | ||
|
|
62aafe0ee7 | ||
|
|
cf3f36ac52 | ||
|
|
b88d2ecd77 | ||
|
|
e691850a2b | ||
|
|
d4bff8dee6 | ||
|
|
187c350805 | ||
|
|
96d1c26f90 | ||
|
|
46a90749f8 | ||
|
|
0c63a4bbda | ||
|
|
e4696e0471 | ||
|
|
8217dc5239 | ||
|
|
2586abc0d3 | ||
|
|
28f84ab3d9 | ||
|
|
813b45aded | ||
|
|
3a9b45e4f2 | ||
|
|
b8e091233e | ||
|
|
0edeefd977 | ||
|
|
6ba53cf1ff | ||
|
|
d7758eb7f4 | ||
|
|
125336aeee | ||
|
|
7cd1cf32ae | ||
|
|
d099e353a4 | ||
|
|
1e4a301c6e | ||
|
|
f53b12d227 | ||
|
|
e2562dcccf | ||
|
|
7b69ae3738 | ||
|
|
ab6df235d7 | ||
|
|
52cd8f4b22 | ||
|
|
e28318c271 | ||
|
|
39ee833c29 | ||
|
|
9019e2bc71 | ||
|
|
9208bf5bf1 | ||
|
|
f0f1698e46 | ||
|
|
eccd7f1c98 | ||
|
|
2587d82af8 | ||
|
|
7ea121b115 | ||
|
|
bb81fbbdfc | ||
|
|
1a00a08b7d | ||
|
|
90ea2a3411 | ||
|
|
8fc6814b6d | ||
|
|
ffced0ed9a | ||
|
|
e7248d9af9 | ||
|
|
6b1a04f59d | ||
|
|
4905407092 | ||
|
|
bd6ec10939 | ||
|
|
e15e6735f1 | ||
|
|
67afd6a462 | ||
|
|
2e2b97c53b | ||
|
|
a35df7fe1f | ||
|
|
fbc8562779 | ||
|
|
b549db959a | ||
|
|
d5188ac68a | ||
|
|
ada9444bf8 | ||
|
|
acc52fd935 | ||
|
|
1100ff1feb | ||
|
|
c17fc3a869 | ||
|
|
4dba697075 | ||
|
|
4305eddb4f | ||
|
|
c2dc44cfd1 | ||
|
|
5fc14de32e | ||
|
|
d245558fd5 | ||
|
|
9d45370e8a | ||
|
|
cc1cc61d36 | ||
|
|
c6740a4908 | ||
|
|
55114bcffe | ||
|
|
4fa5dedc47 | ||
|
|
5525ef2285 | ||
|
|
a7ac969215 | ||
|
|
329cb5a9f8 | ||
|
|
d9b47b33f5 | ||
|
|
3582ac9941 | ||
|
|
2a127a57a7 | ||
|
|
7059400020 | ||
|
|
0b14ef82d4 | ||
|
|
83953af53d | ||
|
|
110cf25c6d | ||
|
|
f2bf6072ec | ||
|
|
5f9abe2e0e | ||
|
|
ea65b672e7 | ||
|
|
93c705fb31 | ||
|
|
0724f623bb | ||
|
|
ffb2b6bc04 | ||
|
|
4a24f58be2 | ||
|
|
da14ce16ec | ||
|
|
18e5abb9dd | ||
|
|
1a75b76916 | ||
|
|
53b56899a0 | ||
|
|
a366482551 | ||
|
|
abcec1e2d3 | ||
|
|
eeab646bfa | ||
|
|
2c69144189 | ||
|
|
f54ebec654 | ||
|
|
2042e1a4d5 | ||
|
|
be14f0cc52 | ||
|
|
edd9199be8 | ||
|
|
bb094cc649 | ||
|
|
dbaa6ed952 | ||
|
|
8c94d894ab | ||
|
|
ffc7eac4dc | ||
|
|
555fd6d926 | ||
|
|
c024ac8f0b | ||
|
|
f00177c0cf | ||
|
|
224d7ae133 | ||
|
|
9b25a7cf77 | ||
|
|
7a908ac07b | ||
|
|
92901637ec | ||
|
|
3590b663ed | ||
|
|
a33bde9cc3 | ||
|
|
ac50fdccfc | ||
|
|
a0c6bf15e9 | ||
|
|
a8ac115310 | ||
|
|
1345b7c1d0 | ||
|
|
d60a652259 | ||
|
|
61a7cecb31 | ||
|
|
192b7e0349 | ||
|
|
80b60fc048 | ||
|
|
842157a6cc | ||
|
|
a63cc01482 | ||
|
|
1bbb6f3ff9 | ||
|
|
93894247a4 | ||
|
|
16bb5e2537 | ||
|
|
d19a6e05b2 | ||
|
|
86e85f9835 | ||
|
|
cc145d20b0 | ||
|
|
881d9040c4 | ||
|
|
1e77ea0944 | ||
|
|
140f0885b2 | ||
|
|
83f71a6610 | ||
|
|
271343a32d | ||
|
|
48857b0030 | ||
|
|
1fe7f5f4e6 | ||
|
|
81b7b2c190 | ||
|
|
460de7d301 | ||
|
|
69022c6db7 | ||
|
|
9044807121 | ||
|
|
24264d3a07 | ||
|
|
8bc70264ef | ||
|
|
c16b862200 | ||
|
|
a96f608469 | ||
|
|
e1f25604ec | ||
|
|
0fe057b5c3 | ||
|
|
be76dda21d | ||
|
|
ecd124dd06 | ||
|
|
4a8951c4ee | ||
|
|
8afba7de85 | ||
|
|
1ce42d3a2f | ||
|
|
2f4d811db4 | ||
|
|
61127f521d | ||
|
|
063e73c0d2 | ||
|
|
d261318e1a | ||
|
|
d33cc00fe9 | ||
|
|
27582f6fd2 | ||
|
|
e328ff4833 | ||
|
|
403462fdb8 | ||
|
|
f22d8e67b4 | ||
|
|
35f33f1614 | ||
|
|
c9f8ddff65 | ||
|
|
f5ae98aaf1 | ||
|
|
073e85381a | ||
|
|
afc939708f | ||
|
|
aabec8b993 | ||
|
|
e5e2fbb16b | ||
|
|
3dd379cdf1 | ||
|
|
a20582aba4 | ||
|
|
09cdbf1b76 | ||
|
|
ca306e7cec | ||
|
|
1bf34f7fe6 | ||
|
|
4144d60017 | ||
|
|
7265682a4d | ||
|
|
08c62a6bf1 | ||
|
|
d61f1cd035 | ||
|
|
1063feb33b | ||
|
|
79f3c1ac8f | ||
|
|
a5c03b763a | ||
|
|
3670d82f1c | ||
|
|
e94a73553d | ||
|
|
577fd71472 | ||
|
|
ef1c4368d0 | ||
|
|
48def71d0c | ||
|
|
c8c20fff71 | ||
|
|
75a18b5ffe | ||
|
|
ea3d39b50e |
13
.github/pull_request_template.md
vendored
Normal file
13
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
################ Please use Gitlab instead of Github ###################################
|
||||
|
||||
Hello, thank you for contributing to slixmpp!
|
||||
|
||||
You’re about to open a pull request on github. However this github repository is not the official place for contributions on slixmpp.
|
||||
|
||||
Please open your merge request on https://lab.louiz.org/poezio/slixmpp/
|
||||
|
||||
You should be able to log in there with your github credentials, clone the slixmpp repository in your namespace, push your existing pull request into a new branch, and then open a merge request with one click, within 3 minutes.
|
||||
|
||||
This will help us review your contribution, avoid spreading things everywhere and it will even run the tests automatically with your changes.
|
||||
|
||||
Thank you.
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ slixmpp.egg-info/
|
||||
*~
|
||||
.baboon/
|
||||
.DS_STORE
|
||||
.idea/
|
||||
|
||||
21
.gitlab-ci.yml
Normal file
21
.gitlab-ci.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
stages:
|
||||
- test
|
||||
- trigger
|
||||
|
||||
test:
|
||||
stage: test
|
||||
tags:
|
||||
- docker
|
||||
image: ubuntu:latest
|
||||
script:
|
||||
- apt update
|
||||
- apt install -y python3 cython3 gpg
|
||||
- ./run_tests.py
|
||||
|
||||
trigger_poezio:
|
||||
stage: trigger
|
||||
tags:
|
||||
- docker
|
||||
image: appropriate/curl:latest
|
||||
script:
|
||||
- curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline
|
||||
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.7"
|
||||
- "3.8-dev"
|
||||
install:
|
||||
- "pip install ."
|
||||
script: testall.py
|
||||
14
CONTRIBUTING.rst
Normal file
14
CONTRIBUTING.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
Contributing to the Slixmpp project
|
||||
===================================
|
||||
|
||||
To contribute, the preferred way is to commit your changes on some
|
||||
publicly-available git repository (on a fork `on github
|
||||
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
|
||||
notify the developers with either:
|
||||
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
|
||||
- a pull request on github
|
||||
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
|
||||
|
||||
Even though Slixmpp’s github repository is just a read-only mirror, we can
|
||||
still be notified of the pull requests and fetch your mirror manually to
|
||||
integrate your changes.
|
||||
3
INSTALL
3
INSTALL
@@ -1,6 +1,7 @@
|
||||
Pre-requisites:
|
||||
- Python 3.4
|
||||
- Python 3.7+
|
||||
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
|
||||
- GnuPG, for testing
|
||||
|
||||
Install:
|
||||
> python3 setup.py install
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
socksipy: A Python SOCKS client module.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Copyright 2006 Dan-Haim. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||
|
||||
26
README.rst
26
README.rst
@@ -1,13 +1,21 @@
|
||||
Slixmpp
|
||||
#########
|
||||
|
||||
Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of
|
||||
Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of
|
||||
SleekXMPP.
|
||||
|
||||
Slixmpp's goals is to only rewrite the core of the library (the low level
|
||||
socket handling, the timers, the events dispatching) in order to remove all
|
||||
threads.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Slixmpp can make use of cython to improve performance on critical modules.
|
||||
To do that, **cython3** is necessary along with **libidn** headers.
|
||||
Otherwise, no compilation is needed. Building is done by running setup.py::
|
||||
|
||||
python3 setup.py build_ext --inplace
|
||||
|
||||
Documentation and Testing
|
||||
-------------------------
|
||||
@@ -28,7 +36,7 @@ The Slixmpp Boilerplate
|
||||
-------------------------
|
||||
Projects using Slixmpp tend to follow a basic pattern for setting up client/component
|
||||
connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp
|
||||
based project. See the documetation or examples directory for more detailed archetypes for
|
||||
based project. See the documentation or examples directory for more detailed archetypes for
|
||||
Slixmpp projects::
|
||||
|
||||
import logging
|
||||
@@ -94,8 +102,18 @@ Slixmpp projects::
|
||||
Slixmpp Credits
|
||||
---------------
|
||||
|
||||
**Maintainer of the slixmpp fork:** Florent Le Coz
|
||||
`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_,
|
||||
**Maintainers:**
|
||||
- Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_),
|
||||
- Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_),
|
||||
|
||||
**Contributors:**
|
||||
- Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_)
|
||||
- Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_)
|
||||
- Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_)
|
||||
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
|
||||
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
|
||||
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
|
||||
- Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
|
||||
|
||||
Credits (SleekXMPP)
|
||||
-------------------
|
||||
|
||||
21
docs/_static/haiku.css
vendored
21
docs/_static/haiku.css
vendored
@@ -408,24 +408,3 @@ div.viewcode-block:target {
|
||||
margin: -1px -12px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
#from_andyet {
|
||||
-webkit-box-shadow: #CCC 0px 0px 3px;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
bottom: 0px;
|
||||
right: 17px;
|
||||
padding: 3px 10px;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#from_andyet h2 {
|
||||
background-image: url("images/from_&yet.png");
|
||||
background-repeat: no-repeat;
|
||||
height: 29px;
|
||||
line-height: 0;
|
||||
text-indent: -9999em;
|
||||
width: 79px;
|
||||
margin-top: 0;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
1
docs/_templates/layout.html
vendored
1
docs/_templates/layout.html
vendored
@@ -65,6 +65,5 @@
|
||||
<div class="bottomnav">
|
||||
{{ nav() }}
|
||||
</div>
|
||||
<a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace
|
||||
prefixes::
|
||||
|
||||
>>> from slixmpp.xmlstream.tostring import tostring
|
||||
>>> from xml.etree import cElementTree as ET
|
||||
>>> from xml.etree import ElementTree as ET
|
||||
>>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
|
||||
>>> ET.tostring(xml)
|
||||
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'
|
||||
|
||||
13
docs/conf.py
13
docs/conf.py
@@ -12,12 +12,17 @@
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import datetime
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# get version automagically from source tree
|
||||
from slixmpp.version import __version__ as version
|
||||
release = ".".join(version.split(".")[0:2])
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
@@ -41,16 +46,18 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Slixmpp'
|
||||
copyright = u'2011, Nathan Fritz, Lance Stout'
|
||||
year = datetime.datetime.now().year
|
||||
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year)
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# auto imported from code!
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
# version = '1.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
# release = '1.4.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -163,7 +163,7 @@ behaviour:
|
||||
namespace = 'jabber:iq:register'
|
||||
name = 'query'
|
||||
plugin_attrib = 'register'
|
||||
interfaces = set(('username', 'password', 'registered', 'remove'))
|
||||
interfaces = {'username', 'password', 'registered', 'remove'}
|
||||
sub_interfaces = interfaces
|
||||
|
||||
def getRegistered(self):
|
||||
@@ -535,10 +535,10 @@ with some additional registration fields implemented.
|
||||
namespace = 'jabber:iq:register'
|
||||
name = 'query'
|
||||
plugin_attrib = 'register'
|
||||
interfaces = set(('username', 'password', 'email', 'nick', 'name',
|
||||
'first', 'last', 'address', 'city', 'state', 'zip',
|
||||
'phone', 'url', 'date', 'misc', 'text', 'key',
|
||||
'registered', 'remove', 'instructions'))
|
||||
interfaces = {'username', 'password', 'email', 'nick', 'name',
|
||||
'first', 'last', 'address', 'city', 'state', 'zip',
|
||||
'phone', 'url', 'date', 'misc', 'text', 'key',
|
||||
'registered', 'remove', 'instructions'}
|
||||
sub_interfaces = interfaces
|
||||
|
||||
def getRegistered(self):
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
Differences from SleekXMPP
|
||||
==========================
|
||||
|
||||
**Python 3.4+ only**
|
||||
slixmpp will only work on python 3.4 and above.
|
||||
**Python 3.7+ only**
|
||||
slixmpp will work on python 3.7 and above. It may work with previous
|
||||
versions but we provide no guarantees.
|
||||
|
||||
**Stanza copies**
|
||||
The same stanza object is given through all the handlers; a handler that
|
||||
|
||||
@@ -259,8 +259,8 @@ Event Index
|
||||
|
||||
Signal that a connection to the XMPP server has been lost and the current
|
||||
stream session has ended. Currently equivalent to :term:`disconnected`, but
|
||||
future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
|
||||
will distinguish the two events.
|
||||
implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
|
||||
distinguish between the two events.
|
||||
|
||||
Plugins that maintain session-based state should clear themselves when
|
||||
this event is fired.
|
||||
|
||||
@@ -11,7 +11,7 @@ Create and Run a Server Component
|
||||
<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>`_.
|
||||
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||
|
||||
Many XMPP applications eventually graduate to requiring to run as a server
|
||||
component in order to meet scalability requirements. To demonstrate how to
|
||||
|
||||
@@ -11,7 +11,7 @@ Slixmpp Quickstart - Echo Bot
|
||||
<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>`_.
|
||||
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||
|
||||
As a basic starting project, we will create an echo bot which will reply to any
|
||||
messages sent to it. We will also go through adding some basic command line configuration
|
||||
@@ -70,7 +70,7 @@ as well.
|
||||
class EchoBot(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(EchoBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
Handling Session Start
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -83,7 +83,7 @@ started. To do that, we will register an event handler for the :term:`session_st
|
||||
.. code-block:: python
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(EchoBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
|
||||
@@ -153,7 +153,7 @@ whenever a messsage is received.
|
||||
.. code-block:: python
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(EchoBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
self.add_event_handler('message', self.message)
|
||||
@@ -329,7 +329,7 @@ The Final Product
|
||||
-----------------
|
||||
|
||||
Here then is what the final result should look like after working through the guide above. The code
|
||||
can also be found in the Slixmpp `examples directory <http://github.com/fritzy/Slixmpp/tree/master/examples>`_.
|
||||
can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_.
|
||||
|
||||
.. compound::
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.. _mucbot:
|
||||
|
||||
=========================
|
||||
Mulit-User Chat (MUC) Bot
|
||||
Multi-User Chat (MUC) Bot
|
||||
=========================
|
||||
|
||||
.. note::
|
||||
@@ -11,7 +11,7 @@ Mulit-User Chat (MUC) Bot
|
||||
<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>`_.
|
||||
from `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||
|
||||
Now that you've got the basic gist of using Slixmpp by following the
|
||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
||||
@@ -63,13 +63,13 @@ has been established:
|
||||
def start(self, event):
|
||||
self.get_roster()
|
||||
self.send_presence()
|
||||
self.plugin['xep_0045'].joinMUC(self.room,
|
||||
self.nick,
|
||||
wait=True)
|
||||
self.plugin['xep_0045'].join_muc(self.room,
|
||||
self.nick,
|
||||
wait=True)
|
||||
|
||||
Note that as in :ref:`echobot`, we need to include send an initial presence and request
|
||||
the roster. Next, we want to join the group chat, so we call the
|
||||
``joinMUC`` method of the MUC plugin.
|
||||
``join_muc`` method of the MUC plugin.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ for the JID that will receive our message, and the string content of the message
|
||||
class SendMsgBot(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password, recipient, msg):
|
||||
super(SendMsgBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.recipient = recipient
|
||||
self.msg = msg
|
||||
@@ -47,11 +47,11 @@ the roster. Next, we want to send our message, and to do that we will use :meth:
|
||||
self.send_message(mto=self.recipient, mbody=self.msg)
|
||||
|
||||
Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`.
|
||||
Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
|
||||
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
|
||||
for the client to disconnect before the send queue is processed and the message is actually
|
||||
sent on the wire. To ensure that our message is processed, we use
|
||||
:meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`.
|
||||
Now, sent stanzas are placed in a queue to pass them to the send thread.
|
||||
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` by default will wait for an
|
||||
acknowledgement from the server for at least `2.0` seconds. This time is configurable with
|
||||
the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect
|
||||
<slixmpp.xmlstream.XMLStream.disconnect>` will not close the connection gracefully.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -61,12 +61,12 @@ sent on the wire. To ensure that our message is processed, we use
|
||||
|
||||
self.send_message(mto=self.recipient, mbody=self.msg)
|
||||
|
||||
self.disconnect(wait=True)
|
||||
self.disconnect()
|
||||
|
||||
.. warning::
|
||||
|
||||
If you happen to be adding stanzas to the send queue faster than the send thread
|
||||
can process them, then :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`
|
||||
can process them, then :meth:`disconnect() <slixmpp.xmlstream.XMLStream.disconnect>`
|
||||
will block and not disconnect.
|
||||
|
||||
Final Product
|
||||
|
||||
@@ -61,7 +61,7 @@ operation using these stanzas without doing any complex operations such as
|
||||
checking an ACL, etc.
|
||||
|
||||
You may find it necessary at some point to revert a particular node or JID to
|
||||
using the default, static handlers. To do so, use the method ``make_static()``.
|
||||
using the default, static handlers. To do so, use the method ``restore_defaults()``.
|
||||
You may also elect to only convert a given set of actions instead.
|
||||
|
||||
Creating a Node Handler
|
||||
@@ -161,8 +161,8 @@ item itself, and the JID and node that will own the item.
|
||||
In this case, the owning JID and node are provided with the
|
||||
parameters ``ijid`` and ``node``.
|
||||
|
||||
Peforming Disco Queries
|
||||
-----------------------
|
||||
Performing Disco Queries
|
||||
------------------------
|
||||
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
|
||||
and their nodes for disco information. Since these methods are wrappers for
|
||||
sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``
|
||||
@@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
info = self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
ifrom='baz@mycomponent.example.com',
|
||||
block=True,
|
||||
timeout=30)
|
||||
info = yield from self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
ifrom='baz@mycomponent.example.com',
|
||||
timeout=30)
|
||||
|
||||
items = self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
|
||||
@@ -4,9 +4,9 @@ Slixmpp
|
||||
.. sidebar:: Get the Code
|
||||
|
||||
The latest source code for Slixmpp may be found on the `Git repo
|
||||
<http://git.poez.io/slixmpp>`_. ::
|
||||
<https://lab.louiz.org/poezio/slixmpp>`_. ::
|
||||
|
||||
git clone git://git.poez.io/slixmpp
|
||||
git clone https://lab.louiz.org/poezio/slixmpp
|
||||
|
||||
An XMPP chat room is available for discussing and getting help with slixmpp.
|
||||
|
||||
@@ -14,14 +14,14 @@ Slixmpp
|
||||
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
|
||||
|
||||
**Reporting bugs**
|
||||
You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
|
||||
You can report bugs at http://lab.louiz.org/poezio/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 is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+,
|
||||
|
||||
Slixmpp's design goals and philosphy are:
|
||||
|
||||
|
||||
@@ -160,9 +160,9 @@ if __name__ == '__main__':
|
||||
|
||||
myDevice = TheDevice(args.nodeid);
|
||||
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="C")
|
||||
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||
|
||||
xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10);
|
||||
xmpp.beClientOrServer(server=True)
|
||||
|
||||
@@ -33,7 +33,7 @@ class CommandBot(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -47,7 +47,7 @@ class CommandBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
# We add the command after session_start has fired
|
||||
# to ensure that the correct full JID is used.
|
||||
@@ -68,7 +68,7 @@ class CommandBot(slixmpp.ClientXMPP):
|
||||
session. Additional, custom data may be saved
|
||||
here to persist across handler callbacks.
|
||||
"""
|
||||
form = self['xep_0004'].makeForm('form', 'Greeting')
|
||||
form = self['xep_0004'].make_form('form', 'Greeting')
|
||||
form['instructions'] = 'Send a custom greeting to a JID'
|
||||
form.addField(var='greeting',
|
||||
ftype='text-single',
|
||||
|
||||
@@ -37,7 +37,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
|
||||
self.add_event_handler("session_start", self.start)
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -51,7 +51,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
# We first create a session dictionary containing:
|
||||
# 'next' -- the handler to execute on a successful response
|
||||
@@ -94,7 +94,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
|
||||
# label="Your greeting" />
|
||||
# </x>
|
||||
|
||||
form = self['xep_0004'].makeForm(ftype='submit')
|
||||
form = self['xep_0004'].make_form(ftype='submit')
|
||||
form.addField(var='greeting',
|
||||
value=session['greeting'])
|
||||
|
||||
@@ -176,4 +176,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
||||
@@ -30,7 +30,7 @@ class AdminCommands(slixmpp.ClientXMPP):
|
||||
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -44,7 +44,7 @@ class AdminCommands(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
def command_success(iq, session):
|
||||
print('Command completed')
|
||||
|
||||
99
examples/confirm_answer.py
Executable file
99
examples/confirm_answer.py
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AnswerConfirm(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic client demonstrating how to confirm or deny an HTTP request.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, trusted):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.add_event_handler("http_confirm", self.confirm)
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
def start(self, *args):
|
||||
self.make_presence().send()
|
||||
|
||||
def prompt(self, stanza):
|
||||
confirm = stanza['confirm']
|
||||
print('Received confirm request %s from %s to access %s using '
|
||||
'method %s' % (
|
||||
confirm['id'], stanza['from'], confirm['url'],
|
||||
confirm['method'])
|
||||
)
|
||||
result = input("Do you accept (y/N)? ")
|
||||
return 'y' == result.lower()
|
||||
|
||||
def confirm(self, stanza):
|
||||
if self.prompt(stanza):
|
||||
reply = stanza.reply()
|
||||
else:
|
||||
reply = stanza.reply()
|
||||
reply.enable('error')
|
||||
reply['error']['type'] = 'auth'
|
||||
reply['error']['code'] = '401'
|
||||
reply['error']['condition'] = 'not-authorized'
|
||||
reply.append(stanza['confirm'])
|
||||
reply.send()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.ERROR,
|
||||
default=logging.INFO)
|
||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.DEBUG,
|
||||
default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
|
||||
# Other options.
|
||||
parser.add_argument("-t", "--trusted", nargs='*',
|
||||
help="List of trusted JIDs")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
|
||||
xmpp = AnswerConfirm(args.jid, args.password, args.trusted)
|
||||
xmpp.register_plugin('xep_0070')
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
124
examples/confirm_ask.py
Executable file
124
examples/confirm_ask.py
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError, IqError
|
||||
from slixmpp import asyncio
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AskConfirm(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic client asking an entity if they confirm the access to an HTTP URL.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, recipient, id, url, method):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.recipient = recipient
|
||||
self.id = id
|
||||
self.url = url
|
||||
self.method = method
|
||||
|
||||
# Will be used to set the proper exit code.
|
||||
self.confirmed = asyncio.Future()
|
||||
|
||||
self.add_event_handler("session_start", self.start)
|
||||
self.add_event_handler("message", self.start)
|
||||
self.add_event_handler("http_confirm_message", self.confirm)
|
||||
|
||||
def confirm(self, message):
|
||||
print(message)
|
||||
if message['confirm']['id'] == self.id:
|
||||
if message['type'] == 'error':
|
||||
self.confirmed.set_result(False)
|
||||
else:
|
||||
self.confirmed.set_result(True)
|
||||
|
||||
async def start(self, event):
|
||||
log.info('Sending confirm request %s to %s who wants to access %s using '
|
||||
'method %s...' % (self.id, self.recipient, self.url, self.method))
|
||||
try:
|
||||
confirmed = await self['xep_0070'].ask_confirm(self.recipient,
|
||||
id=self.id,
|
||||
url=self.url,
|
||||
method=self.method,
|
||||
message='Plz say yes or no for {method} {url} ({id}).')
|
||||
if isinstance(confirmed, slixmpp.Message):
|
||||
confirmed = await self.confirmed
|
||||
else:
|
||||
confirmed = True
|
||||
except IqError:
|
||||
confirmed = False
|
||||
if confirmed:
|
||||
print('Confirmed')
|
||||
else:
|
||||
print('Denied')
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.ERROR,
|
||||
default=logging.INFO)
|
||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.DEBUG,
|
||||
default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
|
||||
# Other options.
|
||||
parser.add_argument("-r", "--recipient", required=True,
|
||||
help="Recipient JID")
|
||||
parser.add_argument("-i", "--id", required=True,
|
||||
help="id TODO")
|
||||
parser.add_argument("-u", "--url", required=True,
|
||||
help="URL the user tried to access")
|
||||
parser.add_argument("-m", "--method", required=True,
|
||||
help="HTTP method used")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
|
||||
xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id,
|
||||
args.url, args.method)
|
||||
xmpp.register_plugin('xep_0070')
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process(forever=False)
|
||||
sys.exit(0 if xmpp.confirmed else 1)
|
||||
@@ -50,7 +50,7 @@ class ActionBot(slixmpp.ClientXMPP):
|
||||
|
||||
register_stanza_plugin(Iq, Action)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -73,7 +73,7 @@ class ActionBot(slixmpp.ClientXMPP):
|
||||
"""
|
||||
self.event('custom_action', iq)
|
||||
|
||||
def _handle_action_event(self, iq):
|
||||
async def _handle_action_event(self, iq):
|
||||
"""
|
||||
Respond to the custom action event.
|
||||
"""
|
||||
@@ -82,17 +82,20 @@ class ActionBot(slixmpp.ClientXMPP):
|
||||
|
||||
if method == 'is_prime' and param == '2':
|
||||
print("got message: %s" % iq)
|
||||
iq.reply()
|
||||
iq['action']['status'] = 'done'
|
||||
iq.send()
|
||||
rep = iq.reply()
|
||||
rep['action']['status'] = 'done'
|
||||
await rep.send()
|
||||
elif method == 'bye':
|
||||
print("got message: %s" % iq)
|
||||
rep = iq.reply()
|
||||
rep['action']['status'] = 'done'
|
||||
await rep.send()
|
||||
self.disconnect()
|
||||
else:
|
||||
print("got message: %s" % iq)
|
||||
iq.reply()
|
||||
iq['action']['status'] = 'error'
|
||||
iq.send()
|
||||
rep = iq.reply()
|
||||
rep['action']['status'] = 'error'
|
||||
await rep.send()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
|
||||
@@ -43,7 +43,7 @@ class ActionUserBot(slixmpp.ClientXMPP):
|
||||
|
||||
register_stanza_plugin(Iq, Action)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -57,11 +57,11 @@ class ActionUserBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
self.send_custom_iq()
|
||||
await self.send_custom_iq()
|
||||
|
||||
def send_custom_iq(self):
|
||||
async def send_custom_iq(self):
|
||||
"""Create and send two custom actions.
|
||||
|
||||
If the first action was successful, then send
|
||||
@@ -74,14 +74,14 @@ class ActionUserBot(slixmpp.ClientXMPP):
|
||||
iq['action']['param'] = '2'
|
||||
|
||||
try:
|
||||
resp = iq.send()
|
||||
resp = await iq.send()
|
||||
if resp['action']['status'] == 'done':
|
||||
#sending bye
|
||||
iq2 = self.Iq()
|
||||
iq2['to'] = self.action_provider
|
||||
iq2['type'] = 'set'
|
||||
iq2['action']['method'] = 'bye'
|
||||
iq2.send(block=False)
|
||||
await iq2.send()
|
||||
|
||||
self.disconnect()
|
||||
except XMPPError:
|
||||
|
||||
@@ -41,7 +41,7 @@ class Action(ElementBase):
|
||||
#: del action['status']
|
||||
#:
|
||||
#: to set, get, or remove its values.
|
||||
interfaces = set(('method', 'param', 'status'))
|
||||
interfaces = {'method', 'param', 'status'}
|
||||
|
||||
#: By default, values in the `interfaces` set are mapped to
|
||||
#: attribute values. This can be changed such that an interface
|
||||
|
||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp.xmlstream.asyncio import asyncio
|
||||
|
||||
|
||||
class Disco(slixmpp.ClientXMPP):
|
||||
@@ -54,8 +53,7 @@ class Disco(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -71,23 +69,19 @@ class Disco(slixmpp.ClientXMPP):
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
self.send_presence()
|
||||
|
||||
try:
|
||||
if self.get in self.info_types:
|
||||
# By using block=True, the result stanza will be
|
||||
# returned. Execution will block until the reply is
|
||||
# received. Non-blocking options would be to listen
|
||||
# for the disco_info event, or passing a handler
|
||||
# function using the callback parameter.
|
||||
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
|
||||
info = await self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node)
|
||||
if self.get in self.items_types:
|
||||
# The same applies from above. Listen for the
|
||||
# disco_items event or pass a callback function
|
||||
# if you need to process a non-blocking request.
|
||||
items = yield from self['xep_0030'].get_items(jid=self.target_jid,
|
||||
items = await self['xep_0030'].get_items(jid=self.target_jid,
|
||||
node=self.target_node)
|
||||
if self.get not in self.info_types and self.get not in self.items_types:
|
||||
logging.error("Invalid disco request type.")
|
||||
|
||||
@@ -47,8 +47,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
self.roster_received.set()
|
||||
self.presences_received.clear()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -65,16 +64,15 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
self.get_roster(callback=self.roster_received_cb)
|
||||
|
||||
print('Waiting for presence updates...\n')
|
||||
yield from self.roster_received.wait()
|
||||
await self.roster_received.wait()
|
||||
print('Roster received')
|
||||
yield from self.presences_received.wait()
|
||||
await self.presences_received.wait()
|
||||
self.disconnect()
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_vcard_avatar(self, pres):
|
||||
async def on_vcard_avatar(self, pres):
|
||||
print("Received vCard avatar update from %s" % pres['from'].bare)
|
||||
try:
|
||||
result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
|
||||
result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
|
||||
timeout=5)
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % pres['from'])
|
||||
@@ -89,14 +87,13 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
with open(filename, 'wb+') as img:
|
||||
img.write(avatar['BINVAL'])
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_avatar(self, msg):
|
||||
async def on_avatar(self, msg):
|
||||
print("Received avatar update from %s" % msg['from'])
|
||||
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
||||
for info in metadata['items']:
|
||||
if not info['url']:
|
||||
try:
|
||||
result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
|
||||
result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
|
||||
timeout=5)
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % msg['from'])
|
||||
@@ -162,4 +159,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
||||
@@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP):
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
|
||||
@@ -55,10 +55,10 @@ class GTalkBot(slixmpp.ClientXMPP):
|
||||
cert.verify('talk.google.com', der_cert)
|
||||
logging.debug("CERT: Found GTalk certificate")
|
||||
except cert.CertificateError as err:
|
||||
log.error(err.message)
|
||||
self.disconnect(send_close=False)
|
||||
logging.error(err.message)
|
||||
self.disconnect()
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -72,7 +72,7 @@ class GTalkBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
|
||||
97
examples/http_over_xmpp.py
Normal file
97
examples/http_over_xmpp.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp import ClientXMPP
|
||||
|
||||
from argparse import ArgumentParser
|
||||
import logging
|
||||
import getpass
|
||||
|
||||
|
||||
class HTTPOverXMPPClient(ClientXMPP):
|
||||
def __init__(self, jid, password):
|
||||
ClientXMPP.__init__(self, jid, password)
|
||||
self.register_plugin('xep_0332') # HTTP over XMPP Transport
|
||||
self.add_event_handler(
|
||||
'session_start', self.session_start
|
||||
)
|
||||
self.add_event_handler('http_request', self.http_request_received)
|
||||
self.add_event_handler('http_response', self.http_response_received)
|
||||
|
||||
def http_request_received(self, iq):
|
||||
pass
|
||||
|
||||
def http_response_received(self, iq):
|
||||
print('HTTP Response Received : %s' % iq)
|
||||
print('From : %s' % iq['from'])
|
||||
print('To : %s' % iq['to'])
|
||||
print('Type : %s' % iq['type'])
|
||||
print('Headers : %s' % iq['resp']['headers'])
|
||||
print('Code : %s' % iq['resp']['code'])
|
||||
print('Message : %s' % iq['resp']['message'])
|
||||
print('Data : %s' % iq['resp']['data'])
|
||||
|
||||
def session_start(self, event):
|
||||
# TODO: Fill in the blanks
|
||||
self['xep_0332'].send_request(
|
||||
to='?', method='?', resource='?', headers={}
|
||||
)
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
#
|
||||
# NOTE: To run this example, fill up the blanks in session_start() and
|
||||
# use the following command.
|
||||
#
|
||||
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
|
||||
#
|
||||
|
||||
parser = ArgumentParser()
|
||||
|
||||
# Output verbosity options.
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', help='set logging to DEBUG', action='store_const',
|
||||
dest='loglevel', const=logging.DEBUG, default=logging.ERROR
|
||||
)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument('-J', '--jid', dest='jid', help='JID')
|
||||
parser.add_argument('-P', '--password', dest='password', help='Password')
|
||||
|
||||
# XMPP server ip and port options.
|
||||
parser.add_argument(
|
||||
'-i', '--ipaddr', dest='ipaddr',
|
||||
help='IP Address of the XMPP server', default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--port', dest='port',
|
||||
help='Port of the XMPP server', default=None
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input('Username: ')
|
||||
if args.password is None:
|
||||
args.password = getpass.getpass('Password: ')
|
||||
|
||||
xmpp = HTTPOverXMPPClient(args.jid, args.password)
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
|
||||
96
examples/http_upload.py
Executable file
96
examples/http_upload.py
Executable file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2018 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HttpUpload(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic client asking an entity if they confirm the access to an HTTP URL.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, recipient, filename, domain=None):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.recipient = recipient
|
||||
self.filename = filename
|
||||
self.domain = domain
|
||||
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
async def start(self, event):
|
||||
log.info('Uploading file %s...', self.filename)
|
||||
def timeout_callback(arg):
|
||||
raise TimeoutError("could not send message in time")
|
||||
url = await self['xep_0363'].upload_file(
|
||||
self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback)
|
||||
log.info('Upload success!')
|
||||
|
||||
log.info('Sending file to %s', self.recipient)
|
||||
html = '<body xmlns="http://www.w3.org/1999/xhtml"><a href="%s">%s</a></body>' % (url, url)
|
||||
self.send_message(self.recipient, url, mhtml=html)
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.ERROR,
|
||||
default=logging.INFO)
|
||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.DEBUG,
|
||||
default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
|
||||
# Other options.
|
||||
parser.add_argument("-r", "--recipient", required=True,
|
||||
help="Recipient JID")
|
||||
parser.add_argument("-f", "--file", required=True,
|
||||
help="File to send")
|
||||
parser.add_argument("--domain",
|
||||
help="Domain to use for HTTP File Upload (leave out for your own server’s)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
|
||||
xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain)
|
||||
xmpp.register_plugin('xep_0071')
|
||||
xmpp.register_plugin('xep_0128')
|
||||
xmpp.register_plugin('xep_0363')
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process(forever=False)
|
||||
@@ -9,7 +9,6 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
@@ -39,8 +38,7 @@ class IBBSender(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -58,13 +56,13 @@ class IBBSender(slixmpp.ClientXMPP):
|
||||
|
||||
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)
|
||||
stream = await 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)
|
||||
await stream.sendfile(self.file, timeout=10)
|
||||
|
||||
# And finally close the stream.
|
||||
yield from stream.close(timeout=10)
|
||||
await stream.close(timeout=10)
|
||||
except (IqError, IqTimeout):
|
||||
print('File transfer errored')
|
||||
else:
|
||||
|
||||
97
examples/mam.py
Executable file
97
examples/mam.py
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2017 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MAM(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic client fetching mam archive messages
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, remote_jid, start):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
self.remote_jid = remote_jid
|
||||
self.start_date = start
|
||||
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
async def start(self, *args):
|
||||
"""
|
||||
Fetch mam results for the specified JID.
|
||||
Use RSM to paginate the results.
|
||||
"""
|
||||
results = self.plugin['xep_0313'].retrieve(jid=self.remote_jid, iterator=True, rsm={'max': 10}, start=self.start_date)
|
||||
page = 1
|
||||
async for rsm in results:
|
||||
print('Page %d' % page)
|
||||
for msg in rsm['mam']['results']:
|
||||
forwarded = msg['mam_result']['forwarded']
|
||||
timestamp = forwarded['delay']['stamp']
|
||||
message = forwarded['stanza']
|
||||
print('[%s] %s: %s' % (timestamp, message['from'], message['body']))
|
||||
page += 1
|
||||
self.disconnect()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.ERROR,
|
||||
default=logging.INFO)
|
||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.DEBUG,
|
||||
default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
|
||||
# Other options
|
||||
parser.add_argument("-r", "--remote-jid", dest="remote_jid",
|
||||
help="Remote JID")
|
||||
parser.add_argument("--start", help="Start date", default='2017-09-20T12:00:00Z')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
if args.remote_jid is None:
|
||||
args.remote_jid = input("Remote JID: ")
|
||||
if args.start is None:
|
||||
args.start = input("Start time: ")
|
||||
|
||||
xmpp = MAM(args.jid, args.password, args.remote_jid, args.start)
|
||||
xmpp.register_plugin('xep_0313')
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process(forever=False)
|
||||
120
examples/markup.py
Executable file
120
examples/markup.py
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.plugins.xep_0394 import stanza as markup_stanza
|
||||
|
||||
|
||||
class EchoBot(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A simple Slixmpp bot that will echo messages it
|
||||
receives, along with a short thank you message.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
# The session_start event will be triggered when
|
||||
# the bot establishes its connection with the server
|
||||
# and the XML streams are ready for use. We want to
|
||||
# listen for this event so that we we can initialize
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
# The message event is triggered whenever a message
|
||||
# stanza is received. Be aware that that includes
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
Typical actions for the session_start event are
|
||||
requesting the roster and broadcasting an initial
|
||||
presence stanza.
|
||||
|
||||
Arguments:
|
||||
event -- An empty dictionary. The session_start
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
await self.get_roster()
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
Process incoming message stanzas. Be aware that this also
|
||||
includes MUC messages and error messages. It is usually
|
||||
a good idea to check the messages's type before processing
|
||||
or sending replies.
|
||||
|
||||
Arguments:
|
||||
msg -- The received message stanza. See the documentation
|
||||
for stanza objects and the Message stanza to see
|
||||
how it may be used.
|
||||
"""
|
||||
body = msg['body']
|
||||
new_body = self['xep_0394'].to_plain_text(body, msg['markup'])
|
||||
xhtml = self['xep_0394'].to_xhtml_im(body, msg['markup'])
|
||||
print('Plain text:', new_body)
|
||||
print('XHTML-IM:', xhtml['body'])
|
||||
message = msg.reply()
|
||||
message['body'] = new_body
|
||||
message['html']['body'] = xhtml['body']
|
||||
self.send(message)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser(description=EchoBot.__doc__)
|
||||
|
||||
# Output verbosity options.
|
||||
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
|
||||
action="store_const", dest="loglevel",
|
||||
const=logging.ERROR, default=logging.INFO)
|
||||
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
|
||||
action="store_const", dest="loglevel",
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
|
||||
# Setup the EchoBot and register plugins. Note that while plugins may
|
||||
# have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = EchoBot(args.jid, args.password)
|
||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
||||
xmpp.register_plugin('xep_0394') # Message Markup
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
@@ -100,8 +100,8 @@ def on_session2(event):
|
||||
new_xmpp.update_roster(jid,
|
||||
name = item['name'],
|
||||
groups = item['groups'])
|
||||
new_xmpp.disconnect()
|
||||
new_xmpp.disconnect()
|
||||
new_xmpp.add_event_handler('session_start', on_session2)
|
||||
|
||||
new_xmpp.connect()
|
||||
new_xmpp.process()
|
||||
new_xmpp.process(forever=False)
|
||||
|
||||
@@ -52,7 +52,7 @@ class MUCBot(slixmpp.ClientXMPP):
|
||||
self.muc_online)
|
||||
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -65,13 +65,13 @@ class MUCBot(slixmpp.ClientXMPP):
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
self.send_presence()
|
||||
self.plugin['xep_0045'].joinMUC(self.room,
|
||||
self.nick,
|
||||
# If a room password is needed, use:
|
||||
# password=the_room_password,
|
||||
wait=True)
|
||||
self.plugin['xep_0045'].join_muc(self.room,
|
||||
self.nick,
|
||||
# If a room password is needed, use:
|
||||
# password=the_room_password,
|
||||
wait=True)
|
||||
|
||||
def muc_message(self, msg):
|
||||
"""
|
||||
|
||||
@@ -13,7 +13,6 @@ import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp import asyncio
|
||||
|
||||
import slixmpp
|
||||
|
||||
@@ -38,8 +37,7 @@ class PingTest(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -53,10 +51,10 @@ class PingTest(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
try:
|
||||
rtt = yield from self['xep_0199'].ping(self.pingjid,
|
||||
rtt = await self['xep_0199'].ping(self.pingjid,
|
||||
timeout=10)
|
||||
logging.info("Success! RTT: %s", rtt)
|
||||
except IqError as e:
|
||||
@@ -111,4 +109,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
||||
@@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP):
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
|
||||
@@ -5,7 +5,6 @@ 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
|
||||
@@ -15,13 +14,13 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password, server,
|
||||
node=None, action='nodes', data=''):
|
||||
super(PubsubClient, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.register_plugin('xep_0030')
|
||||
self.register_plugin('xep_0059')
|
||||
self.register_plugin('xep_0060')
|
||||
|
||||
self.actions = ['nodes', 'create', 'delete',
|
||||
self.actions = ['nodes', 'create', 'delete', 'get_configure',
|
||||
'publish', 'get', 'retract',
|
||||
'purge', 'subscribe', 'unsubscribe']
|
||||
|
||||
@@ -32,80 +31,86 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
self.get_roster()
|
||||
async def start(self, event):
|
||||
await self.get_roster()
|
||||
self.send_presence()
|
||||
|
||||
try:
|
||||
yield from getattr(self, self.action)()
|
||||
await getattr(self, self.action)()
|
||||
except:
|
||||
logging.error('Could not execute: %s', self.action)
|
||||
logging.exception('Could not execute %s:', self.action)
|
||||
self.disconnect()
|
||||
|
||||
def nodes(self):
|
||||
async def nodes(self):
|
||||
try:
|
||||
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||
result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||
for item in result['disco_items']['items']:
|
||||
logging.info(' - %s', str(item))
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retrieve node list: %s', error.format())
|
||||
|
||||
def create(self):
|
||||
async def create(self):
|
||||
try:
|
||||
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||
logging.info('Created node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not create node %s: %s', self.node, error.format())
|
||||
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
try:
|
||||
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||
logging.info('Deleted node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not delete node %s: %s', self.node, error.format())
|
||||
|
||||
def publish(self):
|
||||
async def get_configure(self):
|
||||
try:
|
||||
configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node)
|
||||
logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form'])
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format())
|
||||
|
||||
async def publish(self):
|
||||
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
||||
try:
|
||||
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||
result = await self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
|
||||
except XMPPError as error:
|
||||
logging.error('Could not publish to %s: %s', self.node, error.format())
|
||||
|
||||
def get(self):
|
||||
async def get(self):
|
||||
try:
|
||||
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||
result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||
for item in result['pubsub']['items']['substanzas']:
|
||||
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
|
||||
|
||||
def retract(self):
|
||||
async def retract(self):
|
||||
try:
|
||||
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
await self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
logging.info('Retracted item %s from node %s', self.data, self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
|
||||
|
||||
def purge(self):
|
||||
async def purge(self):
|
||||
try:
|
||||
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
logging.info('Purged all items from node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not purge items from node %s: %s', self.node, error.format())
|
||||
|
||||
def subscribe(self):
|
||||
async def subscribe(self):
|
||||
try:
|
||||
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
subscription = iq['pubsub']['subscription']
|
||||
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
|
||||
except XMPPError as error:
|
||||
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||
|
||||
def unsubscribe(self):
|
||||
async def unsubscribe(self):
|
||||
try:
|
||||
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||
@@ -118,7 +123,7 @@ if __name__ == '__main__':
|
||||
parser = ArgumentParser()
|
||||
parser.version = '%%prog 0.1'
|
||||
parser.usage = "Usage: %%prog [options] <jid> " + \
|
||||
'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \
|
||||
'nodes|create|delete|get_configure|purge|subscribe|unsubscribe|publish|retract|get' + \
|
||||
' [<node> <data>]'
|
||||
|
||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||
@@ -139,7 +144,7 @@ if __name__ == '__main__':
|
||||
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("action", choices=["nodes", "create", "delete", "get_configure", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
|
||||
parser.add_argument("node", nargs='?')
|
||||
parser.add_argument("data", nargs='?')
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from slixmpp.xmlstream.handler import Callback
|
||||
class PubsubEvents(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(PubsubEvents, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.register_plugin('xep_0030')
|
||||
self.register_plugin('xep_0059')
|
||||
@@ -38,8 +38,8 @@ class PubsubEvents(slixmpp.ClientXMPP):
|
||||
# self.add_event_handler('event_prefix_purge', handler)
|
||||
# self.add_event_handler('event_prefix_delete', handler)
|
||||
|
||||
def start(self, event):
|
||||
self.get_roster()
|
||||
async def start(self, event):
|
||||
await self.get_roster()
|
||||
self.send_presence()
|
||||
|
||||
def _publish(self, msg):
|
||||
|
||||
@@ -47,7 +47,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
||||
# for data forms and OOB links that will make that easier.
|
||||
self.add_event_handler("register", self.register)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -61,12 +61,12 @@ class RegisterBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
# We're only concerned about registering, so nothing more to do here.
|
||||
self.disconnect()
|
||||
|
||||
def register(self, iq):
|
||||
async def register(self, iq):
|
||||
"""
|
||||
Fill out and submit a registration form.
|
||||
|
||||
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
||||
resp['register']['password'] = self.password
|
||||
|
||||
try:
|
||||
yield from resp.send()
|
||||
await resp.send()
|
||||
logging.info("Account created for %s!" % self.boundjid)
|
||||
except IqError as e:
|
||||
logging.error("Could not register account: %s" %
|
||||
|
||||
@@ -38,8 +38,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
self.received = set()
|
||||
self.presences_received = asyncio.Event()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -52,21 +51,17 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
future = asyncio.Future()
|
||||
def callback(result):
|
||||
future.set_result(None)
|
||||
try:
|
||||
self.get_roster(callback=callback)
|
||||
yield from future
|
||||
await self.get_roster()
|
||||
except IqError as err:
|
||||
print('Error: %' % err.iq['error']['condition'])
|
||||
print('Error: %s' % err.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
print('Error: Request timed out')
|
||||
self.send_presence()
|
||||
|
||||
|
||||
print('Waiting for presence updates...\n')
|
||||
yield from asyncio.sleep(10)
|
||||
await asyncio.sleep(10)
|
||||
|
||||
print('Roster for %s' % self.boundjid.bare)
|
||||
groups = self.client_roster.groups()
|
||||
@@ -139,4 +134,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
@@ -36,8 +35,7 @@ class S5BSender(slixmpp.ClientXMPP):
|
||||
# and the XML streams are ready for use.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -53,14 +51,14 @@ class S5BSender(slixmpp.ClientXMPP):
|
||||
|
||||
try:
|
||||
# Open the S5B stream in which to write to.
|
||||
proxy = yield from self['xep_0065'].handshake(self.receiver)
|
||||
proxy = await self['xep_0065'].handshake(self.receiver)
|
||||
|
||||
# Send the entire file.
|
||||
while True:
|
||||
data = self.file.read(1048576)
|
||||
if not data:
|
||||
break
|
||||
yield from proxy.write(data)
|
||||
await proxy.write(data)
|
||||
|
||||
# And finally close the stream.
|
||||
proxy.transport.write_eof()
|
||||
|
||||
@@ -38,7 +38,7 @@ class SendMsgBot(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -52,7 +52,7 @@ class SendMsgBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
self.send_message(mto=self.recipient,
|
||||
mbody=self.msg,
|
||||
@@ -107,4 +107,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
||||
@@ -18,7 +18,6 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp import asyncio
|
||||
|
||||
class AvatarSetter(slixmpp.ClientXMPP):
|
||||
|
||||
@@ -33,8 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
|
||||
self.filepath = filepath
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -48,7 +46,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
avatar_file = None
|
||||
try:
|
||||
@@ -68,20 +66,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
used_xep84 = False
|
||||
|
||||
print('Publish XEP-0084 avatar data')
|
||||
result = yield from self['xep_0084'].publish_avatar(avatar)
|
||||
result = await self['xep_0084'].publish_avatar(avatar)
|
||||
if isinstance(result, XMPPError):
|
||||
print('Could not publish XEP-0084 avatar')
|
||||
else:
|
||||
used_xep84 = True
|
||||
|
||||
print('Update vCard with avatar')
|
||||
result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||
result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||
if isinstance(result, XMPPError):
|
||||
print('Could not set vCard avatar')
|
||||
|
||||
if used_xep84:
|
||||
print('Advertise XEP-0084 avatar metadata')
|
||||
result = yield from self['xep_0084'].publish_avatar_metadata([
|
||||
result = await self['xep_0084'].publish_avatar_metadata([
|
||||
{'id': avatar_id,
|
||||
'type': avatar_type,
|
||||
'bytes': avatar_bytes}
|
||||
@@ -139,4 +137,4 @@ if __name__ == '__main__':
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
xmpp.process(forever=False)
|
||||
|
||||
@@ -60,7 +60,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP):
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -74,7 +74,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
|
||||
@@ -22,7 +22,7 @@ from slixmpp import ClientXMPP
|
||||
class LocationBot(ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(LocationBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
self.add_event_handler('user_location_publish',
|
||||
@@ -38,9 +38,9 @@ class LocationBot(ClientXMPP):
|
||||
|
||||
self.current_tune = None
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
self['xep_0115'].update_caps()
|
||||
|
||||
print("Using freegeoip.net to get geolocation.")
|
||||
|
||||
@@ -17,7 +17,7 @@ from slixmpp import ClientXMPP
|
||||
class TuneBot(ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(TuneBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
# Check for the current song every 5 seconds.
|
||||
self.schedule('Check Current Tune', 5, self._update_tune, repeat=True)
|
||||
@@ -35,9 +35,9 @@ class TuneBot(ClientXMPP):
|
||||
|
||||
self.current_tune = None
|
||||
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
await self.get_roster()
|
||||
self['xep_0115'].update_caps()
|
||||
|
||||
def _update_tune(self):
|
||||
|
||||
56
setup.py
56
setup.py
@@ -7,26 +7,20 @@
|
||||
# This software is licensed as described in the README.rst and LICENSE
|
||||
# file, which you should have received as part of this distribution.
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from subprocess import call, DEVNULL, check_output, CalledProcessError
|
||||
from tempfile import TemporaryFile
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
|
||||
try:
|
||||
from Cython.Build import cythonize
|
||||
except ImportError:
|
||||
print('Cython not found, falling back to the slow stringprep module.')
|
||||
ext_modules = None
|
||||
else:
|
||||
ext_modules = cythonize('slixmpp/stringprep.pyx')
|
||||
|
||||
from run_tests import TestCommand
|
||||
from slixmpp.version import __version__
|
||||
|
||||
VERSION = __version__
|
||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, '
|
||||
'Google Talk, etc).')
|
||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).')
|
||||
with open('README.rst', encoding='utf8') as readme:
|
||||
LONG_DESCRIPTION = readme.read()
|
||||
|
||||
@@ -34,12 +28,48 @@ CLASSIFIERS = [
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Topic :: Internet :: XMPP',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
]
|
||||
|
||||
packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')]
|
||||
|
||||
def check_include(library_name, header):
|
||||
command = [os.environ.get('PKG_CONFIG', 'pkg-config'), '--cflags', library_name]
|
||||
try:
|
||||
cflags = check_output(command).decode('utf-8').split()
|
||||
except FileNotFoundError:
|
||||
print('pkg-config not found.')
|
||||
return False
|
||||
except CalledProcessError:
|
||||
# pkg-config already prints the missing libraries on stderr.
|
||||
return False
|
||||
command = [os.environ.get('CC', 'cc')] + cflags + ['-E', '-']
|
||||
with TemporaryFile('w+') as c_file:
|
||||
c_file.write('#include <%s>' % header)
|
||||
c_file.seek(0)
|
||||
try:
|
||||
return call(command, stdin=c_file, stdout=DEVNULL, stderr=DEVNULL) == 0
|
||||
except FileNotFoundError:
|
||||
print('%s headers not found.' % library_name)
|
||||
return False
|
||||
|
||||
HAS_PYTHON_HEADERS = check_include('python3', 'Python.h')
|
||||
HAS_STRINGPREP_HEADERS = check_include('libidn', 'stringprep.h')
|
||||
|
||||
ext_modules = None
|
||||
if HAS_PYTHON_HEADERS and HAS_STRINGPREP_HEADERS:
|
||||
try:
|
||||
from Cython.Build import cythonize
|
||||
except ImportError:
|
||||
print('Cython not found, falling back to the slow stringprep module.')
|
||||
else:
|
||||
ext_modules = cythonize('slixmpp/stringprep.pyx')
|
||||
else:
|
||||
print('Falling back to the slow stringprep module.')
|
||||
|
||||
setup(
|
||||
name="slixmpp",
|
||||
version=VERSION,
|
||||
@@ -47,12 +77,12 @@ setup(
|
||||
long_description=LONG_DESCRIPTION,
|
||||
author='Florent Le Coz',
|
||||
author_email='louiz@louiz.org',
|
||||
url='https://dev.louiz.org/projects/slixmpp',
|
||||
url='https://lab.louiz.org/poezio/slixmpp',
|
||||
license='MIT',
|
||||
platforms=['any'],
|
||||
packages=packages,
|
||||
ext_modules=ext_modules,
|
||||
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
|
||||
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'],
|
||||
classifiers=CLASSIFIERS,
|
||||
cmdclass={'test': TestCommand}
|
||||
)
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
asyncio.sslproto._is_sslproto_available=lambda: False
|
||||
import logging
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
||||
import asyncio
|
||||
# Required for python < 3.7 to use the old ssl implementation
|
||||
# and manage to do starttls as an unintended side effect
|
||||
asyncio.sslproto._is_sslproto_available = lambda: False
|
||||
|
||||
from slixmpp.stanza import Message, Presence, Iq
|
||||
from slixmpp.jid import JID, InvalidJID
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
:license: MIT, see LICENSE for more details
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from slixmpp import plugins, roster, stanza
|
||||
from slixmpp.api import APIRegistry
|
||||
@@ -21,8 +21,6 @@ from slixmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
from slixmpp.stanza import Message, Presence, Iq, StreamError
|
||||
from slixmpp.stanza.roster import Roster
|
||||
from slixmpp.stanza.nick import Nick
|
||||
from slixmpp.stanza.htmlim import HTMLIM
|
||||
|
||||
from slixmpp.xmlstream import XMLStream, JID
|
||||
from slixmpp.xmlstream import ET, register_stanza_plugin
|
||||
@@ -46,8 +44,8 @@ class BaseXMPP(XMLStream):
|
||||
is used during initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, jid='', default_ns='jabber:client'):
|
||||
XMLStream.__init__(self)
|
||||
def __init__(self, jid='', default_ns='jabber:client', **kwargs):
|
||||
XMLStream.__init__(self, **kwargs)
|
||||
|
||||
self.default_ns = default_ns
|
||||
self.stream_ns = 'http://etherx.jabber.org/streams'
|
||||
@@ -71,7 +69,7 @@ class BaseXMPP(XMLStream):
|
||||
#: redirections that will be followed before quitting.
|
||||
self.max_redirects = 5
|
||||
|
||||
self.session_bind_event = threading.Event()
|
||||
self.session_bind_event = asyncio.Event()
|
||||
|
||||
#: A dictionary mapping plugin names to plugins.
|
||||
self.plugin = PluginManager(self)
|
||||
@@ -106,12 +104,15 @@ class BaseXMPP(XMLStream):
|
||||
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
||||
#: messages an ID. Some plugin features require enabling
|
||||
#: this option.
|
||||
self.use_message_ids = False
|
||||
self.use_message_ids = True
|
||||
|
||||
#: Presence updates may optionally be tagged with ID values.
|
||||
#: Setting :attr:`use_message_ids` to `True` will assign all
|
||||
#: outgoing messages an ID.
|
||||
self.use_presence_ids = False
|
||||
self.use_presence_ids = True
|
||||
|
||||
#: XEP-0359 <origin-id/> tag that gets added to <message/> stanzas.
|
||||
self.use_origin_id = True
|
||||
|
||||
#: The API registry is a way to process callbacks based on
|
||||
#: JID+node combinations. Each callback in the registry is
|
||||
@@ -195,7 +196,6 @@ class BaseXMPP(XMLStream):
|
||||
|
||||
# Initialize a few default stanza plugins.
|
||||
register_stanza_plugin(Iq, Roster)
|
||||
register_stanza_plugin(Message, Nick)
|
||||
|
||||
def start_stream_handler(self, xml):
|
||||
"""Save the stream ID once the streams have been established.
|
||||
@@ -221,7 +221,7 @@ class BaseXMPP(XMLStream):
|
||||
self.plugin[name].post_init()
|
||||
self.plugin[name].post_inited = True
|
||||
|
||||
def register_plugin(self, plugin, pconfig={}, module=None):
|
||||
def register_plugin(self, plugin, pconfig=None, module=None):
|
||||
"""Register and configure a plugin for use in this stream.
|
||||
|
||||
:param plugin: The name of the plugin class. Plugin names must
|
||||
@@ -688,7 +688,6 @@ class BaseXMPP(XMLStream):
|
||||
self.address = (host, port)
|
||||
self.default_domain = host
|
||||
self.dns_records = None
|
||||
self.reconnect_delay = None
|
||||
self.reconnect()
|
||||
|
||||
def _handle_message(self, msg):
|
||||
@@ -754,6 +753,9 @@ class BaseXMPP(XMLStream):
|
||||
|
||||
Update the roster with presence information.
|
||||
"""
|
||||
if self.roster[presence['from']].ignore_updates:
|
||||
return
|
||||
|
||||
if not self.is_component and not presence['to'].bare:
|
||||
presence['to'] = self.boundjid
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@
|
||||
:license: MIT, see LICENSE for more details
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from slixmpp.jid import JID
|
||||
from slixmpp.stanza import StreamFeatures
|
||||
from slixmpp.basexmpp import BaseXMPP
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp.xmlstream import XMLStream
|
||||
from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.handler import Callback, CoroutineCallback
|
||||
|
||||
# Flag indicating if DNS SRV records are available for use.
|
||||
try:
|
||||
@@ -50,7 +52,6 @@ class ClientXMPP(BaseXMPP):
|
||||
|
||||
:param jid: The JID of the XMPP user account.
|
||||
:param password: The password for the XMPP user account.
|
||||
:param ssl: **Deprecated.**
|
||||
:param plugin_config: A dictionary of plugin configurations.
|
||||
:param plugin_whitelist: A list of approved plugins that
|
||||
will be loaded when calling
|
||||
@@ -58,9 +59,15 @@ class ClientXMPP(BaseXMPP):
|
||||
:param escape_quotes: **Deprecated.**
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
|
||||
escape_quotes=True, sasl_mech=None, lang='en'):
|
||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
||||
def __init__(self, jid, password, plugin_config=None,
|
||||
plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
|
||||
lang='en', **kwargs):
|
||||
if not plugin_whitelist:
|
||||
plugin_whitelist = []
|
||||
if not plugin_config:
|
||||
plugin_config = {}
|
||||
|
||||
BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs)
|
||||
|
||||
self.escape_quotes = escape_quotes
|
||||
self.plugin_config = plugin_config
|
||||
@@ -99,13 +106,24 @@ class ClientXMPP(BaseXMPP):
|
||||
self.register_stanza(StreamFeatures)
|
||||
|
||||
self.register_handler(
|
||||
Callback('Stream Features',
|
||||
MatchXPath('{%s}features' % self.stream_ns),
|
||||
self._handle_stream_features))
|
||||
CoroutineCallback('Stream Features',
|
||||
MatchXPath('{%s}features' % self.stream_ns),
|
||||
self._handle_stream_features))
|
||||
def roster_push_filter(iq):
|
||||
from_ = iq['from']
|
||||
if from_ and from_ != JID('') and from_ != self.boundjid.bare:
|
||||
reply = iq.reply()
|
||||
reply['type'] = 'error'
|
||||
reply['error']['type'] = 'cancel'
|
||||
reply['error']['code'] = 503
|
||||
reply['error']['condition'] = 'service-unavailable'
|
||||
reply.send()
|
||||
return
|
||||
self.event('roster_update', iq)
|
||||
self.register_handler(
|
||||
Callback('Roster Update',
|
||||
StanzaPath('iq@type=set/roster'),
|
||||
lambda iq: self.event('roster_update', iq)))
|
||||
roster_push_filter))
|
||||
|
||||
# Setup default stream features
|
||||
self.register_plugin('feature_starttls')
|
||||
@@ -135,8 +153,11 @@ class ClientXMPP(BaseXMPP):
|
||||
will be used.
|
||||
|
||||
:param address: A tuple containing the server's host and port.
|
||||
:param use_tls: Indicates if TLS should be used for the
|
||||
connection. Defaults to ``True``.
|
||||
:param force_starttls: Indicates that negotiation should be aborted
|
||||
if the server does not advertise support for
|
||||
STARTTLS. Defaults to ``True``.
|
||||
:param disable_starttls: Disables TLS for the connection.
|
||||
Defaults to ``False``.
|
||||
:param use_ssl: Indicates if the older SSL connection method
|
||||
should be used. Defaults to ``False``.
|
||||
"""
|
||||
@@ -234,7 +255,7 @@ class ClientXMPP(BaseXMPP):
|
||||
orig_cb(resp)
|
||||
callback = wrapped
|
||||
|
||||
iq.send(callback, timeout, timeout_callback)
|
||||
return iq.send(callback, timeout, timeout_callback)
|
||||
|
||||
def _reset_connection_state(self, event=None):
|
||||
#TODO: Use stream state here
|
||||
@@ -244,7 +265,7 @@ class ClientXMPP(BaseXMPP):
|
||||
self.bindfail = False
|
||||
self.features = set()
|
||||
|
||||
def _handle_stream_features(self, features):
|
||||
async def _handle_stream_features(self, features):
|
||||
"""Process the received stream features.
|
||||
|
||||
:param features: The features stanza.
|
||||
@@ -252,7 +273,11 @@ class ClientXMPP(BaseXMPP):
|
||||
for order, name in self._stream_feature_order:
|
||||
if name in features['features']:
|
||||
handler, restart = self._stream_feature_handlers[name]
|
||||
if handler(features) and restart:
|
||||
if asyncio.iscoroutinefunction(handler):
|
||||
result = await handler(features)
|
||||
else:
|
||||
result = handler(features)
|
||||
if result and restart:
|
||||
# Don't continue if the feature requires
|
||||
# restarting the XML stream.
|
||||
return True
|
||||
|
||||
@@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP):
|
||||
Defaults to ``False``.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, secret, host=None, port=None,
|
||||
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
|
||||
def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
|
||||
|
||||
if not plugin_whitelist:
|
||||
plugin_whitelist = []
|
||||
if not plugin_config:
|
||||
plugin_config = {}
|
||||
|
||||
if use_jc_ns:
|
||||
default_ns = 'jabber:client'
|
||||
else:
|
||||
|
||||
@@ -77,7 +77,7 @@ class IqTimeout(XMPPError):
|
||||
"""
|
||||
|
||||
def __init__(self, iq):
|
||||
super(IqTimeout, self).__init__(
|
||||
super().__init__(
|
||||
condition='remote-server-timeout',
|
||||
etype='cancel')
|
||||
|
||||
@@ -94,7 +94,7 @@ class IqError(XMPPError):
|
||||
"""
|
||||
|
||||
def __init__(self, iq):
|
||||
super(IqError, self).__init__(
|
||||
super().__init__(
|
||||
condition=iq['error']['condition'],
|
||||
text=iq['error']['text'],
|
||||
etype=iq['error']['type'])
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_bind.stanza import Bind
|
||||
|
||||
|
||||
register_plugin(FeatureBind)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_bind = FeatureBind
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from slixmpp.jid import JID
|
||||
@@ -34,7 +35,7 @@ class FeatureBind(BasePlugin):
|
||||
register_stanza_plugin(Iq, stanza.Bind)
|
||||
register_stanza_plugin(StreamFeatures, stanza.Bind)
|
||||
|
||||
def _handle_bind_resource(self, features):
|
||||
async def _handle_bind_resource(self, features):
|
||||
"""
|
||||
Handle requesting a specific resource.
|
||||
|
||||
@@ -49,7 +50,7 @@ class FeatureBind(BasePlugin):
|
||||
if self.xmpp.requested_jid.resource:
|
||||
iq['bind']['resource'] = self.xmpp.requested_jid.resource
|
||||
|
||||
iq.send(callback=self._on_bind_response)
|
||||
await iq.send(callback=self._on_bind_response)
|
||||
|
||||
def _on_bind_response(self, response):
|
||||
self.xmpp.boundjid = JID(response['bind']['jid'])
|
||||
|
||||
@@ -16,6 +16,6 @@ class Bind(ElementBase):
|
||||
|
||||
name = 'bind'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
|
||||
interfaces = set(('resource', 'jid'))
|
||||
interfaces = {'resource', 'jid'}
|
||||
sub_interfaces = interfaces
|
||||
plugin_attrib = 'bind'
|
||||
|
||||
@@ -16,7 +16,3 @@ from slixmpp.features.feature_mechanisms.stanza import Failure
|
||||
|
||||
|
||||
register_plugin(FeatureMechanisms)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_mechanisms = FeatureMechanisms
|
||||
|
||||
@@ -49,7 +49,7 @@ class FeatureMechanisms(BasePlugin):
|
||||
if self.security_callback is None:
|
||||
self.security_callback = self._default_security
|
||||
|
||||
creds = self.sasl_callback(set(['username']), set())
|
||||
creds = self.sasl_callback({'username'}, set())
|
||||
if not self.use_mech and not creds['username']:
|
||||
self.use_mech = 'ANONYMOUS'
|
||||
|
||||
@@ -97,12 +97,9 @@ class FeatureMechanisms(BasePlugin):
|
||||
jid = self.xmpp.requested_jid.bare
|
||||
result[value] = creds.get('email', jid)
|
||||
elif value == 'channel_binding':
|
||||
if hasattr(self.xmpp.socket, 'get_channel_binding'):
|
||||
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
|
||||
result[value] = self.xmpp.socket.get_channel_binding()
|
||||
else:
|
||||
log.debug("Channel binding not supported.")
|
||||
log.debug("Use Python 3.3+ for channel binding and " + \
|
||||
"SCRAM-SHA-1-PLUS support")
|
||||
result[value] = None
|
||||
elif value == 'host':
|
||||
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
|
||||
@@ -122,7 +119,7 @@ class FeatureMechanisms(BasePlugin):
|
||||
if value == 'encrypted':
|
||||
if 'starttls' in self.xmpp.features:
|
||||
result[value] = True
|
||||
elif isinstance(self.xmpp.socket, ssl.SSLSocket):
|
||||
elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
|
||||
result[value] = True
|
||||
else:
|
||||
result[value] = False
|
||||
@@ -190,14 +187,14 @@ class FeatureMechanisms(BasePlugin):
|
||||
except sasl.SASLCancelled:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
else:
|
||||
resp.send()
|
||||
|
||||
@@ -210,13 +207,13 @@ class FeatureMechanisms(BasePlugin):
|
||||
resp['value'] = self.mech.process(stanza['value'])
|
||||
except sasl.SASLCancelled:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except sasl.SASLFailed:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except sasl.SASLFailed:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
else:
|
||||
if resp.get_value() == '':
|
||||
resp.del_value()
|
||||
|
||||
@@ -19,12 +19,12 @@ class Auth(StanzaBase):
|
||||
|
||||
name = 'auth'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
interfaces = set(('mechanism', 'value'))
|
||||
interfaces = {'mechanism', 'value'}
|
||||
plugin_attrib = name
|
||||
|
||||
#: Some SASL mechs require sending values as is,
|
||||
#: without converting base64.
|
||||
plain_mechs = set(['X-MESSENGER-OAUTH2'])
|
||||
plain_mechs = {'X-MESSENGER-OAUTH2'}
|
||||
|
||||
def setup(self, xml):
|
||||
StanzaBase.setup(self, xml)
|
||||
|
||||
@@ -19,7 +19,7 @@ class Challenge(StanzaBase):
|
||||
|
||||
name = 'challenge'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
interfaces = set(('value',))
|
||||
interfaces = {'value'}
|
||||
plugin_attrib = name
|
||||
|
||||
def setup(self, xml):
|
||||
|
||||
@@ -16,13 +16,14 @@ class Failure(StanzaBase):
|
||||
|
||||
name = 'failure'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
interfaces = set(('condition', 'text'))
|
||||
interfaces = {'condition', 'text'}
|
||||
plugin_attrib = name
|
||||
sub_interfaces = set(('text',))
|
||||
conditions = set(('aborted', 'account-disabled', 'credentials-expired',
|
||||
'encryption-required', 'incorrect-encoding', 'invalid-authzid',
|
||||
'invalid-mechanism', 'malformed-request', 'mechansism-too-weak',
|
||||
'not-authorized', 'temporary-auth-failure'))
|
||||
sub_interfaces = {'text'}
|
||||
conditions = {'aborted', 'account-disabled', 'credentials-expired',
|
||||
'encryption-required', 'incorrect-encoding',
|
||||
'invalid-authzid', 'invalid-mechanism', 'malformed-request',
|
||||
'mechansism-too-weak', 'not-authorized',
|
||||
'temporary-auth-failure'}
|
||||
|
||||
def setup(self, xml=None):
|
||||
"""
|
||||
|
||||
@@ -16,7 +16,7 @@ class Mechanisms(ElementBase):
|
||||
|
||||
name = 'mechanisms'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
interfaces = set(('mechanisms', 'required'))
|
||||
interfaces = {'mechanisms', 'required'}
|
||||
plugin_attrib = name
|
||||
is_extension = True
|
||||
|
||||
@@ -29,7 +29,7 @@ class Mechanisms(ElementBase):
|
||||
"""
|
||||
"""
|
||||
results = []
|
||||
mechs = self.findall('{%s}mechanism' % self.namespace)
|
||||
mechs = self.xml.findall('{%s}mechanism' % self.namespace)
|
||||
if mechs:
|
||||
for mech in mechs:
|
||||
results.append(mech.text)
|
||||
@@ -47,7 +47,7 @@ class Mechanisms(ElementBase):
|
||||
def del_mechanisms(self):
|
||||
"""
|
||||
"""
|
||||
mechs = self.findall('{%s}mechanism' % self.namespace)
|
||||
mechs = self.xml.findall('{%s}mechanism' % self.namespace)
|
||||
if mechs:
|
||||
for mech in mechs:
|
||||
self.xml.remove(mech)
|
||||
|
||||
@@ -19,7 +19,7 @@ class Response(StanzaBase):
|
||||
|
||||
name = 'response'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
interfaces = set(('value',))
|
||||
interfaces = {'value'}
|
||||
plugin_attrib = name
|
||||
|
||||
def setup(self, xml):
|
||||
|
||||
@@ -18,7 +18,7 @@ class Success(StanzaBase):
|
||||
|
||||
name = 'success'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
interfaces = set(['value'])
|
||||
interfaces = {'value'}
|
||||
plugin_attrib = name
|
||||
|
||||
def setup(self, xml):
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_rosterver.stanza import RosterVer
|
||||
|
||||
|
||||
register_plugin(FeatureRosterVer)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_rosterver = FeatureRosterVer
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_session.stanza import Session
|
||||
|
||||
|
||||
register_plugin(FeatureSession)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_session = FeatureSession
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from slixmpp.stanza import Iq, StreamFeatures
|
||||
@@ -34,17 +35,22 @@ class FeatureSession(BasePlugin):
|
||||
register_stanza_plugin(Iq, stanza.Session)
|
||||
register_stanza_plugin(StreamFeatures, stanza.Session)
|
||||
|
||||
def _handle_start_session(self, features):
|
||||
async def _handle_start_session(self, features):
|
||||
"""
|
||||
Handle the start of the session.
|
||||
|
||||
Arguments:
|
||||
feature -- The stream features element.
|
||||
"""
|
||||
if features['session']['optional']:
|
||||
self.xmpp.sessionstarted = True
|
||||
self.xmpp.event('session_start')
|
||||
return
|
||||
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq.enable('session')
|
||||
iq.send(callback=self._on_start_session_response)
|
||||
await iq.send(callback=self._on_start_session_response)
|
||||
|
||||
def _on_start_session_response(self, response):
|
||||
self.xmpp.features.add('session')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
from slixmpp.xmlstream import ElementBase, ET
|
||||
|
||||
|
||||
class Session(ElementBase):
|
||||
@@ -16,5 +16,19 @@ class Session(ElementBase):
|
||||
|
||||
name = 'session'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-session'
|
||||
interfaces = set()
|
||||
interfaces = {'optional'}
|
||||
plugin_attrib = 'session'
|
||||
|
||||
def get_optional(self):
|
||||
return self.xml.find('{%s}optional' % self.namespace) is not None
|
||||
|
||||
def set_optional(self, value):
|
||||
if value:
|
||||
optional = ET.Element('{%s}optional' % self.namespace)
|
||||
self.xml.append(optional)
|
||||
else:
|
||||
self.del_optional()
|
||||
|
||||
def del_optional(self):
|
||||
optional = self.xml.find('{%s}optional' % self.namespace)
|
||||
self.xml.remove(optional)
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_starttls.stanza import *
|
||||
|
||||
|
||||
register_plugin(FeatureSTARTTLS)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_starttls = FeatureSTARTTLS
|
||||
|
||||
@@ -16,7 +16,7 @@ class STARTTLS(ElementBase):
|
||||
|
||||
name = 'starttls'
|
||||
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
|
||||
interfaces = set(('required',))
|
||||
interfaces = {'required'}
|
||||
plugin_attrib = name
|
||||
|
||||
def get_required(self):
|
||||
|
||||
@@ -12,7 +12,7 @@ from slixmpp.stanza import StreamFeatures
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.xmlstream.matcher import MatchXPath
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.handler import CoroutineCallback
|
||||
from slixmpp.features.feature_starttls import stanza
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class FeatureSTARTTLS(BasePlugin):
|
||||
|
||||
def plugin_init(self):
|
||||
self.xmpp.register_handler(
|
||||
Callback('STARTTLS Proceed',
|
||||
CoroutineCallback('STARTTLS Proceed',
|
||||
MatchXPath(stanza.Proceed.tag_name()),
|
||||
self._handle_starttls_proceed,
|
||||
instream=True))
|
||||
@@ -58,8 +58,8 @@ class FeatureSTARTTLS(BasePlugin):
|
||||
self.xmpp.send(features['starttls'])
|
||||
return True
|
||||
|
||||
def _handle_starttls_proceed(self, proceed):
|
||||
async def _handle_starttls_proceed(self, proceed):
|
||||
"""Restart the XML stream when TLS is accepted."""
|
||||
log.debug("Starting TLS")
|
||||
if self.xmpp.start_tls():
|
||||
if await self.xmpp.start_tls():
|
||||
self.xmpp.features.add('starttls')
|
||||
|
||||
158
slixmpp/jid.py
158
slixmpp/jid.py
@@ -16,6 +16,7 @@ import socket
|
||||
|
||||
from copy import deepcopy
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
|
||||
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
|
||||
|
||||
@@ -71,7 +72,7 @@ def _parse_jid(data):
|
||||
return node, domain, resource
|
||||
|
||||
|
||||
def _validate_node(node):
|
||||
def _validate_node(node: Optional[str]):
|
||||
"""Validate the local, or username, portion of a JID.
|
||||
|
||||
:raises InvalidJID:
|
||||
@@ -79,7 +80,7 @@ def _validate_node(node):
|
||||
:returns: The local portion of a JID, as validated by nodeprep.
|
||||
"""
|
||||
if node is None:
|
||||
return None
|
||||
return ''
|
||||
|
||||
try:
|
||||
node = nodeprep(node)
|
||||
@@ -93,7 +94,7 @@ def _validate_node(node):
|
||||
return node
|
||||
|
||||
|
||||
def _validate_domain(domain):
|
||||
def _validate_domain(domain: str):
|
||||
"""Validate the domain portion of a JID.
|
||||
|
||||
IP literal addresses are left as-is, if valid. Domain names
|
||||
@@ -152,7 +153,7 @@ def _validate_domain(domain):
|
||||
return domain
|
||||
|
||||
|
||||
def _validate_resource(resource):
|
||||
def _validate_resource(resource: Optional[str]):
|
||||
"""Validate the resource portion of a JID.
|
||||
|
||||
:raises InvalidJID:
|
||||
@@ -160,7 +161,7 @@ def _validate_resource(resource):
|
||||
:returns: The local portion of a JID, as validated by resourceprep.
|
||||
"""
|
||||
if resource is None:
|
||||
return None
|
||||
return ''
|
||||
|
||||
try:
|
||||
resource = resourceprep(resource)
|
||||
@@ -174,7 +175,7 @@ def _validate_resource(resource):
|
||||
return resource
|
||||
|
||||
|
||||
def _unescape_node(node):
|
||||
def _unescape_node(node: str):
|
||||
"""Unescape a local portion of a JID.
|
||||
|
||||
.. note::
|
||||
@@ -199,7 +200,11 @@ def _unescape_node(node):
|
||||
return ''.join(unescaped)
|
||||
|
||||
|
||||
def _format_jid(local=None, domain=None, resource=None):
|
||||
def _format_jid(
|
||||
local: Optional[str] = None,
|
||||
domain: Optional[str] = None,
|
||||
resource: Optional[str] = None,
|
||||
):
|
||||
"""Format the given JID components into a full or bare JID.
|
||||
|
||||
:param string local: Optional. The local portion of the JID.
|
||||
@@ -208,16 +213,15 @@ def _format_jid(local=None, domain=None, resource=None):
|
||||
|
||||
:return: A full or bare JID string.
|
||||
"""
|
||||
result = []
|
||||
if domain is None:
|
||||
return ''
|
||||
if local is not None:
|
||||
result.append(local)
|
||||
result.append('@')
|
||||
if domain is not None:
|
||||
result.append(domain)
|
||||
result = local + '@' + domain
|
||||
else:
|
||||
result = domain
|
||||
if resource is not None:
|
||||
result.append('/')
|
||||
result.append(resource)
|
||||
return ''.join(result)
|
||||
result += '/' + resource
|
||||
return result
|
||||
|
||||
|
||||
class InvalidJID(ValueError):
|
||||
@@ -238,12 +242,17 @@ class UnescapedJID:
|
||||
|
||||
__slots__ = ('_node', '_domain', '_resource')
|
||||
|
||||
def __init__(self, node, domain, resource):
|
||||
def __init__(
|
||||
self,
|
||||
node: Optional[str],
|
||||
domain: Optional[str],
|
||||
resource: Optional[str],
|
||||
):
|
||||
self._node = node
|
||||
self._domain = domain
|
||||
self._resource = resource
|
||||
|
||||
def __getattribute__(self, name):
|
||||
def __getattribute__(self, name: str):
|
||||
"""Retrieve the given JID component.
|
||||
|
||||
:param name: one of: user, server, domain, resource,
|
||||
@@ -300,19 +309,23 @@ class JID:
|
||||
:raises InvalidJID:
|
||||
"""
|
||||
|
||||
__slots__ = ('_node', '_domain', '_resource')
|
||||
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
|
||||
|
||||
def __init__(self, jid=None):
|
||||
def __init__(self, jid: Optional[str] = None):
|
||||
if not jid:
|
||||
self._node = None
|
||||
self._domain = None
|
||||
self._resource = None
|
||||
self._node = ''
|
||||
self._domain = ''
|
||||
self._resource = ''
|
||||
self._bare = ''
|
||||
self._full = ''
|
||||
return
|
||||
elif not isinstance(jid, JID):
|
||||
self._node, self._domain, self._resource = _parse_jid(jid)
|
||||
else:
|
||||
self._node = jid._node
|
||||
self._domain = jid._domain
|
||||
self._resource = jid._resource
|
||||
self._update_bare_full()
|
||||
|
||||
def unescape(self):
|
||||
"""Return an unescaped JID object.
|
||||
@@ -329,104 +342,80 @@ class JID:
|
||||
self._domain,
|
||||
self._resource)
|
||||
|
||||
def _update_bare_full(self):
|
||||
"""Format the given JID into a bare and a full JID.
|
||||
"""
|
||||
self._bare = (self._node + '@' + self._domain
|
||||
if self._node
|
||||
else self._domain)
|
||||
self._full = (self._bare + '/' + self._resource
|
||||
if self._resource
|
||||
else self._bare)
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
return self._node or ''
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
return self._node or ''
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return self._node or ''
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self._node or ''
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return self._domain or ''
|
||||
|
||||
@property
|
||||
def server(self):
|
||||
return self._domain or ''
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
return self._domain or ''
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def resource(self):
|
||||
return self._resource or ''
|
||||
return self._resource
|
||||
|
||||
@property
|
||||
def bare(self):
|
||||
return _format_jid(self._node, self._domain)
|
||||
return self._bare
|
||||
|
||||
@property
|
||||
def full(self):
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
|
||||
@property
|
||||
def jid(self):
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
return self._full
|
||||
|
||||
@node.setter
|
||||
def node(self, value):
|
||||
self._node = _validate_node(value)
|
||||
|
||||
@user.setter
|
||||
def user(self, value):
|
||||
self._node = _validate_node(value)
|
||||
|
||||
@local.setter
|
||||
def local(self, value):
|
||||
self._node = _validate_node(value)
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
def node(self, value: str):
|
||||
self._node = _validate_node(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@domain.setter
|
||||
def domain(self, value):
|
||||
self._domain = _validate_domain(value)
|
||||
|
||||
@server.setter
|
||||
def server(self, value):
|
||||
self._domain = _validate_domain(value)
|
||||
|
||||
@host.setter
|
||||
def host(self, value):
|
||||
def domain(self, value: str):
|
||||
self._domain = _validate_domain(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@bare.setter
|
||||
def bare(self, value):
|
||||
def bare(self, value: str):
|
||||
node, domain, resource = _parse_jid(value)
|
||||
assert not resource
|
||||
self._node = node
|
||||
self._domain = domain
|
||||
self._update_bare_full()
|
||||
|
||||
@resource.setter
|
||||
def resource(self, value):
|
||||
def resource(self, value: str):
|
||||
self._resource = _validate_resource(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@full.setter
|
||||
def full(self, value):
|
||||
def full(self, value: str):
|
||||
self._node, self._domain, self._resource = _parse_jid(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@jid.setter
|
||||
def jid(self, value):
|
||||
self._node, self._domain, self._resource = _parse_jid(value)
|
||||
user = node
|
||||
local = node
|
||||
username = node
|
||||
|
||||
server = domain
|
||||
host = domain
|
||||
|
||||
jid = full
|
||||
|
||||
def __str__(self):
|
||||
"""Use the full JID as the string value."""
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
return self._full
|
||||
|
||||
def __repr__(self):
|
||||
"""Use the full JID as the representation."""
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
return self._full
|
||||
|
||||
# pylint: disable=W0212
|
||||
def __eq__(self, other):
|
||||
@@ -434,7 +423,10 @@ class JID:
|
||||
if isinstance(other, UnescapedJID):
|
||||
return False
|
||||
if not isinstance(other, JID):
|
||||
other = JID(other)
|
||||
try:
|
||||
other = JID(other)
|
||||
except InvalidJID:
|
||||
return NotImplemented
|
||||
|
||||
return (self._node == other._node and
|
||||
self._domain == other._domain and
|
||||
@@ -446,4 +438,4 @@ class JID:
|
||||
|
||||
def __hash__(self):
|
||||
"""Hash a JID based on the string version of its full JID."""
|
||||
return hash(_format_jid(self._node, self._domain, self._resource))
|
||||
return hash(self._full)
|
||||
|
||||
@@ -47,6 +47,7 @@ __all__ = [
|
||||
'xep_0108', # User Activity
|
||||
'xep_0115', # Entity Capabilities
|
||||
'xep_0118', # User Tune
|
||||
'xep_0122', # Data Forms Validation
|
||||
'xep_0128', # Extended Service Discovery
|
||||
'xep_0131', # Standard Headers and Internet Metadata
|
||||
'xep_0133', # Service Administration
|
||||
@@ -83,4 +84,7 @@ __all__ = [
|
||||
'xep_0319', # Last User Interaction in Presence
|
||||
'xep_0323', # IoT Systems Sensor Data
|
||||
'xep_0325', # IoT Systems Control
|
||||
'xep_0332', # HTTP Over XMPP Transport
|
||||
'protoxep_reactions', # https://dino.im/xeps/reactions.html
|
||||
'protoxep_occupantid', # https://dino.im/xeps/occupant-id.html
|
||||
]
|
||||
|
||||
@@ -308,7 +308,7 @@ class BasePlugin(object):
|
||||
if key in self.default_config:
|
||||
self.config[key] = value
|
||||
else:
|
||||
super(BasePlugin, self).__setattr__(key, value)
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def _init(self):
|
||||
"""Initialize plugin state, such as registering event handlers.
|
||||
|
||||
@@ -21,15 +21,15 @@ class GmailQuery(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'query'
|
||||
plugin_attrib = 'gmail'
|
||||
interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
|
||||
interfaces = {'newer-than-time', 'newer-than-tid', 'q', 'search'}
|
||||
|
||||
def getSearch(self):
|
||||
def get_search(self):
|
||||
return self['q']
|
||||
|
||||
def setSearch(self, search):
|
||||
def set_search(self, search):
|
||||
self['q'] = search
|
||||
|
||||
def delSearch(self):
|
||||
def del_search(self):
|
||||
del self['q']
|
||||
|
||||
|
||||
@@ -37,20 +37,20 @@ class MailBox(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'mailbox'
|
||||
plugin_attrib = 'mailbox'
|
||||
interfaces = set(('result-time', 'total-matched', 'total-estimate',
|
||||
'url', 'threads', 'matched', 'estimate'))
|
||||
interfaces = {'result-time', 'total-matched', 'total-estimate',
|
||||
'url', 'threads', 'matched', 'estimate'}
|
||||
|
||||
def getThreads(self):
|
||||
def get_threads(self):
|
||||
threads = []
|
||||
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
|
||||
MailThread.name)):
|
||||
threads.append(MailThread(xml=threadXML, parent=None))
|
||||
return threads
|
||||
|
||||
def getMatched(self):
|
||||
def get_matched(self):
|
||||
return self['total-matched']
|
||||
|
||||
def getEstimate(self):
|
||||
def get_estimate(self):
|
||||
return self['total-estimate'] == '1'
|
||||
|
||||
|
||||
@@ -58,11 +58,11 @@ class MailThread(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'mail-thread-info'
|
||||
plugin_attrib = 'thread'
|
||||
interfaces = set(('tid', 'participation', 'messages', 'date',
|
||||
'senders', 'url', 'labels', 'subject', 'snippet'))
|
||||
sub_interfaces = set(('labels', 'subject', 'snippet'))
|
||||
interfaces = {'tid', 'participation', 'messages', 'date',
|
||||
'senders', 'url', 'labels', 'subject', 'snippet'}
|
||||
sub_interfaces = {'labels', 'subject', 'snippet'}
|
||||
|
||||
def getSenders(self):
|
||||
def get_senders(self):
|
||||
senders = []
|
||||
sendersXML = self.xml.find('{%s}senders' % self.namespace)
|
||||
if sendersXML is not None:
|
||||
@@ -75,12 +75,12 @@ class MailSender(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'sender'
|
||||
plugin_attrib = 'sender'
|
||||
interfaces = set(('address', 'name', 'originator', 'unread'))
|
||||
interfaces = {'address', 'name', 'originator', 'unread'}
|
||||
|
||||
def getOriginator(self):
|
||||
def get_originator(self):
|
||||
return self.xml.attrib.get('originator', '0') == '1'
|
||||
|
||||
def getUnread(self):
|
||||
def get_unread(self):
|
||||
return self.xml.attrib.get('unread', '0') == '1'
|
||||
|
||||
|
||||
|
||||
47
slixmpp/plugins/google/__init__.py
Normal file
47
slixmpp/plugins/google/__init__.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.plugins.base import register_plugin, BasePlugin
|
||||
|
||||
from slixmpp.plugins.google.gmail import Gmail
|
||||
from slixmpp.plugins.google.auth import GoogleAuth
|
||||
from slixmpp.plugins.google.settings import GoogleSettings
|
||||
from slixmpp.plugins.google.nosave import GoogleNoSave
|
||||
|
||||
|
||||
class Google(BasePlugin):
|
||||
|
||||
"""
|
||||
Google: Custom GTalk Features
|
||||
|
||||
Also see: <https://developers.google.com/talk/jep_extensions/extensions>
|
||||
"""
|
||||
|
||||
name = 'google'
|
||||
description = 'Google: Custom GTalk Features'
|
||||
dependencies = set([
|
||||
'gmail',
|
||||
'google_settings',
|
||||
'google_nosave',
|
||||
'google_auth'
|
||||
])
|
||||
|
||||
def __getitem__(self, attr):
|
||||
if attr in ('settings', 'nosave', 'auth'):
|
||||
return self.xmpp['google_%s' % attr]
|
||||
elif attr == 'gmail':
|
||||
return self.xmpp['gmail']
|
||||
else:
|
||||
raise KeyError(attr)
|
||||
|
||||
|
||||
register_plugin(Gmail)
|
||||
register_plugin(GoogleAuth)
|
||||
register_plugin(GoogleSettings)
|
||||
register_plugin(GoogleNoSave)
|
||||
register_plugin(Google)
|
||||
10
slixmpp/plugins/google/auth/__init__.py
Normal file
10
slixmpp/plugins/google/auth/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.plugins.google.auth import stanza
|
||||
from slixmpp.plugins.google.auth.auth import GoogleAuth
|
||||
47
slixmpp/plugins/google/auth/auth.py
Normal file
47
slixmpp/plugins/google/auth/auth.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.google.auth import stanza
|
||||
|
||||
|
||||
class GoogleAuth(BasePlugin):
|
||||
|
||||
"""
|
||||
Google: Auth Extensions (JID Domain Discovery, OAuth2)
|
||||
|
||||
Also see:
|
||||
<https://developers.google.com/talk/jep_extensions/jid_domain_change>
|
||||
<https://developers.google.com/talk/jep_extensions/oauth>
|
||||
"""
|
||||
|
||||
name = 'google_auth'
|
||||
description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)'
|
||||
dependencies = set(['feature_mechanisms'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga'
|
||||
|
||||
register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth,
|
||||
stanza.GoogleAuth)
|
||||
|
||||
self.xmpp.add_filter('out', self._auth)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.del_filter('out', self._auth)
|
||||
|
||||
def _auth(self, stanza):
|
||||
if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth):
|
||||
stanza.stream = self.xmpp
|
||||
stanza['google']['client_uses_full_bind_result'] = True
|
||||
if stanza['mechanism'] == 'X-OAUTH2':
|
||||
stanza['google']['service'] = 'oauth2'
|
||||
print(stanza)
|
||||
return stanza
|
||||
47
slixmpp/plugins/google/auth/stanza.py
Normal file
47
slixmpp/plugins/google/auth/stanza.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase, ET
|
||||
|
||||
|
||||
class GoogleAuth(ElementBase):
|
||||
name = 'auth'
|
||||
namespace = 'http://www.google.com/talk/protocol/auth'
|
||||
plugin_attrib = 'google'
|
||||
interfaces = {'client_uses_full_bind_result', 'service'}
|
||||
|
||||
discovery_attr= '{%s}client-uses-full-bind-result' % namespace
|
||||
service_attr= '{%s}service' % namespace
|
||||
|
||||
def setup(self, xml):
|
||||
"""Don't create XML for the plugin."""
|
||||
self.xml = ET.Element('')
|
||||
|
||||
def get_client_uses_full_bind_result(self):
|
||||
return self.parent()._get_attr(self.discovery_attr) == 'true'
|
||||
|
||||
def set_client_uses_full_bind_result(self, value):
|
||||
if value in (True, 'true'):
|
||||
self.parent()._set_attr(self.discovery_attr, 'true')
|
||||
else:
|
||||
self.parent()._del_attr(self.discovery_attr)
|
||||
|
||||
def del_client_uses_full_bind_result(self):
|
||||
self.parent()._del_attr(self.discovery_attr)
|
||||
|
||||
def get_service(self):
|
||||
return self.parent()._get_attr(self.service_attr, '')
|
||||
|
||||
def set_service(self, value):
|
||||
if value:
|
||||
self.parent()._set_attr(self.service_attr, value)
|
||||
else:
|
||||
self.parent()._del_attr(self.service_attr)
|
||||
|
||||
def del_service(self):
|
||||
self.parent()._del_attr(self.service_attr)
|
||||
10
slixmpp/plugins/google/gmail/__init__.py
Normal file
10
slixmpp/plugins/google/gmail/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.plugins.google.gmail import stanza
|
||||
from slixmpp.plugins.google.gmail.notifications import Gmail
|
||||
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from slixmpp.stanza import Iq
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import MatchXPath
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.google.gmail import stanza
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Gmail(BasePlugin):
|
||||
|
||||
"""
|
||||
Google: Gmail Notifications
|
||||
|
||||
Also see <https://developers.google.com/talk/jep_extensions/gmail>.
|
||||
"""
|
||||
|
||||
name = 'gmail'
|
||||
description = 'Google: Gmail Notifications'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, stanza.GmailQuery)
|
||||
register_stanza_plugin(Iq, stanza.MailBox)
|
||||
register_stanza_plugin(Iq, stanza.NewMail)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Gmail New Mail',
|
||||
MatchXPath('{%s}iq/{%s}%s' % (
|
||||
self.xmpp.default_ns,
|
||||
stanza.NewMail.namespace,
|
||||
stanza.NewMail.name)),
|
||||
self._handle_new_mail))
|
||||
|
||||
self._last_result_time = None
|
||||
self._last_result_tid = None
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Gmail New Mail')
|
||||
|
||||
def _handle_new_mail(self, iq):
|
||||
log.info('Gmail: New email!')
|
||||
iq.reply().send()
|
||||
self.xmpp.event('gmail_notification')
|
||||
|
||||
def check(self, timeout=None, callback=None):
|
||||
last_time = self._last_result_time
|
||||
last_tid = self._last_result_tid
|
||||
|
||||
callback = lambda iq: self._update_last_results(iq, callback)
|
||||
|
||||
return self.search(newer_time=last_time,
|
||||
newer_tid=last_tid,
|
||||
timeout=timeout,
|
||||
callback=callback)
|
||||
|
||||
def _update_last_results(self, iq, callback=None):
|
||||
self._last_result_time = iq['gmail_messages']['result_time']
|
||||
threads = iq['gmail_messages']['threads']
|
||||
if threads:
|
||||
self._last_result_tid = threads[0]['tid']
|
||||
if callback:
|
||||
callback(iq)
|
||||
|
||||
def search(self, query=None, newer_time=None, newer_tid=None,
|
||||
timeout=None, callback=None):
|
||||
if not query:
|
||||
log.info('Gmail: Checking for new email')
|
||||
else:
|
||||
log.info('Gmail: Searching for emails matching: "%s"', query)
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['to'] = self.xmpp.boundjid.bare
|
||||
iq['gmail']['search'] = query
|
||||
iq['gmail']['newer_than_time'] = newer_time
|
||||
iq['gmail']['newer_than_tid'] = newer_tid
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
101
slixmpp/plugins/google/gmail/stanza.py
Normal file
101
slixmpp/plugins/google/gmail/stanza.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
class GmailQuery(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'query'
|
||||
plugin_attrib = 'gmail'
|
||||
interfaces = set(['newer_than_time', 'newer_than_tid', 'search'])
|
||||
|
||||
def get_search(self):
|
||||
return self._get_attr('q', '')
|
||||
|
||||
def set_search(self, search):
|
||||
self._set_attr('q', search)
|
||||
|
||||
def del_search(self):
|
||||
self._del_attr('q')
|
||||
|
||||
def get_newer_than_time(self):
|
||||
return self._get_attr('newer-than-time', '')
|
||||
|
||||
def set_newer_than_time(self, value):
|
||||
self._set_attr('newer-than-time', value)
|
||||
|
||||
def del_newer_than_time(self):
|
||||
self._del_attr('newer-than-time')
|
||||
|
||||
def get_newer_than_tid(self):
|
||||
return self._get_attr('newer-than-tid', '')
|
||||
|
||||
def set_newer_than_tid(self, value):
|
||||
self._set_attr('newer-than-tid', value)
|
||||
|
||||
def del_newer_than_tid(self):
|
||||
self._del_attr('newer-than-tid')
|
||||
|
||||
|
||||
class MailBox(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'mailbox'
|
||||
plugin_attrib = 'gmail_messages'
|
||||
interfaces = set(['result_time', 'url', 'matched', 'estimate'])
|
||||
|
||||
def get_matched(self):
|
||||
return self._get_attr('total-matched', '')
|
||||
|
||||
def get_estimate(self):
|
||||
return self._get_attr('total-estimate', '') == '1'
|
||||
|
||||
def get_result_time(self):
|
||||
return self._get_attr('result-time', '')
|
||||
|
||||
|
||||
class MailThread(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'mail-thread-info'
|
||||
plugin_attrib = 'thread'
|
||||
plugin_multi_attrib = 'threads'
|
||||
interfaces = set(['tid', 'participation', 'messages', 'date',
|
||||
'senders', 'url', 'labels', 'subject', 'snippet'])
|
||||
sub_interfaces = set(['labels', 'subject', 'snippet'])
|
||||
|
||||
def get_senders(self):
|
||||
result = []
|
||||
senders = self.xml.findall('{%s}senders/{%s}sender' % (
|
||||
self.namespace, self.namespace))
|
||||
|
||||
for sender in senders:
|
||||
result.append(MailSender(xml=sender))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class MailSender(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'sender'
|
||||
plugin_attrib = name
|
||||
interfaces = set(['address', 'name', 'originator', 'unread'])
|
||||
|
||||
def get_originator(self):
|
||||
return self.xml.attrib.get('originator', '0') == '1'
|
||||
|
||||
def get_unread(self):
|
||||
return self.xml.attrib.get('unread', '0') == '1'
|
||||
|
||||
|
||||
class NewMail(ElementBase):
|
||||
namespace = 'google:mail:notify'
|
||||
name = 'new-mail'
|
||||
plugin_attrib = 'gmail_notification'
|
||||
|
||||
|
||||
register_stanza_plugin(MailBox, MailThread, iterable=True)
|
||||
10
slixmpp/plugins/google/nosave/__init__.py
Normal file
10
slixmpp/plugins/google/nosave/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.plugins.google.nosave import stanza
|
||||
from slixmpp.plugins.google.nosave.nosave import GoogleNoSave
|
||||
78
slixmpp/plugins/google/nosave/nosave.py
Normal file
78
slixmpp/plugins/google/nosave/nosave.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.stanza import Iq, Message
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.google.nosave import stanza
|
||||
|
||||
|
||||
class GoogleNoSave(BasePlugin):
|
||||
|
||||
"""
|
||||
Google: Off the Record Chats
|
||||
|
||||
NOTE: This is NOT an encryption method.
|
||||
|
||||
Also see <https://developers.google.com/talk/jep_extensions/otr>.
|
||||
"""
|
||||
|
||||
name = 'google_nosave'
|
||||
description = 'Google: Off the Record Chats'
|
||||
dependencies = set(['google_settings'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Message, stanza.NoSave)
|
||||
register_stanza_plugin(Iq, stanza.NoSaveQuery)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Google Nosave',
|
||||
StanzaPath('iq@type=set/google_nosave'),
|
||||
self._handle_nosave_change))
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Google Nosave')
|
||||
|
||||
def enable(self, jid=None, timeout=None, callback=None):
|
||||
if jid is None:
|
||||
self.xmpp['google_settings'].update({'archiving_enabled': False},
|
||||
timeout=timeout, callback=callback)
|
||||
else:
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['google_nosave']['item']['jid'] = jid
|
||||
iq['google_nosave']['item']['value'] = True
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def disable(self, jid=None, timeout=None, callback=None):
|
||||
if jid is None:
|
||||
self.xmpp['google_settings'].update({'archiving_enabled': True},
|
||||
timeout=timeout, callback=callback)
|
||||
else:
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['google_nosave']['item']['jid'] = jid
|
||||
iq['google_nosave']['item']['value'] = False
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def get(self, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq.enable('google_nosave')
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def _handle_nosave_change(self, iq):
|
||||
reply = self.xmpp.Iq()
|
||||
reply['type'] = 'result'
|
||||
reply['id'] = iq['id']
|
||||
reply['to'] = iq['from']
|
||||
reply.send()
|
||||
self.xmpp.event('google_nosave_change', iq)
|
||||
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.jid import JID
|
||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
class NoSave(ElementBase):
|
||||
name = 'x'
|
||||
namespace = 'google:nosave'
|
||||
plugin_attrib = 'google_nosave'
|
||||
interfaces = {'value'}
|
||||
|
||||
def get_value(self):
|
||||
return self._get_attr('value', '') == 'enabled'
|
||||
|
||||
def set_value(self, value):
|
||||
self._set_attr('value', 'enabled' if value else 'disabled')
|
||||
|
||||
|
||||
class NoSaveQuery(ElementBase):
|
||||
name = 'query'
|
||||
namespace = 'google:nosave'
|
||||
plugin_attrib = 'google_nosave'
|
||||
interfaces = set()
|
||||
|
||||
|
||||
class Item(ElementBase):
|
||||
name = 'item'
|
||||
namespace = 'google:nosave'
|
||||
plugin_attrib = 'item'
|
||||
plugin_multi_attrib = 'items'
|
||||
interfaces = {'jid', 'source', 'value'}
|
||||
|
||||
def get_value(self):
|
||||
return self._get_attr('value', '') == 'enabled'
|
||||
|
||||
def set_value(self, value):
|
||||
self._set_attr('value', 'enabled' if value else 'disabled')
|
||||
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid', ''))
|
||||
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def get_source(self):
|
||||
return JID(self._get_attr('source', ''))
|
||||
|
||||
def set_source(self, value):
|
||||
self._set_attr('source', str(value))
|
||||
|
||||
|
||||
register_stanza_plugin(NoSaveQuery, Item)
|
||||
10
slixmpp/plugins/google/settings/__init__.py
Normal file
10
slixmpp/plugins/google/settings/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.plugins.google.settings import stanza
|
||||
from slixmpp.plugins.google.settings.settings import GoogleSettings
|
||||
63
slixmpp/plugins/google/settings/settings.py
Normal file
63
slixmpp/plugins/google/settings/settings.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.stanza import Iq
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.google.settings import stanza
|
||||
|
||||
|
||||
class GoogleSettings(BasePlugin):
|
||||
|
||||
"""
|
||||
Google: Gmail Notifications
|
||||
|
||||
Also see <https://developers.google.com/talk/jep_extensions/usersettings>.
|
||||
"""
|
||||
|
||||
name = 'google_settings'
|
||||
description = 'Google: User Settings'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, stanza.UserSettings)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Google Settings',
|
||||
StanzaPath('iq@type=set/google_settings'),
|
||||
self._handle_settings_change))
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Google Settings')
|
||||
|
||||
def get(self, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq.enable('google_settings')
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def update(self, settings, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq.enable('google_settings')
|
||||
|
||||
for setting, value in settings.items():
|
||||
iq['google_settings'][setting] = value
|
||||
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def _handle_settings_change(self, iq):
|
||||
reply = self.xmpp.Iq()
|
||||
reply['type'] = 'result'
|
||||
reply['id'] = iq['id']
|
||||
reply['to'] = iq['from']
|
||||
reply.send()
|
||||
self.xmpp.event('google_settings_change', iq)
|
||||
110
slixmpp/plugins/google/settings/stanza.py
Normal file
110
slixmpp/plugins/google/settings/stanza.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ET, ElementBase
|
||||
|
||||
|
||||
class UserSettings(ElementBase):
|
||||
name = 'usersetting'
|
||||
namespace = 'google:setting'
|
||||
plugin_attrib = 'google_settings'
|
||||
interfaces = set(['auto_accept_suggestions',
|
||||
'mail_notifications',
|
||||
'archiving_enabled',
|
||||
'gmail',
|
||||
'email_verified',
|
||||
'domain_privacy_notice',
|
||||
'display_name'])
|
||||
|
||||
def _get_setting(self, setting):
|
||||
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
|
||||
if xml is not None:
|
||||
return xml.attrib.get('value', '') == 'true'
|
||||
return False
|
||||
|
||||
def _set_setting(self, setting, value):
|
||||
self._del_setting(setting)
|
||||
if value in (True, False):
|
||||
xml = ET.Element('{%s}%s' % (self.namespace, setting))
|
||||
xml.attrib['value'] = 'true' if value else 'false'
|
||||
self.xml.append(xml)
|
||||
|
||||
def _del_setting(self, setting):
|
||||
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
|
||||
if xml is not None:
|
||||
self.xml.remove(xml)
|
||||
|
||||
def get_display_name(self):
|
||||
xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname'))
|
||||
if xml is not None:
|
||||
return xml.attrib.get('value', '')
|
||||
return ''
|
||||
|
||||
def set_display_name(self, value):
|
||||
self._del_setting(setting)
|
||||
if value:
|
||||
xml = ET.Element('{%s}%s' % (self.namespace, 'displayname'))
|
||||
xml.attrib['value'] = value
|
||||
self.xml.append(xml)
|
||||
|
||||
def del_display_name(self):
|
||||
self._del_setting('displayname')
|
||||
|
||||
def get_auto_accept_suggestions(self):
|
||||
return self._get_setting('autoacceptsuggestions')
|
||||
|
||||
def get_mail_notifications(self):
|
||||
return self._get_setting('mailnotifications')
|
||||
|
||||
def get_archiving_enabled(self):
|
||||
return self._get_setting('archivingenabled')
|
||||
|
||||
def get_gmail(self):
|
||||
return self._get_setting('gmail')
|
||||
|
||||
def get_email_verified(self):
|
||||
return self._get_setting('emailverified')
|
||||
|
||||
def get_domain_privacy_notice(self):
|
||||
return self._get_setting('domainprivacynotice')
|
||||
|
||||
def set_auto_accept_suggestions(self, value):
|
||||
self._set_setting('autoacceptsuggestions', value)
|
||||
|
||||
def set_mail_notifications(self, value):
|
||||
self._set_setting('mailnotifications', value)
|
||||
|
||||
def set_archiving_enabled(self, value):
|
||||
self._set_setting('archivingenabled', value)
|
||||
|
||||
def set_gmail(self, value):
|
||||
self._set_setting('gmail', value)
|
||||
|
||||
def set_email_verified(self, value):
|
||||
self._set_setting('emailverified', value)
|
||||
|
||||
def set_domain_privacy_notice(self, value):
|
||||
self._set_setting('domainprivacynotice', value)
|
||||
|
||||
def del_auto_accept_suggestions(self):
|
||||
self._del_setting('autoacceptsuggestions')
|
||||
|
||||
def del_mail_notifications(self):
|
||||
self._del_setting('mailnotifications')
|
||||
|
||||
def del_archiving_enabled(self):
|
||||
self._del_setting('archivingenabled')
|
||||
|
||||
def del_gmail(self):
|
||||
self._del_setting('gmail')
|
||||
|
||||
def del_email_verified(self):
|
||||
self._del_setting('emailverified')
|
||||
|
||||
def del_domain_privacy_notice(self):
|
||||
self._del_setting('domainprivacynotice')
|
||||
12
slixmpp/plugins/protoxep_occupantid/__init__.py
Normal file
12
slixmpp/plugins/protoxep_occupantid/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
from slixmpp.plugins.protoxep_occupantid.occupantid import XEP_OccupantID
|
||||
from slixmpp.plugins.protoxep_occupantid.stanza import OccupantID
|
||||
|
||||
register_plugin(XEP_OccupantID)
|
||||
23
slixmpp/plugins/protoxep_occupantid/occupantid.py
Normal file
23
slixmpp/plugins/protoxep_occupantid/occupantid.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.stanza import Message, Presence
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
|
||||
from slixmpp.plugins.protoxep_occupantid import stanza
|
||||
|
||||
|
||||
class XEP_OccupantID(BasePlugin):
|
||||
name = 'protoxep_occupantid'
|
||||
description = 'XEP-XXXX: Anonymous unique occupant identifiers for MUCs'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Message, stanza.OccupantID)
|
||||
register_stanza_plugin(Presence, stanza.OccupantID)
|
||||
16
slixmpp/plugins/protoxep_occupantid/stanza.py
Normal file
16
slixmpp/plugins/protoxep_occupantid/stanza.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class OccupantID(ElementBase):
|
||||
name = 'occupant-id'
|
||||
plugin_attrib = 'occupant-id'
|
||||
namespace = 'urn:xmpp:occupant-id:0'
|
||||
interfaces = {'id'}
|
||||
11
slixmpp/plugins/protoxep_reactions/__init__.py
Normal file
11
slixmpp/plugins/protoxep_reactions/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
from slixmpp.plugins.protoxep_reactions.reactions import XEP_Reactions
|
||||
|
||||
register_plugin(XEP_Reactions)
|
||||
54
slixmpp/plugins/protoxep_reactions/reactions.py
Normal file
54
slixmpp/plugins/protoxep_reactions/reactions.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from typing import Iterable
|
||||
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.stanza import Message
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.xmlstream.matcher import MatchXMLMask
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
|
||||
from slixmpp.plugins.protoxep_reactions import stanza
|
||||
|
||||
|
||||
class XEP_Reactions(BasePlugin):
|
||||
name = 'protoxep_reactions'
|
||||
description = 'XEP-XXXX: Message Reactions'
|
||||
dependencies = {'xep_0030'}
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
self.xmpp.register_handler(
|
||||
Callback(
|
||||
'Reaction received',
|
||||
MatchXMLMask('<message><reactions xmlns="urn:xmpp:reactions:0"/></message>'),
|
||||
self._handle_reactions,
|
||||
)
|
||||
)
|
||||
self.xmpp['xep_0030'].add_feature('urn:xmpp:reactions:0')
|
||||
register_stanza_plugin(Message, stanza.Reactions)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Reaction received')
|
||||
self.xmpp['xep_0030'].remove_feature('urn:xmpp:reactions:0')
|
||||
|
||||
def _handle_reactions(self, message: Message):
|
||||
self.xmpp.event('reactions', message)
|
||||
|
||||
@staticmethod
|
||||
def set_reactions(message: Message, to_id: str, reactions: Iterable[str]):
|
||||
"""
|
||||
Add reactions to a Message object.
|
||||
"""
|
||||
reactions_stanza = stanza.Reactions()
|
||||
reactions_stanza['to'] = to_id
|
||||
for reaction in reactions:
|
||||
reaction_stanza = stanza.Reaction()
|
||||
reaction_stanza['value'] = reaction
|
||||
reactions_stanza.append(reaction_stanza)
|
||||
message.append(reactions_stanza)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user