Compare commits
329 Commits
1.1.11
...
sleek-1.2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef2f5d2978 | ||
|
|
62671e0f56 | ||
|
|
93869f77a0 | ||
|
|
8282d135cc | ||
|
|
9acc78c81d | ||
|
|
3642469630 | ||
|
|
34cd20339c | ||
|
|
7548f44047 | ||
|
|
7cf55ef695 | ||
|
|
543250da13 | ||
|
|
69e55d7316 | ||
|
|
158411e918 | ||
|
|
3f873002c4 | ||
|
|
818f4e5973 | ||
|
|
c8d6e512d2 | ||
|
|
a2423b8499 | ||
|
|
49acdac776 | ||
|
|
7e1587faa2 | ||
|
|
84a6ed8e80 | ||
|
|
654420e351 | ||
|
|
651915f31c | ||
|
|
d9db1b84fe | ||
|
|
bd03f071c6 | ||
|
|
eb6ac68d5c | ||
|
|
848e6ebd83 | ||
|
|
f76524fc9f | ||
|
|
b95532b68b | ||
|
|
d002d4c06f | ||
|
|
7c03cc622c | ||
|
|
cebfd84416 | ||
|
|
12995e280e | ||
|
|
4ae6d44efc | ||
|
|
01e1878900 | ||
|
|
df9ad82336 | ||
|
|
c183fd5e35 | ||
|
|
820d07f309 | ||
|
|
f4e3c04bbf | ||
|
|
540d6e9dbb | ||
|
|
79a3a2befd | ||
|
|
08a0fd5420 | ||
|
|
92d6bc6875 | ||
|
|
fb5d20c4f8 | ||
|
|
65e3122f52 | ||
|
|
be874e3c70 | ||
|
|
beae845281 | ||
|
|
6f64dac262 | ||
|
|
cd2d25cf87 | ||
|
|
b8b2f37e7b | ||
|
|
00152358de | ||
|
|
a2784be4d6 | ||
|
|
ad7a57103d | ||
|
|
19b24b276d | ||
|
|
23750357e2 | ||
|
|
07284f380f | ||
|
|
e60401278f | ||
|
|
24c474a9ec | ||
|
|
8fd3781ef5 | ||
|
|
c85f2494a8 | ||
|
|
6c2fa7a382 | ||
|
|
45689fd879 | ||
|
|
45a2cfb01b | ||
|
|
c4bb6c900c | ||
|
|
f7c042fc77 | ||
|
|
b20dc9fe2b | ||
|
|
a030e05993 | ||
|
|
648b03f811 | ||
|
|
e57e321d33 | ||
|
|
b6e53c7b1b | ||
|
|
1c3bfd949b | ||
|
|
6401c9aaaa | ||
|
|
c02adbb8e1 | ||
|
|
88e64dbfae | ||
|
|
afd48b9e08 | ||
|
|
db0ab9a0b3 | ||
|
|
556e4bd74d | ||
|
|
d439c4f215 | ||
|
|
a9f2e1482c | ||
|
|
2c26fb0d76 | ||
|
|
18dde97c8c | ||
|
|
85bc6f5301 | ||
|
|
8f364b9a95 | ||
|
|
ee6c5632ac | ||
|
|
cc81a0e8da | ||
|
|
262652992d | ||
|
|
eb63825dfd | ||
|
|
c49017c6f1 | ||
|
|
7d08bd3142 | ||
|
|
f12c241dca | ||
|
|
cedc9dd175 | ||
|
|
669e708b70 | ||
|
|
e76a483931 | ||
|
|
c0437d2de8 | ||
|
|
37a8043202 | ||
|
|
f4c69d4045 | ||
|
|
a3606d9e4d | ||
|
|
805f1c0e39 | ||
|
|
7430a8ca40 | ||
|
|
1776e2edcc | ||
|
|
baf9aaf26c | ||
|
|
4864b07e13 | ||
|
|
13c919773e | ||
|
|
ed3a4fb8d4 | ||
|
|
df3e826d0a | ||
|
|
a9e7d489b8 | ||
|
|
da6b549f8b | ||
|
|
76e07a9089 | ||
|
|
4a590d1497 | ||
|
|
82e1508d6f | ||
|
|
400f08db9d | ||
|
|
e48b650caa | ||
|
|
d9f595283a | ||
|
|
85fd14f47f | ||
|
|
b7adaafb3e | ||
|
|
d0bba87cdd | ||
|
|
2cc75d4bbd | ||
|
|
24bd591faa | ||
|
|
2e9ccd0623 | ||
|
|
7b49c82210 | ||
|
|
d3284f1604 | ||
|
|
3279697128 | ||
|
|
60cfab995f | ||
|
|
8ec18bdb2c | ||
|
|
3c3cd65235 | ||
|
|
7ac75de19d | ||
|
|
fae39e1ab4 | ||
|
|
3732139fc3 | ||
|
|
0a2737dc77 | ||
|
|
481971928c | ||
|
|
020197718f | ||
|
|
a0c77c04a5 | ||
|
|
620ee9719f | ||
|
|
c0d02d9935 | ||
|
|
01356d23e5 | ||
|
|
8b73c2bcff | ||
|
|
5a771dbe2f | ||
|
|
9ba5b644cf | ||
|
|
f76f0c3787 | ||
|
|
01abd6a705 | ||
|
|
44e2b5d945 | ||
|
|
82bbe5d1a6 | ||
|
|
a1d71d31e8 | ||
|
|
766e0b685d | ||
|
|
58f5e4702b | ||
|
|
d9906756cf | ||
|
|
9a45ebd98b | ||
|
|
7f9ff9d0e7 | ||
|
|
8c763fcf43 | ||
|
|
6dd4456b11 | ||
|
|
c30c47d291 | ||
|
|
d8c9662302 | ||
|
|
ec5e819b16 | ||
|
|
55e50ad979 | ||
|
|
99ecb166d3 | ||
|
|
cdeae7e72f | ||
|
|
fbf79755d7 | ||
|
|
78bd21b7cf | ||
|
|
88c7c29954 | ||
|
|
d4dde89ea6 | ||
|
|
774bf35fab | ||
|
|
1a2db7fb11 | ||
|
|
da3223ac92 | ||
|
|
b0fed5a48d | ||
|
|
43132dab85 | ||
|
|
badd327360 | ||
|
|
9a6bfc6614 | ||
|
|
79914fb56b | ||
|
|
75a792eb6f | ||
|
|
23f112602c | ||
|
|
639a3aa832 | ||
|
|
79a8c5ceae | ||
|
|
97a2f4449d | ||
|
|
7f42d15175 | ||
|
|
ef9c8e910c | ||
|
|
a1b33da9ca | ||
|
|
1741059cf6 | ||
|
|
1f137735e1 | ||
|
|
a186972f09 | ||
|
|
751628401e | ||
|
|
403b1802ec | ||
|
|
9165cbf7f6 | ||
|
|
bad405bea9 | ||
|
|
4f9a95b011 | ||
|
|
903e641457 | ||
|
|
f34b9399cc | ||
|
|
7d0d96f940 | ||
|
|
27196a21ae | ||
|
|
ea0381fa09 | ||
|
|
3423589ba1 | ||
|
|
1f9286d39e | ||
|
|
93b8e66b5d | ||
|
|
a1716de683 | ||
|
|
ccf7916257 | ||
|
|
d86adfa1b1 | ||
|
|
648f3f978a | ||
|
|
5e4b8bd67c | ||
|
|
64ef690432 | ||
|
|
41991b5982 | ||
|
|
01da222d67 | ||
|
|
518eee05c2 | ||
|
|
1dbfa29a1e | ||
|
|
6bac4741f6 | ||
|
|
a0266dac6f | ||
|
|
ce977a7809 | ||
|
|
8644a83ed9 | ||
|
|
7b45245b1d | ||
|
|
f04f4e4a1a | ||
|
|
b07f1b3bd3 | ||
|
|
0e7486d7b4 | ||
|
|
6c0afb87b9 | ||
|
|
e5750b368e | ||
|
|
ef76f923ad | ||
|
|
2c04ae084c | ||
|
|
91dc58d967 | ||
|
|
0e2abe74d5 | ||
|
|
fea444925e | ||
|
|
0998429b07 | ||
|
|
597eb1779c | ||
|
|
9ae3a7dbff | ||
|
|
3519e845a3 | ||
|
|
29c049612a | ||
|
|
ed48185732 | ||
|
|
f431bbfca2 | ||
|
|
8b29900be4 | ||
|
|
6f8a4f8354 | ||
|
|
def34f0e42 | ||
|
|
e25a49f804 | ||
|
|
b820351f64 | ||
|
|
0eb009496e | ||
|
|
2c2498b658 | ||
|
|
a1d988fed5 | ||
|
|
b0c50b7a59 | ||
|
|
1a2b404076 | ||
|
|
2d066c34fd | ||
|
|
7a1ed64985 | ||
|
|
1b449585f7 | ||
|
|
032d41dbb8 | ||
|
|
3a7569e3ea | ||
|
|
d444930494 | ||
|
|
6045a6bfb3 | ||
|
|
f3f543b31e | ||
|
|
0fea4262ea | ||
|
|
4b7ec4a32a | ||
|
|
a00eee1bbe | ||
|
|
06a690a259 | ||
|
|
14c9e9a9cc | ||
|
|
931d49560a | ||
|
|
3655827ef2 | ||
|
|
12e0e1a16b | ||
|
|
0d448b8221 | ||
|
|
77f2a339e1 | ||
|
|
7c485c6a8b | ||
|
|
fc07e23ff8 | ||
|
|
84a2fc382b | ||
|
|
44e7585bf8 | ||
|
|
a2c60a4911 | ||
|
|
73ce9a5ecc | ||
|
|
d385b9e708 | ||
|
|
67147570e9 | ||
|
|
df9ac58d05 | ||
|
|
19a78f63f4 | ||
|
|
e20610ab80 | ||
|
|
f52a10b061 | ||
|
|
7d382a2bfd | ||
|
|
09bec1c4fe | ||
|
|
ff28b0a005 | ||
|
|
a249f8736a | ||
|
|
f09adf0014 | ||
|
|
04dc68f5f6 | ||
|
|
5c25208fb5 | ||
|
|
962dfad216 | ||
|
|
14aa831169 | ||
|
|
75d904ed01 | ||
|
|
f81d5e4bd6 | ||
|
|
2f65fdbc76 | ||
|
|
2f4149c7d0 | ||
|
|
fb4275648c | ||
|
|
06a9d9fc30 | ||
|
|
44ce01a70b | ||
|
|
c2189b4ecd | ||
|
|
c9b2cf6043 | ||
|
|
16ec0f151a | ||
|
|
c42f1ad4c7 | ||
|
|
a3ec1af205 | ||
|
|
2e580304f9 | ||
|
|
5492e9028d | ||
|
|
060c9ab679 | ||
|
|
78f0325398 | ||
|
|
1efe049959 | ||
|
|
2393148908 | ||
|
|
c7594b3ef0 | ||
|
|
b210870f48 | ||
|
|
5d6019a962 | ||
|
|
eb5df1aa37 | ||
|
|
546066d677 | ||
|
|
3234596974 | ||
|
|
5820d49cd4 | ||
|
|
1ab66e5767 | ||
|
|
aab2682f9a | ||
|
|
55d332bcc8 | ||
|
|
f89df6e70c | ||
|
|
250d28e870 | ||
|
|
19f65c8510 | ||
|
|
f70b49882f | ||
|
|
a7b092a305 | ||
|
|
daa73a3f3c | ||
|
|
0b51afe87a | ||
|
|
2b298766c9 | ||
|
|
10664d723b | ||
|
|
c012208a8f | ||
|
|
0953896d2d | ||
|
|
cf9e89d0ae | ||
|
|
48dd01b0bb | ||
|
|
7247efe055 | ||
|
|
8def3758e4 | ||
|
|
1851ab6f5f | ||
|
|
289b052338 | ||
|
|
26147f5ae0 | ||
|
|
ae01f1071a | ||
|
|
dcdf5dcd09 | ||
|
|
c59a6d0f51 | ||
|
|
2cd936318d | ||
|
|
2f38857681 | ||
|
|
39505ae1ff | ||
|
|
44ee0633f2 | ||
|
|
b52d2768b0 | ||
|
|
cf24b870b1 | ||
|
|
69cffce7dc | ||
|
|
a14979375b | ||
|
|
40ef4a16b1 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,4 +1,4 @@
|
|||||||
*.pyc
|
*.py[co]
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
MANIFEST
|
MANIFEST
|
||||||
@@ -7,3 +7,8 @@ docs/_build/
|
|||||||
.tox/
|
.tox/
|
||||||
.coverage
|
.coverage
|
||||||
sleekxmpp.egg-info/
|
sleekxmpp.egg-info/
|
||||||
|
.ropeproject/
|
||||||
|
4913
|
||||||
|
*~
|
||||||
|
.baboon/
|
||||||
|
.DS_STORE
|
||||||
|
|||||||
31
LICENSE
31
LICENSE
@@ -69,8 +69,8 @@ modification, are permitted provided that the following conditions are met:
|
|||||||
* Redistributions in binary form must reproduce the above copyright
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
notice, this list of conditions and the following disclaimer in the
|
notice, this list of conditions and the following disclaimer in the
|
||||||
documentation and/or other materials provided with the distribution.
|
documentation and/or other materials provided with the distribution.
|
||||||
* Neither the name of Red Innovation nor the names of its contributors
|
* Neither the name of Red Innovation nor the names of its contributors
|
||||||
may be used to endorse or promote products derived from this software
|
may be used to endorse or promote products derived from this software
|
||||||
without specific prior written permission.
|
without specific prior written permission.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
|
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
|
||||||
@@ -119,7 +119,7 @@ SUELTA – A PURE-PYTHON SASL CLIENT LIBRARY
|
|||||||
|
|
||||||
This software is subject to "The MIT License"
|
This software is subject to "The MIT License"
|
||||||
|
|
||||||
Copyright 2007-2010 David Alan Cridland
|
Copyright 2004-2013 David Alan Cridland
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -167,3 +167,28 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
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
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
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.
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github
|
|||||||
``develop`` branch.
|
``develop`` branch.
|
||||||
|
|
||||||
**Latest Release**
|
**Latest Release**
|
||||||
- `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_
|
- `1.2.5 <http://github.com/fritzy/SleekXMPP/zipball/1.2.5>`_
|
||||||
|
|
||||||
**Develop Releases**
|
**Develop Releases**
|
||||||
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ handler function to process registration requests.
|
|||||||
self.description = "In-Band Registration"
|
self.description = "In-Band Registration"
|
||||||
self.xep = "0077"
|
self.xep = "0077"
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
self.xmpp.register_handler(
|
||||||
Callback('In-Band Registration',
|
Callback('In-Band Registration',
|
||||||
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
|
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
|
||||||
self.__handleRegistration))
|
self.__handleRegistration))
|
||||||
@@ -601,7 +601,7 @@ with some additional registration fields implemented.
|
|||||||
self.form_instructions = ""
|
self.form_instructions = ""
|
||||||
self.backend = UserStore()
|
self.backend = UserStore()
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
self.xmpp.register_handler(
|
||||||
Callback('In-Band Registration',
|
Callback('In-Band Registration',
|
||||||
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
|
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
|
||||||
self.__handleRegistration))
|
self.__handleRegistration))
|
||||||
|
|||||||
@@ -6,14 +6,20 @@ Event Index
|
|||||||
|
|
||||||
connected
|
connected
|
||||||
- **Data:** ``{}``
|
- **Data:** ``{}``
|
||||||
- **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP`
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
|
|
||||||
Signal that a connection has been made with the XMPP server, but a session
|
Signal that a connection has been made with the XMPP server, but a session
|
||||||
has not yet been established.
|
has not yet been established.
|
||||||
|
|
||||||
|
connection_failed
|
||||||
|
- **Data:** ``{}`` or ``Failure Stanza`` if available
|
||||||
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
|
|
||||||
|
Signal that a connection can not be established after number of attempts.
|
||||||
|
|
||||||
changed_status
|
changed_status
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
- **Data:** :py:class:`~sleekxmpp.Presence`
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
|
||||||
|
|
||||||
Triggered when a presence stanza is received from a JID with a show type
|
Triggered when a presence stanza is received from a JID with a show type
|
||||||
different than the last presence stanza from the same JID.
|
different than the last presence stanza from the same JID.
|
||||||
@@ -65,8 +71,8 @@ Event Index
|
|||||||
|
|
||||||
disconnected
|
disconnected
|
||||||
- **Data:** ``{}``
|
- **Data:** ``{}``
|
||||||
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
|
|
||||||
Signal that the connection with the XMPP server has been lost.
|
Signal that the connection with the XMPP server has been lost.
|
||||||
|
|
||||||
entity_time
|
entity_time
|
||||||
@@ -93,16 +99,16 @@ Event Index
|
|||||||
|
|
||||||
got_online
|
got_online
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
- **Data:** :py:class:`~sleekxmpp.Presence`
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
|
||||||
|
|
||||||
If a presence stanza is received from a JID which was previously marked as
|
If a presence stanza is received from a JID which was previously marked as
|
||||||
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
|
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
|
||||||
or '``xa``', then this event is triggered as well.
|
or '``xa``', then this event is triggered as well.
|
||||||
|
|
||||||
got_offline
|
got_offline
|
||||||
- **Data:** :py:class:`~sleekxmpp.Presence`
|
- **Data:** :py:class:`~sleekxmpp.Presence`
|
||||||
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
|
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
|
||||||
|
|
||||||
Signal that an unavailable presence stanza has been received from a JID.
|
Signal that an unavailable presence stanza has been received from a JID.
|
||||||
|
|
||||||
groupchat_invite
|
groupchat_invite
|
||||||
@@ -110,7 +116,7 @@ Event Index
|
|||||||
- **Source:**
|
- **Source:**
|
||||||
|
|
||||||
groupchat_direct_invite
|
groupchat_direct_invite
|
||||||
- **Data:** :py:class:`~sleekxmpp.Message`
|
- **Data:** :py:class:`~sleekxmpp.Message`
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
|
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
|
||||||
|
|
||||||
groupchat_message
|
groupchat_message
|
||||||
@@ -147,18 +153,18 @@ Event Index
|
|||||||
sure to check the message type in order to handle error messages.
|
sure to check the message type in order to handle error messages.
|
||||||
|
|
||||||
message_form
|
message_form
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
||||||
|
|
||||||
Currently the same as :term:`message_xform`.
|
Currently the same as :term:`message_xform`.
|
||||||
|
|
||||||
message_xform
|
message_xform
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
||||||
|
|
||||||
Triggered whenever a data form is received inside a message.
|
Triggered whenever a data form is received inside a message.
|
||||||
|
|
||||||
mucc::[room]::got_offline
|
muc::[room]::got_offline
|
||||||
- **Data:**
|
- **Data:**
|
||||||
- **Source:**
|
- **Source:**
|
||||||
|
|
||||||
@@ -187,8 +193,8 @@ Event Index
|
|||||||
A presence stanza with a type of '``error``' is received.
|
A presence stanza with a type of '``error``' is received.
|
||||||
|
|
||||||
presence_form
|
presence_form
|
||||||
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
|
||||||
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
|
||||||
|
|
||||||
This event is present in the XEP-0004 plugin code, but is currently not used.
|
This event is present in the XEP-0004 plugin code, but is currently not used.
|
||||||
|
|
||||||
@@ -229,22 +235,20 @@ Event Index
|
|||||||
A presence stanza with a type of '``unsubscribed``' is received.
|
A presence stanza with a type of '``unsubscribed``' is received.
|
||||||
|
|
||||||
roster_update
|
roster_update
|
||||||
- **Data:** :py:class:`~sleekxmpp.stanza.Roster`
|
- **Data:** :py:class:`~sleekxmpp.stanza.Roster`
|
||||||
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
|
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
|
||||||
|
|
||||||
An IQ result containing roster entries is received.
|
An IQ result containing roster entries is received.
|
||||||
|
|
||||||
sent_presence
|
sent_presence
|
||||||
- **Data:** ``{}``
|
- **Data:** ``{}``
|
||||||
- **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
|
- **Source:** :py:class:`~sleekxmpp.roster.multi.Roster`
|
||||||
|
|
||||||
Signal that an initial presence stanza has been written to the XML stream.
|
Signal that an initial presence stanza has been written to the XML stream.
|
||||||
|
|
||||||
session_end
|
session_end
|
||||||
- **Data:** ``{}``
|
- **Data:** ``{}``
|
||||||
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
|
|
||||||
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
|
|
||||||
|
|
||||||
Signal that a connection to the XMPP server has been lost and the current
|
Signal that a connection to the XMPP server has been lost and the current
|
||||||
stream session has ended. Currently equivalent to :term:`disconnected`, but
|
stream session has ended. Currently equivalent to :term:`disconnected`, but
|
||||||
@@ -256,14 +260,14 @@ Event Index
|
|||||||
|
|
||||||
session_start
|
session_start
|
||||||
- **Data:** ``{}``
|
- **Data:** ``{}``
|
||||||
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
|
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
|
||||||
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
|
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
|
||||||
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
|
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
|
||||||
|
|
||||||
Signal that a connection to the XMPP server has been made and a session has been established.
|
Signal that a connection to the XMPP server has been made and a session has been established.
|
||||||
|
|
||||||
socket_error
|
socket_error
|
||||||
- **Data:** ``Socket`` exception object
|
- **Data:** ``Socket`` exception object
|
||||||
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
|
||||||
|
|
||||||
stream_error
|
stream_error
|
||||||
|
|||||||
203
examples/IoT_TestDevice.py
Executable file
203
examples/IoT_TestDevice.py
Executable file
@@ -0,0 +1,203 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Implementation of xeps for Internet of Things
|
||||||
|
http://wiki.xmpp.org/web/Tech_pages/IoT_systems
|
||||||
|
Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
# This can be used when you are in a test environment and need to make paths right
|
||||||
|
sys.path=['/Users/jocke/Dropbox/06_dev/SleekXMPP']+sys.path
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import unittest
|
||||||
|
import distutils.core
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from glob import glob
|
||||||
|
from os.path import splitext, basename, join as pjoin
|
||||||
|
from optparse import OptionParser
|
||||||
|
from urllib import urlopen
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0323.device import Device
|
||||||
|
|
||||||
|
#from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
class IoT_TestDevice(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A simple IoT device that can act as server or client
|
||||||
|
"""
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
self.add_event_handler("session_start", self.session_start)
|
||||||
|
self.add_event_handler("message", self.message)
|
||||||
|
self.device=None
|
||||||
|
self.releaseMe=False
|
||||||
|
self.beServer=True
|
||||||
|
self.clientJID=None
|
||||||
|
|
||||||
|
def datacallback(self,from_jid,result,nodeId=None,timestamp=None,fields=None,error_msg=None):
|
||||||
|
"""
|
||||||
|
This method will be called when you ask another IoT device for data with the xep_0323
|
||||||
|
se script below for the registration of the callback
|
||||||
|
"""
|
||||||
|
logging.debug("we got data %s from %s",str(result),from_jid)
|
||||||
|
|
||||||
|
def beClientOrServer(self,server=True,clientJID=None ):
|
||||||
|
if server:
|
||||||
|
self.beServer=True
|
||||||
|
self.clientJID=None
|
||||||
|
else:
|
||||||
|
self.beServer=False
|
||||||
|
self.clientJID=clientJID
|
||||||
|
|
||||||
|
def testForRelease(self):
|
||||||
|
# todo thread safe
|
||||||
|
return self.releaseMe
|
||||||
|
|
||||||
|
def doReleaseMe(self):
|
||||||
|
# todo thread safe
|
||||||
|
self.releaseMe=True
|
||||||
|
|
||||||
|
def addDevice(self, device):
|
||||||
|
self.device=device
|
||||||
|
|
||||||
|
def session_start(self, event):
|
||||||
|
self.send_presence()
|
||||||
|
self.get_roster()
|
||||||
|
# tell your preffered friend that you are alive
|
||||||
|
self.send_message(mto='jocke@jabber.sust.se', mbody=self.boundjid.bare +' is now online use xep_323 stanza to talk to me')
|
||||||
|
|
||||||
|
if not(self.beServer):
|
||||||
|
session=self['xep_0323'].request_data(self.boundjid.full,self.clientJID,self.datacallback)
|
||||||
|
|
||||||
|
def message(self, msg):
|
||||||
|
if msg['type'] in ('chat', 'normal'):
|
||||||
|
logging.debug("got normal chat message" + str(msg))
|
||||||
|
ip=urlopen('http://icanhazip.com').read()
|
||||||
|
msg.reply("Hi I am " + self.boundjid.full + " and I am on IP " + ip).send()
|
||||||
|
else:
|
||||||
|
logging.debug("got unknown message type %s", str(msg['type']))
|
||||||
|
|
||||||
|
class TheDevice(Device):
|
||||||
|
"""
|
||||||
|
This is the actual device object that you will use to get information from your real hardware
|
||||||
|
You will be called in the refresh method when someone is requesting information from you
|
||||||
|
"""
|
||||||
|
def __init__(self,nodeId):
|
||||||
|
Device.__init__(self,nodeId)
|
||||||
|
self.counter=0
|
||||||
|
|
||||||
|
def refresh(self,fields):
|
||||||
|
"""
|
||||||
|
the implementation of the refresh method
|
||||||
|
"""
|
||||||
|
self._set_momentary_timestamp(self._get_timestamp())
|
||||||
|
self.counter+=self.counter
|
||||||
|
self._add_field_momentary_data(self, "Temperature", self.counter)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
# Setup the command line arguments.
|
||||||
|
#
|
||||||
|
# This script can act both as
|
||||||
|
# "server" an IoT device that can provide sensorinformation
|
||||||
|
# python IoT_TestDevice.py -j "serverjid@yourdomain.com" -p "password" -n "TestIoT" --debug
|
||||||
|
#
|
||||||
|
# "client" an IoT device or other party that would like to get data from another device
|
||||||
|
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
optp.add_option('-t', '--pingto', help='set jid to ping',
|
||||||
|
action='store', type='string', dest='pingjid',
|
||||||
|
default=None)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
optp.add_option("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
|
||||||
|
# IoT test
|
||||||
|
optp.add_option("-c", "--sensorjid", dest="sensorjid",
|
||||||
|
help="Another device to call for data on", default=None)
|
||||||
|
optp.add_option("-n", "--nodeid", dest="nodeid",
|
||||||
|
help="I am a device get ready to be called", default=None)
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = raw_input("Username: ")
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass("Password: ")
|
||||||
|
|
||||||
|
|
||||||
|
xmpp = IoT_TestDevice(opts.jid,opts.password)
|
||||||
|
xmpp.register_plugin('xep_0030')
|
||||||
|
#xmpp['xep_0030'].add_feature(feature='urn:xmpp:iot:sensordata',
|
||||||
|
# node=None,
|
||||||
|
# jid=None)
|
||||||
|
xmpp.register_plugin('xep_0323')
|
||||||
|
xmpp.register_plugin('xep_0325')
|
||||||
|
|
||||||
|
if opts.nodeid:
|
||||||
|
|
||||||
|
# xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn',
|
||||||
|
# node=opts.nodeid,
|
||||||
|
# jid=xmpp.boundjid.full)
|
||||||
|
|
||||||
|
myDevice = TheDevice(opts.nodeid);
|
||||||
|
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
|
||||||
|
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
|
||||||
|
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
|
||||||
|
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
|
||||||
|
|
||||||
|
xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10);
|
||||||
|
xmpp.beClientOrServer(server=True)
|
||||||
|
while not(xmpp.testForRelease()):
|
||||||
|
xmpp.connect()
|
||||||
|
xmpp.process(block=True)
|
||||||
|
logging.debug("lost connection")
|
||||||
|
if opts.sensorjid:
|
||||||
|
logging.debug("will try to call another device for data")
|
||||||
|
xmpp.beClientOrServer(server=False,clientJID=opts.sensorjid)
|
||||||
|
xmpp.connect()
|
||||||
|
xmpp.process(block=True)
|
||||||
|
logging.debug("ready ending")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print "noopp didn't happen"
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class ActionBot(sleekxmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
self.registerHandler(
|
self.register_handler(
|
||||||
Callback('Some custom iq',
|
Callback('Some custom iq',
|
||||||
StanzaPath('iq@type=set/action'),
|
StanzaPath('iq@type=set/action'),
|
||||||
self._handle_action))
|
self._handle_action))
|
||||||
|
|||||||
0
examples/download_avatars.py
Normal file → Executable file
0
examples/download_avatars.py
Normal file → Executable file
@@ -38,7 +38,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
|
|||||||
|
|
||||||
self.register_plugin('xep_0030') # Service Discovery
|
self.register_plugin('xep_0030') # Service Discovery
|
||||||
self.register_plugin('xep_0047', {
|
self.register_plugin('xep_0047', {
|
||||||
'accept_stream': self.accept_stream
|
'auto_accept': True
|
||||||
}) # In-band Bytestreams
|
}) # In-band Bytestreams
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
@@ -48,7 +48,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
self.add_event_handler("ibb_stream_start", self.stream_opened)
|
self.add_event_handler("ibb_stream_start", self.stream_opened, threaded=True)
|
||||||
self.add_event_handler("ibb_stream_data", self.stream_data)
|
self.add_event_handler("ibb_stream_data", self.stream_data)
|
||||||
|
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
@@ -69,7 +69,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
|
|||||||
|
|
||||||
def accept_stream(self, iq):
|
def accept_stream(self, iq):
|
||||||
"""
|
"""
|
||||||
Check that it is ok to accept a stream request.
|
Check that it is ok to accept a stream request.
|
||||||
|
|
||||||
Controlling stream acceptance can be done via either:
|
Controlling stream acceptance can be done via either:
|
||||||
- setting 'auto_accept' to False in the plugin
|
- setting 'auto_accept' to False in the plugin
|
||||||
@@ -83,9 +83,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def stream_opened(self, stream):
|
def stream_opened(self, stream):
|
||||||
# NOTE: IBB streams are bi-directional, so the original sender is
|
print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid))
|
||||||
# now the opened stream's receiver.
|
|
||||||
print('Stream opened: %s from %s' % (stream.sid, stream.receiver))
|
|
||||||
|
|
||||||
# You could run a loop reading from the stream using stream.recv(),
|
# You could run a loop reading from the stream using stream.recv(),
|
||||||
# or use the ibb_stream_data event.
|
# or use the ibb_stream_data event.
|
||||||
|
|||||||
120
examples/migrate_roster.py
Executable file
120
examples/migrate_roster.py
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
import sleekxmpp
|
||||||
|
|
||||||
|
# Python versions before 3.0 do not use UTF-8 encoding
|
||||||
|
# by default. To ensure that Unicode is handled properly
|
||||||
|
# throughout SleekXMPP, we will set the default encoding
|
||||||
|
# ourselves to UTF-8.
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
from sleekxmpp.util.misc_ops import setdefaultencoding
|
||||||
|
setdefaultencoding('utf8')
|
||||||
|
else:
|
||||||
|
raw_input = input
|
||||||
|
|
||||||
|
|
||||||
|
# Setup the command line arguments.
|
||||||
|
optp = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||||
|
action='store_const', dest='loglevel',
|
||||||
|
const=5, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
optp.add_option("--oldjid", dest="old_jid",
|
||||||
|
help="JID of the old account")
|
||||||
|
optp.add_option("--oldpassword", dest="old_password",
|
||||||
|
help="password of the old account")
|
||||||
|
|
||||||
|
optp.add_option("--newjid", dest="new_jid",
|
||||||
|
help="JID of the old account")
|
||||||
|
optp.add_option("--newpassword", dest="new_password",
|
||||||
|
help="password of the old account")
|
||||||
|
|
||||||
|
|
||||||
|
opts, args = optp.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.old_jid is None:
|
||||||
|
opts.old_jid = raw_input("Old JID: ")
|
||||||
|
if opts.old_password is None:
|
||||||
|
opts.old_password = getpass.getpass("Old Password: ")
|
||||||
|
|
||||||
|
if opts.new_jid is None:
|
||||||
|
opts.new_jid = raw_input("New JID: ")
|
||||||
|
if opts.new_password is None:
|
||||||
|
opts.new_password = getpass.getpass("New Password: ")
|
||||||
|
|
||||||
|
|
||||||
|
old_xmpp = sleekxmpp.ClientXMPP(opts.old_jid, opts.old_password)
|
||||||
|
|
||||||
|
# If you are connecting to Facebook and wish to use the
|
||||||
|
# X-FACEBOOK-PLATFORM authentication mechanism, you will need
|
||||||
|
# your API key and an access token. Then you'll set:
|
||||||
|
# xmpp.credentials['api_key'] = 'THE_API_KEY'
|
||||||
|
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
|
||||||
|
|
||||||
|
# If you are connecting to MSN, then you will need an
|
||||||
|
# access token, and it does not matter what JID you
|
||||||
|
# specify other than that the domain is 'messenger.live.com',
|
||||||
|
# so '_@messenger.live.com' will work. You can specify
|
||||||
|
# the access token as so:
|
||||||
|
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
|
||||||
|
|
||||||
|
# If you are working with an OpenFire server, you may need
|
||||||
|
# to adjust the SSL version used:
|
||||||
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
|
# If you want to verify the SSL certificates offered by a server:
|
||||||
|
# xmpp.ca_certs = "path/to/ca/cert"
|
||||||
|
|
||||||
|
roster = []
|
||||||
|
|
||||||
|
def on_session(event):
|
||||||
|
roster.append(old_xmpp.get_roster())
|
||||||
|
old_xmpp.disconnect()
|
||||||
|
old_xmpp.add_event_handler('session_start', on_session)
|
||||||
|
|
||||||
|
if old_xmpp.connect():
|
||||||
|
old_xmpp.process(block=True)
|
||||||
|
|
||||||
|
if not roster:
|
||||||
|
print('No roster to migrate')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
new_xmpp = sleekxmpp.ClientXMPP(opts.new_jid, opts.new_password)
|
||||||
|
def on_session2(event):
|
||||||
|
new_xmpp.get_roster()
|
||||||
|
new_xmpp.send_presence()
|
||||||
|
|
||||||
|
logging.info(roster[0])
|
||||||
|
data = roster[0]['roster']['items']
|
||||||
|
logging.info(data)
|
||||||
|
|
||||||
|
for jid, item in data.items():
|
||||||
|
if item['subscription'] != 'none':
|
||||||
|
new_xmpp.send_presence(ptype='subscribe', pto=jid)
|
||||||
|
new_xmpp.update_roster(jid,
|
||||||
|
name = item['name'],
|
||||||
|
groups = item['groups'])
|
||||||
|
new_xmpp.disconnect()
|
||||||
|
new_xmpp.add_event_handler('session_start', on_session2)
|
||||||
|
|
||||||
|
if new_xmpp.connect():
|
||||||
|
new_xmpp.process(block=True)
|
||||||
@@ -37,7 +37,7 @@ class PingTest(sleekxmpp.ClientXMPP):
|
|||||||
def __init__(self, jid, password, pingjid):
|
def __init__(self, jid, password, pingjid):
|
||||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
if pingjid is None:
|
if pingjid is None:
|
||||||
pingjid = self.jid
|
pingjid = self.boundjid.bare
|
||||||
self.pingjid = pingjid
|
self.pingjid = pingjid
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
@@ -62,16 +62,18 @@ class PingTest(sleekxmpp.ClientXMPP):
|
|||||||
"""
|
"""
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
result = self['xep_0199'].send_ping(self.pingjid,
|
|
||||||
timeout=10,
|
try:
|
||||||
errorfalse=True)
|
rtt = self['xep_0199'].ping(self.pingjid,
|
||||||
logging.info("Pinging...")
|
timeout=10)
|
||||||
if result is False:
|
logging.info("Success! RTT: %s", rtt)
|
||||||
logging.info("Couldn't ping.")
|
except IqError as e:
|
||||||
self.disconnect()
|
logging.info("Error pinging %s: %s",
|
||||||
sys.exit(1)
|
self.pingjid,
|
||||||
else:
|
e.iq['error']['condition'])
|
||||||
logging.info("Success! RTT: %s", str(result))
|
except IqTimeout:
|
||||||
|
logging.info("No response from %s", self.pingjid)
|
||||||
|
finally:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
7
examples/pubsub_client.py
Normal file → Executable file
7
examples/pubsub_client.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import getpass
|
import getpass
|
||||||
@@ -20,7 +23,7 @@ else:
|
|||||||
|
|
||||||
class PubsubClient(sleekxmpp.ClientXMPP):
|
class PubsubClient(sleekxmpp.ClientXMPP):
|
||||||
|
|
||||||
def __init__(self, jid, password, server,
|
def __init__(self, jid, password, server,
|
||||||
node=None, action='list', data=''):
|
node=None, action='list', data=''):
|
||||||
super(PubsubClient, self).__init__(jid, password)
|
super(PubsubClient, self).__init__(jid, password)
|
||||||
|
|
||||||
@@ -28,7 +31,7 @@ class PubsubClient(sleekxmpp.ClientXMPP):
|
|||||||
self.register_plugin('xep_0059')
|
self.register_plugin('xep_0059')
|
||||||
self.register_plugin('xep_0060')
|
self.register_plugin('xep_0060')
|
||||||
|
|
||||||
self.actions = ['nodes', 'create', 'delete',
|
self.actions = ['nodes', 'create', 'delete',
|
||||||
'publish', 'get', 'retract',
|
'publish', 'get', 'retract',
|
||||||
'purge', 'subscribe', 'unsubscribe']
|
'purge', 'subscribe', 'unsubscribe']
|
||||||
|
|
||||||
|
|||||||
5
examples/pubsub_events.py
Normal file → Executable file
5
examples/pubsub_events.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import getpass
|
import getpass
|
||||||
@@ -77,7 +80,7 @@ class PubsubEvents(sleekxmpp.ClientXMPP):
|
|||||||
"""Handle receiving a node deletion event."""
|
"""Handle receiving a node deletion event."""
|
||||||
print('Deleted node %s' % (
|
print('Deleted node %s' % (
|
||||||
msg['pubsub_event']['delete']['node']))
|
msg['pubsub_event']['delete']['node']))
|
||||||
|
|
||||||
def _config(self, msg):
|
def _config(self, msg):
|
||||||
"""Handle receiving a node configuration event."""
|
"""Handle receiving a node configuration event."""
|
||||||
print('Configured node %s:' % (
|
print('Configured node %s:' % (
|
||||||
|
|||||||
8
examples/register_account.py
Normal file → Executable file
8
examples/register_account.py
Normal file → Executable file
@@ -51,7 +51,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
|
|||||||
|
|
||||||
# The register event provides an Iq result stanza with
|
# The register event provides an Iq result stanza with
|
||||||
# a registration form from the server. This may include
|
# a registration form from the server. This may include
|
||||||
# the basic registration fields, a data form, an
|
# the basic registration fields, a data form, an
|
||||||
# out-of-band URL, or any combination. For more advanced
|
# out-of-band URL, or any combination. For more advanced
|
||||||
# cases, you will need to examine the fields provided
|
# cases, you will need to examine the fields provided
|
||||||
# and respond accordingly. SleekXMPP provides plugins
|
# and respond accordingly. SleekXMPP provides plugins
|
||||||
@@ -104,7 +104,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
|
|||||||
resp.send(now=True)
|
resp.send(now=True)
|
||||||
logging.info("Account created for %s!" % self.boundjid)
|
logging.info("Account created for %s!" % self.boundjid)
|
||||||
except IqError as e:
|
except IqError as e:
|
||||||
logging.error("Could not register account: %s" %
|
logging.error("Could not register account: %s" %
|
||||||
e.iq['error']['text'])
|
e.iq['error']['text'])
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
except IqTimeout:
|
except IqTimeout:
|
||||||
@@ -153,6 +153,10 @@ if __name__ == '__main__':
|
|||||||
xmpp.register_plugin('xep_0066') # Out-of-band Data
|
xmpp.register_plugin('xep_0066') # Out-of-band Data
|
||||||
xmpp.register_plugin('xep_0077') # In-band Registration
|
xmpp.register_plugin('xep_0077') # In-band Registration
|
||||||
|
|
||||||
|
# Some servers don't advertise support for inband registration, even
|
||||||
|
# though they allow it. If this applies to your server, use:
|
||||||
|
xmpp['xep_0077'].force_registration = True
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you may need
|
# If you are working with an OpenFire server, you may need
|
||||||
# to adjust the SSL version used:
|
# to adjust the SSL version used:
|
||||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|||||||
0
examples/roster_browser.py
Normal file → Executable file
0
examples/roster_browser.py
Normal file → Executable file
29
examples/rpc_async.py
Normal file → Executable file
29
examples/rpc_async.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2011 Dann Martens
|
Copyright (C) 2011 Dann Martens
|
||||||
@@ -11,34 +14,34 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
class Boomerang(Endpoint):
|
class Boomerang(Endpoint):
|
||||||
|
|
||||||
def FQN(self):
|
def FQN(self):
|
||||||
return 'boomerang'
|
return 'boomerang'
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def throw(self):
|
def throw(self):
|
||||||
print "Duck!"
|
print "Duck!"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
|
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
|
||||||
|
|
||||||
session.new_handler(ANY_ALL, Boomerang)
|
session.new_handler(ANY_ALL, Boomerang)
|
||||||
|
|
||||||
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
|
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
|
||||||
|
|
||||||
callback = Future()
|
callback = Future()
|
||||||
|
|
||||||
boomerang.async(callback).throw()
|
boomerang.async(callback).throw()
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
29
examples/rpc_client_side.py
Normal file → Executable file
29
examples/rpc_client_side.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2011 Dann Martens
|
Copyright (C) 2011 Dann Martens
|
||||||
@@ -12,18 +15,18 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
class Thermostat(Endpoint):
|
class Thermostat(Endpoint):
|
||||||
|
|
||||||
def FQN(self):
|
def FQN(self):
|
||||||
return 'thermostat'
|
return 'thermostat'
|
||||||
|
|
||||||
def __init__(self, initial_temperature):
|
def __init__(self, initial_temperature):
|
||||||
self._temperature = initial_temperature
|
self._temperature = initial_temperature
|
||||||
self._event = threading.Event()
|
self._event = threading.Event()
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def set_temperature(self, temperature):
|
def set_temperature(self, temperature):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def get_temperature(self):
|
def get_temperature(self):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
@@ -31,23 +34,23 @@ class Thermostat(Endpoint):
|
|||||||
@remote(False)
|
@remote(False)
|
||||||
def release(self):
|
def release(self):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
session = Remote.new_session('operator@xmpp.org/rpc', '*****')
|
session = Remote.new_session('operator@xmpp.org/rpc', '*****')
|
||||||
|
|
||||||
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
|
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
|
||||||
|
|
||||||
print("Current temperature is %s" % thermostat.get_temperature())
|
print("Current temperature is %s" % thermostat.get_temperature())
|
||||||
|
|
||||||
thermostat.set_temperature(20)
|
thermostat.set_temperature(20)
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
31
examples/rpc_server_side.py
Normal file → Executable file
31
examples/rpc_server_side.py
Normal file → Executable file
@@ -1,3 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2011 Dann Martens
|
Copyright (C) 2011 Dann Martens
|
||||||
@@ -11,42 +14,42 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
class Thermostat(Endpoint):
|
class Thermostat(Endpoint):
|
||||||
|
|
||||||
def FQN(self):
|
def FQN(self):
|
||||||
return 'thermostat'
|
return 'thermostat'
|
||||||
|
|
||||||
def __init__(self, initial_temperature):
|
def __init__(self, initial_temperature):
|
||||||
self._temperature = initial_temperature
|
self._temperature = initial_temperature
|
||||||
self._event = threading.Event()
|
self._event = threading.Event()
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def set_temperature(self, temperature):
|
def set_temperature(self, temperature):
|
||||||
print("Setting temperature to %s" % temperature)
|
print("Setting temperature to %s" % temperature)
|
||||||
self._temperature = temperature
|
self._temperature = temperature
|
||||||
|
|
||||||
@remote
|
@remote
|
||||||
def get_temperature(self):
|
def get_temperature(self):
|
||||||
return self._temperature
|
return self._temperature
|
||||||
|
|
||||||
@remote(False)
|
@remote(False)
|
||||||
def release(self):
|
def release(self):
|
||||||
self._event.set()
|
self._event.set()
|
||||||
|
|
||||||
def wait_for_release(self):
|
def wait_for_release(self):
|
||||||
self._event.wait()
|
self._event.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
||||||
session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
|
session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
|
||||||
|
|
||||||
thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
|
thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
|
||||||
|
|
||||||
thermostat.wait_for_release()
|
thermostat.wait_for_release()
|
||||||
|
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|||||||
0
examples/set_avatar.py
Normal file → Executable file
0
examples/set_avatar.py
Normal file → Executable file
0
examples/thirdparty_auth.py
Normal file → Executable file
0
examples/thirdparty_auth.py
Normal file → Executable file
0
examples/user_location.py
Normal file → Executable file
0
examples/user_location.py
Normal file → Executable file
0
examples/user_tune.py
Normal file → Executable file
0
examples/user_tune.py
Normal file → Executable file
19
setup.py
19
setup.py
@@ -42,6 +42,7 @@ CLASSIFIERS = [ 'Intended Audience :: Developers',
|
|||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
'Programming Language :: Python :: 3.1',
|
'Programming Language :: Python :: 3.1',
|
||||||
'Programming Language :: Python :: 3.2',
|
'Programming Language :: Python :: 3.2',
|
||||||
|
'Programming Language :: Python :: 3.3',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -62,18 +63,22 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0012',
|
'sleekxmpp/plugins/xep_0012',
|
||||||
'sleekxmpp/plugins/xep_0013',
|
'sleekxmpp/plugins/xep_0013',
|
||||||
'sleekxmpp/plugins/xep_0016',
|
'sleekxmpp/plugins/xep_0016',
|
||||||
|
'sleekxmpp/plugins/xep_0020',
|
||||||
'sleekxmpp/plugins/xep_0027',
|
'sleekxmpp/plugins/xep_0027',
|
||||||
'sleekxmpp/plugins/xep_0030',
|
'sleekxmpp/plugins/xep_0030',
|
||||||
'sleekxmpp/plugins/xep_0030/stanza',
|
'sleekxmpp/plugins/xep_0030/stanza',
|
||||||
'sleekxmpp/plugins/xep_0033',
|
'sleekxmpp/plugins/xep_0033',
|
||||||
'sleekxmpp/plugins/xep_0047',
|
'sleekxmpp/plugins/xep_0047',
|
||||||
|
'sleekxmpp/plugins/xep_0048',
|
||||||
'sleekxmpp/plugins/xep_0049',
|
'sleekxmpp/plugins/xep_0049',
|
||||||
'sleekxmpp/plugins/xep_0050',
|
'sleekxmpp/plugins/xep_0050',
|
||||||
'sleekxmpp/plugins/xep_0054',
|
'sleekxmpp/plugins/xep_0054',
|
||||||
'sleekxmpp/plugins/xep_0059',
|
'sleekxmpp/plugins/xep_0059',
|
||||||
'sleekxmpp/plugins/xep_0060',
|
'sleekxmpp/plugins/xep_0060',
|
||||||
'sleekxmpp/plugins/xep_0060/stanza',
|
'sleekxmpp/plugins/xep_0060/stanza',
|
||||||
|
'sleekxmpp/plugins/xep_0065',
|
||||||
'sleekxmpp/plugins/xep_0066',
|
'sleekxmpp/plugins/xep_0066',
|
||||||
|
'sleekxmpp/plugins/xep_0071',
|
||||||
'sleekxmpp/plugins/xep_0077',
|
'sleekxmpp/plugins/xep_0077',
|
||||||
'sleekxmpp/plugins/xep_0078',
|
'sleekxmpp/plugins/xep_0078',
|
||||||
'sleekxmpp/plugins/xep_0080',
|
'sleekxmpp/plugins/xep_0080',
|
||||||
@@ -82,17 +87,21 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0086',
|
'sleekxmpp/plugins/xep_0086',
|
||||||
'sleekxmpp/plugins/xep_0091',
|
'sleekxmpp/plugins/xep_0091',
|
||||||
'sleekxmpp/plugins/xep_0092',
|
'sleekxmpp/plugins/xep_0092',
|
||||||
|
'sleekxmpp/plugins/xep_0095',
|
||||||
|
'sleekxmpp/plugins/xep_0096',
|
||||||
'sleekxmpp/plugins/xep_0107',
|
'sleekxmpp/plugins/xep_0107',
|
||||||
'sleekxmpp/plugins/xep_0108',
|
'sleekxmpp/plugins/xep_0108',
|
||||||
'sleekxmpp/plugins/xep_0115',
|
'sleekxmpp/plugins/xep_0115',
|
||||||
'sleekxmpp/plugins/xep_0118',
|
'sleekxmpp/plugins/xep_0118',
|
||||||
'sleekxmpp/plugins/xep_0128',
|
'sleekxmpp/plugins/xep_0128',
|
||||||
'sleekxmpp/plugins/xep_0131',
|
'sleekxmpp/plugins/xep_0131',
|
||||||
|
'sleekxmpp/plugins/xep_0152',
|
||||||
'sleekxmpp/plugins/xep_0153',
|
'sleekxmpp/plugins/xep_0153',
|
||||||
'sleekxmpp/plugins/xep_0172',
|
'sleekxmpp/plugins/xep_0172',
|
||||||
'sleekxmpp/plugins/xep_0184',
|
'sleekxmpp/plugins/xep_0184',
|
||||||
'sleekxmpp/plugins/xep_0186',
|
'sleekxmpp/plugins/xep_0186',
|
||||||
'sleekxmpp/plugins/xep_0191',
|
'sleekxmpp/plugins/xep_0191',
|
||||||
|
'sleekxmpp/plugins/xep_0196',
|
||||||
'sleekxmpp/plugins/xep_0198',
|
'sleekxmpp/plugins/xep_0198',
|
||||||
'sleekxmpp/plugins/xep_0199',
|
'sleekxmpp/plugins/xep_0199',
|
||||||
'sleekxmpp/plugins/xep_0202',
|
'sleekxmpp/plugins/xep_0202',
|
||||||
@@ -109,6 +118,16 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0297',
|
'sleekxmpp/plugins/xep_0297',
|
||||||
'sleekxmpp/plugins/xep_0308',
|
'sleekxmpp/plugins/xep_0308',
|
||||||
'sleekxmpp/plugins/xep_0313',
|
'sleekxmpp/plugins/xep_0313',
|
||||||
|
'sleekxmpp/plugins/xep_0319',
|
||||||
|
'sleekxmpp/plugins/xep_0323',
|
||||||
|
'sleekxmpp/plugins/xep_0323/stanza',
|
||||||
|
'sleekxmpp/plugins/xep_0325',
|
||||||
|
'sleekxmpp/plugins/xep_0325/stanza',
|
||||||
|
'sleekxmpp/plugins/google',
|
||||||
|
'sleekxmpp/plugins/google/gmail',
|
||||||
|
'sleekxmpp/plugins/google/auth',
|
||||||
|
'sleekxmpp/plugins/google/settings',
|
||||||
|
'sleekxmpp/plugins/google/nosave',
|
||||||
'sleekxmpp/features',
|
'sleekxmpp/features',
|
||||||
'sleekxmpp/features/feature_mechanisms',
|
'sleekxmpp/features/feature_mechanisms',
|
||||||
'sleekxmpp/features/feature_mechanisms/stanza',
|
'sleekxmpp/features/feature_mechanisms/stanza',
|
||||||
|
|||||||
@@ -6,14 +6,25 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sleekxmpp.basexmpp import BaseXMPP
|
import logging
|
||||||
from sleekxmpp.clientxmpp import ClientXMPP
|
if hasattr(logging, 'NullHandler'):
|
||||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
NullHandler = logging.NullHandler
|
||||||
|
else:
|
||||||
|
class NullHandler(logging.Handler):
|
||||||
|
def handle(self, record):
|
||||||
|
pass
|
||||||
|
logging.getLogger(__name__).addHandler(NullHandler())
|
||||||
|
del NullHandler
|
||||||
|
|
||||||
|
|
||||||
from sleekxmpp.stanza import Message, Presence, Iq
|
from sleekxmpp.stanza import Message, Presence, Iq
|
||||||
from sleekxmpp.jid import JID, InvalidJID
|
from sleekxmpp.jid import JID, InvalidJID
|
||||||
|
from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin
|
||||||
from sleekxmpp.xmlstream.handler import *
|
from sleekxmpp.xmlstream.handler import *
|
||||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
|
from sleekxmpp.xmlstream import XMLStream, RestartStream
|
||||||
from sleekxmpp.xmlstream.matcher import *
|
from sleekxmpp.xmlstream.matcher import *
|
||||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
|
from sleekxmpp.basexmpp import BaseXMPP
|
||||||
|
from sleekxmpp.clientxmpp import ClientXMPP
|
||||||
|
from sleekxmpp.componentxmpp import ComponentXMPP
|
||||||
|
|
||||||
from sleekxmpp.version import __version__, __version_info__
|
from sleekxmpp.version import __version__, __version_info__
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import sys
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import sleekxmpp
|
from sleekxmpp import plugins, roster, stanza
|
||||||
from sleekxmpp import plugins, features, roster
|
|
||||||
from sleekxmpp.api import APIRegistry
|
from sleekxmpp.api import APIRegistry
|
||||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
@@ -34,8 +33,7 @@ from sleekxmpp.xmlstream.matcher import MatchXPath
|
|||||||
from sleekxmpp.xmlstream.handler import Callback
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
from sleekxmpp.xmlstream.stanzabase import XML_NS
|
from sleekxmpp.xmlstream.stanzabase import XML_NS
|
||||||
|
|
||||||
from sleekxmpp.features import *
|
from sleekxmpp.plugins import PluginManager, load_plugin
|
||||||
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -148,7 +146,7 @@ class BaseXMPP(XMLStream):
|
|||||||
|
|
||||||
#: A reference to :mod:`sleekxmpp.stanza` to make accessing
|
#: A reference to :mod:`sleekxmpp.stanza` to make accessing
|
||||||
#: stanza classes easier.
|
#: stanza classes easier.
|
||||||
self.stanza = sleekxmpp.stanza
|
self.stanza = stanza
|
||||||
|
|
||||||
self.register_handler(
|
self.register_handler(
|
||||||
Callback('IM',
|
Callback('IM',
|
||||||
@@ -201,7 +199,6 @@ class BaseXMPP(XMLStream):
|
|||||||
# Initialize a few default stanza plugins.
|
# Initialize a few default stanza plugins.
|
||||||
register_stanza_plugin(Iq, Roster)
|
register_stanza_plugin(Iq, Roster)
|
||||||
register_stanza_plugin(Message, Nick)
|
register_stanza_plugin(Message, Nick)
|
||||||
register_stanza_plugin(Message, HTMLIM)
|
|
||||||
|
|
||||||
def start_stream_handler(self, xml):
|
def start_stream_handler(self, xml):
|
||||||
"""Save the stream ID once the streams have been established.
|
"""Save the stream ID once the streams have been established.
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
|
|
||||||
self.add_event_handler('connected', self._reset_connection_state)
|
self.add_event_handler('connected', self._reset_connection_state)
|
||||||
self.add_event_handler('session_bind', self._handle_session_bind)
|
self.add_event_handler('session_bind', self._handle_session_bind)
|
||||||
|
self.add_event_handler('roster_update', self._handle_roster)
|
||||||
|
|
||||||
self.register_stanza(StreamFeatures)
|
self.register_stanza(StreamFeatures)
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
self.register_handler(
|
self.register_handler(
|
||||||
Callback('Roster Update',
|
Callback('Roster Update',
|
||||||
StanzaPath('iq@type=set/roster'),
|
StanzaPath('iq@type=set/roster'),
|
||||||
self._handle_roster))
|
lambda iq: self.event('roster_update', iq)))
|
||||||
|
|
||||||
# Setup default stream features
|
# Setup default stream features
|
||||||
self.register_plugin('feature_starttls')
|
self.register_plugin('feature_starttls')
|
||||||
@@ -114,8 +115,10 @@ class ClientXMPP(BaseXMPP):
|
|||||||
self.register_plugin('feature_session')
|
self.register_plugin('feature_session')
|
||||||
self.register_plugin('feature_rosterver')
|
self.register_plugin('feature_rosterver')
|
||||||
self.register_plugin('feature_preapproval')
|
self.register_plugin('feature_preapproval')
|
||||||
self.register_plugin('feature_mechanisms',
|
self.register_plugin('feature_mechanisms')
|
||||||
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
|
||||||
|
if sasl_mech:
|
||||||
|
self['feature_mechanisms'].use_mech = sasl_mech
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def password(self):
|
def password(self):
|
||||||
@@ -133,7 +136,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
be attempted. If that fails, the server user in the JID
|
be attempted. If that fails, the server user in the JID
|
||||||
will be used.
|
will be used.
|
||||||
|
|
||||||
:param address -- A tuple containing the server's host and port.
|
:param address: A tuple containing the server's host and port.
|
||||||
:param reattempt: If ``True``, repeat attempting to connect if an
|
:param reattempt: If ``True``, repeat attempting to connect if an
|
||||||
error occurs. Defaults to ``True``.
|
error occurs. Defaults to ``True``.
|
||||||
:param use_tls: Indicates if TLS should be used for the
|
:param use_tls: Indicates if TLS should be used for the
|
||||||
@@ -152,8 +155,6 @@ class ClientXMPP(BaseXMPP):
|
|||||||
address = (self.boundjid.host, 5222)
|
address = (self.boundjid.host, 5222)
|
||||||
self.dns_service = 'xmpp-client'
|
self.dns_service = 'xmpp-client'
|
||||||
|
|
||||||
self._expected_server_name = self.boundjid.host
|
|
||||||
|
|
||||||
return XMLStream.connect(self, address[0], address[1],
|
return XMLStream.connect(self, address[0], address[1],
|
||||||
use_tls=use_tls, use_ssl=use_ssl,
|
use_tls=use_tls, use_ssl=use_ssl,
|
||||||
reattempt=reattempt)
|
reattempt=reattempt)
|
||||||
@@ -242,14 +243,22 @@ class ClientXMPP(BaseXMPP):
|
|||||||
if 'rosterver' in self.features:
|
if 'rosterver' in self.features:
|
||||||
iq['roster']['ver'] = self.client_roster.version
|
iq['roster']['ver'] = self.client_roster.version
|
||||||
|
|
||||||
if not block and callback is None:
|
|
||||||
callback = lambda resp: self._handle_roster(resp)
|
if not block or callback is not None:
|
||||||
|
block = False
|
||||||
|
if callback is None:
|
||||||
|
callback = lambda resp: self.event('roster_update', resp)
|
||||||
|
else:
|
||||||
|
orig_cb = callback
|
||||||
|
def wrapped(resp):
|
||||||
|
self.event('roster_update', resp)
|
||||||
|
orig_cb(resp)
|
||||||
|
callback = wrapped
|
||||||
|
|
||||||
response = iq.send(block, timeout, callback)
|
response = iq.send(block, timeout, callback)
|
||||||
self.event('roster_received', response)
|
|
||||||
|
|
||||||
if block:
|
if block:
|
||||||
self._handle_roster(response)
|
self.event('roster_update', response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _reset_connection_state(self, event=None):
|
def _reset_connection_state(self, event=None):
|
||||||
@@ -300,7 +309,6 @@ class ClientXMPP(BaseXMPP):
|
|||||||
|
|
||||||
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
||||||
|
|
||||||
self.event("roster_update", iq)
|
|
||||||
if iq['type'] == 'set':
|
if iq['type'] == 'set':
|
||||||
resp = self.Iq(stype='result',
|
resp = self.Iq(stype='result',
|
||||||
sto=iq['from'],
|
sto=iq['from'],
|
||||||
|
|||||||
@@ -123,12 +123,6 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
"""
|
"""
|
||||||
if xml.tag.startswith('{jabber:client}'):
|
if xml.tag.startswith('{jabber:client}'):
|
||||||
xml.tag = xml.tag.replace('jabber:client', self.default_ns)
|
xml.tag = xml.tag.replace('jabber:client', self.default_ns)
|
||||||
|
|
||||||
# The incoming_filter call is only made on top level stanza
|
|
||||||
# elements. So we manually continue filtering on sub-elements.
|
|
||||||
for sub in xml:
|
|
||||||
self.incoming_filter(sub)
|
|
||||||
|
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
def start_stream_handler(self, xml):
|
def start_stream_handler(self, xml):
|
||||||
@@ -158,8 +152,8 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
"""
|
"""
|
||||||
self.session_bind_event.set()
|
self.session_bind_event.set()
|
||||||
self.session_started_event.set()
|
self.session_started_event.set()
|
||||||
self.event("session_bind", self.boundjid, direct=True)
|
self.event('session_bind', self.boundjid, direct=True)
|
||||||
self.event("session_start")
|
self.event('session_start')
|
||||||
|
|
||||||
def _handle_probe(self, pres):
|
def _handle_probe(self, pres):
|
||||||
self.roster[pres['to']][pres['from']].handle_probe(pres)
|
self.roster[pres['to']][pres['from']].handle_probe(pres)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class XMPPError(Exception):
|
|||||||
Defaults to ``True``.
|
Defaults to ``True``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, condition='undefined-condition', text=None,
|
def __init__(self, condition='undefined-condition', text='',
|
||||||
etype='cancel', extension=None, extension_ns=None,
|
etype='cancel', extension=None, extension_ns=None,
|
||||||
extension_args=None, clear=True):
|
extension_args=None, clear=True):
|
||||||
if extension_args is None:
|
if extension_args is None:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from sleekxmpp.jid import JID
|
|||||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
from sleekxmpp.stanza import Iq, StreamFeatures
|
||||||
from sleekxmpp.features.feature_bind import stanza
|
from sleekxmpp.features.feature_bind import stanza
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
from sleekxmpp.plugins import BasePlugin, register_plugin
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -41,12 +41,12 @@ class FeatureBind(BasePlugin):
|
|||||||
Arguments:
|
Arguments:
|
||||||
features -- The stream features stanza.
|
features -- The stream features stanza.
|
||||||
"""
|
"""
|
||||||
log.debug("Requesting resource: %s", self.xmpp.boundjid.resource)
|
log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource)
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq.enable('bind')
|
iq.enable('bind')
|
||||||
if self.xmpp.boundjid.resource:
|
if self.xmpp.requested_jid.resource:
|
||||||
iq['bind']['resource'] = self.xmpp.boundjid.resource
|
iq['bind']['resource'] = self.xmpp.requested_jid.resource
|
||||||
response = iq.send(now=True)
|
response = iq.send(now=True)
|
||||||
|
|
||||||
self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
|
self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
|
||||||
@@ -56,10 +56,10 @@ class FeatureBind(BasePlugin):
|
|||||||
|
|
||||||
self.xmpp.features.add('bind')
|
self.xmpp.features.add('bind')
|
||||||
|
|
||||||
log.info("Node set to: %s", self.xmpp.boundjid.full)
|
log.info("JID set to: %s", self.xmpp.boundjid.full)
|
||||||
|
|
||||||
if 'session' not in features['features']:
|
if 'session' not in features['features']:
|
||||||
log.debug("Established Session")
|
log.debug("Established Session")
|
||||||
self.xmpp.sessionstarted = True
|
self.xmpp.sessionstarted = True
|
||||||
self.xmpp.session_started_event.set()
|
self.xmpp.session_started_event.set()
|
||||||
self.xmpp.event("session_start")
|
self.xmpp.event('session_start')
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import ssl
|
import ssl
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -44,15 +43,16 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
if not self.use_mech and not self.xmpp.requested_jid.user:
|
|
||||||
self.use_mech = 'ANONYMOUS'
|
|
||||||
|
|
||||||
if self.sasl_callback is None:
|
if self.sasl_callback is None:
|
||||||
self.sasl_callback = self._default_credentials
|
self.sasl_callback = self._default_credentials
|
||||||
|
|
||||||
if self.security_callback is None:
|
if self.security_callback is None:
|
||||||
self.security_callback = self._default_security
|
self.security_callback = self._default_security
|
||||||
|
|
||||||
|
creds = self.sasl_callback(set(['username']), set())
|
||||||
|
if not self.use_mech and not creds['username']:
|
||||||
|
self.use_mech = 'ANONYMOUS'
|
||||||
|
|
||||||
self.mech = None
|
self.mech = None
|
||||||
self.mech_list = set()
|
self.mech_list = set()
|
||||||
self.attempted_mechs = set()
|
self.attempted_mechs = set()
|
||||||
@@ -92,27 +92,26 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
values = required_values.union(optional_values)
|
values = required_values.union(optional_values)
|
||||||
for value in values:
|
for value in values:
|
||||||
if value == 'username':
|
if value == 'username':
|
||||||
result[value] = self.xmpp.requested_jid.user
|
result[value] = creds.get('username', self.xmpp.requested_jid.user)
|
||||||
elif value == 'password':
|
|
||||||
result[value] = creds['password']
|
|
||||||
elif value == 'authzid':
|
|
||||||
result[value] = creds.get('authzid', '')
|
|
||||||
elif value == 'email':
|
elif value == 'email':
|
||||||
jid = self.xmpp.requested_jid.bare
|
jid = self.xmpp.requested_jid.bare
|
||||||
result[value] = creds.get('email', jid)
|
result[value] = creds.get('email', jid)
|
||||||
elif value == 'channel_binding':
|
elif value == 'channel_binding':
|
||||||
if sys.version_info >= (3, 3):
|
if hasattr(self.xmpp.socket, 'get_channel_binding'):
|
||||||
result[value] = self.xmpp.socket.get_channel_binding()
|
result[value] = self.xmpp.socket.get_channel_binding()
|
||||||
else:
|
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
|
result[value] = None
|
||||||
elif value == 'host':
|
elif value == 'host':
|
||||||
result[value] = self.xmpp.requested_jid.domain
|
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
|
||||||
elif value == 'realm':
|
elif value == 'realm':
|
||||||
result[value] = self.xmpp.requested_jid.domain
|
result[value] = creds.get('realm', self.xmpp.requested_jid.domain)
|
||||||
elif value == 'service-name':
|
elif value == 'service-name':
|
||||||
result[value] = self.xmpp._service_name
|
result[value] = creds.get('service-name', self.xmpp._service_name)
|
||||||
elif value == 'service':
|
elif value == 'service':
|
||||||
result[value] = 'xmpp'
|
result[value] = creds.get('service', 'xmpp')
|
||||||
elif value in creds:
|
elif value in creds:
|
||||||
result[value] = creds[value]
|
result[value] = creds[value]
|
||||||
return result
|
return result
|
||||||
@@ -174,8 +173,12 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
except sasl.SASLNoAppropriateMechanism:
|
except sasl.SASLNoAppropriateMechanism:
|
||||||
log.error("No appropriate login method.")
|
log.error("No appropriate login method.")
|
||||||
self.xmpp.event("no_auth", direct=True)
|
self.xmpp.event("no_auth", direct=True)
|
||||||
|
self.xmpp.event("failed_auth", direct=True)
|
||||||
self.attempted_mechs = set()
|
self.attempted_mechs = set()
|
||||||
return self.xmpp.disconnect()
|
return self.xmpp.disconnect()
|
||||||
|
except StringPrepError:
|
||||||
|
log.exception("A credential value did not pass SASLprep.")
|
||||||
|
self.xmpp.disconnect()
|
||||||
|
|
||||||
resp = stanza.Auth(self.xmpp)
|
resp = stanza.Auth(self.xmpp)
|
||||||
resp['mechanism'] = self.mech.name
|
resp['mechanism'] = self.mech.name
|
||||||
@@ -192,9 +195,6 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
"A security breach is possible.")
|
"A security breach is possible.")
|
||||||
self.attempted_mechs.add(self.mech.name)
|
self.attempted_mechs.add(self.mech.name)
|
||||||
self.xmpp.disconnect()
|
self.xmpp.disconnect()
|
||||||
except StringPrepError:
|
|
||||||
log.exception("A credential value did not pass SASLprep.")
|
|
||||||
self.xmpp.disconnect()
|
|
||||||
else:
|
else:
|
||||||
resp.send(now=True)
|
resp.send(now=True)
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Auth(StanzaBase):
|
|||||||
if not self['mechanism'] in self.plain_mechs:
|
if not self['mechanism'] in self.plain_mechs:
|
||||||
if values:
|
if values:
|
||||||
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
|
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
|
||||||
else:
|
elif values == b'':
|
||||||
self.xml.text = '='
|
self.xml.text = '='
|
||||||
else:
|
else:
|
||||||
self.xml.text = bytes(values).decode('utf-8')
|
self.xml.text = bytes(values).decode('utf-8')
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
from sleekxmpp.stanza import StreamFeatures
|
||||||
from sleekxmpp.features.feature_preapproval import stanza
|
from sleekxmpp.features.feature_preapproval import stanza
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
from sleekxmpp.plugins.base import BasePlugin
|
from sleekxmpp.plugins.base import BasePlugin
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sleekxmpp.stanza import Iq, StreamFeatures
|
from sleekxmpp.stanza import StreamFeatures
|
||||||
from sleekxmpp.features.feature_rosterver import stanza
|
from sleekxmpp.features.feature_rosterver import stanza
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
from sleekxmpp.plugins.base import BasePlugin
|
from sleekxmpp.plugins.base import BasePlugin
|
||||||
|
|||||||
@@ -51,4 +51,4 @@ class FeatureSession(BasePlugin):
|
|||||||
log.debug("Established Session")
|
log.debug("Established Session")
|
||||||
self.xmpp.sessionstarted = True
|
self.xmpp.sessionstarted = True
|
||||||
self.xmpp.session_started_event.set()
|
self.xmpp.session_started_event.set()
|
||||||
self.xmpp.event("session_start")
|
self.xmpp.event('session_start')
|
||||||
|
|||||||
144
sleekxmpp/jid.py
144
sleekxmpp/jid.py
@@ -19,6 +19,8 @@ import stringprep
|
|||||||
import threading
|
import threading
|
||||||
import encodings.idna
|
import encodings.idna
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
from sleekxmpp.util import stringprep_profiles
|
from sleekxmpp.util import stringprep_profiles
|
||||||
from sleekxmpp.thirdparty import OrderedDict
|
from sleekxmpp.thirdparty import OrderedDict
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ def _cache(key, parts, locked):
|
|||||||
with JID_CACHE_LOCK:
|
with JID_CACHE_LOCK:
|
||||||
while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
|
while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
|
||||||
found = None
|
found = None
|
||||||
for key, item in JID_CACHE.iteritems():
|
for key, item in JID_CACHE.items():
|
||||||
if not item[1]: # if not locked
|
if not item[1]: # if not locked
|
||||||
found = key
|
found = key
|
||||||
break
|
break
|
||||||
@@ -202,7 +204,7 @@ def _validate_domain(domain):
|
|||||||
socket.inet_pton(socket.AF_INET6, domain.strip('[]'))
|
socket.inet_pton(socket.AF_INET6, domain.strip('[]'))
|
||||||
domain = '[%s]' % domain.strip('[]')
|
domain = '[%s]' % domain.strip('[]')
|
||||||
ip_addr = True
|
ip_addr = True
|
||||||
except socket.error:
|
except (socket.error, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not ip_addr:
|
if not ip_addr:
|
||||||
@@ -228,7 +230,7 @@ def _validate_domain(domain):
|
|||||||
|
|
||||||
for char in label:
|
for char in label:
|
||||||
if char in ILLEGAL_CHARS:
|
if char in ILLEGAL_CHARS:
|
||||||
raise InvalidJID('Domain contains illegar characters')
|
raise InvalidJID('Domain contains illegal characters')
|
||||||
|
|
||||||
if '-' in (label[0], label[-1]):
|
if '-' in (label[0], label[-1]):
|
||||||
raise InvalidJID('Domain started or ended with -')
|
raise InvalidJID('Domain started or ended with -')
|
||||||
@@ -506,50 +508,100 @@ class JID(object):
|
|||||||
"""
|
"""
|
||||||
self._jid = JID(data)._jid
|
self._jid = JID(data)._jid
|
||||||
|
|
||||||
# pylint: disable=R0911
|
@property
|
||||||
def __getattr__(self, name):
|
def resource(self):
|
||||||
"""Retrieve the given JID component.
|
return self._jid[2] or ''
|
||||||
|
|
||||||
:param name: one of: user, server, domain, resource,
|
@property
|
||||||
full, or bare.
|
def user(self):
|
||||||
"""
|
return self._jid[0] or ''
|
||||||
if name == 'resource':
|
|
||||||
return self._jid[2] or ''
|
|
||||||
elif name in ('user', 'username', 'local', 'node'):
|
|
||||||
return self._jid[0] or ''
|
|
||||||
elif name in ('server', 'domain', 'host'):
|
|
||||||
return self._jid[1] or ''
|
|
||||||
elif name in ('full', 'jid'):
|
|
||||||
return _format_jid(*self._jid)
|
|
||||||
elif name == 'bare':
|
|
||||||
return _format_jid(self._jid[0], self._jid[1])
|
|
||||||
elif name == '_jid':
|
|
||||||
return getattr(super(JID, self), '_jid')
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# pylint: disable=W0212
|
@property
|
||||||
def __setattr__(self, name, value):
|
def local(self):
|
||||||
"""Update the given JID component.
|
return self._jid[0] or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node(self):
|
||||||
|
return self._jid[0] or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self):
|
||||||
|
return self._jid[0] or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bare(self):
|
||||||
|
return _format_jid(self._jid[0], self._jid[1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def server(self):
|
||||||
|
return self._jid[1] or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def domain(self):
|
||||||
|
return self._jid[1] or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
return self._jid[1] or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full(self):
|
||||||
|
return _format_jid(*self._jid)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def jid(self):
|
||||||
|
return _format_jid(*self._jid)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bare(self):
|
||||||
|
return _format_jid(self._jid[0], self._jid[1])
|
||||||
|
|
||||||
|
|
||||||
|
@resource.setter
|
||||||
|
def resource(self, value):
|
||||||
|
self._jid = JID(self, resource=value)._jid
|
||||||
|
|
||||||
|
@user.setter
|
||||||
|
def user(self, value):
|
||||||
|
self._jid = JID(self, local=value)._jid
|
||||||
|
|
||||||
|
@username.setter
|
||||||
|
def username(self, value):
|
||||||
|
self._jid = JID(self, local=value)._jid
|
||||||
|
|
||||||
|
@local.setter
|
||||||
|
def local(self, value):
|
||||||
|
self._jid = JID(self, local=value)._jid
|
||||||
|
|
||||||
|
@node.setter
|
||||||
|
def node(self, value):
|
||||||
|
self._jid = JID(self, local=value)._jid
|
||||||
|
|
||||||
|
@server.setter
|
||||||
|
def server(self, value):
|
||||||
|
self._jid = JID(self, domain=value)._jid
|
||||||
|
|
||||||
|
@domain.setter
|
||||||
|
def domain(self, value):
|
||||||
|
self._jid = JID(self, domain=value)._jid
|
||||||
|
|
||||||
|
@host.setter
|
||||||
|
def host(self, value):
|
||||||
|
self._jid = JID(self, domain=value)._jid
|
||||||
|
|
||||||
|
@full.setter
|
||||||
|
def full(self, value):
|
||||||
|
self._jid = JID(value)._jid
|
||||||
|
|
||||||
|
@jid.setter
|
||||||
|
def jid(self, value):
|
||||||
|
self._jid = JID(value)._jid
|
||||||
|
|
||||||
|
@bare.setter
|
||||||
|
def bare(self, value):
|
||||||
|
parsed = JID(value)._jid
|
||||||
|
self._jid = (parsed[0], parsed[1], self._jid[2])
|
||||||
|
|
||||||
:param name: one of: ``user``, ``username``, ``local``,
|
|
||||||
``node``, ``server``, ``domain``, ``host``,
|
|
||||||
``resource``, ``full``, ``jid``, or ``bare``.
|
|
||||||
:param value: The new string value of the JID component.
|
|
||||||
"""
|
|
||||||
if name == '_jid':
|
|
||||||
super(JID, self).__setattr__('_jid', value)
|
|
||||||
elif name == 'resource':
|
|
||||||
self._jid = JID(self, resource=value)._jid
|
|
||||||
elif name in ('user', 'username', 'local', 'node'):
|
|
||||||
self._jid = JID(self, local=value)._jid
|
|
||||||
elif name in ('server', 'domain', 'host'):
|
|
||||||
self._jid = JID(self, domain=value)._jid
|
|
||||||
elif name in ('full', 'jid'):
|
|
||||||
self._jid = JID(value)._jid
|
|
||||||
elif name == 'bare':
|
|
||||||
parsed = JID(value)._jid
|
|
||||||
self._jid = (parsed[0], parsed[1], self._jid[2])
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Use the full JID as the string value."""
|
"""Use the full JID as the string value."""
|
||||||
@@ -580,3 +632,7 @@ class JID(object):
|
|||||||
def __copy__(self):
|
def __copy__(self):
|
||||||
"""Generate a duplicate JID."""
|
"""Generate a duplicate JID."""
|
||||||
return JID(self)
|
return JID(self)
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
"""Generate a duplicate JID."""
|
||||||
|
return JID(deepcopy(str(self), memo))
|
||||||
|
|||||||
@@ -11,28 +11,30 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin
|
|||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Non-standard
|
|
||||||
'gmail_notify', # Gmail searching and notifications
|
|
||||||
|
|
||||||
# XEPS
|
# XEPS
|
||||||
'xep_0004', # Data Forms
|
'xep_0004', # Data Forms
|
||||||
'xep_0009', # Jabber-RPC
|
'xep_0009', # Jabber-RPC
|
||||||
'xep_0012', # Last Activity
|
'xep_0012', # Last Activity
|
||||||
'xep_0013', # Flexible Offline Message Retrieval
|
'xep_0013', # Flexible Offline Message Retrieval
|
||||||
'xep_0016', # Privacy Lists
|
'xep_0016', # Privacy Lists
|
||||||
|
'xep_0020', # Feature Negotiation
|
||||||
'xep_0027', # Current Jabber OpenPGP Usage
|
'xep_0027', # Current Jabber OpenPGP Usage
|
||||||
'xep_0030', # Service Discovery
|
'xep_0030', # Service Discovery
|
||||||
'xep_0033', # Extended Stanza Addresses
|
'xep_0033', # Extended Stanza Addresses
|
||||||
'xep_0045', # Multi-User Chat (Client)
|
'xep_0045', # Multi-User Chat (Client)
|
||||||
'xep_0047', # In-Band Bytestreams
|
'xep_0047', # In-Band Bytestreams
|
||||||
|
'xep_0048', # Bookmarks
|
||||||
'xep_0049', # Private XML Storage
|
'xep_0049', # Private XML Storage
|
||||||
'xep_0050', # Ad-hoc Commands
|
'xep_0050', # Ad-hoc Commands
|
||||||
'xep_0054', # vcard-temp
|
'xep_0054', # vcard-temp
|
||||||
'xep_0059', # Result Set Management
|
'xep_0059', # Result Set Management
|
||||||
'xep_0060', # Pubsub (Client)
|
'xep_0060', # Pubsub (Client)
|
||||||
|
'xep_0065', # SOCKS5 Bytestreams
|
||||||
'xep_0066', # Out of Band Data
|
'xep_0066', # Out of Band Data
|
||||||
|
'xep_0071', # XHTML-IM
|
||||||
'xep_0077', # In-Band Registration
|
'xep_0077', # In-Band Registration
|
||||||
# 'xep_0078', # Non-SASL auth. Don't automatically load
|
# 'xep_0078', # Non-SASL auth. Don't automatically load
|
||||||
|
'xep_0079', # Advanced Message Processing
|
||||||
'xep_0080', # User Location
|
'xep_0080', # User Location
|
||||||
'xep_0082', # XMPP Date and Time Profiles
|
'xep_0082', # XMPP Date and Time Profiles
|
||||||
'xep_0084', # User Avatar
|
'xep_0084', # User Avatar
|
||||||
@@ -48,12 +50,14 @@ __all__ = [
|
|||||||
'xep_0128', # Extended Service Discovery
|
'xep_0128', # Extended Service Discovery
|
||||||
'xep_0131', # Standard Headers and Internet Metadata
|
'xep_0131', # Standard Headers and Internet Metadata
|
||||||
'xep_0133', # Service Administration
|
'xep_0133', # Service Administration
|
||||||
|
'xep_0152', # Reachability Addresses
|
||||||
'xep_0153', # vCard-Based Avatars
|
'xep_0153', # vCard-Based Avatars
|
||||||
'xep_0163', # Personal Eventing Protocol
|
'xep_0163', # Personal Eventing Protocol
|
||||||
'xep_0172', # User Nickname
|
'xep_0172', # User Nickname
|
||||||
'xep_0184', # Message Receipts
|
'xep_0184', # Message Receipts
|
||||||
'xep_0186', # Invisible Command
|
'xep_0186', # Invisible Command
|
||||||
'xep_0191', # Blocking Command
|
'xep_0191', # Blocking Command
|
||||||
|
'xep_0196', # User Gaming
|
||||||
'xep_0198', # Stream Management
|
'xep_0198', # Stream Management
|
||||||
'xep_0199', # Ping
|
'xep_0199', # Ping
|
||||||
'xep_0202', # Entity Time
|
'xep_0202', # Entity Time
|
||||||
@@ -76,4 +80,7 @@ __all__ = [
|
|||||||
'xep_0302', # XMPP Compliance Suites 2012
|
'xep_0302', # XMPP Compliance Suites 2012
|
||||||
'xep_0308', # Last Message Correction
|
'xep_0308', # Last Message Correction
|
||||||
'xep_0313', # Message Archive Management
|
'xep_0313', # Message Archive Management
|
||||||
|
'xep_0319', # Last User Interaction in Presence
|
||||||
|
'xep_0323', # IoT Systems Sensor Data
|
||||||
|
'xep_0325', # IoT Systems Control
|
||||||
]
|
]
|
||||||
|
|||||||
47
sleekxmpp/plugins/google/__init__.py
Normal file
47
sleekxmpp/plugins/google/__init__.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin, BasePlugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.google.gmail import Gmail
|
||||||
|
from sleekxmpp.plugins.google.auth import GoogleAuth
|
||||||
|
from sleekxmpp.plugins.google.settings import GoogleSettings
|
||||||
|
from sleekxmpp.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
sleekxmpp/plugins/google/auth/__init__.py
Normal file
10
sleekxmpp/plugins/google/auth/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.google.auth import stanza
|
||||||
|
from sleekxmpp.plugins.google.auth.auth import GoogleAuth
|
||||||
52
sleekxmpp/plugins/google/auth/auth.py
Normal file
52
sleekxmpp/plugins/google/auth/auth.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.google.auth import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
49
sleekxmpp/plugins/google/auth/stanza.py
Normal file
49
sleekxmpp/plugins/google/auth/stanza.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, ET
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleAuth(ElementBase):
|
||||||
|
name = 'auth'
|
||||||
|
namespace = 'http://www.google.com/talk/protocol/auth'
|
||||||
|
plugin_attrib = 'google'
|
||||||
|
interfaces = set(['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('')
|
||||||
|
print('setting up google extension')
|
||||||
|
|
||||||
|
def get_client_uses_full_bind_result(self):
|
||||||
|
return self.parent()._get_attr(self.disovery_attr) == 'true'
|
||||||
|
|
||||||
|
def set_client_uses_full_bind_result(self, value):
|
||||||
|
print('>>>', 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
sleekxmpp/plugins/google/gmail/__init__.py
Normal file
10
sleekxmpp/plugins/google/gmail/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.google.gmail import stanza
|
||||||
|
from sleekxmpp.plugins.google.gmail.notifications import Gmail
|
||||||
96
sleekxmpp/plugins/google/gmail/notifications.py
Normal file
96
sleekxmpp/plugins/google/gmail/notifications.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Iq
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import MatchXPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.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, block=True, timeout=None, callback=None):
|
||||||
|
last_time = self._last_result_time
|
||||||
|
last_tid = self._last_result_tid
|
||||||
|
|
||||||
|
if not block:
|
||||||
|
callback = lambda iq: self._update_last_results(iq, callback)
|
||||||
|
|
||||||
|
resp = self.search(newer_time=last_time,
|
||||||
|
newer_tid=last_tid,
|
||||||
|
block=block,
|
||||||
|
timeout=timeout,
|
||||||
|
callback=callback)
|
||||||
|
|
||||||
|
if block:
|
||||||
|
self._update_last_results(resp)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def _update_last_results(self, iq, callback=None):
|
||||||
|
self._last_result_time = data['gmail_messages']['result_time']
|
||||||
|
threads = data['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, block=True,
|
||||||
|
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(block=block, timeout=timeout, callback=callback)
|
||||||
101
sleekxmpp/plugins/google/gmail/stanza.py
Normal file
101
sleekxmpp/plugins/google/gmail/stanza.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.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
sleekxmpp/plugins/google/nosave/__init__.py
Normal file
10
sleekxmpp/plugins/google/nosave/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.google.nosave import stanza
|
||||||
|
from sleekxmpp.plugins.google.nosave.nosave import GoogleNoSave
|
||||||
83
sleekxmpp/plugins/google/nosave/nosave.py
Normal file
83
sleekxmpp/plugins/google/nosave/nosave.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Iq, Message
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.google.nosave import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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, block=True, timeout=None, callback=None):
|
||||||
|
if jid is None:
|
||||||
|
self.xmpp['google_settings'].update({'archiving_enabled': False},
|
||||||
|
block=block, 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(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def disable(self, jid=None, block=True, timeout=None, callback=None):
|
||||||
|
if jid is None:
|
||||||
|
self.xmpp['google_settings'].update({'archiving_enabled': True},
|
||||||
|
block=block, 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(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def get(self, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq.enable('google_nosave')
|
||||||
|
return iq.send(block=block, 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
sleekxmpp/plugins/google/nosave/stanza.py
Normal file
59
sleekxmpp/plugins/google/nosave/stanza.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.jid import JID
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class NoSave(ElementBase):
|
||||||
|
name = 'x'
|
||||||
|
namespace = 'google:nosave'
|
||||||
|
plugin_attrib = 'google_nosave'
|
||||||
|
interfaces = set(['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 = set(['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):
|
||||||
|
self._set_attr('source', str(value))
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(NoSaveQuery, Item)
|
||||||
10
sleekxmpp/plugins/google/settings/__init__.py
Normal file
10
sleekxmpp/plugins/google/settings/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.google.settings import stanza
|
||||||
|
from sleekxmpp.plugins.google.settings.settings import GoogleSettings
|
||||||
65
sleekxmpp/plugins/google/settings/settings.py
Normal file
65
sleekxmpp/plugins/google/settings/settings.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Iq
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.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, block=True, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq.enable('google_settings')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def update(self, settings, block=True, 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(block=block, 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
sleekxmpp/plugins/google/settings/stanza.py
Normal file
110
sleekxmpp/plugins/google/settings/stanza.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
from . import base
|
|
||||||
import logging
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class jobs(base.base_plugin):
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = 'pubsubjob'
|
|
||||||
self.description = "Job distribution over Pubsub"
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
pass
|
|
||||||
#TODO add event
|
|
||||||
|
|
||||||
def createJobNode(self, host, jid, node, config=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def createJob(self, host, node, jobid=None, payload=None):
|
|
||||||
return self.xmpp.plugin['xep_0060'].setItem(host, node, ((jobid, payload),))
|
|
||||||
|
|
||||||
def claimJob(self, host, node, jobid, ifrom=None):
|
|
||||||
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed'))
|
|
||||||
|
|
||||||
def unclaimJob(self, host, node, jobid):
|
|
||||||
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
|
|
||||||
|
|
||||||
def finishJob(self, host, node, jobid, payload=None):
|
|
||||||
finished = ET.Element('{http://andyet.net/protocol/pubsubjob}finished')
|
|
||||||
if payload is not None:
|
|
||||||
finished.append(payload)
|
|
||||||
return self._setState(host, node, jobid, finished)
|
|
||||||
|
|
||||||
def _setState(self, host, node, jobid, state, ifrom=None):
|
|
||||||
iq = self.xmpp.Iq()
|
|
||||||
iq['to'] = host
|
|
||||||
if ifrom: iq['from'] = ifrom
|
|
||||||
iq['type'] = 'set'
|
|
||||||
iq['psstate']['node'] = node
|
|
||||||
iq['psstate']['item'] = jobid
|
|
||||||
iq['psstate']['payload'] = state
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or type(result) == bool or result['type'] != 'result':
|
|
||||||
log.error("Unable to change %s:%s to %s", node, jobid, state)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
@@ -1,421 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
from . import base
|
|
||||||
import logging
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
import copy
|
|
||||||
import logging
|
|
||||||
#TODO support item groups and results
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class old_0004(base.base_plugin):
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = '0004'
|
|
||||||
self.description = '*Deprecated Data Forms'
|
|
||||||
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form')
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
base.base_plugin.post_init(self)
|
|
||||||
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
|
|
||||||
log.warning("This implementation of XEP-0004 is deprecated.")
|
|
||||||
|
|
||||||
def handler_message_xform(self, xml):
|
|
||||||
object = self.handle_form(xml)
|
|
||||||
self.xmpp.event("message_form", object)
|
|
||||||
|
|
||||||
def handler_presence_xform(self, xml):
|
|
||||||
object = self.handle_form(xml)
|
|
||||||
self.xmpp.event("presence_form", object)
|
|
||||||
|
|
||||||
def handle_form(self, xml):
|
|
||||||
xmlform = xml.find('{jabber:x:data}x')
|
|
||||||
object = self.buildForm(xmlform)
|
|
||||||
self.xmpp.event("message_xform", object)
|
|
||||||
return object
|
|
||||||
|
|
||||||
def buildForm(self, xml):
|
|
||||||
form = Form(ftype=xml.attrib['type'])
|
|
||||||
form.fromXML(xml)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def makeForm(self, ftype='form', title='', instructions=''):
|
|
||||||
return Form(self.xmpp, ftype, title, instructions)
|
|
||||||
|
|
||||||
class FieldContainer(object):
|
|
||||||
def __init__(self, stanza = 'form'):
|
|
||||||
self.fields = []
|
|
||||||
self.field = {}
|
|
||||||
self.stanza = stanza
|
|
||||||
|
|
||||||
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
|
|
||||||
self.field[var] = FormField(var, ftype, label, desc, required, value)
|
|
||||||
self.fields.append(self.field[var])
|
|
||||||
return self.field[var]
|
|
||||||
|
|
||||||
def buildField(self, xml):
|
|
||||||
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
|
|
||||||
self.fields.append(self.field[xml.get('var', '__unnamed__')])
|
|
||||||
self.field[xml.get('var', '__unnamed__')].buildField(xml)
|
|
||||||
|
|
||||||
def buildContainer(self, xml):
|
|
||||||
self.stanza = xml.tag
|
|
||||||
for field in xml.findall('{jabber:x:data}field'):
|
|
||||||
self.buildField(field)
|
|
||||||
|
|
||||||
def getXML(self, ftype):
|
|
||||||
container = ET.Element(self.stanza)
|
|
||||||
for field in self.fields:
|
|
||||||
container.append(field.getXML(ftype))
|
|
||||||
return container
|
|
||||||
|
|
||||||
class Form(FieldContainer):
|
|
||||||
types = ('form', 'submit', 'cancel', 'result')
|
|
||||||
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
|
|
||||||
if not ftype in self.types:
|
|
||||||
raise ValueError("Invalid Form Type")
|
|
||||||
FieldContainer.__init__(self)
|
|
||||||
self.xmpp = xmpp
|
|
||||||
self.type = ftype
|
|
||||||
self.title = title
|
|
||||||
self.instructions = instructions
|
|
||||||
self.reported = []
|
|
||||||
self.items = []
|
|
||||||
|
|
||||||
def merge(self, form2):
|
|
||||||
form1 = Form(ftype=self.type)
|
|
||||||
form1.fromXML(self.getXML(self.type))
|
|
||||||
for field in form2.fields:
|
|
||||||
if not field.var in form1.field:
|
|
||||||
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
|
|
||||||
else:
|
|
||||||
form1.field[field.var].value = field.value
|
|
||||||
for option, label in field.options:
|
|
||||||
if (option, label) not in form1.field[field.var].options:
|
|
||||||
form1.fields[field.var].addOption(option, label)
|
|
||||||
return form1
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
newform = Form(ftype=self.type)
|
|
||||||
newform.fromXML(self.getXML(self.type))
|
|
||||||
return newform
|
|
||||||
|
|
||||||
def update(self, form):
|
|
||||||
values = form.getValues()
|
|
||||||
for var in values:
|
|
||||||
if var in self.fields:
|
|
||||||
self.fields[var].setValue(self.fields[var])
|
|
||||||
|
|
||||||
def getValues(self):
|
|
||||||
result = {}
|
|
||||||
for field in self.fields:
|
|
||||||
value = field.value
|
|
||||||
if len(value) == 1:
|
|
||||||
value = value[0]
|
|
||||||
result[field.var] = value
|
|
||||||
return result
|
|
||||||
|
|
||||||
def setValues(self, values={}):
|
|
||||||
for field in values:
|
|
||||||
if field in self.field:
|
|
||||||
if isinstance(values[field], list) or isinstance(values[field], tuple):
|
|
||||||
for value in values[field]:
|
|
||||||
self.field[field].setValue(value)
|
|
||||||
else:
|
|
||||||
self.field[field].setValue(values[field])
|
|
||||||
|
|
||||||
def fromXML(self, xml):
|
|
||||||
self.buildForm(xml)
|
|
||||||
|
|
||||||
def addItem(self):
|
|
||||||
newitem = FieldContainer('item')
|
|
||||||
self.items.append(newitem)
|
|
||||||
return newitem
|
|
||||||
|
|
||||||
def buildItem(self, xml):
|
|
||||||
newitem = self.addItem()
|
|
||||||
newitem.buildContainer(xml)
|
|
||||||
|
|
||||||
def addReported(self):
|
|
||||||
reported = FieldContainer('reported')
|
|
||||||
self.reported.append(reported)
|
|
||||||
return reported
|
|
||||||
|
|
||||||
def buildReported(self, xml):
|
|
||||||
reported = self.addReported()
|
|
||||||
reported.buildContainer(xml)
|
|
||||||
|
|
||||||
def setTitle(self, title):
|
|
||||||
self.title = title
|
|
||||||
|
|
||||||
def setInstructions(self, instructions):
|
|
||||||
self.instructions = instructions
|
|
||||||
|
|
||||||
def setType(self, ftype):
|
|
||||||
self.type = ftype
|
|
||||||
|
|
||||||
def getXMLMessage(self, to):
|
|
||||||
msg = self.xmpp.makeMessage(to)
|
|
||||||
msg.append(self.getXML())
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def buildForm(self, xml):
|
|
||||||
self.type = xml.get('type', 'form')
|
|
||||||
if xml.find('{jabber:x:data}title') is not None:
|
|
||||||
self.setTitle(xml.find('{jabber:x:data}title').text)
|
|
||||||
if xml.find('{jabber:x:data}instructions') is not None:
|
|
||||||
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
|
|
||||||
for field in xml.findall('{jabber:x:data}field'):
|
|
||||||
self.buildField(field)
|
|
||||||
for reported in xml.findall('{jabber:x:data}reported'):
|
|
||||||
self.buildReported(reported)
|
|
||||||
for item in xml.findall('{jabber:x:data}item'):
|
|
||||||
self.buildItem(item)
|
|
||||||
|
|
||||||
#def getXML(self, tostring = False):
|
|
||||||
def getXML(self, ftype=None):
|
|
||||||
if ftype:
|
|
||||||
self.type = ftype
|
|
||||||
form = ET.Element('{jabber:x:data}x')
|
|
||||||
form.attrib['type'] = self.type
|
|
||||||
if self.title and self.type in ('form', 'result'):
|
|
||||||
title = ET.Element('{jabber:x:data}title')
|
|
||||||
title.text = self.title
|
|
||||||
form.append(title)
|
|
||||||
if self.instructions and self.type == 'form':
|
|
||||||
instructions = ET.Element('{jabber:x:data}instructions')
|
|
||||||
instructions.text = self.instructions
|
|
||||||
form.append(instructions)
|
|
||||||
for field in self.fields:
|
|
||||||
form.append(field.getXML(self.type))
|
|
||||||
for reported in self.reported:
|
|
||||||
form.append(reported.getXML('{jabber:x:data}reported'))
|
|
||||||
for item in self.items:
|
|
||||||
form.append(item.getXML(self.type))
|
|
||||||
#if tostring:
|
|
||||||
# form = self.xmpp.tostring(form)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def getXHTML(self):
|
|
||||||
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
|
|
||||||
if self.title:
|
|
||||||
title = ET.Element('h2')
|
|
||||||
title.text = self.title
|
|
||||||
form.append(title)
|
|
||||||
if self.instructions:
|
|
||||||
instructions = ET.Element('p')
|
|
||||||
instructions.text = self.instructions
|
|
||||||
form.append(instructions)
|
|
||||||
for field in self.fields:
|
|
||||||
form.append(field.getXHTML())
|
|
||||||
for field in self.reported:
|
|
||||||
form.append(field.getXHTML())
|
|
||||||
for field in self.items:
|
|
||||||
form.append(field.getXHTML())
|
|
||||||
return form
|
|
||||||
|
|
||||||
|
|
||||||
def makeSubmit(self):
|
|
||||||
self.setType('submit')
|
|
||||||
|
|
||||||
class FormField(object):
|
|
||||||
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
|
|
||||||
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
|
|
||||||
lbtypes = ('fixed', 'text-multi')
|
|
||||||
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
|
|
||||||
if not ftype in self.types:
|
|
||||||
raise ValueError("Invalid Field Type")
|
|
||||||
self.type = ftype
|
|
||||||
self.var = var
|
|
||||||
self.label = label
|
|
||||||
self.desc = desc
|
|
||||||
self.options = []
|
|
||||||
self.required = False
|
|
||||||
self.value = []
|
|
||||||
if self.type in self.listtypes:
|
|
||||||
self.islist = True
|
|
||||||
else:
|
|
||||||
self.islist = False
|
|
||||||
if self.type in self.lbtypes:
|
|
||||||
self.islinebreak = True
|
|
||||||
else:
|
|
||||||
self.islinebreak = False
|
|
||||||
if value:
|
|
||||||
self.setValue(value)
|
|
||||||
|
|
||||||
def addOption(self, value, label):
|
|
||||||
if self.islist:
|
|
||||||
self.options.append((value, label))
|
|
||||||
else:
|
|
||||||
raise ValueError("Cannot add options to non-list type field.")
|
|
||||||
|
|
||||||
def setTrue(self):
|
|
||||||
if self.type == 'boolean':
|
|
||||||
self.value = [True]
|
|
||||||
|
|
||||||
def setFalse(self):
|
|
||||||
if self.type == 'boolean':
|
|
||||||
self.value = [False]
|
|
||||||
|
|
||||||
def require(self):
|
|
||||||
self.required = True
|
|
||||||
|
|
||||||
def setDescription(self, desc):
|
|
||||||
self.desc = desc
|
|
||||||
|
|
||||||
def setValue(self, value):
|
|
||||||
if self.type == 'boolean':
|
|
||||||
if value in ('1', 1, True, 'true', 'True', 'yes'):
|
|
||||||
value = True
|
|
||||||
else:
|
|
||||||
value = False
|
|
||||||
if self.islinebreak and value is not None:
|
|
||||||
self.value += value.split('\n')
|
|
||||||
else:
|
|
||||||
if len(self.value) and (not self.islist or self.type == 'list-single'):
|
|
||||||
self.value = [value]
|
|
||||||
else:
|
|
||||||
self.value.append(value)
|
|
||||||
|
|
||||||
def delValue(self, value):
|
|
||||||
if type(self.value) == type([]):
|
|
||||||
try:
|
|
||||||
idx = self.value.index(value)
|
|
||||||
if idx != -1:
|
|
||||||
self.value.pop(idx)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.value = ''
|
|
||||||
|
|
||||||
def setAnswer(self, value):
|
|
||||||
self.setValue(value)
|
|
||||||
|
|
||||||
def buildField(self, xml):
|
|
||||||
self.type = xml.get('type', 'text-single')
|
|
||||||
self.label = xml.get('label', '')
|
|
||||||
for option in xml.findall('{jabber:x:data}option'):
|
|
||||||
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
|
|
||||||
for value in xml.findall('{jabber:x:data}value'):
|
|
||||||
self.setValue(value.text)
|
|
||||||
if xml.find('{jabber:x:data}required') is not None:
|
|
||||||
self.require()
|
|
||||||
if xml.find('{jabber:x:data}desc') is not None:
|
|
||||||
self.setDescription(xml.find('{jabber:x:data}desc').text)
|
|
||||||
|
|
||||||
def getXML(self, ftype):
|
|
||||||
field = ET.Element('{jabber:x:data}field')
|
|
||||||
if ftype != 'result':
|
|
||||||
field.attrib['type'] = self.type
|
|
||||||
if self.type != 'fixed':
|
|
||||||
if self.var:
|
|
||||||
field.attrib['var'] = self.var
|
|
||||||
if self.label:
|
|
||||||
field.attrib['label'] = self.label
|
|
||||||
if ftype == 'form':
|
|
||||||
for option in self.options:
|
|
||||||
optionxml = ET.Element('{jabber:x:data}option')
|
|
||||||
optionxml.attrib['label'] = option[1]
|
|
||||||
optionval = ET.Element('{jabber:x:data}value')
|
|
||||||
optionval.text = option[0]
|
|
||||||
optionxml.append(optionval)
|
|
||||||
field.append(optionxml)
|
|
||||||
if self.required:
|
|
||||||
required = ET.Element('{jabber:x:data}required')
|
|
||||||
field.append(required)
|
|
||||||
if self.desc:
|
|
||||||
desc = ET.Element('{jabber:x:data}desc')
|
|
||||||
desc.text = self.desc
|
|
||||||
field.append(desc)
|
|
||||||
for value in self.value:
|
|
||||||
valuexml = ET.Element('{jabber:x:data}value')
|
|
||||||
if value is True or value is False:
|
|
||||||
if value:
|
|
||||||
valuexml.text = '1'
|
|
||||||
else:
|
|
||||||
valuexml.text = '0'
|
|
||||||
else:
|
|
||||||
valuexml.text = value
|
|
||||||
field.append(valuexml)
|
|
||||||
return field
|
|
||||||
|
|
||||||
def getXHTML(self):
|
|
||||||
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
|
|
||||||
if self.label:
|
|
||||||
label = ET.Element('p')
|
|
||||||
label.text = "%s: " % self.label
|
|
||||||
else:
|
|
||||||
label = ET.Element('p')
|
|
||||||
label.text = "%s: " % self.var
|
|
||||||
field.append(label)
|
|
||||||
if self.type == 'boolean':
|
|
||||||
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
|
|
||||||
if len(self.value) and self.value[0] in (True, 'true', '1'):
|
|
||||||
formf.attrib['checked'] = 'checked'
|
|
||||||
elif self.type == 'fixed':
|
|
||||||
formf = ET.Element('p')
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
field.append(formf)
|
|
||||||
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif self.type == 'hidden':
|
|
||||||
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif self.type in ('jid-multi', 'list-multi'):
|
|
||||||
formf = ET.Element('select', {'name': self.var})
|
|
||||||
for option in self.options:
|
|
||||||
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
|
|
||||||
optf.text = option[1]
|
|
||||||
if option[1] in self.value:
|
|
||||||
optf.attrib['selected'] = 'selected'
|
|
||||||
formf.append(option)
|
|
||||||
elif self.type in ('jid-single', 'text-single'):
|
|
||||||
formf = ET.Element('input', {'type': 'text', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.attrib['value'] = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif self.type == 'list-single':
|
|
||||||
formf = ET.Element('select', {'name': self.var})
|
|
||||||
for option in self.options:
|
|
||||||
optf = ET.Element('option', {'value': option[0]})
|
|
||||||
optf.text = option[1]
|
|
||||||
if not optf.text:
|
|
||||||
optf.text = option[0]
|
|
||||||
if option[1] in self.value:
|
|
||||||
optf.attrib['selected'] = 'selected'
|
|
||||||
formf.append(optf)
|
|
||||||
elif self.type == 'text-multi':
|
|
||||||
formf = ET.Element('textarea', {'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if not formf.text:
|
|
||||||
formf.text = ' '
|
|
||||||
elif self.type == 'text-private':
|
|
||||||
formf = ET.Element('input', {'type': 'password', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.attrib['value'] = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
label.append(formf)
|
|
||||||
return field
|
|
||||||
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
"""
|
|
||||||
XEP-0009 XMPP Remote Procedure Calls
|
|
||||||
"""
|
|
||||||
from __future__ import with_statement
|
|
||||||
from . import base
|
|
||||||
import logging
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
import copy
|
|
||||||
import time
|
|
||||||
import base64
|
|
||||||
|
|
||||||
def py2xml(*args):
|
|
||||||
params = ET.Element("params")
|
|
||||||
for x in args:
|
|
||||||
param = ET.Element("param")
|
|
||||||
param.append(_py2xml(x))
|
|
||||||
params.append(param) #<params><param>...
|
|
||||||
return params
|
|
||||||
|
|
||||||
def _py2xml(*args):
|
|
||||||
for x in args:
|
|
||||||
val = ET.Element("value")
|
|
||||||
if type(x) is int:
|
|
||||||
i4 = ET.Element("i4")
|
|
||||||
i4.text = str(x)
|
|
||||||
val.append(i4)
|
|
||||||
if type(x) is bool:
|
|
||||||
boolean = ET.Element("boolean")
|
|
||||||
boolean.text = str(int(x))
|
|
||||||
val.append(boolean)
|
|
||||||
elif type(x) is str:
|
|
||||||
string = ET.Element("string")
|
|
||||||
string.text = x
|
|
||||||
val.append(string)
|
|
||||||
elif type(x) is float:
|
|
||||||
double = ET.Element("double")
|
|
||||||
double.text = str(x)
|
|
||||||
val.append(double)
|
|
||||||
elif type(x) is rpcbase64:
|
|
||||||
b64 = ET.Element("Base64")
|
|
||||||
b64.text = x.encoded()
|
|
||||||
val.append(b64)
|
|
||||||
elif type(x) is rpctime:
|
|
||||||
iso = ET.Element("dateTime.iso8601")
|
|
||||||
iso.text = str(x)
|
|
||||||
val.append(iso)
|
|
||||||
elif type(x) is list:
|
|
||||||
array = ET.Element("array")
|
|
||||||
data = ET.Element("data")
|
|
||||||
for y in x:
|
|
||||||
data.append(_py2xml(y))
|
|
||||||
array.append(data)
|
|
||||||
val.append(array)
|
|
||||||
elif type(x) is dict:
|
|
||||||
struct = ET.Element("struct")
|
|
||||||
for y in x.keys():
|
|
||||||
member = ET.Element("member")
|
|
||||||
name = ET.Element("name")
|
|
||||||
name.text = y
|
|
||||||
member.append(name)
|
|
||||||
member.append(_py2xml(x[y]))
|
|
||||||
struct.append(member)
|
|
||||||
val.append(struct)
|
|
||||||
return val
|
|
||||||
|
|
||||||
def xml2py(params):
|
|
||||||
vals = []
|
|
||||||
for param in params.findall('param'):
|
|
||||||
vals.append(_xml2py(param.find('value')))
|
|
||||||
return vals
|
|
||||||
|
|
||||||
def _xml2py(value):
|
|
||||||
if value.find('i4') is not None:
|
|
||||||
return int(value.find('i4').text)
|
|
||||||
if value.find('int') is not None:
|
|
||||||
return int(value.find('int').text)
|
|
||||||
if value.find('boolean') is not None:
|
|
||||||
return bool(value.find('boolean').text)
|
|
||||||
if value.find('string') is not None:
|
|
||||||
return value.find('string').text
|
|
||||||
if value.find('double') is not None:
|
|
||||||
return float(value.find('double').text)
|
|
||||||
if value.find('Base64') is not None:
|
|
||||||
return rpcbase64(value.find('Base64').text)
|
|
||||||
if value.find('dateTime.iso8601') is not None:
|
|
||||||
return rpctime(value.find('dateTime.iso8601'))
|
|
||||||
if value.find('struct') is not None:
|
|
||||||
struct = {}
|
|
||||||
for member in value.find('struct').findall('member'):
|
|
||||||
struct[member.find('name').text] = _xml2py(member.find('value'))
|
|
||||||
return struct
|
|
||||||
if value.find('array') is not None:
|
|
||||||
array = []
|
|
||||||
for val in value.find('array').find('data').findall('value'):
|
|
||||||
array.append(_xml2py(val))
|
|
||||||
return array
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
class rpcbase64(object):
|
|
||||||
def __init__(self, data):
|
|
||||||
#base 64 encoded string
|
|
||||||
self.data = data
|
|
||||||
|
|
||||||
def decode(self):
|
|
||||||
return base64.decodestring(data)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.decode()
|
|
||||||
|
|
||||||
def encoded(self):
|
|
||||||
return self.data
|
|
||||||
|
|
||||||
class rpctime(object):
|
|
||||||
def __init__(self,data=None):
|
|
||||||
#assume string data is in iso format YYYYMMDDTHH:MM:SS
|
|
||||||
if type(data) is str:
|
|
||||||
self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
|
|
||||||
elif type(data) is time.struct_time:
|
|
||||||
self.timestamp = data
|
|
||||||
elif data is None:
|
|
||||||
self.timestamp = time.gmtime()
|
|
||||||
else:
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
def iso8601(self):
|
|
||||||
#return a iso8601 string
|
|
||||||
return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.iso8601()
|
|
||||||
|
|
||||||
class JabberRPCEntry(object):
|
|
||||||
def __init__(self,call):
|
|
||||||
self.call = call
|
|
||||||
self.result = None
|
|
||||||
self.error = None
|
|
||||||
self.allow = {} #{'<jid>':['<resource1>',...],...}
|
|
||||||
self.deny = {}
|
|
||||||
|
|
||||||
def check_acl(self, jid, resource):
|
|
||||||
#Check for deny
|
|
||||||
if jid in self.deny.keys():
|
|
||||||
if self.deny[jid] == None or resource in self.deny[jid]:
|
|
||||||
return False
|
|
||||||
#Check for allow
|
|
||||||
if allow == None:
|
|
||||||
return True
|
|
||||||
if jid in self.allow.keys():
|
|
||||||
if self.allow[jid] == None or resource in self.allow[jid]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def acl_allow(self, jid, resource):
|
|
||||||
if jid == None:
|
|
||||||
self.allow = None
|
|
||||||
elif resource == None:
|
|
||||||
self.allow[jid] = None
|
|
||||||
elif jid in self.allow.keys():
|
|
||||||
self.allow[jid].append(resource)
|
|
||||||
else:
|
|
||||||
self.allow[jid] = [resource]
|
|
||||||
|
|
||||||
def acl_deny(self, jid, resource):
|
|
||||||
if jid == None:
|
|
||||||
self.deny = None
|
|
||||||
elif resource == None:
|
|
||||||
self.deny[jid] = None
|
|
||||||
elif jid in self.deny.keys():
|
|
||||||
self.deny[jid].append(resource)
|
|
||||||
else:
|
|
||||||
self.deny[jid] = [resource]
|
|
||||||
|
|
||||||
def call_method(self, args):
|
|
||||||
ret = self.call(*args)
|
|
||||||
|
|
||||||
class xep_0009(base.base_plugin):
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = '0009'
|
|
||||||
self.description = 'Jabber-RPC'
|
|
||||||
self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
|
|
||||||
self._callMethod, name='Jabber RPC Call')
|
|
||||||
self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
|
|
||||||
self._callResult, name='Jabber RPC Result')
|
|
||||||
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
|
|
||||||
self._callError, name='Jabber RPC Error')
|
|
||||||
self.entries = {}
|
|
||||||
self.activeCalls = []
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
base.base_plugin.post_init(self)
|
|
||||||
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
|
|
||||||
self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc')
|
|
||||||
|
|
||||||
def register_call(self, method, name=None):
|
|
||||||
#@returns an string that can be used in acl commands.
|
|
||||||
with self.lock:
|
|
||||||
if name is None:
|
|
||||||
self.entries[method.__name__] = JabberRPCEntry(method)
|
|
||||||
return method.__name__
|
|
||||||
else:
|
|
||||||
self.entries[name] = JabberRPCEntry(method)
|
|
||||||
return name
|
|
||||||
|
|
||||||
def acl_allow(self, entry, jid=None, resource=None):
|
|
||||||
#allow the method entry to be called by the given jid and resource.
|
|
||||||
#if jid is None it will allow any jid/resource.
|
|
||||||
#if resource is None it will allow any resource belonging to the jid.
|
|
||||||
with self.lock:
|
|
||||||
if self.entries[entry]:
|
|
||||||
self.entries[entry].acl_allow(jid,resource)
|
|
||||||
else:
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
def acl_deny(self, entry, jid=None, resource=None):
|
|
||||||
#Note: by default all requests are denied unless allowed with acl_allow.
|
|
||||||
#If you deny an entry it will not be allowed regardless of acl_allow
|
|
||||||
with self.lock:
|
|
||||||
if self.entries[entry]:
|
|
||||||
self.entries[entry].acl_deny(jid,resource)
|
|
||||||
else:
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
def unregister_call(self, entry):
|
|
||||||
#removes the registered call
|
|
||||||
with self.lock:
|
|
||||||
if self.entries[entry]:
|
|
||||||
del self.entries[entry]
|
|
||||||
else:
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
def makeMethodCallQuery(self,pmethod,params):
|
|
||||||
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
|
|
||||||
methodCall = ET.Element('methodCall')
|
|
||||||
methodName = ET.Element('methodName')
|
|
||||||
methodName.text = pmethod
|
|
||||||
methodCall.append(methodName)
|
|
||||||
methodCall.append(params)
|
|
||||||
query.append(methodCall)
|
|
||||||
return query
|
|
||||||
|
|
||||||
def makeIqMethodCall(self,pto,pmethod,params):
|
|
||||||
iq = self.xmpp.makeIqSet()
|
|
||||||
iq.set('to',pto)
|
|
||||||
iq.append(self.makeMethodCallQuery(pmethod,params))
|
|
||||||
return iq
|
|
||||||
|
|
||||||
def makeIqMethodResponse(self,pto,pid,params):
|
|
||||||
iq = self.xmpp.makeIqResult(pid)
|
|
||||||
iq.set('to',pto)
|
|
||||||
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
|
|
||||||
methodResponse = ET.Element('methodResponse')
|
|
||||||
methodResponse.append(params)
|
|
||||||
query.append(methodResponse)
|
|
||||||
return iq
|
|
||||||
|
|
||||||
def makeIqMethodError(self,pto,id,pmethod,params,condition):
|
|
||||||
iq = self.xmpp.makeIqError(id)
|
|
||||||
iq.set('to',pto)
|
|
||||||
iq.append(self.makeMethodCallQuery(pmethod,params))
|
|
||||||
iq.append(self.xmpp['xep_0086'].makeError(condition))
|
|
||||||
return iq
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def call_remote(self, pto, pmethod, *args):
|
|
||||||
#calls a remote method. Returns the id of the Iq.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _callMethod(self,xml):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _callResult(self,xml):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _callError(self,xml):
|
|
||||||
pass
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
"""
|
|
||||||
SleekXMPP: The Sleek XMPP Library
|
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
See the file LICENSE for copying permission.
|
|
||||||
"""
|
|
||||||
from __future__ import with_statement
|
|
||||||
from . import base
|
|
||||||
import logging
|
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
import time
|
|
||||||
|
|
||||||
class old_0050(base.base_plugin):
|
|
||||||
"""
|
|
||||||
XEP-0050 Ad-Hoc Commands
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = '0050'
|
|
||||||
self.description = 'Ad-Hoc Commands'
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None')
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute')
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True)
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel')
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete')
|
|
||||||
self.commands = {}
|
|
||||||
self.sessions = {}
|
|
||||||
self.sd = self.xmpp.plugin['xep_0030']
|
|
||||||
|
|
||||||
def post_init(self):
|
|
||||||
base.base_plugin.post_init(self)
|
|
||||||
self.sd.add_feature('http://jabber.org/protocol/commands')
|
|
||||||
|
|
||||||
def addCommand(self, node, name, form, pointer=None, multi=False):
|
|
||||||
self.sd.add_item(None, name, 'http://jabber.org/protocol/commands', node)
|
|
||||||
self.sd.add_identity('automation', 'command-node', name, node)
|
|
||||||
self.sd.add_feature('http://jabber.org/protocol/commands', node)
|
|
||||||
self.sd.add_feature('jabber:x:data', node)
|
|
||||||
self.commands[node] = (name, form, pointer, multi)
|
|
||||||
|
|
||||||
def getNewSession(self):
|
|
||||||
return str(time.time()) + '-' + self.xmpp.getNewId()
|
|
||||||
|
|
||||||
def handler_command(self, xml):
|
|
||||||
in_command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
sessionid = in_command.get('sessionid', None)
|
|
||||||
node = in_command.get('node')
|
|
||||||
sessionid = self.getNewSession()
|
|
||||||
name, form, pointer, multi = self.commands[node]
|
|
||||||
self.sessions[sessionid] = {}
|
|
||||||
self.sessions[sessionid]['jid'] = xml.get('from')
|
|
||||||
self.sessions[sessionid]['to'] = xml.get('to')
|
|
||||||
self.sessions[sessionid]['past'] = [(form, None)]
|
|
||||||
self.sessions[sessionid]['next'] = pointer
|
|
||||||
npointer = pointer
|
|
||||||
if multi:
|
|
||||||
actions = ['next']
|
|
||||||
status = 'executing'
|
|
||||||
else:
|
|
||||||
if pointer is None:
|
|
||||||
status = 'completed'
|
|
||||||
actions = []
|
|
||||||
else:
|
|
||||||
status = 'executing'
|
|
||||||
actions = ['complete']
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
|
|
||||||
|
|
||||||
def handler_command_complete(self, xml):
|
|
||||||
in_command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
sessionid = in_command.get('sessionid', None)
|
|
||||||
pointer = self.sessions[sessionid]['next']
|
|
||||||
results = self.xmpp.plugin['old_0004'].makeForm('result')
|
|
||||||
results.fromXML(in_command.find('{jabber:x:data}x'))
|
|
||||||
pointer(results,sessionid)
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[]))
|
|
||||||
del self.sessions[in_command.get('sessionid')]
|
|
||||||
|
|
||||||
|
|
||||||
def handler_command_next(self, xml):
|
|
||||||
in_command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
sessionid = in_command.get('sessionid', None)
|
|
||||||
pointer = self.sessions[sessionid]['next']
|
|
||||||
results = self.xmpp.plugin['old_0004'].makeForm('result')
|
|
||||||
results.fromXML(in_command.find('{jabber:x:data}x'))
|
|
||||||
form, npointer, next = pointer(results,sessionid)
|
|
||||||
self.sessions[sessionid]['next'] = npointer
|
|
||||||
self.sessions[sessionid]['past'].append((form, pointer))
|
|
||||||
actions = []
|
|
||||||
actions.append('prev')
|
|
||||||
if npointer is None:
|
|
||||||
status = 'completed'
|
|
||||||
else:
|
|
||||||
status = 'executing'
|
|
||||||
if next:
|
|
||||||
actions.append('next')
|
|
||||||
else:
|
|
||||||
actions.append('complete')
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
|
|
||||||
|
|
||||||
def handler_command_cancel(self, xml):
|
|
||||||
command = xml.find('{http://jabber.org/protocol/commands}command')
|
|
||||||
try:
|
|
||||||
del self.sessions[command.get('sessionid')]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled'))
|
|
||||||
|
|
||||||
def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]):
|
|
||||||
if not id:
|
|
||||||
id = self.xmpp.getNewId()
|
|
||||||
iq = self.xmpp.makeIqResult(id)
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
iq.attrib['to'] = to
|
|
||||||
command = ET.Element('{http://jabber.org/protocol/commands}command')
|
|
||||||
command.attrib['node'] = node
|
|
||||||
command.attrib['status'] = status
|
|
||||||
xmlactions = ET.Element('actions')
|
|
||||||
for action in actions:
|
|
||||||
xmlactions.append(ET.Element(action))
|
|
||||||
if xmlactions:
|
|
||||||
command.append(xmlactions)
|
|
||||||
if not sessionid:
|
|
||||||
sessionid = self.getNewSession()
|
|
||||||
else:
|
|
||||||
iq.attrib['from'] = self.sessions[sessionid]['to']
|
|
||||||
command.attrib['sessionid'] = sessionid
|
|
||||||
if form is not None:
|
|
||||||
if hasattr(form,'getXML'):
|
|
||||||
form = form.getXML()
|
|
||||||
command.append(form)
|
|
||||||
iq.append(command)
|
|
||||||
return iq
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
from __future__ import with_statement
|
|
||||||
from . import base
|
|
||||||
import logging
|
|
||||||
#from xml.etree import cElementTree as ET
|
|
||||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
|
|
||||||
from . import stanza_pubsub
|
|
||||||
from . xep_0004 import Form
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class xep_0060(base.base_plugin):
|
|
||||||
"""
|
|
||||||
XEP-0060 Publish Subscribe
|
|
||||||
"""
|
|
||||||
|
|
||||||
def plugin_init(self):
|
|
||||||
self.xep = '0060'
|
|
||||||
self.description = 'Publish-Subscribe'
|
|
||||||
|
|
||||||
def create_node(self, jid, node, config=None, collection=False, ntype=None):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
create = ET.Element('create')
|
|
||||||
create.set('node', node)
|
|
||||||
pubsub.append(create)
|
|
||||||
configure = ET.Element('configure')
|
|
||||||
if collection:
|
|
||||||
ntype = 'collection'
|
|
||||||
#if config is None:
|
|
||||||
# submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
|
|
||||||
#else:
|
|
||||||
if config is not None:
|
|
||||||
submitform = config
|
|
||||||
if 'FORM_TYPE' in submitform.field:
|
|
||||||
submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
|
|
||||||
else:
|
|
||||||
submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
|
|
||||||
if ntype:
|
|
||||||
if 'pubsub#node_type' in submitform.field:
|
|
||||||
submitform.field['pubsub#node_type'].setValue(ntype)
|
|
||||||
else:
|
|
||||||
submitform.addField('pubsub#node_type', value=ntype)
|
|
||||||
else:
|
|
||||||
if 'pubsub#node_type' in submitform.field:
|
|
||||||
submitform.field['pubsub#node_type'].setValue('leaf')
|
|
||||||
else:
|
|
||||||
submitform.addField('pubsub#node_type', value='leaf')
|
|
||||||
submitform['type'] = 'submit'
|
|
||||||
configure.append(submitform.xml)
|
|
||||||
pubsub.append(configure)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is False or result is None or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def subscribe(self, jid, node, bare=True, subscribee=None):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
subscribe = ET.Element('subscribe')
|
|
||||||
subscribe.attrib['node'] = node
|
|
||||||
if subscribee is None:
|
|
||||||
if bare:
|
|
||||||
subscribe.attrib['jid'] = self.xmpp.boundjid.bare
|
|
||||||
else:
|
|
||||||
subscribe.attrib['jid'] = self.xmpp.boundjid.full
|
|
||||||
else:
|
|
||||||
subscribe.attrib['jid'] = subscribee
|
|
||||||
pubsub.append(subscribe)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is False or result is None or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def unsubscribe(self, jid, node, bare=True, subscribee=None):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
unsubscribe = ET.Element('unsubscribe')
|
|
||||||
unsubscribe.attrib['node'] = node
|
|
||||||
if subscribee is None:
|
|
||||||
if bare:
|
|
||||||
unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
|
|
||||||
else:
|
|
||||||
unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
|
|
||||||
else:
|
|
||||||
unsubscribe.attrib['jid'] = subscribee
|
|
||||||
pubsub.append(unsubscribe)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is False or result is None or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def getNodeConfig(self, jid, node=None): # if no node, then grab default
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
if node is not None:
|
|
||||||
configure = ET.Element('configure')
|
|
||||||
configure.attrib['node'] = node
|
|
||||||
else:
|
|
||||||
configure = ET.Element('default')
|
|
||||||
pubsub.append(configure)
|
|
||||||
#TODO: Add configure support.
|
|
||||||
iq = self.xmpp.makeIqGet()
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result == False or result['type'] == 'error':
|
|
||||||
log.warning("got error instead of config")
|
|
||||||
return False
|
|
||||||
if node is not None:
|
|
||||||
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
|
|
||||||
else:
|
|
||||||
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
|
|
||||||
if not form or form is None:
|
|
||||||
log.error("No form found.")
|
|
||||||
return False
|
|
||||||
return Form(xml=form)
|
|
||||||
|
|
||||||
def getNodeSubscriptions(self, jid, node):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
subscriptions = ET.Element('subscriptions')
|
|
||||||
subscriptions.attrib['node'] = node
|
|
||||||
pubsub.append(subscriptions)
|
|
||||||
iq = self.xmpp.makeIqGet()
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result == False or result['type'] == 'error':
|
|
||||||
log.warning("got error instead of config")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
|
|
||||||
if results is None:
|
|
||||||
return False
|
|
||||||
subs = {}
|
|
||||||
for sub in results:
|
|
||||||
subs[sub.get('jid')] = sub.get('subscription')
|
|
||||||
return subs
|
|
||||||
|
|
||||||
def getNodeAffiliations(self, jid, node):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
affiliations = ET.Element('affiliations')
|
|
||||||
affiliations.attrib['node'] = node
|
|
||||||
pubsub.append(affiliations)
|
|
||||||
iq = self.xmpp.makeIqGet()
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result == False or result['type'] == 'error':
|
|
||||||
log.warning("got error instead of config")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
|
|
||||||
if results is None:
|
|
||||||
return False
|
|
||||||
subs = {}
|
|
||||||
for sub in results:
|
|
||||||
subs[sub.get('jid')] = sub.get('affiliation')
|
|
||||||
return subs
|
|
||||||
|
|
||||||
def deleteNode(self, jid, node):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
iq = self.xmpp.makeIqSet()
|
|
||||||
delete = ET.Element('delete')
|
|
||||||
delete.attrib['node'] = node
|
|
||||||
pubsub.append(delete)
|
|
||||||
iq.append(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
result = iq.send()
|
|
||||||
if result is not None and result is not False and result['type'] != 'error':
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def setNodeConfig(self, jid, node, config):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
configure = ET.Element('configure')
|
|
||||||
configure.attrib['node'] = node
|
|
||||||
config = config.getXML('submit')
|
|
||||||
configure.append(config)
|
|
||||||
pubsub.append(configure)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result['type'] == 'error':
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def setItem(self, jid, node, items=[]):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
publish = ET.Element('publish')
|
|
||||||
publish.attrib['node'] = node
|
|
||||||
for pub_item in items:
|
|
||||||
id, payload = pub_item
|
|
||||||
item = ET.Element('item')
|
|
||||||
if id is not None:
|
|
||||||
item.attrib['id'] = id
|
|
||||||
item.append(payload)
|
|
||||||
publish.append(item)
|
|
||||||
pubsub.append(publish)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result is False or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def addItem(self, jid, node, items=[]):
|
|
||||||
return self.setItem(jid, node, items)
|
|
||||||
|
|
||||||
def deleteItem(self, jid, node, item):
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
|
|
||||||
retract = ET.Element('retract')
|
|
||||||
retract.attrib['node'] = node
|
|
||||||
itemn = ET.Element('item')
|
|
||||||
itemn.attrib['id'] = item
|
|
||||||
retract.append(itemn)
|
|
||||||
pubsub.append(retract)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result is False or result['type'] == 'error': return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def getNodes(self, jid):
|
|
||||||
response = self.xmpp.plugin['xep_0030'].getItems(jid)
|
|
||||||
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
|
|
||||||
nodes = {}
|
|
||||||
if items is not None and items is not False:
|
|
||||||
for item in items:
|
|
||||||
nodes[item.get('node')] = item.get('name')
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
def getItems(self, jid, node):
|
|
||||||
response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
|
|
||||||
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
|
|
||||||
nodeitems = []
|
|
||||||
if items is not None and items is not False:
|
|
||||||
for item in items:
|
|
||||||
nodeitems.append(item.get('node'))
|
|
||||||
return nodeitems
|
|
||||||
|
|
||||||
def addNodeToCollection(self, jid, child, parent=''):
|
|
||||||
config = self.getNodeConfig(jid, child)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#collection'].setValue(parent)
|
|
||||||
except KeyError:
|
|
||||||
log.warning("pubsub#collection doesn't exist in config, trying to add it")
|
|
||||||
config.addField('pubsub#collection', value=parent)
|
|
||||||
if not self.setNodeConfig(jid, child, config):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
|
|
||||||
if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
|
|
||||||
raise TypeError
|
|
||||||
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
|
|
||||||
affs = ET.Element('affiliations')
|
|
||||||
affs.attrib['node'] = node
|
|
||||||
aff = ET.Element('affiliation')
|
|
||||||
aff.attrib['jid'] = user_jid
|
|
||||||
aff.attrib['affiliation'] = affiliation
|
|
||||||
affs.append(aff)
|
|
||||||
pubsub.append(affs)
|
|
||||||
iq = self.xmpp.makeIqSet(pubsub)
|
|
||||||
iq.attrib['to'] = ps_jid
|
|
||||||
iq.attrib['from'] = self.xmpp.boundjid.full
|
|
||||||
id = iq['id']
|
|
||||||
result = iq.send()
|
|
||||||
if result is None or result is False or result['type'] == 'error':
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def addNodeToCollection(self, jid, child, parent=''):
|
|
||||||
config = self.getNodeConfig(jid, child)
|
|
||||||
if not config or config is None:
|
|
||||||
self.lasterror = "Config Error"
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
config.field['pubsub#collection'].setValue(parent)
|
|
||||||
except KeyError:
|
|
||||||
log.warning("pubsub#collection doesn't exist in config, trying to add it")
|
|
||||||
config.addField('pubsub#collection', value=parent)
|
|
||||||
if not self.setNodeConfig(jid, child, config):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def removeNodeFromCollection(self, jid, child):
|
|
||||||
self.addNodeToCollection(jid, child, '')
|
|
||||||
|
|
||||||
@@ -41,10 +41,11 @@ class FormField(ElementBase):
|
|||||||
self._type = value
|
self._type = value
|
||||||
|
|
||||||
def add_option(self, label='', value=''):
|
def add_option(self, label='', value=''):
|
||||||
if self._type in self.option_types:
|
if self._type is None or self._type in self.option_types:
|
||||||
opt = FieldOption(parent=self)
|
opt = FieldOption()
|
||||||
opt['label'] = label
|
opt['label'] = label
|
||||||
opt['value'] = value
|
opt['value'] = value
|
||||||
|
self.append(opt)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Cannot add options to " + \
|
raise ValueError("Cannot add options to " + \
|
||||||
"a %s field." % self['type'])
|
"a %s field." % self['type'])
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Form(ElementBase):
|
|||||||
if kwtype is None:
|
if kwtype is None:
|
||||||
kwtype = ftype
|
kwtype = ftype
|
||||||
|
|
||||||
field = FormField(parent=self)
|
field = FormField()
|
||||||
field['var'] = var
|
field['var'] = var
|
||||||
field['type'] = kwtype
|
field['type'] = kwtype
|
||||||
field['value'] = value
|
field['value'] = value
|
||||||
@@ -77,6 +77,7 @@ class Form(ElementBase):
|
|||||||
field['options'] = options
|
field['options'] = options
|
||||||
else:
|
else:
|
||||||
del field['type']
|
del field['type']
|
||||||
|
self.append(field)
|
||||||
return field
|
return field
|
||||||
|
|
||||||
def getXML(self, type='submit'):
|
def getXML(self, type='submit'):
|
||||||
@@ -144,10 +145,9 @@ class Form(ElementBase):
|
|||||||
|
|
||||||
def get_fields(self, use_dict=False):
|
def get_fields(self, use_dict=False):
|
||||||
fields = OrderedDict()
|
fields = OrderedDict()
|
||||||
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
|
for stanza in self['substanzas']:
|
||||||
for fieldXML in fieldsXML:
|
if isinstance(stanza, FormField):
|
||||||
field = FormField(xml=fieldXML)
|
fields[stanza['var']] = stanza
|
||||||
fields[field['var']] = field
|
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
def get_instructions(self):
|
def get_instructions(self):
|
||||||
@@ -221,6 +221,8 @@ class Form(ElementBase):
|
|||||||
def set_values(self, values):
|
def set_values(self, values):
|
||||||
fields = self['fields']
|
fields = self['fields']
|
||||||
for field in values:
|
for field in values:
|
||||||
|
if field not in fields:
|
||||||
|
fields[field] = self.add_field(var=field)
|
||||||
fields[field]['value'] = values[field]
|
fields[field]['value'] = values[field]
|
||||||
|
|
||||||
def merge(self, other):
|
def merge(self, other):
|
||||||
|
|||||||
@@ -32,15 +32,15 @@ class XEP_0009(BasePlugin):
|
|||||||
register_stanza_plugin(RPCQuery, MethodCall)
|
register_stanza_plugin(RPCQuery, MethodCall)
|
||||||
register_stanza_plugin(RPCQuery, MethodResponse)
|
register_stanza_plugin(RPCQuery, MethodResponse)
|
||||||
|
|
||||||
self.xmpp.registerHandler(
|
self.xmpp.register_handler(
|
||||||
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
|
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
|
||||||
self._handle_method_call)
|
self._handle_method_call)
|
||||||
)
|
)
|
||||||
self.xmpp.registerHandler(
|
self.xmpp.register_handler(
|
||||||
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
|
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
|
||||||
self._handle_method_response)
|
self._handle_method_response)
|
||||||
)
|
)
|
||||||
self.xmpp.registerHandler(
|
self.xmpp.register_handler(
|
||||||
Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
|
Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
|
||||||
self._handle_error)
|
self._handle_error)
|
||||||
)
|
)
|
||||||
|
|||||||
16
sleekxmpp/plugins/xep_0020/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0020/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0020 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0020.stanza import FeatureNegotiation
|
||||||
|
from sleekxmpp.plugins.xep_0020.feature_negotiation import XEP_0020
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0020)
|
||||||
36
sleekxmpp/plugins/xep_0020/feature_negotiation.py
Normal file
36
sleekxmpp/plugins/xep_0020/feature_negotiation.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Iq, Message
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin, JID
|
||||||
|
from sleekxmpp.plugins.xep_0020 import stanza, FeatureNegotiation
|
||||||
|
from sleekxmpp.plugins.xep_0004 import Form
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0020(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0020'
|
||||||
|
description = 'XEP-0020: Feature Negotiation'
|
||||||
|
dependencies = set(['xep_0004', 'xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace)
|
||||||
|
|
||||||
|
register_stanza_plugin(FeatureNegotiation, Form)
|
||||||
|
|
||||||
|
register_stanza_plugin(Iq, FeatureNegotiation)
|
||||||
|
register_stanza_plugin(Message, FeatureNegotiation)
|
||||||
17
sleekxmpp/plugins/xep_0020/stanza.py
Normal file
17
sleekxmpp/plugins/xep_0020/stanza.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureNegotiation(ElementBase):
|
||||||
|
|
||||||
|
name = 'feature'
|
||||||
|
namespace = 'http://jabber.org/protocol/feature-neg'
|
||||||
|
plugin_attrib = 'feature_neg'
|
||||||
|
interfaces = set()
|
||||||
@@ -24,7 +24,7 @@ def _extract_data(data, kind):
|
|||||||
if not begin_headers and 'BEGIN PGP %s' % kind in line:
|
if not begin_headers and 'BEGIN PGP %s' % kind in line:
|
||||||
begin_headers = True
|
begin_headers = True
|
||||||
continue
|
continue
|
||||||
if begin_headers and line.stripped() == '':
|
if begin_headers and line.strip() == '':
|
||||||
begin_data = True
|
begin_data = True
|
||||||
continue
|
continue
|
||||||
if 'END PGP %s' % kind in line:
|
if 'END PGP %s' % kind in line:
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class Encrypted(ElementBase):
|
|||||||
def set_encrypted(self, value):
|
def set_encrypted(self, value):
|
||||||
parent = self.parent()
|
parent = self.parent()
|
||||||
xmpp = parent.stream
|
xmpp = parent.stream
|
||||||
data = xmpp['xep_0027'].encrypt(value, parent['to'].bare)
|
data = xmpp['xep_0027'].encrypt(value, parent['to'])
|
||||||
if data:
|
if data:
|
||||||
self.xml.text = data
|
self.xml.text = data
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -128,9 +128,10 @@ class DiscoItems(ElementBase):
|
|||||||
def del_items(self):
|
def del_items(self):
|
||||||
"""Remove all items."""
|
"""Remove all items."""
|
||||||
self._items = set()
|
self._items = set()
|
||||||
for item in self['substanzas']:
|
items = [i for i in self.iterables if isinstance(i, DiscoItem)]
|
||||||
if isinstance(item, DiscoItem):
|
for item in items:
|
||||||
self.xml.remove(item.xml)
|
self.xml.remove(item.xml)
|
||||||
|
self.iterables.remove(item)
|
||||||
|
|
||||||
|
|
||||||
class DiscoItem(ElementBase):
|
class DiscoItem(ElementBase):
|
||||||
|
|||||||
@@ -125,11 +125,12 @@ class XEP_0045(BasePlugin):
|
|||||||
self.xep = '0045'
|
self.xep = '0045'
|
||||||
# load MUC support in presence stanzas
|
# load MUC support in presence stanzas
|
||||||
register_stanza_plugin(Presence, MUCPresence)
|
register_stanza_plugin(Presence, MUCPresence)
|
||||||
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
|
self.xmpp.register_handler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
|
||||||
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
|
self.xmpp.register_handler(Callback('MUCError', MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns), self.handle_groupchat_error_message))
|
||||||
self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
|
self.xmpp.register_handler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
|
||||||
self.xmpp.registerHandler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change))
|
self.xmpp.register_handler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
|
||||||
self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % (
|
self.xmpp.register_handler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change))
|
||||||
|
self.xmpp.register_handler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % (
|
||||||
self.xmpp.default_ns,
|
self.xmpp.default_ns,
|
||||||
'http://jabber.org/protocol/muc#user',
|
'http://jabber.org/protocol/muc#user',
|
||||||
'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite))
|
'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite))
|
||||||
@@ -179,6 +180,14 @@ class XEP_0045(BasePlugin):
|
|||||||
self.xmpp.event('groupchat_message', msg)
|
self.xmpp.event('groupchat_message', msg)
|
||||||
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
|
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
|
||||||
|
|
||||||
|
def handle_groupchat_error_message(self, msg):
|
||||||
|
""" Handle a message error event in a muc.
|
||||||
|
"""
|
||||||
|
self.xmpp.event('groupchat_message_error', msg)
|
||||||
|
self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def handle_groupchat_subject(self, msg):
|
def handle_groupchat_subject(self, msg):
|
||||||
""" Handle a message coming from a muc indicating
|
""" Handle a message coming from a muc indicating
|
||||||
a change of subject (or announcing it when joining the room)
|
a change of subject (or announcing it when joining the room)
|
||||||
@@ -198,30 +207,9 @@ class XEP_0045(BasePlugin):
|
|||||||
if entry is not None and entry['jid'].full == jid:
|
if entry is not None and entry['jid'].full == jid:
|
||||||
return nick
|
return nick
|
||||||
|
|
||||||
def getRoomForm(self, room, ifrom=None):
|
|
||||||
iq = self.xmpp.makeIqGet()
|
|
||||||
iq['to'] = room
|
|
||||||
if ifrom is not None:
|
|
||||||
iq['from'] = ifrom
|
|
||||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
|
||||||
iq.append(query)
|
|
||||||
# For now, swallow errors to preserve existing API
|
|
||||||
try:
|
|
||||||
result = iq.send()
|
|
||||||
except IqError:
|
|
||||||
return False
|
|
||||||
except IqTimeout:
|
|
||||||
return False
|
|
||||||
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
|
|
||||||
if xform is None: return False
|
|
||||||
form = self.xmpp.plugin['old_0004'].buildForm(xform)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def configureRoom(self, room, form=None, ifrom=None):
|
def configureRoom(self, room, form=None, ifrom=None):
|
||||||
if form is None:
|
if form is None:
|
||||||
form = self.getRoomForm(room, ifrom=ifrom)
|
form = self.getRoomConfig(room, ifrom=ifrom)
|
||||||
#form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
|
|
||||||
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
|
|
||||||
iq = self.xmpp.makeIqSet()
|
iq = self.xmpp.makeIqSet()
|
||||||
iq['to'] = room
|
iq['to'] = room
|
||||||
if ifrom is not None:
|
if ifrom is not None:
|
||||||
@@ -310,6 +298,24 @@ class XEP_0045(BasePlugin):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setRole(self, room, nick, role):
|
||||||
|
""" Change role property of a nick in a room.
|
||||||
|
Typically, roles are temporary (they last only as long as you are in the
|
||||||
|
room), whereas affiliations are permanent (they last across groupchat
|
||||||
|
sessions).
|
||||||
|
"""
|
||||||
|
if role not in ('moderator', 'participant', 'visitor', 'none'):
|
||||||
|
raise TypeError
|
||||||
|
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
||||||
|
item = ET.Element('item', {'role':role, 'nick':nick})
|
||||||
|
query.append(item)
|
||||||
|
iq = self.xmpp.makeIqSet(query)
|
||||||
|
iq['to'] = room
|
||||||
|
result = iq.send()
|
||||||
|
if result is False or result['type'] != 'result':
|
||||||
|
raise ValueError
|
||||||
|
return True
|
||||||
|
|
||||||
def invite(self, room, jid, reason='', mfrom=''):
|
def invite(self, room, jid, reason='', mfrom=''):
|
||||||
""" Invite a jid to a room."""
|
""" Invite a jid to a room."""
|
||||||
msg = self.xmpp.makeMessage(room)
|
msg = self.xmpp.makeMessage(room)
|
||||||
|
|||||||
@@ -21,21 +21,25 @@ class XEP_0047(BasePlugin):
|
|||||||
dependencies = set(['xep_0030'])
|
dependencies = set(['xep_0030'])
|
||||||
stanza = stanza
|
stanza = stanza
|
||||||
default_config = {
|
default_config = {
|
||||||
|
'block_size': 4096,
|
||||||
'max_block_size': 8192,
|
'max_block_size': 8192,
|
||||||
'window_size': 1,
|
'window_size': 1,
|
||||||
'auto_accept': True,
|
'auto_accept': False,
|
||||||
'accept_stream': None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.streams = {}
|
self._streams = {}
|
||||||
self.pending_streams = {}
|
self._pending_streams = {}
|
||||||
self.pending_close_streams = {}
|
self._pending_lock = threading.Lock()
|
||||||
self._stream_lock = threading.Lock()
|
self._stream_lock = threading.Lock()
|
||||||
|
|
||||||
|
self._preauthed_sids_lock = threading.Lock()
|
||||||
|
self._preauthed_sids = {}
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Open)
|
register_stanza_plugin(Iq, Open)
|
||||||
register_stanza_plugin(Iq, Close)
|
register_stanza_plugin(Iq, Close)
|
||||||
register_stanza_plugin(Iq, Data)
|
register_stanza_plugin(Iq, Data)
|
||||||
|
register_stanza_plugin(Message, Data)
|
||||||
|
|
||||||
self.xmpp.register_handler(Callback(
|
self.xmpp.register_handler(Callback(
|
||||||
'IBB Open',
|
'IBB Open',
|
||||||
@@ -52,27 +56,71 @@ class XEP_0047(BasePlugin):
|
|||||||
StanzaPath('iq@type=set/ibb_data'),
|
StanzaPath('iq@type=set/ibb_data'),
|
||||||
self._handle_data))
|
self._handle_data))
|
||||||
|
|
||||||
|
self.xmpp.register_handler(Callback(
|
||||||
|
'IBB Message Data',
|
||||||
|
StanzaPath('message/ibb_data'),
|
||||||
|
self._handle_data))
|
||||||
|
|
||||||
|
self.api.register(self._authorized, 'authorized', default=True)
|
||||||
|
self.api.register(self._authorized_sid, 'authorized_sid', default=True)
|
||||||
|
self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
|
||||||
|
self.api.register(self._get_stream, 'get_stream', default=True)
|
||||||
|
self.api.register(self._set_stream, 'set_stream', default=True)
|
||||||
|
self.api.register(self._del_stream, 'del_stream', default=True)
|
||||||
|
|
||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
self.xmpp.remove_handler('IBB Open')
|
self.xmpp.remove_handler('IBB Open')
|
||||||
self.xmpp.remove_handler('IBB Close')
|
self.xmpp.remove_handler('IBB Close')
|
||||||
self.xmpp.remove_handler('IBB Data')
|
self.xmpp.remove_handler('IBB Data')
|
||||||
|
self.xmpp.remove_handler('IBB Message Data')
|
||||||
self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb')
|
self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb')
|
||||||
|
|
||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
|
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
|
||||||
|
|
||||||
|
def _get_stream(self, jid, sid, peer_jid, data):
|
||||||
|
return self._streams.get((jid, sid, peer_jid), None)
|
||||||
|
|
||||||
|
def _set_stream(self, jid, sid, peer_jid, stream):
|
||||||
|
self._streams[(jid, sid, peer_jid)] = stream
|
||||||
|
|
||||||
|
def _del_stream(self, jid, sid, peer_jid, data):
|
||||||
|
with self._stream_lock:
|
||||||
|
if (jid, sid, peer_jid) in self._streams:
|
||||||
|
del self._streams[(jid, sid, peer_jid)]
|
||||||
|
|
||||||
def _accept_stream(self, iq):
|
def _accept_stream(self, iq):
|
||||||
if self.accept_stream is not None:
|
receiver = iq['to']
|
||||||
return self.accept_stream(iq)
|
sender = iq['from']
|
||||||
|
sid = iq['ibb_open']['sid']
|
||||||
|
|
||||||
|
if self.api['authorized_sid'](receiver, sid, sender, iq):
|
||||||
|
return True
|
||||||
|
return self.api['authorized'](receiver, sid, sender, iq)
|
||||||
|
|
||||||
|
def _authorized(self, jid, sid, ifrom, iq):
|
||||||
if self.auto_accept:
|
if self.auto_accept:
|
||||||
if iq['ibb_open']['block_size'] <= self.max_block_size:
|
if iq['ibb_open']['block_size'] <= self.max_block_size:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def open_stream(self, jid, block_size=4096, sid=None, window=1,
|
def _authorized_sid(self, jid, sid, ifrom, iq):
|
||||||
|
with self._preauthed_sids_lock:
|
||||||
|
if (jid, sid, ifrom) in self._preauthed_sids:
|
||||||
|
del self._preauthed_sids[(jid, sid, ifrom)]
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _preauthorize_sid(self, jid, sid, ifrom, data):
|
||||||
|
with self._preauthed_sids_lock:
|
||||||
|
self._preauthed_sids[(jid, sid, ifrom)] = True
|
||||||
|
|
||||||
|
def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False,
|
||||||
ifrom=None, block=True, timeout=None, callback=None):
|
ifrom=None, block=True, timeout=None, callback=None):
|
||||||
if sid is None:
|
if sid is None:
|
||||||
sid = str(uuid.uuid4())
|
sid = str(uuid.uuid4())
|
||||||
|
if block_size is None:
|
||||||
|
block_size = self.block_size
|
||||||
|
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
@@ -83,12 +131,13 @@ class XEP_0047(BasePlugin):
|
|||||||
iq['ibb_open']['stanza'] = 'iq'
|
iq['ibb_open']['stanza'] = 'iq'
|
||||||
|
|
||||||
stream = IBBytestream(self.xmpp, sid, block_size,
|
stream = IBBytestream(self.xmpp, sid, block_size,
|
||||||
iq['to'], iq['from'], window)
|
iq['from'], iq['to'], window,
|
||||||
|
use_messages)
|
||||||
|
|
||||||
with self._stream_lock:
|
with self._stream_lock:
|
||||||
self.pending_streams[iq['id']] = stream
|
self._pending_streams[iq['id']] = stream
|
||||||
|
|
||||||
self.pending_streams[iq['id']] = stream
|
self._pending_streams[iq['id']] = stream
|
||||||
|
|
||||||
if block:
|
if block:
|
||||||
resp = iq.send(timeout=timeout)
|
resp = iq.send(timeout=timeout)
|
||||||
@@ -108,49 +157,59 @@ class XEP_0047(BasePlugin):
|
|||||||
def _handle_opened_stream(self, iq):
|
def _handle_opened_stream(self, iq):
|
||||||
if iq['type'] == 'result':
|
if iq['type'] == 'result':
|
||||||
with self._stream_lock:
|
with self._stream_lock:
|
||||||
stream = self.pending_streams.get(iq['id'], None)
|
stream = self._pending_streams.get(iq['id'], None)
|
||||||
if stream is not None:
|
if stream is not None:
|
||||||
stream.sender = iq['to']
|
log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from'])
|
||||||
stream.receiver = iq['from']
|
stream.self_jid = iq['to']
|
||||||
stream.stream_started.set()
|
stream.peer_jid = iq['from']
|
||||||
self.streams[stream.sid] = stream
|
stream.stream_started.set()
|
||||||
self.xmpp.event('ibb_stream_start', stream)
|
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
|
||||||
|
self.xmpp.event('ibb_stream_start', stream)
|
||||||
|
self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream)
|
||||||
|
|
||||||
with self._stream_lock:
|
with self._stream_lock:
|
||||||
if iq['id'] in self.pending_streams:
|
if iq['id'] in self._pending_streams:
|
||||||
del self.pending_streams[iq['id']]
|
del self._pending_streams[iq['id']]
|
||||||
|
|
||||||
def _handle_open_request(self, iq):
|
def _handle_open_request(self, iq):
|
||||||
sid = iq['ibb_open']['sid']
|
sid = iq['ibb_open']['sid']
|
||||||
size = iq['ibb_open']['block_size']
|
size = iq['ibb_open']['block_size'] or self.block_size
|
||||||
|
|
||||||
|
log.debug('Received IBB stream request from %s', iq['from'])
|
||||||
|
|
||||||
|
if not sid:
|
||||||
|
raise XMPPError(etype='modify', condition='bad-request')
|
||||||
|
|
||||||
if not self._accept_stream(iq):
|
if not self._accept_stream(iq):
|
||||||
raise XMPPError('not-acceptable')
|
raise XMPPError(etype='modify', condition='not-acceptable')
|
||||||
|
|
||||||
if size > self.max_block_size:
|
if size > self.max_block_size:
|
||||||
raise XMPPError('resource-constraint')
|
raise XMPPError('resource-constraint')
|
||||||
|
|
||||||
stream = IBBytestream(self.xmpp, sid, size,
|
stream = IBBytestream(self.xmpp, sid, size,
|
||||||
iq['from'], iq['to'],
|
iq['to'], iq['from'],
|
||||||
self.window_size)
|
self.window_size)
|
||||||
stream.stream_started.set()
|
stream.stream_started.set()
|
||||||
self.streams[sid] = stream
|
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
|
||||||
iq.reply()
|
iq.reply()
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
self.xmpp.event('ibb_stream_start', stream)
|
self.xmpp.event('ibb_stream_start', stream)
|
||||||
|
self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream)
|
||||||
|
|
||||||
def _handle_data(self, iq):
|
def _handle_data(self, stanza):
|
||||||
sid = iq['ibb_data']['sid']
|
sid = stanza['ibb_data']['sid']
|
||||||
stream = self.streams.get(sid, None)
|
stream = self.api['get_stream'](stanza['to'], sid, stanza['from'])
|
||||||
if stream is not None and iq['from'] != stream.sender:
|
if stream is not None and stanza['from'] == stream.peer_jid:
|
||||||
stream._recv_data(iq)
|
stream._recv_data(stanza)
|
||||||
else:
|
else:
|
||||||
raise XMPPError('item-not-found')
|
raise XMPPError('item-not-found')
|
||||||
|
|
||||||
def _handle_close(self, iq):
|
def _handle_close(self, iq):
|
||||||
sid = iq['ibb_close']['sid']
|
sid = iq['ibb_close']['sid']
|
||||||
stream = self.streams.get(sid, None)
|
stream = self.api['get_stream'](iq['to'], sid, iq['from'])
|
||||||
if stream is not None and iq['from'] != stream.sender:
|
if stream is not None and iq['from'] == stream.peer_jid:
|
||||||
stream._closed(iq)
|
stream._closed(iq)
|
||||||
|
self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid)
|
||||||
else:
|
else:
|
||||||
raise XMPPError('item-not-found')
|
raise XMPPError('item-not-found')
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import socket
|
|||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Iq
|
||||||
from sleekxmpp.util import Queue
|
from sleekxmpp.util import Queue
|
||||||
from sleekxmpp.exceptions import XMPPError
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
|
||||||
@@ -11,14 +12,17 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class IBBytestream(object):
|
class IBBytestream(object):
|
||||||
|
|
||||||
def __init__(self, xmpp, sid, block_size, to, ifrom, window_size=1):
|
def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False):
|
||||||
self.xmpp = xmpp
|
self.xmpp = xmpp
|
||||||
self.sid = sid
|
self.sid = sid
|
||||||
self.block_size = block_size
|
self.block_size = block_size
|
||||||
self.window_size = window_size
|
self.window_size = window_size
|
||||||
|
self.use_messages = use_messages
|
||||||
|
|
||||||
self.receiver = to
|
if jid is None:
|
||||||
self.sender = ifrom
|
jid = xmpp.boundjid
|
||||||
|
self.self_jid = jid
|
||||||
|
self.peer_jid = peer
|
||||||
|
|
||||||
self.send_seq = -1
|
self.send_seq = -1
|
||||||
self.recv_seq = -1
|
self.recv_seq = -1
|
||||||
@@ -46,16 +50,27 @@ class IBBytestream(object):
|
|||||||
with self._send_seq_lock:
|
with self._send_seq_lock:
|
||||||
self.send_seq = (self.send_seq + 1) % 65535
|
self.send_seq = (self.send_seq + 1) % 65535
|
||||||
seq = self.send_seq
|
seq = self.send_seq
|
||||||
iq = self.xmpp.Iq()
|
if self.use_messages:
|
||||||
iq['type'] = 'set'
|
msg = self.xmpp.Message()
|
||||||
iq['to'] = self.receiver
|
msg['to'] = self.peer_jid
|
||||||
iq['from'] = self.sender
|
msg['from'] = self.self_jid
|
||||||
iq['ibb_data']['sid'] = self.sid
|
msg['id'] = self.xmpp.new_id()
|
||||||
iq['ibb_data']['seq'] = seq
|
msg['ibb_data']['sid'] = self.sid
|
||||||
iq['ibb_data']['data'] = data
|
msg['ibb_data']['seq'] = seq
|
||||||
self.window_empty.clear()
|
msg['ibb_data']['data'] = data
|
||||||
self.window_ids.add(iq['id'])
|
msg.send()
|
||||||
iq.send(block=False, callback=self._recv_ack)
|
self.send_window.release()
|
||||||
|
else:
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['to'] = self.peer_jid
|
||||||
|
iq['from'] = self.self_jid
|
||||||
|
iq['ibb_data']['sid'] = self.sid
|
||||||
|
iq['ibb_data']['seq'] = seq
|
||||||
|
iq['ibb_data']['data'] = data
|
||||||
|
self.window_empty.clear()
|
||||||
|
self.window_ids.add(iq['id'])
|
||||||
|
iq.send(block=False, callback=self._recv_ack)
|
||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
def sendall(self, data):
|
def sendall(self, data):
|
||||||
@@ -71,23 +86,25 @@ class IBBytestream(object):
|
|||||||
if iq['type'] == 'error':
|
if iq['type'] == 'error':
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def _recv_data(self, iq):
|
def _recv_data(self, stanza):
|
||||||
with self._recv_seq_lock:
|
with self._recv_seq_lock:
|
||||||
new_seq = iq['ibb_data']['seq']
|
new_seq = stanza['ibb_data']['seq']
|
||||||
if new_seq != (self.recv_seq + 1) % 65535:
|
if new_seq != (self.recv_seq + 1) % 65535:
|
||||||
self.close()
|
self.close()
|
||||||
raise XMPPError('unexpected-request')
|
raise XMPPError('unexpected-request')
|
||||||
self.recv_seq = new_seq
|
self.recv_seq = new_seq
|
||||||
|
|
||||||
data = iq['ibb_data']['data']
|
data = stanza['ibb_data']['data']
|
||||||
if len(data) > self.block_size:
|
if len(data) > self.block_size:
|
||||||
self.close()
|
self.close()
|
||||||
raise XMPPError('not-acceptable')
|
raise XMPPError('not-acceptable')
|
||||||
|
|
||||||
self.recv_queue.put(data)
|
self.recv_queue.put(data)
|
||||||
self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data})
|
self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data})
|
||||||
iq.reply()
|
|
||||||
iq.send()
|
if isinstance(stanza, Iq):
|
||||||
|
stanza.reply()
|
||||||
|
stanza.send()
|
||||||
|
|
||||||
def recv(self, *args, **kwargs):
|
def recv(self, *args, **kwargs):
|
||||||
return self.read(block=True)
|
return self.read(block=True)
|
||||||
@@ -106,8 +123,8 @@ class IBBytestream(object):
|
|||||||
def close(self):
|
def close(self):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['to'] = self.receiver
|
iq['to'] = self.peer_jid
|
||||||
iq['from'] = self.sender
|
iq['from'] = self.self_jid
|
||||||
iq['ibb_close']['sid'] = self.sid
|
iq['ibb_close']['sid'] = self.sid
|
||||||
self.stream_out_closed.set()
|
self.stream_out_closed.set()
|
||||||
iq.send(block=False,
|
iq.send(block=False,
|
||||||
@@ -117,9 +134,6 @@ class IBBytestream(object):
|
|||||||
def _closed(self, iq):
|
def _closed(self, iq):
|
||||||
self.stream_in_closed.set()
|
self.stream_in_closed.set()
|
||||||
self.stream_out_closed.set()
|
self.stream_out_closed.set()
|
||||||
while not self.window_empty.is_set():
|
|
||||||
log.info('waiting for send window to empty')
|
|
||||||
self.window_empty.wait(timeout=1)
|
|
||||||
iq.reply()
|
iq.reply()
|
||||||
iq.send()
|
iq.send()
|
||||||
self.xmpp.event('ibb_stream_end', self)
|
self.xmpp.event('ibb_stream_end', self)
|
||||||
|
|||||||
15
sleekxmpp/plugins/xep_0048/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0048/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0048.stanza import Bookmarks, Conference, URL
|
||||||
|
from sleekxmpp.plugins.xep_0048.bookmarks import XEP_0048
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0048)
|
||||||
76
sleekxmpp/plugins/xep_0048/bookmarks.py
Normal file
76
sleekxmpp/plugins/xep_0048/bookmarks.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Iq
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0048 import stanza, Bookmarks, Conference, URL
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0048(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0048'
|
||||||
|
description = 'XEP-0048: Bookmarks'
|
||||||
|
dependencies = set(['xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223'])
|
||||||
|
stanza = stanza
|
||||||
|
default_config = {
|
||||||
|
'auto_join': False,
|
||||||
|
'storage_method': 'xep_0049'
|
||||||
|
}
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, Bookmarks)
|
||||||
|
|
||||||
|
self.xmpp['xep_0049'].register(Bookmarks)
|
||||||
|
self.xmpp['xep_0163'].register_pep('bookmarks', Bookmarks)
|
||||||
|
|
||||||
|
self.xmpp.add_event_handler('session_start', self._autojoin)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.del_event_handler('session_start', self._autojoin)
|
||||||
|
|
||||||
|
def _autojoin(self, __):
|
||||||
|
if not self.auto_join:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.get_bookmarks(method=self.storage_method)
|
||||||
|
except XMPPError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.storage_method == 'xep_0223':
|
||||||
|
bookmarks = result['pubsub']['items']['item']['bookmarks']
|
||||||
|
else:
|
||||||
|
bookmarks = result['private']['bookmarks']
|
||||||
|
|
||||||
|
for conf in bookmarks['conferences']:
|
||||||
|
if conf['autojoin']:
|
||||||
|
log.debug('Auto joining %s as %s', conf['jid'], conf['nick'])
|
||||||
|
self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'],
|
||||||
|
password=conf['password'])
|
||||||
|
|
||||||
|
def set_bookmarks(self, bookmarks, method=None, **iqargs):
|
||||||
|
if not method:
|
||||||
|
method = self.storage_method
|
||||||
|
return self.xmpp[method].store(bookmarks, **iqargs)
|
||||||
|
|
||||||
|
def get_bookmarks(self, method=None, **iqargs):
|
||||||
|
if not method:
|
||||||
|
method = self.storage_method
|
||||||
|
|
||||||
|
loc = 'storage:bookmarks' if method == 'xep_0223' else 'bookmarks'
|
||||||
|
|
||||||
|
return self.xmpp[method].retrieve(loc, **iqargs)
|
||||||
65
sleekxmpp/plugins/xep_0048/stanza.py
Normal file
65
sleekxmpp/plugins/xep_0048/stanza.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class Bookmarks(ElementBase):
|
||||||
|
name = 'storage'
|
||||||
|
namespace = 'storage:bookmarks'
|
||||||
|
plugin_attrib = 'bookmarks'
|
||||||
|
interfaces = set()
|
||||||
|
|
||||||
|
def add_conference(self, jid, nick, name=None, autojoin=None, password=None):
|
||||||
|
conf = Conference()
|
||||||
|
conf['jid'] = jid
|
||||||
|
conf['nick'] = nick
|
||||||
|
if name is None:
|
||||||
|
name = jid
|
||||||
|
conf['name'] = name
|
||||||
|
conf['autojoin'] = autojoin
|
||||||
|
conf['password'] = password
|
||||||
|
self.append(conf)
|
||||||
|
|
||||||
|
def add_url(self, url, name=None):
|
||||||
|
saved_url = URL()
|
||||||
|
saved_url['url'] = url
|
||||||
|
if name is None:
|
||||||
|
name = url
|
||||||
|
saved_url['name'] = name
|
||||||
|
self.append(saved_url)
|
||||||
|
|
||||||
|
|
||||||
|
class Conference(ElementBase):
|
||||||
|
name = 'conference'
|
||||||
|
namespace = 'storage:bookmarks'
|
||||||
|
plugin_attrib = 'conference'
|
||||||
|
plugin_multi_attrib = 'conferences'
|
||||||
|
interfaces = set(['nick', 'password', 'autojoin', 'jid', 'name'])
|
||||||
|
sub_interfaces = set(['nick', 'password'])
|
||||||
|
|
||||||
|
def get_autojoin(self):
|
||||||
|
value = self._get_attr('autojoin')
|
||||||
|
return value in ('1', 'true')
|
||||||
|
|
||||||
|
def set_autojoin(self, value):
|
||||||
|
del self['autojoin']
|
||||||
|
if value in ('1', 'true', True):
|
||||||
|
self._set_attr('autojoin', 'true')
|
||||||
|
|
||||||
|
|
||||||
|
class URL(ElementBase):
|
||||||
|
name = 'url'
|
||||||
|
namespace = 'storage:bookmarks'
|
||||||
|
plugin_attrib = 'url'
|
||||||
|
plugin_multi_attrib = 'urls'
|
||||||
|
interfaces = set(['url', 'name'])
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(Bookmarks, Conference, iterable=True)
|
||||||
|
register_stanza_plugin(Bookmarks, URL, iterable=True)
|
||||||
@@ -267,20 +267,50 @@ class XEP_0050(BasePlugin):
|
|||||||
iq -- The command continuation request.
|
iq -- The command continuation request.
|
||||||
"""
|
"""
|
||||||
sessionid = iq['command']['sessionid']
|
sessionid = iq['command']['sessionid']
|
||||||
session = self.sessions[sessionid]
|
session = self.sessions.get(sessionid)
|
||||||
|
|
||||||
handler = session['next']
|
if session:
|
||||||
interfaces = session['interfaces']
|
handler = session['next']
|
||||||
results = []
|
interfaces = session['interfaces']
|
||||||
for stanza in iq['command']['substanzas']:
|
results = []
|
||||||
if stanza.plugin_attrib in interfaces:
|
for stanza in iq['command']['substanzas']:
|
||||||
results.append(stanza)
|
if stanza.plugin_attrib in interfaces:
|
||||||
if len(results) == 1:
|
results.append(stanza)
|
||||||
results = results[0]
|
if len(results) == 1:
|
||||||
|
results = results[0]
|
||||||
|
|
||||||
session = handler(results, session)
|
session = handler(results, session)
|
||||||
|
|
||||||
self._process_command_response(iq, session)
|
self._process_command_response(iq, session)
|
||||||
|
else:
|
||||||
|
raise XMPPError('item-not-found')
|
||||||
|
|
||||||
|
def _handle_command_prev(self, iq):
|
||||||
|
"""
|
||||||
|
Process a request for the prev step in the workflow
|
||||||
|
for a command with multiple steps.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
iq -- The command continuation request.
|
||||||
|
"""
|
||||||
|
sessionid = iq['command']['sessionid']
|
||||||
|
session = self.sessions.get(sessionid)
|
||||||
|
|
||||||
|
if session:
|
||||||
|
handler = session['prev']
|
||||||
|
interfaces = session['interfaces']
|
||||||
|
results = []
|
||||||
|
for stanza in iq['command']['substanzas']:
|
||||||
|
if stanza.plugin_attrib in interfaces:
|
||||||
|
results.append(stanza)
|
||||||
|
if len(results) == 1:
|
||||||
|
results = results[0]
|
||||||
|
|
||||||
|
session = handler(results, session)
|
||||||
|
|
||||||
|
self._process_command_response(iq, session)
|
||||||
|
else:
|
||||||
|
raise XMPPError('item-not-found')
|
||||||
|
|
||||||
def _process_command_response(self, iq, session):
|
def _process_command_response(self, iq, session):
|
||||||
"""
|
"""
|
||||||
@@ -348,23 +378,23 @@ class XEP_0050(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
node = iq['command']['node']
|
node = iq['command']['node']
|
||||||
sessionid = iq['command']['sessionid']
|
sessionid = iq['command']['sessionid']
|
||||||
session = self.sessions[sessionid]
|
|
||||||
handler = session['cancel']
|
|
||||||
|
|
||||||
if handler:
|
session = self.sessions.get(sessionid)
|
||||||
handler(iq, session)
|
|
||||||
|
|
||||||
try:
|
if session:
|
||||||
|
handler = session['cancel']
|
||||||
|
if handler:
|
||||||
|
handler(iq, session)
|
||||||
del self.sessions[sessionid]
|
del self.sessions[sessionid]
|
||||||
except:
|
iq.reply()
|
||||||
pass
|
iq['command']['node'] = node
|
||||||
|
iq['command']['sessionid'] = sessionid
|
||||||
|
iq['command']['status'] = 'canceled'
|
||||||
|
iq['command']['notes'] = session['notes']
|
||||||
|
iq.send()
|
||||||
|
else:
|
||||||
|
raise XMPPError('item-not-found')
|
||||||
|
|
||||||
iq.reply()
|
|
||||||
iq['command']['node'] = node
|
|
||||||
iq['command']['sessionid'] = sessionid
|
|
||||||
iq['command']['status'] = 'canceled'
|
|
||||||
iq['command']['notes'] = session['notes']
|
|
||||||
iq.send()
|
|
||||||
|
|
||||||
def _handle_command_complete(self, iq):
|
def _handle_command_complete(self, iq):
|
||||||
"""
|
"""
|
||||||
@@ -378,28 +408,32 @@ class XEP_0050(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
node = iq['command']['node']
|
node = iq['command']['node']
|
||||||
sessionid = iq['command']['sessionid']
|
sessionid = iq['command']['sessionid']
|
||||||
session = self.sessions[sessionid]
|
session = self.sessions.get(sessionid)
|
||||||
handler = session['next']
|
|
||||||
interfaces = session['interfaces']
|
|
||||||
results = []
|
|
||||||
for stanza in iq['command']['substanzas']:
|
|
||||||
if stanza.plugin_attrib in interfaces:
|
|
||||||
results.append(stanza)
|
|
||||||
if len(results) == 1:
|
|
||||||
results = results[0]
|
|
||||||
|
|
||||||
if handler:
|
if session:
|
||||||
handler(results, session)
|
handler = session['next']
|
||||||
|
interfaces = session['interfaces']
|
||||||
|
results = []
|
||||||
|
for stanza in iq['command']['substanzas']:
|
||||||
|
if stanza.plugin_attrib in interfaces:
|
||||||
|
results.append(stanza)
|
||||||
|
if len(results) == 1:
|
||||||
|
results = results[0]
|
||||||
|
|
||||||
iq.reply()
|
if handler:
|
||||||
iq['command']['node'] = node
|
handler(results, session)
|
||||||
iq['command']['sessionid'] = sessionid
|
|
||||||
iq['command']['actions'] = []
|
|
||||||
iq['command']['status'] = 'completed'
|
|
||||||
iq['command']['notes'] = session['notes']
|
|
||||||
iq.send()
|
|
||||||
|
|
||||||
del self.sessions[sessionid]
|
del self.sessions[sessionid]
|
||||||
|
|
||||||
|
iq.reply()
|
||||||
|
iq['command']['node'] = node
|
||||||
|
iq['command']['sessionid'] = sessionid
|
||||||
|
iq['command']['actions'] = []
|
||||||
|
iq['command']['status'] = 'completed'
|
||||||
|
iq['command']['notes'] = session['notes']
|
||||||
|
iq.send()
|
||||||
|
else:
|
||||||
|
raise XMPPError('item-not-found')
|
||||||
|
|
||||||
# =================================================================
|
# =================================================================
|
||||||
# Client side (command user) API
|
# Client side (command user) API
|
||||||
@@ -537,7 +571,7 @@ class XEP_0050(BasePlugin):
|
|||||||
else:
|
else:
|
||||||
iq.send(block=False, callback=self._handle_command_result)
|
iq.send(block=False, callback=self._handle_command_result)
|
||||||
|
|
||||||
def continue_command(self, session):
|
def continue_command(self, session, direction='next'):
|
||||||
"""
|
"""
|
||||||
Execute the next action of the command.
|
Execute the next action of the command.
|
||||||
|
|
||||||
@@ -551,7 +585,7 @@ class XEP_0050(BasePlugin):
|
|||||||
self.send_command(session['jid'],
|
self.send_command(session['jid'],
|
||||||
session['node'],
|
session['node'],
|
||||||
ifrom=session.get('from', None),
|
ifrom=session.get('from', None),
|
||||||
action='next',
|
action=direction,
|
||||||
payload=session.get('payload', None),
|
payload=session.get('payload', None),
|
||||||
sessionid=session['id'],
|
sessionid=session['id'],
|
||||||
flow=True,
|
flow=True,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sleekxmpp import Iq
|
from sleekxmpp import JID, Iq
|
||||||
from sleekxmpp.exceptions import XMPPError
|
from sleekxmpp.exceptions import XMPPError
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
from sleekxmpp.xmlstream.handler import Callback
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
@@ -59,10 +59,20 @@ class XEP_0054(BasePlugin):
|
|||||||
def make_vcard(self):
|
def make_vcard(self):
|
||||||
return VCardTemp()
|
return VCardTemp()
|
||||||
|
|
||||||
def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
|
def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
|
||||||
block=True, callback=None, timeout=None):
|
block=True, callback=None, timeout=None):
|
||||||
if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
|
if local is None:
|
||||||
local = True
|
if jid is not None and not isinstance(jid, JID):
|
||||||
|
jid = JID(jid)
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
if jid.domain == self.xmpp.boundjid.domain:
|
||||||
|
local = True
|
||||||
|
else:
|
||||||
|
if str(jid) == str(self.xmpp.boundjid):
|
||||||
|
local = True
|
||||||
|
jid = jid.full
|
||||||
|
elif jid in (None, ''):
|
||||||
|
local = True
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
vcard = self.api['get_vcard'](jid, None, ifrom)
|
vcard = self.api['get_vcard'](jid, None, ifrom)
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ class XEP_0060(BasePlugin):
|
|||||||
callback=None, timeout=None):
|
callback=None, timeout=None):
|
||||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||||
iq['pubsub_owner']['configure']['node'] = node
|
iq['pubsub_owner']['configure']['node'] = node
|
||||||
iq['pubsub_owner']['configure']['form'].values = config.values
|
iq['pubsub_owner']['configure'].append(config)
|
||||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||||
|
|
||||||
def publish(self, jid, node, id=None, payload=None, options=None,
|
def publish(self, jid, node, id=None, payload=None, options=None,
|
||||||
|
|||||||
@@ -74,7 +74,12 @@ class Item(ElementBase):
|
|||||||
|
|
||||||
def set_payload(self, value):
|
def set_payload(self, value):
|
||||||
del self['payload']
|
del self['payload']
|
||||||
self.append(value)
|
if isinstance(value, ElementBase):
|
||||||
|
if value.tag_name() in self.plugin_tag_map:
|
||||||
|
self.init_plugin(value.plugin_attrib, existing_xml=value.xml)
|
||||||
|
self.xml.append(value.xml)
|
||||||
|
else:
|
||||||
|
self.xml.append(value)
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
childs = list(self.xml)
|
childs = list(self.xml)
|
||||||
@@ -243,39 +248,6 @@ class PublishOptions(ElementBase):
|
|||||||
self.parent().xml.remove(self.xml)
|
self.parent().xml.remove(self.xml)
|
||||||
|
|
||||||
|
|
||||||
class PubsubState(ElementBase):
|
|
||||||
"""This is an experimental pubsub extension."""
|
|
||||||
namespace = 'http://jabber.org/protocol/psstate'
|
|
||||||
name = 'state'
|
|
||||||
plugin_attrib = 'psstate'
|
|
||||||
interfaces = set(('node', 'item', 'payload'))
|
|
||||||
|
|
||||||
def set_payload(self, value):
|
|
||||||
self.xml.append(value)
|
|
||||||
|
|
||||||
def get_payload(self):
|
|
||||||
childs = list(self.xml)
|
|
||||||
if len(childs) > 0:
|
|
||||||
return childs[0]
|
|
||||||
|
|
||||||
def del_payload(self):
|
|
||||||
for child in self.xml:
|
|
||||||
self.xml.remove(child)
|
|
||||||
|
|
||||||
|
|
||||||
class PubsubStateEvent(ElementBase):
|
|
||||||
"""This is an experimental pubsub extension."""
|
|
||||||
namespace = 'http://jabber.org/protocol/psstate#event'
|
|
||||||
name = 'event'
|
|
||||||
plugin_attrib = 'psstate_event'
|
|
||||||
intefaces = set(tuple())
|
|
||||||
|
|
||||||
|
|
||||||
register_stanza_plugin(Iq, PubsubState)
|
|
||||||
register_stanza_plugin(Message, PubsubStateEvent)
|
|
||||||
register_stanza_plugin(PubsubStateEvent, PubsubState)
|
|
||||||
|
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Pubsub)
|
register_stanza_plugin(Iq, Pubsub)
|
||||||
register_stanza_plugin(Pubsub, Affiliations)
|
register_stanza_plugin(Pubsub, Affiliations)
|
||||||
register_stanza_plugin(Pubsub, Configure)
|
register_stanza_plugin(Pubsub, Configure)
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ class DefaultConfig(ElementBase):
|
|||||||
return self['form']
|
return self['form']
|
||||||
|
|
||||||
def set_config(self, value):
|
def set_config(self, value):
|
||||||
self['form'].values = value.values
|
del self['from']
|
||||||
|
self.append(value)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +94,9 @@ class OwnerRedirect(ElementBase):
|
|||||||
|
|
||||||
|
|
||||||
class OwnerSubscriptions(Subscriptions):
|
class OwnerSubscriptions(Subscriptions):
|
||||||
|
name = 'subscriptions'
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
plugin_attrib = name
|
||||||
interfaces = set(('node',))
|
interfaces = set(('node',))
|
||||||
|
|
||||||
def append(self, subscription):
|
def append(self, subscription):
|
||||||
|
|||||||
7
sleekxmpp/plugins/xep_0065/__init__.py
Normal file
7
sleekxmpp/plugins/xep_0065/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0065.stanza import Socks5
|
||||||
|
from sleekxmpp.plugins.xep_0065.proxy import XEP_0065
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0065)
|
||||||
292
sleekxmpp/plugins/xep_0065/proxy.py
Normal file
292
sleekxmpp/plugins/xep_0065/proxy.py
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from hashlib import sha1
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Iq
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.plugins.base import base_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0065 import stanza, Socks5
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0065(base_plugin):
|
||||||
|
|
||||||
|
name = 'xep_0065'
|
||||||
|
description = "Socks5 Bytestreams"
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
default_config = {
|
||||||
|
'auto_accept': False
|
||||||
|
}
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, Socks5)
|
||||||
|
|
||||||
|
self._proxies = {}
|
||||||
|
self._sessions = {}
|
||||||
|
self._sessions_lock = threading.Lock()
|
||||||
|
|
||||||
|
self._preauthed_sids_lock = threading.Lock()
|
||||||
|
self._preauthed_sids = {}
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Socks5 Bytestreams',
|
||||||
|
StanzaPath('iq@type=set/socks/streamhost'),
|
||||||
|
self._handle_streamhost))
|
||||||
|
|
||||||
|
self.api.register(self._authorized, 'authorized', default=True)
|
||||||
|
self.api.register(self._authorized_sid, 'authorized_sid', default=True)
|
||||||
|
self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature(Socks5.namespace)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Socks5 Bytestreams')
|
||||||
|
self.xmpp.remove_handler('Socks5 Streamhost Used')
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace)
|
||||||
|
|
||||||
|
def get_socket(self, sid):
|
||||||
|
"""Returns the socket associated to the SID."""
|
||||||
|
return self._sessions.get(sid, None)
|
||||||
|
|
||||||
|
def handshake(self, to, ifrom=None, sid=None, timeout=None):
|
||||||
|
""" Starts the handshake to establish the socks5 bytestreams
|
||||||
|
connection.
|
||||||
|
"""
|
||||||
|
if not self._proxies:
|
||||||
|
self._proxies = self.discover_proxies()
|
||||||
|
|
||||||
|
if sid is None:
|
||||||
|
sid = uuid4().hex
|
||||||
|
|
||||||
|
used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
|
||||||
|
proxy = used['socks']['streamhost_used']['jid']
|
||||||
|
|
||||||
|
if proxy not in self._proxies:
|
||||||
|
log.warning('Received unknown SOCKS5 proxy: %s', proxy)
|
||||||
|
return
|
||||||
|
|
||||||
|
with self._sessions_lock:
|
||||||
|
self._sessions[sid] = self._connect_proxy(
|
||||||
|
sid,
|
||||||
|
self.xmpp.boundjid,
|
||||||
|
to,
|
||||||
|
self._proxies[proxy][0],
|
||||||
|
self._proxies[proxy][1],
|
||||||
|
peer=to)
|
||||||
|
|
||||||
|
# Request that the proxy activate the session with the target.
|
||||||
|
self.activate(proxy, sid, to, timeout=timeout)
|
||||||
|
socket = self.get_socket(sid)
|
||||||
|
self.xmpp.event('stream:%s:%s' % (sid, to), socket)
|
||||||
|
return socket
|
||||||
|
|
||||||
|
def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
if sid is None:
|
||||||
|
sid = uuid4().hex
|
||||||
|
|
||||||
|
# Requester initiates S5B negotiation with Target by sending
|
||||||
|
# IQ-set that includes the JabberID and network address of
|
||||||
|
# StreamHost as well as the StreamID (SID) of the proposed
|
||||||
|
# bytestream.
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['to'] = to
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq['socks']['sid'] = sid
|
||||||
|
for proxy, (host, port) in self._proxies.items():
|
||||||
|
iq['socks'].add_streamhost(proxy, host, port)
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
|
||||||
|
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
|
||||||
|
if jid is None:
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
jid = self.xmpp.server
|
||||||
|
else:
|
||||||
|
jid = self.xmpp.boundjid.server
|
||||||
|
|
||||||
|
discovered = set()
|
||||||
|
|
||||||
|
disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
|
||||||
|
|
||||||
|
for item in disco_items['disco_items']['items']:
|
||||||
|
try:
|
||||||
|
disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout)
|
||||||
|
except XMPPError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Verify that the identity is a bytestream proxy.
|
||||||
|
identities = disco_info['disco_info']['identities']
|
||||||
|
for identity in identities:
|
||||||
|
if identity[0] == 'proxy' and identity[1] == 'bytestreams':
|
||||||
|
discovered.add(disco_info['from'])
|
||||||
|
|
||||||
|
for jid in discovered:
|
||||||
|
try:
|
||||||
|
addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
|
||||||
|
self._proxies[jid] = (addr['socks']['streamhost']['host'],
|
||||||
|
addr['socks']['streamhost']['port'])
|
||||||
|
except XMPPError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return self._proxies
|
||||||
|
|
||||||
|
def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
"""Get the network information of a proxy."""
|
||||||
|
iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom)
|
||||||
|
iq.enable('socks')
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def _handle_streamhost(self, iq):
|
||||||
|
"""Handle incoming SOCKS5 session request."""
|
||||||
|
sid = iq['socks']['sid']
|
||||||
|
if not sid:
|
||||||
|
raise XMPPError(etype='modify', condition='bad-request')
|
||||||
|
|
||||||
|
if not self._accept_stream(iq):
|
||||||
|
raise XMPPError(etype='modify', condition='not-acceptable')
|
||||||
|
|
||||||
|
streamhosts = iq['socks']['streamhosts']
|
||||||
|
conn = None
|
||||||
|
used_streamhost = None
|
||||||
|
|
||||||
|
sender = iq['from']
|
||||||
|
for streamhost in streamhosts:
|
||||||
|
try:
|
||||||
|
conn = self._connect_proxy(sid,
|
||||||
|
sender,
|
||||||
|
self.xmpp.boundjid,
|
||||||
|
streamhost['host'],
|
||||||
|
streamhost['port'],
|
||||||
|
peer=sender)
|
||||||
|
used_streamhost = streamhost['jid']
|
||||||
|
break
|
||||||
|
except socket.error:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise XMPPError(etype='cancel', condition='item-not-found')
|
||||||
|
|
||||||
|
iq.reply()
|
||||||
|
with self._sessions_lock:
|
||||||
|
self._sessions[sid] = conn
|
||||||
|
iq['socks']['sid'] = sid
|
||||||
|
iq['socks']['streamhost_used']['jid'] = used_streamhost
|
||||||
|
iq.send()
|
||||||
|
self.xmpp.event('socks5_stream', conn)
|
||||||
|
self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn)
|
||||||
|
|
||||||
|
def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
|
"""Activate the socks5 session that has been negotiated."""
|
||||||
|
iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom)
|
||||||
|
iq['socks']['sid'] = sid
|
||||||
|
iq['socks']['activate'] = target
|
||||||
|
iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def deactivate(self, sid):
|
||||||
|
"""Closes the proxy socket associated with this SID."""
|
||||||
|
sock = self._sessions.get(sid)
|
||||||
|
if sock:
|
||||||
|
try:
|
||||||
|
# sock.close() will also delete sid from self._sessions (see _connect_proxy)
|
||||||
|
sock.close()
|
||||||
|
except socket.error:
|
||||||
|
pass
|
||||||
|
# Though this should not be neccessary remove the closed session anyway
|
||||||
|
with self._sessions_lock:
|
||||||
|
if sid in self._sessions:
|
||||||
|
log.warn(('SOCKS5 session with sid = "%s" was not ' +
|
||||||
|
'removed from _sessions by sock.close()') % sid)
|
||||||
|
del self._sessions[sid]
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Closes all proxy sockets."""
|
||||||
|
for sid, sock in self._sessions.items():
|
||||||
|
sock.close()
|
||||||
|
with self._sessions_lock:
|
||||||
|
self._sessions = {}
|
||||||
|
|
||||||
|
def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None):
|
||||||
|
""" Establishes a connection between the client and the server-side
|
||||||
|
Socks5 proxy.
|
||||||
|
|
||||||
|
sid : The StreamID. <str>
|
||||||
|
requester : The JID of the requester. <str>
|
||||||
|
target : The JID of the target. <str>
|
||||||
|
proxy_host : The hostname or the IP of the proxy. <str>
|
||||||
|
proxy_port : The port of the proxy. <str> or <int>
|
||||||
|
peer : The JID for the other side of the stream, regardless
|
||||||
|
of target or requester status.
|
||||||
|
"""
|
||||||
|
# Because the xep_0065 plugin uses the proxy_port as string,
|
||||||
|
# the Proxy class accepts the proxy_port argument as a string
|
||||||
|
# or an integer. Here, we force to use the port as an integer.
|
||||||
|
proxy_port = int(proxy_port)
|
||||||
|
|
||||||
|
sock = socksocket()
|
||||||
|
sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port)
|
||||||
|
|
||||||
|
# The hostname MUST be SHA1(SID + Requester JID + Target JID)
|
||||||
|
# where the output is hexadecimal-encoded (not binary).
|
||||||
|
digest = sha1()
|
||||||
|
digest.update(sid)
|
||||||
|
digest.update(str(requester))
|
||||||
|
digest.update(str(target))
|
||||||
|
|
||||||
|
dest = digest.hexdigest()
|
||||||
|
|
||||||
|
# The port MUST be 0.
|
||||||
|
sock.connect((dest, 0))
|
||||||
|
log.info('Socket connected.')
|
||||||
|
|
||||||
|
_close = sock.close
|
||||||
|
def close(*args, **kwargs):
|
||||||
|
with self._sessions_lock:
|
||||||
|
if sid in self._sessions:
|
||||||
|
del self._sessions[sid]
|
||||||
|
_close()
|
||||||
|
log.info('Socket closed.')
|
||||||
|
sock.close = close
|
||||||
|
|
||||||
|
sock.peer_jid = peer
|
||||||
|
sock.self_jid = target if requester == peer else requester
|
||||||
|
|
||||||
|
self.xmpp.event('socks_connected', sid)
|
||||||
|
return sock
|
||||||
|
|
||||||
|
def _accept_stream(self, iq):
|
||||||
|
receiver = iq['to']
|
||||||
|
sender = iq['from']
|
||||||
|
sid = iq['socks']['sid']
|
||||||
|
|
||||||
|
if self.api['authorized_sid'](receiver, sid, sender, iq):
|
||||||
|
return True
|
||||||
|
return self.api['authorized'](receiver, sid, sender, iq)
|
||||||
|
|
||||||
|
def _authorized(self, jid, sid, ifrom, iq):
|
||||||
|
return self.auto_accept
|
||||||
|
|
||||||
|
def _authorized_sid(self, jid, sid, ifrom, iq):
|
||||||
|
with self._preauthed_sids_lock:
|
||||||
|
log.debug('>>> authed sids: %s', self._preauthed_sids)
|
||||||
|
log.debug('>>> lookup: %s %s %s', jid, sid, ifrom)
|
||||||
|
if (jid, sid, ifrom) in self._preauthed_sids:
|
||||||
|
del self._preauthed_sids[(jid, sid, ifrom)]
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _preauthorize_sid(self, jid, sid, ifrom, data):
|
||||||
|
log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data)
|
||||||
|
with self._preauthed_sids_lock:
|
||||||
|
self._preauthed_sids[(jid, sid, ifrom)] = True
|
||||||
47
sleekxmpp/plugins/xep_0065/stanza.py
Normal file
47
sleekxmpp/plugins/xep_0065/stanza.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
from sleekxmpp.jid import JID
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class Socks5(ElementBase):
|
||||||
|
name = 'query'
|
||||||
|
namespace = 'http://jabber.org/protocol/bytestreams'
|
||||||
|
plugin_attrib = 'socks'
|
||||||
|
interfaces = set(['sid', 'activate'])
|
||||||
|
sub_interfaces = set(['activate'])
|
||||||
|
|
||||||
|
def add_streamhost(self, jid, host, port):
|
||||||
|
sh = StreamHost(parent=self)
|
||||||
|
sh['jid'] = jid
|
||||||
|
sh['host'] = host
|
||||||
|
sh['port'] = port
|
||||||
|
|
||||||
|
|
||||||
|
class StreamHost(ElementBase):
|
||||||
|
name = 'streamhost'
|
||||||
|
namespace = 'http://jabber.org/protocol/bytestreams'
|
||||||
|
plugin_attrib = 'streamhost'
|
||||||
|
plugin_multi_attrib = 'streamhosts'
|
||||||
|
interfaces = set(['host', 'jid', 'port'])
|
||||||
|
|
||||||
|
def set_jid(self, value):
|
||||||
|
return self._set_attr('jid', str(value))
|
||||||
|
|
||||||
|
def get_jid(self):
|
||||||
|
return JID(self._get_attr('jid'))
|
||||||
|
|
||||||
|
|
||||||
|
class StreamHostUsed(ElementBase):
|
||||||
|
name = 'streamhost-used'
|
||||||
|
namespace = 'http://jabber.org/protocol/bytestreams'
|
||||||
|
plugin_attrib = 'streamhost_used'
|
||||||
|
interfaces = set(['jid'])
|
||||||
|
|
||||||
|
def set_jid(self, value):
|
||||||
|
return self._set_attr('jid', str(value))
|
||||||
|
|
||||||
|
def get_jid(self):
|
||||||
|
return JID(self._get_attr('jid'))
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(Socks5, StreamHost, iterable=True)
|
||||||
|
register_stanza_plugin(Socks5, StreamHostUsed)
|
||||||
15
sleekxmpp/plugins/xep_0071/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0071/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0071.stanza import XHTML_IM
|
||||||
|
from sleekxmpp.plugins.xep_0071.xhtml_im import XEP_0071
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0071)
|
||||||
81
sleekxmpp/plugins/xep_0071/stanza.py
Normal file
81
sleekxmpp/plugins/xep_0071/stanza.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Message
|
||||||
|
from sleekxmpp.util import unicode
|
||||||
|
from sleekxmpp.thirdparty import OrderedDict
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring
|
||||||
|
|
||||||
|
|
||||||
|
XHTML_NS = 'http://www.w3.org/1999/xhtml'
|
||||||
|
|
||||||
|
|
||||||
|
class XHTML_IM(ElementBase):
|
||||||
|
|
||||||
|
namespace = 'http://jabber.org/protocol/xhtml-im'
|
||||||
|
name = 'html'
|
||||||
|
interfaces = set(['body'])
|
||||||
|
lang_interfaces = set(['body'])
|
||||||
|
plugin_attrib = name
|
||||||
|
|
||||||
|
def set_body(self, content, lang=None):
|
||||||
|
if lang is None:
|
||||||
|
lang = self.get_lang()
|
||||||
|
self.del_body(lang)
|
||||||
|
if lang == '*':
|
||||||
|
for sublang, subcontent in content.items():
|
||||||
|
self.set_body(subcontent, sublang)
|
||||||
|
else:
|
||||||
|
if isinstance(content, type(ET.Element('test'))):
|
||||||
|
content = unicode(ET.tostring(content))
|
||||||
|
else:
|
||||||
|
content = unicode(content)
|
||||||
|
header = '<body xmlns="%s"' % XHTML_NS
|
||||||
|
if lang:
|
||||||
|
header = '%s xml:lang="%s"' % (header, lang)
|
||||||
|
content = '%s>%s</body>' % (header, content)
|
||||||
|
xhtml = ET.fromstring(content)
|
||||||
|
self.xml.append(xhtml)
|
||||||
|
|
||||||
|
def get_body(self, lang=None):
|
||||||
|
"""Return the contents of the HTML body."""
|
||||||
|
if lang is None:
|
||||||
|
lang = self.get_lang()
|
||||||
|
|
||||||
|
bodies = self.xml.findall('{%s}body' % XHTML_NS)
|
||||||
|
|
||||||
|
if lang == '*':
|
||||||
|
result = OrderedDict()
|
||||||
|
for body in bodies:
|
||||||
|
body_lang = body.attrib.get('{%s}lang' % self.xml_ns, '')
|
||||||
|
body_result = []
|
||||||
|
body_result.append(body.text if body.text else '')
|
||||||
|
for child in body:
|
||||||
|
body_result.append(tostring(child, xmlns=XHTML_NS))
|
||||||
|
body_result.append(body.tail if body.tail else '')
|
||||||
|
result[body_lang] = ''.join(body_result)
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
for body in bodies:
|
||||||
|
if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
|
||||||
|
result = []
|
||||||
|
result.append(body.text if body.text else '')
|
||||||
|
for child in body:
|
||||||
|
result.append(tostring(child, xmlns=XHTML_NS))
|
||||||
|
result.append(body.tail if body.tail else '')
|
||||||
|
return ''.join(result)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def del_body(self, lang=None):
|
||||||
|
if lang is None:
|
||||||
|
lang = self.get_lang()
|
||||||
|
bodies = self.xml.findall('{%s}body' % XHTML_NS)
|
||||||
|
for body in bodies:
|
||||||
|
if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
|
||||||
|
self.xml.remove(body)
|
||||||
|
return
|
||||||
30
sleekxmpp/plugins/xep_0071/xhtml_im.py
Normal file
30
sleekxmpp/plugins/xep_0071/xhtml_im.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Message
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins.xep_0071 import stanza, XHTML_IM
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0071(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0071'
|
||||||
|
description = 'XEP-0071: XHTML-IM'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, XHTML_IM)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature(feature=XHTML_IM.namespace)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=XHTML_IM.namespace)
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import ssl
|
||||||
|
|
||||||
from sleekxmpp.stanza import StreamFeatures, Iq
|
from sleekxmpp.stanza import StreamFeatures, Iq
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin, JID
|
from sleekxmpp.xmlstream import register_stanza_plugin, JID
|
||||||
@@ -29,6 +30,7 @@ class XEP_0077(BasePlugin):
|
|||||||
stanza = stanza
|
stanza = stanza
|
||||||
default_config = {
|
default_config = {
|
||||||
'create_account': True,
|
'create_account': True,
|
||||||
|
'force_registration': False,
|
||||||
'order': 50
|
'order': 50
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,10 +47,29 @@ class XEP_0077(BasePlugin):
|
|||||||
register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
|
register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
|
||||||
register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
|
register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
|
||||||
|
|
||||||
|
self.xmpp.add_event_handler('connected', self._force_registration)
|
||||||
|
|
||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
if not self.xmpp.is_component:
|
if not self.xmpp.is_component:
|
||||||
self.xmpp.unregister_feature('register', self.order)
|
self.xmpp.unregister_feature('register', self.order)
|
||||||
|
|
||||||
|
def _force_registration(self, event):
|
||||||
|
if self.force_registration:
|
||||||
|
self.xmpp.add_filter('in', self._force_stream_feature)
|
||||||
|
|
||||||
|
def _force_stream_feature(self, stanza):
|
||||||
|
if isinstance(stanza, StreamFeatures):
|
||||||
|
if self.xmpp.use_tls or self.xmpp.use_ssl:
|
||||||
|
if 'starttls' not in self.xmpp.features:
|
||||||
|
return stanza
|
||||||
|
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
|
||||||
|
return stanza
|
||||||
|
if 'mechanisms' not in self.xmpp.features:
|
||||||
|
log.debug('Forced adding in-band registration stream feature')
|
||||||
|
stanza.enable('register')
|
||||||
|
self.xmpp.del_filter('in', self._force_stream_feature)
|
||||||
|
return stanza
|
||||||
|
|
||||||
def _handle_register_feature(self, features):
|
def _handle_register_feature(self, features):
|
||||||
if 'mechanisms' in self.xmpp.features:
|
if 'mechanisms' in self.xmpp.features:
|
||||||
# We have already logged in with an account
|
# We have already logged in with an account
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
import random
|
import random
|
||||||
@@ -98,7 +99,7 @@ class XEP_0078(BasePlugin):
|
|||||||
# A resource is required, so create a random one if necessary
|
# A resource is required, so create a random one if necessary
|
||||||
resource = self.xmpp.requested_jid.resource
|
resource = self.xmpp.requested_jid.resource
|
||||||
if not resource:
|
if not resource:
|
||||||
resource = uuid.uuid4()
|
resource = str(uuid.uuid4())
|
||||||
|
|
||||||
iq['auth']['resource'] = resource
|
iq['auth']['resource'] = resource
|
||||||
|
|
||||||
|
|||||||
18
sleekxmpp/plugins/xep_0079/__init__.py
Normal file
18
sleekxmpp/plugins/xep_0079/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0079.stanza import (
|
||||||
|
AMP, Rule, InvalidRules, UnsupportedConditions,
|
||||||
|
UnsupportedActions, FailedRules, FailedRule,
|
||||||
|
AMPFeature)
|
||||||
|
from sleekxmpp.plugins.xep_0079.amp import XEP_0079
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0079)
|
||||||
79
sleekxmpp/plugins/xep_0079/amp.py
Normal file
79
sleekxmpp/plugins/xep_0079/amp.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permissio
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Message, Error, StreamFeatures
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath, MatchMany
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0079 import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0079(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0079 Advanced Message Processing
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0079'
|
||||||
|
description = 'XEP-0079: Advanced Message Processing'
|
||||||
|
dependencies = set(['xep_0030'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, stanza.AMP)
|
||||||
|
register_stanza_plugin(Error, stanza.InvalidRules)
|
||||||
|
register_stanza_plugin(Error, stanza.UnsupportedConditions)
|
||||||
|
register_stanza_plugin(Error, stanza.UnsupportedActions)
|
||||||
|
register_stanza_plugin(Error, stanza.FailedRules)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('AMP Response',
|
||||||
|
MatchMany([
|
||||||
|
StanzaPath('message/error/failed_rules'),
|
||||||
|
StanzaPath('message/amp')
|
||||||
|
]),
|
||||||
|
self._handle_amp_response))
|
||||||
|
|
||||||
|
if not self.xmpp.is_component:
|
||||||
|
self.xmpp.register_feature('amp',
|
||||||
|
self._handle_amp_feature,
|
||||||
|
restart=False,
|
||||||
|
order=9000)
|
||||||
|
register_stanza_plugin(StreamFeatures, stanza.AMPFeature)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('AMP Response')
|
||||||
|
|
||||||
|
def _handle_amp_response(self, msg):
|
||||||
|
log.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
|
||||||
|
if msg['type'] == 'error':
|
||||||
|
self.xmpp.event('amp_error', msg)
|
||||||
|
elif msg['amp']['status'] in ('alert', 'notify'):
|
||||||
|
self.xmpp.event('amp_%s' % msg['amp']['status'], msg)
|
||||||
|
|
||||||
|
def _handle_amp_feature(self, features):
|
||||||
|
log.debug('Advanced Message Processing is available.')
|
||||||
|
self.xmpp.features.add('amp')
|
||||||
|
|
||||||
|
def discover_support(self, jid=None, **iqargs):
|
||||||
|
if jid is None:
|
||||||
|
if self.xmpp.is_component:
|
||||||
|
jid = self.xmpp.server_host
|
||||||
|
else:
|
||||||
|
jid = self.xmpp.boundjid.host
|
||||||
|
|
||||||
|
return self.xmpp['xep_0030'].get_info(
|
||||||
|
jid=jid,
|
||||||
|
node='http://jabber.org/protocol/amp',
|
||||||
|
**iqargs)
|
||||||
96
sleekxmpp/plugins/xep_0079/stanza.py
Normal file
96
sleekxmpp/plugins/xep_0079/stanza.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class AMP(ElementBase):
|
||||||
|
namespace = 'http://jabber.org/protocol/amp'
|
||||||
|
name = 'amp'
|
||||||
|
plugin_attrib = 'amp'
|
||||||
|
interfaces = set(['from', 'to', 'status', 'per_hop'])
|
||||||
|
|
||||||
|
def get_from(self):
|
||||||
|
return JID(self._get_attr('from'))
|
||||||
|
|
||||||
|
def set_from(self, value):
|
||||||
|
return self._set_attr('from', str(value))
|
||||||
|
|
||||||
|
def get_to(self):
|
||||||
|
return JID(self._get_attr('from'))
|
||||||
|
|
||||||
|
def set_to(self, value):
|
||||||
|
return self._set_attr('to', str(value))
|
||||||
|
|
||||||
|
def get_per_hop(self):
|
||||||
|
return self._get_attr('per-hop') == 'true'
|
||||||
|
|
||||||
|
def set_per_hop(self, value):
|
||||||
|
if value:
|
||||||
|
return self._set_attr('per-hop', 'true')
|
||||||
|
else:
|
||||||
|
return self._del_attr('per-hop')
|
||||||
|
|
||||||
|
def del_per_hop(self):
|
||||||
|
return self._del_attr('per-hop')
|
||||||
|
|
||||||
|
def add_rule(self, action, condition, value):
|
||||||
|
rule = Rule(parent=self)
|
||||||
|
rule['action'] = action
|
||||||
|
rule['condition'] = condition
|
||||||
|
rule['value'] = value
|
||||||
|
|
||||||
|
|
||||||
|
class Rule(ElementBase):
|
||||||
|
namespace = 'http://jabber.org/protocol/amp'
|
||||||
|
name = 'rule'
|
||||||
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'rules'
|
||||||
|
interfaces = set(['action', 'condition', 'value'])
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRules(ElementBase):
|
||||||
|
namespace = 'http://jabber.org/protocol/amp'
|
||||||
|
name = 'invalid-rules'
|
||||||
|
plugin_attrib = 'invalid_rules'
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedConditions(ElementBase):
|
||||||
|
namespace = 'http://jabber.org/protocol/amp'
|
||||||
|
name = 'unsupported-conditions'
|
||||||
|
plugin_attrib = 'unsupported_conditions'
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedActions(ElementBase):
|
||||||
|
namespace = 'http://jabber.org/protocol/amp'
|
||||||
|
name = 'unsupported-actions'
|
||||||
|
plugin_attrib = 'unsupported_actions'
|
||||||
|
|
||||||
|
|
||||||
|
class FailedRule(Rule):
|
||||||
|
namespace = 'http://jabber.org/protocol/amp#errors'
|
||||||
|
|
||||||
|
|
||||||
|
class FailedRules(ElementBase):
|
||||||
|
namespace = 'http://jabber.org/protocol/amp#errors'
|
||||||
|
name = 'failed-rules'
|
||||||
|
plugin_attrib = 'failed_rules'
|
||||||
|
|
||||||
|
|
||||||
|
class AMPFeature(ElementBase):
|
||||||
|
namespace = 'http://jabber.org/features/amp'
|
||||||
|
name = 'amp'
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(AMP, Rule, iterable=True)
|
||||||
|
register_stanza_plugin(InvalidRules, Rule, iterable=True)
|
||||||
|
register_stanza_plugin(UnsupportedConditions, Rule, iterable=True)
|
||||||
|
register_stanza_plugin(UnsupportedActions, Rule, iterable=True)
|
||||||
|
register_stanza_plugin(FailedRules, FailedRule, iterable=True)
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
|
||||||
from sleekxmpp.plugins import BasePlugin, register_plugin
|
from sleekxmpp.plugins import BasePlugin, register_plugin
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class XEP_0084(BasePlugin):
|
|||||||
metadata.add_pointer(pointer)
|
metadata.add_pointer(pointer)
|
||||||
|
|
||||||
return self.xmpp['xep_0163'].publish(metadata,
|
return self.xmpp['xep_0163'].publish(metadata,
|
||||||
|
id=info['id'],
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
block=block,
|
||||||
callback=callback,
|
callback=callback,
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ class LegacyDelay(ElementBase):
|
|||||||
interfaces = set(('from', 'stamp', 'text'))
|
interfaces = set(('from', 'stamp', 'text'))
|
||||||
|
|
||||||
def get_from(self):
|
def get_from(self):
|
||||||
return JID(self._get_attr('from'))
|
from_ = self._get_attr('from')
|
||||||
|
return JID(from_) if from_ else None
|
||||||
|
|
||||||
def set_from(self, value):
|
def set_from(self, value):
|
||||||
self._set_attr('from', str(value))
|
self._set_attr('from', str(value))
|
||||||
|
|
||||||
def get_stamp(self):
|
def get_stamp(self):
|
||||||
timestamp = self._get_attr('stamp')
|
timestamp = self._get_attr('stamp')
|
||||||
return xep_0082.parse('%sZ' % timestamp)
|
return xep_0082.parse('%sZ' % timestamp) if timestamp else None
|
||||||
|
|
||||||
def set_stamp(self, value):
|
def set_stamp(self, value):
|
||||||
if isinstance(value, dt.datetime):
|
if isinstance(value, dt.datetime):
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class XEP_0092(BasePlugin):
|
|||||||
iq['software_version']['os'] = self.os
|
iq['software_version']['os'] = self.os
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
def get_version(self, jid, ifrom=None):
|
def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None):
|
||||||
"""
|
"""
|
||||||
Retrieve the software version of a remote agent.
|
Retrieve the software version of a remote agent.
|
||||||
|
|
||||||
@@ -82,14 +82,4 @@ class XEP_0092(BasePlugin):
|
|||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['query'] = Version.namespace
|
iq['query'] = Version.namespace
|
||||||
|
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||||
result = iq.send()
|
|
||||||
|
|
||||||
if result and result['type'] != 'error':
|
|
||||||
values = result['software_version'].values
|
|
||||||
del values['lang']
|
|
||||||
return values
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
XEP_0092.getVersion = XEP_0092.get_version
|
|
||||||
|
|||||||
16
sleekxmpp/plugins/xep_0095/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0095/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0095 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0095.stanza import SI
|
||||||
|
from sleekxmpp.plugins.xep_0095.stream_initiation import XEP_0095
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0095)
|
||||||
25
sleekxmpp/plugins/xep_0095/stanza.py
Normal file
25
sleekxmpp/plugins/xep_0095/stanza.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class SI(ElementBase):
|
||||||
|
name = 'si'
|
||||||
|
namespace = 'http://jabber.org/protocol/si'
|
||||||
|
plugin_attrib = 'si'
|
||||||
|
interfaces = set(['id', 'mime_type', 'profile'])
|
||||||
|
|
||||||
|
def get_mime_type(self):
|
||||||
|
return self._get_attr('mime-type', 'application/octet-stream')
|
||||||
|
|
||||||
|
def set_mime_type(self, value):
|
||||||
|
self._set_attr('mime-type', value)
|
||||||
|
|
||||||
|
def del_mime_type(self):
|
||||||
|
self._del_attr('mime-type')
|
||||||
214
sleekxmpp/plugins/xep_0095/stream_initiation.py
Normal file
214
sleekxmpp/plugins/xep_0095/stream_initiation.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sleekxmpp import Iq, Message
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin, JID
|
||||||
|
from sleekxmpp.plugins.xep_0095 import stanza, SI
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
SOCKS5 = 'http://jabber.org/protocol/bytestreams'
|
||||||
|
IBB = 'http://jabber.org/protocol/ibb'
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0095(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0095'
|
||||||
|
description = 'XEP-0095: Stream Initiation'
|
||||||
|
dependencies = set(['xep_0020', 'xep_0030', 'xep_0047', 'xep_0065'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
self._profiles = {}
|
||||||
|
self._methods = {}
|
||||||
|
self._methods_order = []
|
||||||
|
self._pending_lock = threading.Lock()
|
||||||
|
self._pending= {}
|
||||||
|
|
||||||
|
self.register_method(SOCKS5, 'xep_0065', 100)
|
||||||
|
self.register_method(IBB, 'xep_0047', 50)
|
||||||
|
|
||||||
|
register_stanza_plugin(Iq, SI)
|
||||||
|
register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('SI Request',
|
||||||
|
StanzaPath('iq@type=set/si'),
|
||||||
|
self._handle_request))
|
||||||
|
|
||||||
|
self.api.register(self._add_pending, 'add_pending', default=True)
|
||||||
|
self.api.register(self._get_pending, 'get_pending', default=True)
|
||||||
|
self.api.register(self._del_pending, 'del_pending', default=True)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature(SI.namespace)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('SI Request')
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=SI.namespace)
|
||||||
|
|
||||||
|
def register_profile(self, profile_name, plugin):
|
||||||
|
self._profiles[profile_name] = plugin
|
||||||
|
|
||||||
|
def unregister_profile(self, profile_name):
|
||||||
|
try:
|
||||||
|
del self._profiles[profile_name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def register_method(self, method, plugin_name, order=50):
|
||||||
|
self._methods[method] = (plugin_name, order)
|
||||||
|
self._methods_order.append((order, method, plugin_name))
|
||||||
|
self._methods_order.sort()
|
||||||
|
|
||||||
|
def unregister_method(self, method):
|
||||||
|
if method in self._methods:
|
||||||
|
plugin_name, order = self._methods[method]
|
||||||
|
del self._methods[method]
|
||||||
|
self._methods_order.remove((order, method, plugin_name))
|
||||||
|
self._methods_order.sort()
|
||||||
|
|
||||||
|
def _handle_request(self, iq):
|
||||||
|
profile = iq['si']['profile']
|
||||||
|
sid = iq['si']['id']
|
||||||
|
|
||||||
|
if not sid:
|
||||||
|
raise XMPPError(etype='modify', condition='bad-request')
|
||||||
|
if profile not in self._profiles:
|
||||||
|
raise XMPPError(
|
||||||
|
etype='modify',
|
||||||
|
condition='bad-request',
|
||||||
|
extension='bad-profile',
|
||||||
|
extension_ns=SI.namespace)
|
||||||
|
|
||||||
|
neg = iq['si']['feature_neg']['form']['fields']
|
||||||
|
options = neg['stream-method']['options'] or []
|
||||||
|
methods = []
|
||||||
|
for opt in options:
|
||||||
|
methods.append(opt['value'])
|
||||||
|
for method in methods:
|
||||||
|
if method in self._methods:
|
||||||
|
supported = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise XMPPError('bad-request',
|
||||||
|
extension='no-valid-streams',
|
||||||
|
extension_ns=SI.namespace)
|
||||||
|
|
||||||
|
selected_method = None
|
||||||
|
log.debug('Available: %s', methods)
|
||||||
|
for order, method, plugin in self._methods_order:
|
||||||
|
log.debug('Testing: %s', method)
|
||||||
|
if method in methods:
|
||||||
|
selected_method = method
|
||||||
|
break
|
||||||
|
|
||||||
|
receiver = iq['to']
|
||||||
|
sender = iq['from']
|
||||||
|
|
||||||
|
self.api['add_pending'](receiver, sid, sender, {
|
||||||
|
'response_id': iq['id'],
|
||||||
|
'method': selected_method,
|
||||||
|
'profile': profile
|
||||||
|
})
|
||||||
|
self.xmpp.event('si_request', iq)
|
||||||
|
|
||||||
|
def offer(self, jid, sid=None, mime_type=None, profile=None,
|
||||||
|
methods=None, payload=None, ifrom=None,
|
||||||
|
**iqargs):
|
||||||
|
if sid is None:
|
||||||
|
sid = uuid4().hex
|
||||||
|
if methods is None:
|
||||||
|
methods = list(self._methods.keys())
|
||||||
|
if not isinstance(methods, (list, tuple, set)):
|
||||||
|
methods = [methods]
|
||||||
|
|
||||||
|
si = self.xmpp.Iq()
|
||||||
|
si['to'] = jid
|
||||||
|
si['from'] = ifrom
|
||||||
|
si['type'] = 'set'
|
||||||
|
si['si']['id'] = sid
|
||||||
|
si['si']['mime_type'] = mime_type
|
||||||
|
si['si']['profile'] = profile
|
||||||
|
if not isinstance(payload, (list, tuple, set)):
|
||||||
|
payload = [payload]
|
||||||
|
for item in payload:
|
||||||
|
si['si'].append(item)
|
||||||
|
si['si']['feature_neg']['form'].add_field(
|
||||||
|
var='stream-method',
|
||||||
|
ftype='list-single',
|
||||||
|
options=methods)
|
||||||
|
return si.send(**iqargs)
|
||||||
|
|
||||||
|
def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None):
|
||||||
|
stream = self.api['get_pending'](ifrom, sid, jid)
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['id'] = stream['response_id']
|
||||||
|
iq['to'] = jid
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['type'] = 'result'
|
||||||
|
if payload:
|
||||||
|
iq['si'].append(payload)
|
||||||
|
iq['si']['feature_neg']['form']['type'] = 'submit'
|
||||||
|
iq['si']['feature_neg']['form'].add_field(
|
||||||
|
var='stream-method',
|
||||||
|
ftype='list-single',
|
||||||
|
value=stream['method'])
|
||||||
|
|
||||||
|
if ifrom is None:
|
||||||
|
ifrom = self.xmpp.boundjid
|
||||||
|
|
||||||
|
method_plugin = self._methods[stream['method']][0]
|
||||||
|
self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid)
|
||||||
|
|
||||||
|
self.api['del_pending'](ifrom, sid, jid)
|
||||||
|
|
||||||
|
if stream_handler:
|
||||||
|
self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid),
|
||||||
|
stream_handler,
|
||||||
|
threaded=True,
|
||||||
|
disposable=True)
|
||||||
|
return iq.send()
|
||||||
|
|
||||||
|
def decline(self, jid, sid, ifrom=None):
|
||||||
|
stream = self.api['get_pending'](ifrom, sid, jid)
|
||||||
|
if not stream:
|
||||||
|
return
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['id'] = stream['response_id']
|
||||||
|
iq['to'] = jid
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['type'] = 'error'
|
||||||
|
iq['error']['condition'] = 'forbidden'
|
||||||
|
iq['error']['text'] = 'Offer declined'
|
||||||
|
self.api['del_pending'](ifrom, sid, jid)
|
||||||
|
return iq.send()
|
||||||
|
|
||||||
|
def _add_pending(self, jid, node, ifrom, data):
|
||||||
|
with self._pending_lock:
|
||||||
|
self._pending[(jid, node, ifrom)] = data
|
||||||
|
|
||||||
|
def _get_pending(self, jid, node, ifrom, data):
|
||||||
|
with self._pending_lock:
|
||||||
|
return self._pending.get((jid, node, ifrom), None)
|
||||||
|
|
||||||
|
def _del_pending(self, jid, node, ifrom, data):
|
||||||
|
with self._pending_lock:
|
||||||
|
if (jid, node, ifrom) in self._pending:
|
||||||
|
del self._pending[(jid, node, ifrom)]
|
||||||
16
sleekxmpp/plugins/xep_0096/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0096/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0096 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0096.stanza import File
|
||||||
|
from sleekxmpp.plugins.xep_0096.file_transfer import XEP_0096
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0096)
|
||||||
58
sleekxmpp/plugins/xep_0096/file_transfer.py
Normal file
58
sleekxmpp/plugins/xep_0096/file_transfer.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp import Iq, Message
|
||||||
|
from sleekxmpp.plugins import BasePlugin
|
||||||
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from sleekxmpp.xmlstream import register_stanza_plugin, JID
|
||||||
|
from sleekxmpp.plugins.xep_0096 import stanza, File
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0096(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0096'
|
||||||
|
description = 'XEP-0096: SI File Transfer'
|
||||||
|
dependencies = set(['xep_0095'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File)
|
||||||
|
|
||||||
|
self.xmpp['xep_0095'].register_profile(File.namespace, self)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature(File.namespace)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=File.namespace)
|
||||||
|
self.xmpp['xep_0095'].unregister_profile(File.namespace, self)
|
||||||
|
|
||||||
|
def request_file_transfer(self, jid, sid=None, name=None, size=None,
|
||||||
|
desc=None, hash=None, date=None,
|
||||||
|
allow_ranged=False, mime_type=None,
|
||||||
|
**iqargs):
|
||||||
|
data = File()
|
||||||
|
data['name'] = name
|
||||||
|
data['size'] = size
|
||||||
|
data['date'] = date
|
||||||
|
data['desc'] = desc
|
||||||
|
if allow_ranged:
|
||||||
|
data.enable('range')
|
||||||
|
|
||||||
|
return self.xmpp['xep_0095'].offer(jid,
|
||||||
|
sid=sid,
|
||||||
|
mime_type=mime_type,
|
||||||
|
profile=File.namespace,
|
||||||
|
payload=data,
|
||||||
|
**iqargs)
|
||||||
48
sleekxmpp/plugins/xep_0096/stanza.py
Normal file
48
sleekxmpp/plugins/xep_0096/stanza.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
from sleekxmpp.plugins import xep_0082
|
||||||
|
|
||||||
|
|
||||||
|
class File(ElementBase):
|
||||||
|
name = 'file'
|
||||||
|
namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
|
||||||
|
plugin_attrib = 'file'
|
||||||
|
interfaces = set(['name', 'size', 'date', 'hash', 'desc'])
|
||||||
|
sub_interfaces = set(['desc'])
|
||||||
|
|
||||||
|
def set_size(self, value):
|
||||||
|
self._set_attr('size', str(value))
|
||||||
|
|
||||||
|
def get_date(self):
|
||||||
|
timestamp = self._get_attr('date')
|
||||||
|
return xep_0082.parse(timestamp)
|
||||||
|
|
||||||
|
def set_date(self, value):
|
||||||
|
if isinstance(value, dt.datetime):
|
||||||
|
value = xep_0082.format_datetime(value)
|
||||||
|
self._set_attr('date', value)
|
||||||
|
|
||||||
|
|
||||||
|
class Range(ElementBase):
|
||||||
|
name = 'range'
|
||||||
|
namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
|
||||||
|
plugin_attrib = 'range'
|
||||||
|
interfaces = set(['length', 'offset'])
|
||||||
|
|
||||||
|
def set_length(self, value):
|
||||||
|
self._set_attr('length', str(value))
|
||||||
|
|
||||||
|
def set_offset(self, value):
|
||||||
|
self._set_attr('offset', str(value))
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(File, Range)
|
||||||
@@ -9,8 +9,9 @@
|
|||||||
import logging
|
import logging
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
import base64
|
||||||
|
import threading
|
||||||
|
|
||||||
import sleekxmpp
|
from sleekxmpp import __version__
|
||||||
from sleekxmpp.stanza import StreamFeatures, Presence, Iq
|
from sleekxmpp.stanza import StreamFeatures, Presence, Iq
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin, JID
|
from sleekxmpp.xmlstream import register_stanza_plugin, JID
|
||||||
from sleekxmpp.xmlstream.handler import Callback
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
@@ -45,8 +46,7 @@ class XEP_0115(BasePlugin):
|
|||||||
'md5': hashlib.md5}
|
'md5': hashlib.md5}
|
||||||
|
|
||||||
if self.caps_node is None:
|
if self.caps_node is None:
|
||||||
ver = sleekxmpp.__version__
|
self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__
|
||||||
self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver
|
|
||||||
|
|
||||||
register_stanza_plugin(Presence, stanza.Capabilities)
|
register_stanza_plugin(Presence, stanza.Capabilities)
|
||||||
register_stanza_plugin(StreamFeatures, stanza.Capabilities)
|
register_stanza_plugin(StreamFeatures, stanza.Capabilities)
|
||||||
@@ -90,6 +90,9 @@ class XEP_0115(BasePlugin):
|
|||||||
disco.assign_verstring = self.assign_verstring
|
disco.assign_verstring = self.assign_verstring
|
||||||
disco.get_verstring = self.get_verstring
|
disco.get_verstring = self.get_verstring
|
||||||
|
|
||||||
|
self._processing_lock = threading.Lock()
|
||||||
|
self._processing = set()
|
||||||
|
|
||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
|
self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
|
||||||
self.xmpp.del_filter('out', self._filter_add_caps)
|
self.xmpp.del_filter('out', self._filter_add_caps)
|
||||||
@@ -135,17 +138,22 @@ class XEP_0115(BasePlugin):
|
|||||||
|
|
||||||
def _process_caps(self, pres):
|
def _process_caps(self, pres):
|
||||||
if not pres['caps']['hash']:
|
if not pres['caps']['hash']:
|
||||||
log.debug("Received unsupported legacy caps.")
|
log.debug("Received unsupported legacy caps: %s, %s, %s",
|
||||||
|
pres['caps']['node'],
|
||||||
|
pres['caps']['ver'],
|
||||||
|
pres['caps']['ext'])
|
||||||
self.xmpp.event('entity_caps_legacy', pres)
|
self.xmpp.event('entity_caps_legacy', pres)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
ver = pres['caps']['ver']
|
||||||
|
|
||||||
existing_verstring = self.get_verstring(pres['from'].full)
|
existing_verstring = self.get_verstring(pres['from'].full)
|
||||||
if str(existing_verstring) == str(pres['caps']['ver']):
|
if str(existing_verstring) == str(ver):
|
||||||
return
|
return
|
||||||
|
|
||||||
existing_caps = self.get_caps(verstring=pres['caps']['ver'])
|
existing_caps = self.get_caps(verstring=ver)
|
||||||
if existing_caps is not None:
|
if existing_caps is not None:
|
||||||
self.assign_verstring(pres['from'], pres['caps']['ver'])
|
self.assign_verstring(pres['from'], ver)
|
||||||
return
|
return
|
||||||
|
|
||||||
if pres['caps']['hash'] not in self.hashes:
|
if pres['caps']['hash'] not in self.hashes:
|
||||||
@@ -156,9 +164,16 @@ class XEP_0115(BasePlugin):
|
|||||||
except XMPPError:
|
except XMPPError:
|
||||||
return
|
return
|
||||||
|
|
||||||
log.debug("New caps verification string: %s", pres['caps']['ver'])
|
# Only lookup the same caps once at a time.
|
||||||
|
with self._processing_lock:
|
||||||
|
if ver in self._processing:
|
||||||
|
log.debug('Already processing verstring %s' % ver)
|
||||||
|
return
|
||||||
|
self._processing.add(ver)
|
||||||
|
|
||||||
|
log.debug("New caps verification string: %s", ver)
|
||||||
try:
|
try:
|
||||||
node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
|
node = '%s#%s' % (pres['caps']['node'], ver)
|
||||||
caps = self.xmpp['xep_0030'].get_info(pres['from'], node)
|
caps = self.xmpp['xep_0030'].get_info(pres['from'], node)
|
||||||
|
|
||||||
if isinstance(caps, Iq):
|
if isinstance(caps, Iq):
|
||||||
@@ -168,7 +183,10 @@ class XEP_0115(BasePlugin):
|
|||||||
pres['caps']['ver']):
|
pres['caps']['ver']):
|
||||||
self.assign_verstring(pres['from'], pres['caps']['ver'])
|
self.assign_verstring(pres['from'], pres['caps']['ver'])
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
log.debug("Could not retrieve disco#info results for caps")
|
log.debug("Could not retrieve disco#info results for caps for %s", node)
|
||||||
|
|
||||||
|
with self._processing_lock:
|
||||||
|
self._processing.remove(ver)
|
||||||
|
|
||||||
def _validate_caps(self, caps, hash, check_verstring):
|
def _validate_caps(self, caps, hash, check_verstring):
|
||||||
# Check Identities
|
# Check Identities
|
||||||
@@ -179,7 +197,6 @@ class XEP_0115(BasePlugin):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Check Features
|
# Check Features
|
||||||
|
|
||||||
full_features = caps.get_features(dedupe=False)
|
full_features = caps.get_features(dedupe=False)
|
||||||
deduped_features = caps.get_features()
|
deduped_features = caps.get_features()
|
||||||
if len(full_features) != len(deduped_features):
|
if len(full_features) != len(deduped_features):
|
||||||
@@ -190,29 +207,32 @@ class XEP_0115(BasePlugin):
|
|||||||
form_types = []
|
form_types = []
|
||||||
deduped_form_types = set()
|
deduped_form_types = set()
|
||||||
for stanza in caps['substanzas']:
|
for stanza in caps['substanzas']:
|
||||||
if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
|
if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
|
||||||
if 'FORM_TYPE' in stanza['fields']:
|
log.debug("Non form extension found, ignoring for caps")
|
||||||
f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
|
caps.xml.remove(stanza.xml)
|
||||||
form_types.append(f_type)
|
continue
|
||||||
deduped_form_types.add(f_type)
|
if 'FORM_TYPE' in stanza['fields']:
|
||||||
if len(form_types) != len(deduped_form_types):
|
f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
|
||||||
log.debug("Duplicated FORM_TYPE values, " + \
|
form_types.append(f_type)
|
||||||
"invalid for caps")
|
deduped_form_types.add(f_type)
|
||||||
|
if len(form_types) != len(deduped_form_types):
|
||||||
|
log.debug("Duplicated FORM_TYPE values, " + \
|
||||||
|
"invalid for caps")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(f_type) > 1:
|
||||||
|
deduped_type = set(f_type)
|
||||||
|
if len(f_type) != len(deduped_type):
|
||||||
|
log.debug("Extra FORM_TYPE data, invalid for caps")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if len(f_type) > 1:
|
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
|
||||||
deduped_type = set(f_type)
|
log.debug("Field FORM_TYPE type not 'hidden', " + \
|
||||||
if len(f_type) != len(deduped_type):
|
"ignoring form for caps")
|
||||||
log.debug("Extra FORM_TYPE data, invalid for caps")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
|
|
||||||
log.debug("Field FORM_TYPE type not 'hidden', " + \
|
|
||||||
"ignoring form for caps")
|
|
||||||
caps.xml.remove(stanza.xml)
|
|
||||||
else:
|
|
||||||
log.debug("No FORM_TYPE found, ignoring form for caps")
|
|
||||||
caps.xml.remove(stanza.xml)
|
caps.xml.remove(stanza.xml)
|
||||||
|
else:
|
||||||
|
log.debug("No FORM_TYPE found, ignoring form for caps")
|
||||||
|
caps.xml.remove(stanza.xml)
|
||||||
|
|
||||||
verstring = self.generate_verstring(caps, hash)
|
verstring = self.generate_verstring(caps, hash)
|
||||||
if verstring != check_verstring:
|
if verstring != check_verstring:
|
||||||
@@ -272,7 +292,7 @@ class XEP_0115(BasePlugin):
|
|||||||
binary = hash(S.encode('utf8')).digest()
|
binary = hash(S.encode('utf8')).digest()
|
||||||
return base64.b64encode(binary).decode('utf-8')
|
return base64.b64encode(binary).decode('utf-8')
|
||||||
|
|
||||||
def update_caps(self, jid=None, node=None):
|
def update_caps(self, jid=None, node=None, preserve=False):
|
||||||
try:
|
try:
|
||||||
info = self.xmpp['xep_0030'].get_info(jid, node, local=True)
|
info = self.xmpp['xep_0030'].get_info(jid, node, local=True)
|
||||||
if isinstance(info, Iq):
|
if isinstance(info, Iq):
|
||||||
@@ -286,19 +306,11 @@ class XEP_0115(BasePlugin):
|
|||||||
self.assign_verstring(jid, ver)
|
self.assign_verstring(jid, ver)
|
||||||
|
|
||||||
if self.xmpp.session_started_event.is_set() and self.broadcast:
|
if self.xmpp.session_started_event.is_set() and self.broadcast:
|
||||||
# Check if we've sent directed presence. If we haven't, we
|
if self.xmpp.is_component or preserve:
|
||||||
# can just send a normal presence stanza. If we have, then
|
|
||||||
# we will send presence to each contact individually so
|
|
||||||
# that we don't clobber existing statuses.
|
|
||||||
directed = False
|
|
||||||
for contact in self.xmpp.roster[jid]:
|
|
||||||
if self.xmpp.roster[jid][contact].last_status is not None:
|
|
||||||
directed = True
|
|
||||||
if not directed:
|
|
||||||
self.xmpp.roster[jid].send_last_presence()
|
|
||||||
else:
|
|
||||||
for contact in self.xmpp.roster[jid]:
|
for contact in self.xmpp.roster[jid]:
|
||||||
self.xmpp.roster[jid][contact].send_last_presence()
|
self.xmpp.roster[jid][contact].send_last_presence()
|
||||||
|
else:
|
||||||
|
self.xmpp.roster[jid].send_last_presence()
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
16
sleekxmpp/plugins/xep_0152/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0152/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.xep_0152 import stanza
|
||||||
|
from sleekxmpp.plugins.xep_0152.stanza import Reachability
|
||||||
|
from sleekxmpp.plugins.xep_0152.reachability import XEP_0152
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0152)
|
||||||
93
sleekxmpp/plugins/xep_0152/reachability.py
Normal file
93
sleekxmpp/plugins/xep_0152/reachability.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sleekxmpp.plugins.base import BasePlugin
|
||||||
|
from sleekxmpp.plugins.xep_0152 import stanza, Reachability
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0152(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
XEP-0152: Reachability Addresses
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0152'
|
||||||
|
description = 'XEP-0152: Reachability Addresses'
|
||||||
|
dependencies = set(['xep_0163'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace)
|
||||||
|
self.xmpp['xep_0163'].remove_interest(Reachability.namespace)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0163'].register_pep('reachability', Reachability)
|
||||||
|
|
||||||
|
def publish_reachability(self, addresses, options=None,
|
||||||
|
ifrom=None, block=True, callback=None, timeout=None):
|
||||||
|
"""
|
||||||
|
Publish alternative addresses where the user can be reached.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
addresses -- A list of dictionaries containing the URI and
|
||||||
|
optional description for each address.
|
||||||
|
options -- Optional form of publish options.
|
||||||
|
ifrom -- Specify the sender's JID.
|
||||||
|
block -- Specify if the send call will block until a response
|
||||||
|
is received, or a timeout occurs. Defaults to True.
|
||||||
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
|
before exiting the send call if blocking is used.
|
||||||
|
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
|
callback -- Optional reference to a stream handler function. Will
|
||||||
|
be executed when a reply stanza is received.
|
||||||
|
"""
|
||||||
|
if not isinstance(addresses, (list, tuple)):
|
||||||
|
addresses = [addresses]
|
||||||
|
reach = Reachability()
|
||||||
|
for address in addresses:
|
||||||
|
if not hasattr(address, 'items'):
|
||||||
|
address = {'uri': address}
|
||||||
|
|
||||||
|
addr = stanza.Address()
|
||||||
|
for key, val in address.items():
|
||||||
|
addr[key] = val
|
||||||
|
reach.append(addr)
|
||||||
|
return self.xmpp['xep_0163'].publish(reach,
|
||||||
|
node=Reachability.namespace,
|
||||||
|
options=options,
|
||||||
|
ifrom=ifrom,
|
||||||
|
block=block,
|
||||||
|
callback=callback,
|
||||||
|
timeout=timeout)
|
||||||
|
|
||||||
|
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
|
||||||
|
"""
|
||||||
|
Clear existing user activity information to stop notifications.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
ifrom -- Specify the sender's JID.
|
||||||
|
block -- Specify if the send call will block until a response
|
||||||
|
is received, or a timeout occurs. Defaults to True.
|
||||||
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
|
before exiting the send call if blocking is used.
|
||||||
|
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
|
callback -- Optional reference to a stream handler function. Will
|
||||||
|
be executed when a reply stanza is received.
|
||||||
|
"""
|
||||||
|
reach = Reachability()
|
||||||
|
return self.xmpp['xep_0163'].publish(reach,
|
||||||
|
node=Reachability.namespace,
|
||||||
|
ifrom=ifrom,
|
||||||
|
block=block,
|
||||||
|
callback=callback,
|
||||||
|
timeout=timeout)
|
||||||
29
sleekxmpp/plugins/xep_0152/stanza.py
Normal file
29
sleekxmpp/plugins/xep_0152/stanza.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"""
|
||||||
|
SleekXMPP: The Sleek XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class Reachability(ElementBase):
|
||||||
|
name = 'reach'
|
||||||
|
namespace = 'urn:xmpp:reach:0'
|
||||||
|
plugin_attrib = 'reach'
|
||||||
|
interfaces = set()
|
||||||
|
|
||||||
|
|
||||||
|
class Address(ElementBase):
|
||||||
|
name = 'addr'
|
||||||
|
namespace = 'urn:xmpp:reach:0'
|
||||||
|
plugin_attrib = 'address'
|
||||||
|
plugin_multi_attrib = 'addresses'
|
||||||
|
interfaces = set(['uri', 'desc'])
|
||||||
|
lang_interfaces = set(['desc'])
|
||||||
|
sub_interfaces = set(['desc'])
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(Reachability, Address, iterable=True)
|
||||||
@@ -10,12 +10,9 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from sleekxmpp import JID
|
|
||||||
from sleekxmpp.stanza import Presence
|
from sleekxmpp.stanza import Presence
|
||||||
from sleekxmpp.exceptions import XMPPError
|
from sleekxmpp.exceptions import XMPPError
|
||||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
|
||||||
from sleekxmpp.xmlstream.handler import Callback
|
|
||||||
from sleekxmpp.plugins.base import BasePlugin
|
from sleekxmpp.plugins.base import BasePlugin
|
||||||
from sleekxmpp.plugins.xep_0153 import stanza, VCardTempUpdate
|
from sleekxmpp.plugins.xep_0153 import stanza, VCardTempUpdate
|
||||||
|
|
||||||
@@ -78,8 +75,17 @@ class XEP_0153(BasePlugin):
|
|||||||
self.xmpp.roster[jid].send_last_presence()
|
self.xmpp.roster[jid].send_last_presence()
|
||||||
|
|
||||||
def _start(self, event):
|
def _start(self, event):
|
||||||
vcard = self.xmpp['xep_0054'].get_vcard()
|
try:
|
||||||
self._allow_advertising.set()
|
vcard = self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
|
||||||
|
data = vcard['vcard_temp']['PHOTO']['BINVAL']
|
||||||
|
if not data:
|
||||||
|
new_hash = ''
|
||||||
|
else:
|
||||||
|
new_hash = hashlib.sha1(data).hexdigest()
|
||||||
|
self.api['set_hash'](self.xmpp.boundjid, args=new_hash)
|
||||||
|
self._allow_advertising.set()
|
||||||
|
except XMPPError:
|
||||||
|
log.debug('Could not retrieve vCard for %s' % self.xmpp.boundjid.bare)
|
||||||
|
|
||||||
def _end(self, event):
|
def _end(self, event):
|
||||||
self._allow_advertising.clear()
|
self._allow_advertising.clear()
|
||||||
@@ -118,6 +124,13 @@ class XEP_0153(BasePlugin):
|
|||||||
log.debug('Could not retrieve vCard for %s' % jid)
|
log.debug('Could not retrieve vCard for %s' % jid)
|
||||||
|
|
||||||
def _recv_presence(self, pres):
|
def _recv_presence(self, pres):
|
||||||
|
try:
|
||||||
|
if pres['muc']['affiliation']:
|
||||||
|
# Don't process vCard avatars for MUC occupants
|
||||||
|
# since they all share the same bare JID.
|
||||||
|
return
|
||||||
|
except: pass
|
||||||
|
|
||||||
if not pres.match('presence/vcard_temp_update'):
|
if not pres.match('presence/vcard_temp_update'):
|
||||||
self.api['set_hash'](pres['from'], args=None)
|
self.api['set_hash'](pres['from'], args=None)
|
||||||
return
|
return
|
||||||
@@ -125,7 +138,7 @@ class XEP_0153(BasePlugin):
|
|||||||
data = pres['vcard_temp_update']['photo']
|
data = pres['vcard_temp_update']['photo']
|
||||||
if data is None:
|
if data is None:
|
||||||
return
|
return
|
||||||
elif data == '' or data != self.api['get_hash'](pres['to']):
|
elif data == '' or data != self.api['get_hash'](pres['from']):
|
||||||
ifrom = pres['to'] if self.xmpp.is_component else None
|
ifrom = pres['to'] if self.xmpp.is_component else None
|
||||||
self.api['reset_hash'](pres['from'], ifrom=ifrom)
|
self.api['reset_hash'](pres['from'], ifrom=ifrom)
|
||||||
self.xmpp.event('vcard_avatar_update', pres)
|
self.xmpp.event('vcard_avatar_update', pres)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user