Compare commits
232 Commits
exp_idle_c
...
sleek-merg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4512248901 | ||
|
|
e42d651d7e | ||
|
|
4305eddb4f | ||
|
|
c2dc44cfd1 | ||
|
|
5fc14de32e | ||
|
|
d245558fd5 | ||
|
|
9d45370e8a | ||
|
|
cc1cc61d36 | ||
|
|
c6740a4908 | ||
|
|
55114bcffe | ||
|
|
4fa5dedc47 | ||
|
|
5525ef2285 | ||
|
|
a7ac969215 | ||
|
|
329cb5a9f8 | ||
|
|
d9b47b33f5 | ||
|
|
3582ac9941 | ||
|
|
2a127a57a7 | ||
|
|
7059400020 | ||
|
|
0b14ef82d4 | ||
|
|
83953af53d | ||
|
|
110cf25c6d | ||
|
|
f2bf6072ec | ||
|
|
5f9abe2e0e | ||
|
|
ea65b672e7 | ||
|
|
93c705fb31 | ||
|
|
0724f623bb | ||
|
|
82e549c0e9 | ||
|
|
1aa15792b4 | ||
|
|
ffb2b6bc04 | ||
|
|
27f98bf22c | ||
|
|
3978078710 | ||
|
|
00a0698720 | ||
|
|
4a24f58be2 | ||
|
|
da14ce16ec | ||
|
|
18e5abb9dd | ||
|
|
1a75b76916 | ||
|
|
53b56899a0 | ||
|
|
804b23d390 | ||
|
|
04eaf52b1d | ||
|
|
dc7fef1064 | ||
|
|
488c433555 | ||
|
|
9c5dd024b1 | ||
|
|
6e61adf3db | ||
|
|
041bd63864 | ||
|
|
a366482551 | ||
|
|
a721084f6e | ||
|
|
1b4187fa56 | ||
|
|
cf7a60705e | ||
|
|
349b05b9b7 | ||
|
|
9fbacf377a | ||
|
|
2da9e35cbc | ||
|
|
8adc8fa2ba | ||
|
|
9efa909dfc | ||
|
|
7f21fdbe26 | ||
|
|
f9c7fa92ea | ||
|
|
e75a160d52 | ||
|
|
170bd51387 | ||
|
|
abcec1e2d3 | ||
|
|
eeab646bfa | ||
|
|
2c69144189 | ||
|
|
f54ebec654 | ||
|
|
2ce931cb7a | ||
|
|
84eddd2ed2 | ||
|
|
2042e1a4d5 | ||
|
|
be14f0cc52 | ||
|
|
edd9199be8 | ||
|
|
bb094cc649 | ||
|
|
dbaa6ed952 | ||
|
|
8c94d894ab | ||
|
|
ffc7eac4dc | ||
|
|
555fd6d926 | ||
|
|
c024ac8f0b | ||
|
|
f00177c0cf | ||
|
|
d0ad25745a | ||
|
|
55be23a6da | ||
|
|
75ba283572 | ||
|
|
f7164d35d2 | ||
|
|
4afbb0322b | ||
|
|
7bce1ecc8a | ||
|
|
bbce16d526 | ||
|
|
c29fc39ef1 | ||
|
|
8335c08782 | ||
|
|
224d7ae133 | ||
|
|
04bff00171 | ||
|
|
f3e31baf04 | ||
|
|
9b25a7cf77 | ||
|
|
7a908ac07b | ||
|
|
92901637ec | ||
|
|
3590b663ed | ||
|
|
a33bde9cc3 | ||
|
|
ac50fdccfc | ||
|
|
a0c6bf15e9 | ||
|
|
a2852eb249 | ||
|
|
f1e6d6b0a9 | ||
|
|
116a33ba51 | ||
|
|
a8ac115310 | ||
|
|
1345b7c1d0 | ||
|
|
d60a652259 | ||
|
|
61a7cecb31 | ||
|
|
192b7e0349 | ||
|
|
80b60fc048 | ||
|
|
b8d7b9520c | ||
|
|
0305ce66b7 | ||
|
|
474405ab90 | ||
|
|
4415d3be1a | ||
|
|
058c530787 | ||
|
|
766d0dfd40 | ||
|
|
ac31913a65 | ||
|
|
d34ddf33db | ||
|
|
eb4e09b0ca | ||
|
|
ce085bf4f4 | ||
|
|
990113f8e7 | ||
|
|
aa022204ee | ||
|
|
c1f23b566b | ||
|
|
45f7cb8bda | ||
|
|
bdb1f66ac9 | ||
|
|
842157a6cc | ||
|
|
a63cc01482 | ||
|
|
1bbb6f3ff9 | ||
|
|
93894247a4 | ||
|
|
16bb5e2537 | ||
|
|
d19a6e05b2 | ||
|
|
86e85f9835 | ||
|
|
cc145d20b0 | ||
|
|
881d9040c4 | ||
|
|
1e77ea0944 | ||
|
|
140f0885b2 | ||
|
|
83f71a6610 | ||
|
|
271343a32d | ||
|
|
48857b0030 | ||
|
|
1fe7f5f4e6 | ||
|
|
d5b1904ebb | ||
|
|
b6b0e82dec | ||
|
|
632b7b4afe | ||
|
|
81b7b2c190 | ||
|
|
460de7d301 | ||
|
|
69022c6db7 | ||
|
|
0ef3fa2703 | ||
|
|
8da269de88 | ||
|
|
93ce318259 | ||
|
|
997928de91 | ||
|
|
83d00a5913 | ||
|
|
bf5d7c83af | ||
|
|
c66a4d4097 | ||
|
|
e112e86475 | ||
|
|
e034b31d6b | ||
|
|
18a4978456 | ||
|
|
17464b10a4 | ||
|
|
6fb3ecd414 | ||
|
|
c214e4f037 | ||
|
|
2ee05d9616 | ||
|
|
f795ac02e3 | ||
|
|
6e8235544c | ||
|
|
6e35948276 | ||
|
|
4da870fd19 | ||
|
|
cd7ff685fb | ||
|
|
1e4944d47e | ||
|
|
e68135f59f | ||
|
|
6408c5a747 | ||
|
|
115fe954ac | ||
|
|
3d243f7da5 | ||
|
|
ea5615f236 | ||
|
|
69da1c1d7c | ||
|
|
e85fa4203e | ||
|
|
506ca69917 | ||
|
|
8ac0ecdf40 | ||
|
|
dbd8115557 | ||
|
|
74b4ea20bf | ||
|
|
11fbaa4241 | ||
|
|
8fd0d7c993 | ||
|
|
1450d36377 | ||
|
|
06358d0665 | ||
|
|
2b3b86e281 | ||
|
|
92e4bc752a | ||
|
|
ffb2e05f21 | ||
|
|
1e2665df19 | ||
|
|
4d063e287e | ||
|
|
44f02fb3ab | ||
|
|
f6b3a0c6cf | ||
|
|
8b36e918e8 | ||
|
|
9044807121 | ||
|
|
24264d3a07 | ||
|
|
8bc70264ef | ||
|
|
957c635fb7 | ||
|
|
4027927c6e | ||
|
|
c16b862200 | ||
|
|
a96f608469 | ||
|
|
e1f25604ec | ||
|
|
0fe057b5c3 | ||
|
|
be76dda21d | ||
|
|
ecd124dd06 | ||
|
|
4a8951c4ee | ||
|
|
8afba7de85 | ||
|
|
1ce42d3a2f | ||
|
|
2f4d811db4 | ||
|
|
61127f521d | ||
|
|
62eefdbd6a | ||
|
|
225e07eb64 | ||
|
|
063e73c0d2 | ||
|
|
d261318e1a | ||
|
|
d33cc00fe9 | ||
|
|
27582f6fd2 | ||
|
|
e328ff4833 | ||
|
|
403462fdb8 | ||
|
|
f22d8e67b4 | ||
|
|
35f33f1614 | ||
|
|
c9f8ddff65 | ||
|
|
f5ae98aaf1 | ||
|
|
073e85381a | ||
|
|
afc939708f | ||
|
|
aabec8b993 | ||
|
|
e5e2fbb16b | ||
|
|
3dd379cdf1 | ||
|
|
a20582aba4 | ||
|
|
09cdbf1b76 | ||
|
|
ca306e7cec | ||
|
|
1bf34f7fe6 | ||
|
|
4144d60017 | ||
|
|
7265682a4d | ||
|
|
08c62a6bf1 | ||
|
|
d61f1cd035 | ||
|
|
1063feb33b | ||
|
|
79f3c1ac8f | ||
|
|
a5c03b763a | ||
|
|
3670d82f1c | ||
|
|
e94a73553d | ||
|
|
577fd71472 | ||
|
|
ef1c4368d0 | ||
|
|
48def71d0c | ||
|
|
c8c20fff71 | ||
|
|
75a18b5ffe | ||
|
|
ea3d39b50e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ slixmpp.egg-info/
|
|||||||
*~
|
*~
|
||||||
.baboon/
|
.baboon/
|
||||||
.DS_STORE
|
.DS_STORE
|
||||||
|
.idea/
|
||||||
|
|||||||
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- "2.6"
|
||||||
|
- "2.7"
|
||||||
|
- "3.2"
|
||||||
|
- "3.3"
|
||||||
|
- "3.4"
|
||||||
|
install:
|
||||||
|
- "pip install ."
|
||||||
|
script: testall.py
|
||||||
5
INSTALL
5
INSTALL
@@ -1,5 +1,6 @@
|
|||||||
Pre-requisites:
|
Pre-requisites:
|
||||||
- Python 3.1 or 2.6
|
- Python 3.4
|
||||||
|
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
|
||||||
|
|
||||||
Install:
|
Install:
|
||||||
> python3 setup.py install
|
> python3 setup.py install
|
||||||
@@ -9,4 +10,4 @@ Root install:
|
|||||||
|
|
||||||
To test:
|
To test:
|
||||||
> cd examples
|
> cd examples
|
||||||
> python echo_client.py -v -j [USER@example.com] -p [PASSWORD]
|
> python3 echo_client.py -d -j [USER@example.com] -p [PASSWORD]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
include README.rst
|
include README.rst
|
||||||
include LICENSE
|
include LICENSE
|
||||||
include testall.py
|
include run_tests.py
|
||||||
|
include slixmpp/stringprep.pyx
|
||||||
recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png
|
recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png
|
||||||
recursive-include examples *.py
|
recursive-include examples *.py
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ Slixmpp's goals is to only rewrite the core of the library (the low level
|
|||||||
socket handling, the timers, the events dispatching) in order to remove all
|
socket handling, the timers, the events dispatching) in order to remove all
|
||||||
threads.
|
threads.
|
||||||
|
|
||||||
|
|
||||||
Documentation and Testing
|
Documentation and Testing
|
||||||
-------------------------
|
-------------------------
|
||||||
Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``.
|
Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``.
|
||||||
@@ -21,7 +20,7 @@ be in ``docs/_build/html``::
|
|||||||
|
|
||||||
To run the test suite for Slixmpp::
|
To run the test suite for Slixmpp::
|
||||||
|
|
||||||
python testall.py
|
python run_tests.py
|
||||||
|
|
||||||
|
|
||||||
The Slixmpp Boilerplate
|
The Slixmpp Boilerplate
|
||||||
@@ -88,7 +87,7 @@ Slixmpp projects::
|
|||||||
|
|
||||||
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
||||||
xmpp.connect()
|
xmpp.connect()
|
||||||
xmpp.process(block=True)
|
xmpp.process(forever=True)
|
||||||
|
|
||||||
|
|
||||||
Slixmpp Credits
|
Slixmpp Credits
|
||||||
@@ -97,8 +96,8 @@ Slixmpp Credits
|
|||||||
**Maintainer of the slixmpp fork:** Florent Le Coz
|
**Maintainer of the slixmpp fork:** Florent Le Coz
|
||||||
`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_,
|
`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_,
|
||||||
|
|
||||||
Credits
|
Credits (SleekXMPP)
|
||||||
-------
|
-------------------
|
||||||
|
|
||||||
**Main Author:** Nathan Fritz
|
**Main Author:** Nathan Fritz
|
||||||
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
|
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
|
||||||
|
|||||||
8
docs/api/stanza/iq.rst
Normal file
8
docs/api/stanza/iq.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
IQ Stanza
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. module:: slixmpp.stanza
|
||||||
|
|
||||||
|
.. autoclass:: Iq
|
||||||
|
:members:
|
||||||
|
|
||||||
7
docs/api/stanza/message.rst
Normal file
7
docs/api/stanza/message.rst
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Message Stanza
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. module:: slixmpp.stanza
|
||||||
|
|
||||||
|
.. autoclass:: Message
|
||||||
|
:members:
|
||||||
8
docs/api/stanza/presence.rst
Normal file
8
docs/api/stanza/presence.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Presence Stanza
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. module:: slixmpp.stanza
|
||||||
|
|
||||||
|
.. autoclass:: Presence
|
||||||
|
:members:
|
||||||
|
|
||||||
8
docs/api/stanza/rootstanza.rst
Normal file
8
docs/api/stanza/rootstanza.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Root Stanza
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. module:: slixmpp.stanza.rootstanza
|
||||||
|
|
||||||
|
.. autoclass:: RootStanza
|
||||||
|
:members:
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
.. module:: slixmpp.xmlstream.filesocket
|
|
||||||
|
|
||||||
.. _filesocket:
|
|
||||||
|
|
||||||
Python 2.6 File Socket Shims
|
|
||||||
============================
|
|
||||||
|
|
||||||
.. autoclass:: FileSocket
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: Socket26
|
|
||||||
:members:
|
|
||||||
@@ -10,15 +10,19 @@ The Basic Handler
|
|||||||
|
|
||||||
Callback
|
Callback
|
||||||
--------
|
--------
|
||||||
.. module:: slixmpp.xmlstream.handler.callback
|
.. module:: slixmpp.xmlstream.handler
|
||||||
|
|
||||||
.. autoclass:: Callback
|
.. autoclass:: Callback
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
CoroutineCallback
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. autoclass:: CoroutineCallback
|
||||||
|
:members:
|
||||||
|
|
||||||
Waiter
|
Waiter
|
||||||
------
|
------
|
||||||
.. module:: slixmpp.xmlstream.handler.waiter
|
|
||||||
|
|
||||||
.. autoclass:: Waiter
|
.. autoclass:: Waiter
|
||||||
:members:
|
:members:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Jabber IDs (JID)
|
Jabber IDs (JID)
|
||||||
=================
|
=================
|
||||||
|
|
||||||
.. module:: slixmpp.xmlstream.jid
|
.. module:: slixmpp.jid
|
||||||
|
|
||||||
.. autoclass:: JID
|
.. autoclass:: JID
|
||||||
:members:
|
:members:
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
=========
|
|
||||||
Scheduler
|
|
||||||
=========
|
|
||||||
|
|
||||||
.. module:: slixmpp.xmlstream.scheduler
|
|
||||||
|
|
||||||
.. autoclass:: Task
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: Scheduler
|
|
||||||
:members:
|
|
||||||
@@ -61,8 +61,8 @@ interacting with a given :term:`stanza` a :term:`stanza object`.
|
|||||||
To make dealing with more complicated and nested :term:`stanzas <stanza>`
|
To make dealing with more complicated and nested :term:`stanzas <stanza>`
|
||||||
or XML chunks easier, :term:`stanza objects <stanza object>` can be
|
or XML chunks easier, :term:`stanza objects <stanza object>` can be
|
||||||
composed in two ways: as iterable child objects or as plugins. Iterable
|
composed in two ways: as iterable child objects or as plugins. Iterable
|
||||||
child stanzas, or :term:`substanzas`, are accessible through a special
|
child stanzas, or :term:`substanzas <substanza>`, are accessible through a
|
||||||
``'substanzas'`` interface. This option is useful for stanzas which
|
special ``'substanzas'`` interface. This option is useful for stanzas which
|
||||||
may contain more than one of the same kind of element. When there is
|
may contain more than one of the same kind of element. When there is
|
||||||
only one child element, the plugin method is more useful. For plugins,
|
only one child element, the plugin method is more useful. For plugins,
|
||||||
a parent stanza object delegates one of its XML child elements to the
|
a parent stanza object delegates one of its XML child elements to the
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace because that is already declared by the stream header. But, if
|
|||||||
you create a :class:`~slixmpp.stanza.message.Message` instance and dump
|
you create a :class:`~slixmpp.stanza.message.Message` instance and dump
|
||||||
it to the terminal, the ``jabber:client`` namespace will appear.
|
it to the terminal, the ``jabber:client`` namespace will appear.
|
||||||
|
|
||||||
.. autofunction:: tostring
|
.. autofunction:: slixmpp.xmlstream.tostring
|
||||||
|
|
||||||
Escaping Special Characters
|
Escaping Special Characters
|
||||||
---------------------------
|
---------------------------
|
||||||
@@ -43,4 +43,5 @@ In the future, the use of CDATA sections may be allowed to reduce the
|
|||||||
size of escaped text or for when other XMPP processing agents do not
|
size of escaped text or for when other XMPP processing agents do not
|
||||||
undertand these entities.
|
undertand these entities.
|
||||||
|
|
||||||
.. autofunction:: xml_escape
|
..
|
||||||
|
autofunction:: xml_escape
|
||||||
|
|||||||
@@ -24,21 +24,20 @@ patterns is received; these callbacks are also referred to as :term:`stream
|
|||||||
handlers <stream handler>`. The class also provides a basic eventing system
|
handlers <stream handler>`. The class also provides a basic eventing system
|
||||||
which can be triggered either manually or on a timed schedule.
|
which can be triggered either manually or on a timed schedule.
|
||||||
|
|
||||||
The Main Threads
|
The event loop
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
:class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances run using at
|
:class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the
|
||||||
least three background threads: the send thread, the read thread, and the
|
:class:`asyncio.BaseProtocol` class, and therefore do not have to handle
|
||||||
scheduler thread. The send thread is in charge of monitoring the send queue
|
reads and writes directly, but receive data through
|
||||||
and writing text to the outgoing XML stream. The read thread pulls text off
|
:meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write
|
||||||
of the incoming XML stream and stores the results in an event queue. The
|
data in the socket transport.
|
||||||
scheduler thread is used to emit events after a given period of time.
|
|
||||||
|
|
||||||
Additionally, the main event processing loop may be executed in its
|
Upon receiving data, :term:`stream handlers <stream handler>` are run
|
||||||
own thread if Slixmpp is being used in the background for another
|
immediately, except if they are coroutines, in which case they are
|
||||||
application.
|
scheduled using :meth:`asyncio.async`.
|
||||||
|
|
||||||
Short-lived threads may also be spawned as requested for threaded
|
:term:`Event handlers <event handler>` (which are called inside
|
||||||
:term:`event handlers <event handler>`.
|
:term:`stream handlers <stream handler>`) work the same way.
|
||||||
|
|
||||||
How XML Text is Turned into Action
|
How XML Text is Turned into Action
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@@ -53,7 +52,7 @@ when this bit of XML is received (with an assumed namespace of
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|
||||||
1. **Convert XML strings into objects.**
|
#. **Convert XML strings into objects.**
|
||||||
|
|
||||||
Incoming text is parsed and converted into XML objects (using
|
Incoming text is parsed and converted into XML objects (using
|
||||||
ElementTree) which are then wrapped into what are referred to as
|
ElementTree) which are then wrapped into what are referred to as
|
||||||
@@ -66,65 +65,43 @@ when this bit of XML is received (with an assumed namespace of
|
|||||||
``{jabber:client}message`` is associated with the class
|
``{jabber:client}message`` is associated with the class
|
||||||
:class:`~slixmpp.stanza.Message`.
|
:class:`~slixmpp.stanza.Message`.
|
||||||
|
|
||||||
2. **Match stanza objects to callbacks.**
|
#. **Match stanza objects to callbacks.**
|
||||||
|
|
||||||
These objects are then compared against the stored patterns associated
|
These objects are then compared against the stored patterns associated
|
||||||
with the registered callback handlers. For each match, a copy of the
|
with the registered callback handlers.
|
||||||
:term:`stanza object` is paired with a reference to the handler and
|
|
||||||
placed into the event queue.
|
|
||||||
|
|
||||||
Our :class:`~slixmpp.stanza.Message` object is thus paired with the message stanza handler
|
Each handler matching our :term:`stanza object` is then added to a list.
|
||||||
:meth:`BaseXMPP._handle_message` to create the tuple::
|
|
||||||
|
|
||||||
('stanza', stanza_obj, handler)
|
#. **Processing callbacks**
|
||||||
|
|
||||||
3. **Process the event queue.**
|
Every handler in the list is then called with the :term:`stanza object`
|
||||||
|
as a parameter; if the handler is a
|
||||||
|
:class:`~slixmpp.xmlstream.handler.CoroutineCallback`
|
||||||
|
then it will be scheduled in the event loop using :meth:`asyncio.async`
|
||||||
|
instead of run.
|
||||||
|
|
||||||
The event queue is the heart of Slixmpp. Nearly every action that
|
#. **Raise Custom Events**
|
||||||
takes place is first inserted into this queue, whether that be received
|
|
||||||
stanzas, custom events, or scheduled events.
|
|
||||||
|
|
||||||
When the stanza is pulled out of the event queue with an associated
|
|
||||||
callback, the callback function is executed with the stanza as its only
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
The callback, aka :term:`stream handler`, is executed in the main event
|
|
||||||
processing thread. If the handler blocks, event processing will also
|
|
||||||
block.
|
|
||||||
|
|
||||||
4. **Raise Custom Events**
|
|
||||||
|
|
||||||
Since a :term:`stream handler` shouldn't block, if extensive processing
|
Since a :term:`stream handler` shouldn't block, if extensive processing
|
||||||
for a stanza is required (such as needing to send and receive an
|
for a stanza is required (such as needing to send and receive an
|
||||||
:class:`~slixmpp.stanza.Iq` stanza), then custom events must be used.
|
:class:`~slixmpp.stanza.Iq` stanza), then custom events must be used.
|
||||||
These events are not explicitly tied to the incoming XML stream and may
|
These events are not explicitly tied to the incoming XML stream and may
|
||||||
be raised at any time. Importantly, these events may be handled in their
|
be raised at any time.
|
||||||
own thread.
|
|
||||||
|
|
||||||
When the event is raised, a copy of the stanza is created for each
|
In contrast to :term:`stream handlers <stream handler>`, these functions
|
||||||
handler registered for the event. In contrast to :term:`stream handlers
|
are referred to as :term:`event handlers <event handler>`.
|
||||||
<stream handler>`, these functions are referred to as :term:`event
|
|
||||||
handlers <event handler>`. Each stanza/handler pair is then put into the
|
|
||||||
event queue.
|
|
||||||
|
|
||||||
The code for :meth:`BaseXMPP._handle_message` follows this pattern, and
|
The code for :meth:`BaseXMPP._handle_message` follows this pattern, and
|
||||||
raises a ``'message'`` event::
|
raises a ``'message'`` event
|
||||||
|
|
||||||
self.event('message', msg)
|
.. code-block:: python
|
||||||
|
|
||||||
The event call then places the message object back into the event queue
|
self.event('message', msg)
|
||||||
paired with an :term:`event handler`::
|
|
||||||
|
|
||||||
('event', 'message', msg_copy1, custom_event_handler_1)
|
#. **Process Custom Events**
|
||||||
('event', 'message', msg_copy2, custom_evetn_handler_2)
|
|
||||||
|
|
||||||
5. **Process Custom Events**
|
The :term:`event handlers <event handler>` are then executed, passing
|
||||||
|
the stanza as the only argument.
|
||||||
The stanza and :term:`event handler` are then pulled from the event
|
|
||||||
queue, and the handler is executed, passing the stanza as its only
|
|
||||||
argument. If the handler was registered as threaded, then a new thread
|
|
||||||
will be spawned for it.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Events may be raised without needing :term:`stanza objects <stanza object>`.
|
Events may be raised without needing :term:`stanza objects <stanza object>`.
|
||||||
@@ -135,9 +112,9 @@ when this bit of XML is received (with an assumed namespace of
|
|||||||
Finally, after a long trek, our message is handed off to the user's
|
Finally, after a long trek, our message is handed off to the user's
|
||||||
custom handler in order to do awesome stuff::
|
custom handler in order to do awesome stuff::
|
||||||
|
|
||||||
msg.reply()
|
reply = msg.reply()
|
||||||
msg['body'] = "Hey! This is awesome!"
|
reply['body'] = "Hey! This is awesome!"
|
||||||
msg.send()
|
reply.send()
|
||||||
|
|
||||||
|
|
||||||
.. index:: BaseXMPP, XMLStream
|
.. index:: BaseXMPP, XMLStream
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ html_theme = 'haiku'
|
|||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
html_title = 'Slixmpp'
|
html_title = 'slixmpp'
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
html_short_title = '%s Documentation' % release
|
html_short_title = '%s Documentation' % release
|
||||||
@@ -219,4 +219,4 @@ man_pages = [
|
|||||||
[u'Nathan Fritz, Lance Stout'], 1)
|
[u'Nathan Fritz, Lance Stout'], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
intersphinx_mapping = {'python': ('http://docs.python.org/3.2', 'python-objects.inv')}
|
intersphinx_mapping = {'python': ('http://docs.python.org/3.4', 'python-objects.inv')}
|
||||||
|
|||||||
@@ -634,8 +634,9 @@ with some additional registration fields implemented.
|
|||||||
if self.backend.register(iq['from'].bare, iq['register']):
|
if self.backend.register(iq['from'].bare, iq['register']):
|
||||||
# Successful registration
|
# Successful registration
|
||||||
self.xmpp.event('registered_user', iq)
|
self.xmpp.event('registered_user', iq)
|
||||||
iq.reply().set_payload(iq['register'].xml)
|
reply = iq.reply()
|
||||||
iq.send()
|
reply.set_payload(iq['register'].xml)
|
||||||
|
reply.send()
|
||||||
else:
|
else:
|
||||||
# Conflicting registration
|
# Conflicting registration
|
||||||
self._sendError(iq, '409', 'cancel', 'conflict',
|
self._sendError(iq, '409', 'cancel', 'conflict',
|
||||||
@@ -666,14 +667,16 @@ with some additional registration fields implemented.
|
|||||||
# Add a blank field
|
# Add a blank field
|
||||||
reg.addField(field)
|
reg.addField(field)
|
||||||
|
|
||||||
iq.reply().set_payload(reg.xml)
|
reply = iq.reply()
|
||||||
iq.send()
|
reply.set_payload(reg.xml)
|
||||||
|
reply.send()
|
||||||
|
|
||||||
def _sendError(self, iq, code, error_type, name, text=''):
|
def _sendError(self, iq, code, error_type, name, text=''):
|
||||||
iq.reply().set_payload(iq['register'].xml)
|
reply = iq.reply()
|
||||||
iq.error()
|
reply.set_payload(iq['register'].xml)
|
||||||
iq['error']['code'] = code
|
reply.error()
|
||||||
iq['error']['type'] = error_type
|
reply['error']['code'] = code
|
||||||
iq['error']['condition'] = name
|
reply['error']['type'] = error_type
|
||||||
iq['error']['text'] = text
|
reply['error']['condition'] = name
|
||||||
iq.send()
|
reply['error']['text'] = text
|
||||||
|
reply.send()
|
||||||
|
|||||||
47
docs/differences.rst
Normal file
47
docs/differences.rst
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.. _differences:
|
||||||
|
|
||||||
|
Differences from SleekXMPP
|
||||||
|
==========================
|
||||||
|
|
||||||
|
**Python 3.4+ only**
|
||||||
|
slixmpp will only work on python 3.4 and above.
|
||||||
|
|
||||||
|
**Stanza copies**
|
||||||
|
The same stanza object is given through all the handlers; a handler that
|
||||||
|
edits the stanza object should make its own copy.
|
||||||
|
|
||||||
|
**Replies**
|
||||||
|
Because stanzas are not copied anymore,
|
||||||
|
:meth:`Stanza.reply() <.StanzaBase.reply>` calls
|
||||||
|
(for :class:`IQs <.Iq>`, :class:`Messages <.Message>`, etc)
|
||||||
|
now return a new object instead of editing the stanza object
|
||||||
|
in-place.
|
||||||
|
|
||||||
|
**Block and threaded arguments**
|
||||||
|
All the functions that had a ``threaded=`` or ``block=`` argument
|
||||||
|
do not have it anymore. Also, :meth:`.Iq.send` **does not block
|
||||||
|
anymore**.
|
||||||
|
|
||||||
|
**Coroutine facilities**
|
||||||
|
**See** :ref:`using_asyncio`
|
||||||
|
|
||||||
|
If an event handler is a coroutine, it will be called asynchronously
|
||||||
|
in the event loop instead of inside the event caller.
|
||||||
|
|
||||||
|
A CoroutineCallback class has been added to create coroutine stream
|
||||||
|
handlers, which will be also handled in the event loop.
|
||||||
|
|
||||||
|
The :class:`~.slixmpp.stanza.Iq` object’s :meth:`~.slixmpp.stanza.Iq.send`
|
||||||
|
method now **always** return a :class:`~.asyncio.Future` which result will be set
|
||||||
|
to the IQ reply when it is received, or to ``None`` if the IQ is not of
|
||||||
|
type ``get`` or ``set``.
|
||||||
|
|
||||||
|
Many plugins (WIP) calls which retrieve information also return the same
|
||||||
|
future.
|
||||||
|
|
||||||
|
**Architectural differences**
|
||||||
|
slixmpp does not have an event queue anymore, and instead processes
|
||||||
|
handlers directly after receiving the XML stanza.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If you find something that doesn’t work but should, please report it.
|
||||||
@@ -152,6 +152,13 @@ Event Index
|
|||||||
Makes the contents of message stanzas available whenever one is received. Be
|
Makes the contents of message stanzas available whenever one is received. Be
|
||||||
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_error
|
||||||
|
- **Data:** :py:class:`~slixmpp.Message`
|
||||||
|
- **Source:** :py:class:`BaseXMPP <slixmpp.BaseXMPP>`
|
||||||
|
|
||||||
|
Makes the contents of message stanzas available whenever one is received.
|
||||||
|
Only handler messages with an ``error`` type.
|
||||||
|
|
||||||
message_form
|
message_form
|
||||||
- **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form`
|
- **Data:** :py:class:`~slixmpp.plugins.xep_0004.Form`
|
||||||
- **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004`
|
- **Source:** :py:class:`~slixmpp.plugins.xep_0004.xep_0004`
|
||||||
|
|||||||
@@ -7,19 +7,11 @@ Create and Run a Server Component
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
If you have any issues working through this quickstart guide
|
||||||
or the other tutorials here, please either send a message to the
|
join the chat room at `slixmpp@muc.poez.io
|
||||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip``
|
with `Git <http://git.poez.io/slixmpp>`_.
|
||||||
or ``easy_install``.
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
pip install slixmpp # Or: easy_install slixmpp
|
|
||||||
|
|
||||||
|
|
||||||
Many XMPP applications eventually graduate to requiring to run as a server
|
Many XMPP applications eventually graduate to requiring to run as a server
|
||||||
component in order to meet scalability requirements. To demonstrate how to
|
component in order to meet scalability requirements. To demonstrate how to
|
||||||
|
|||||||
@@ -7,19 +7,11 @@ Slixmpp Quickstart - Echo Bot
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
If you have any issues working through this quickstart guide
|
||||||
or the other tutorials here, please either send a message to the
|
join the chat room at `slixmpp@muc.poez.io
|
||||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip``
|
with `Git <http://git.poez.io/slixmpp>`_.
|
||||||
or ``easy_install``.
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
pip install slixmpp # Or: easy_install slixmpp
|
|
||||||
|
|
||||||
|
|
||||||
As a basic starting project, we will create an echo bot which will reply to any
|
As a basic starting project, we will create an echo bot which will reply to any
|
||||||
messages sent to it. We will also go through adding some basic command line configuration
|
messages sent to it. We will also go through adding some basic command line configuration
|
||||||
@@ -44,6 +36,7 @@ To get started, here is a brief outline of the structure that the final project
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import getpass
|
import getpass
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
@@ -59,24 +52,6 @@ To get started, here is a brief outline of the structure that the final project
|
|||||||
|
|
||||||
'''Finally, we connect the bot and start listening for messages'''
|
'''Finally, we connect the bot and start listening for messages'''
|
||||||
|
|
||||||
Default Encoding
|
|
||||||
----------------
|
|
||||||
XMPP requires support for UTF-8 and so Slixmpp must use UTF-8 as well. In
|
|
||||||
Python3 this is simple because Unicode is the default string type. For Python2.6+
|
|
||||||
the situation is not as easy because standard strings are simply byte arrays and
|
|
||||||
use ASCII. We can get Python to use UTF-8 as the default encoding by including:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
from slixmpp.util.misc_ops import setdefaultencoding
|
|
||||||
setdefaultencoding('utf8')
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Until we are able to ensure that Slixmpp will always use Unicode in Python2.6+, this
|
|
||||||
may cause issues embedding Slixmpp into other applications which assume ASCII encoding.
|
|
||||||
|
|
||||||
Creating the EchoBot Class
|
Creating the EchoBot Class
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
@@ -313,9 +288,9 @@ the ``EchoBot.__init__`` method instead.
|
|||||||
xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||||
|
|
||||||
Now we're ready to connect and begin echoing messages. If you have the package
|
Now we're ready to connect and begin echoing messages. If you have the package
|
||||||
``dnspython`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method
|
``aiodns`` installed, then the :meth:`slixmpp.clientxmpp.ClientXMPP` method
|
||||||
will perform a DNS query to find the appropriate server to connect to for the
|
will perform a DNS query to find the appropriate server to connect to for the
|
||||||
given JID. If you do not have ``dnspython``, then Slixmpp will attempt to
|
given JID. If you do not have ``aiodns``, then Slixmpp will attempt to
|
||||||
connect to the hostname used by the JID, unless an address tuple is supplied
|
connect to the hostname used by the JID, unless an address tuple is supplied
|
||||||
to :meth:`slixmpp.clientxmpp.ClientXMPP`.
|
to :meth:`slixmpp.clientxmpp.ClientXMPP`.
|
||||||
|
|
||||||
@@ -330,22 +305,6 @@ to :meth:`slixmpp.clientxmpp.ClientXMPP`.
|
|||||||
else:
|
else:
|
||||||
print('Unable to connect')
|
print('Unable to connect')
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
For Google Talk users withouth ``dnspython`` installed, the above code
|
|
||||||
should look like:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
# .. option parsing & echo bot configuration
|
|
||||||
|
|
||||||
if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
xmpp.process(block=True)
|
|
||||||
else:
|
|
||||||
print('Unable to connect')
|
|
||||||
|
|
||||||
To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process`
|
To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process`
|
||||||
which will start the event handling, send queue, and XML reader threads. It will also call
|
which will start the event handling, send queue, and XML reader threads. It will also call
|
||||||
the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By
|
the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By
|
||||||
|
|||||||
@@ -7,19 +7,11 @@ Mulit-User Chat (MUC) Bot
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
If you have any issues working through this quickstart guide
|
||||||
or the other tutorials here, please either send a message to the
|
join the chat room at `slixmpp@muc.poez.io
|
||||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip``
|
from `Git <http://git.poez.io/slixmpp>`_.
|
||||||
or ``easy_install``.
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
pip install slixmpp # Or: easy_install slixmpp
|
|
||||||
|
|
||||||
|
|
||||||
Now that you've got the basic gist of using Slixmpp by following the
|
Now that you've got the basic gist of using Slixmpp by following the
|
||||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ Enable HTTP Proxy Support
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
If you have any issues working through this quickstart guide
|
||||||
or the other tutorials here, please either send a message to the
|
join the chat room at `slixmpp@muc.poez.io
|
||||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
In some instances, you may wish to route XMPP traffic through
|
In some instances, you may wish to route XMPP traffic through
|
||||||
an HTTP proxy, probably to get around restrictive firewalls.
|
an HTTP proxy, probably to get around restrictive firewalls.
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ Sign in, Send a Message, and Disconnect
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you have any issues working through this quickstart guide
|
If you have any issues working through this quickstart guide
|
||||||
or the other tutorials here, please either send a message to the
|
join the chat room at `slixmpp@muc.poez.io
|
||||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
or join the chat room at `sleek@conference.jabber.org
|
|
||||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
|
||||||
|
|
||||||
A common use case for Slixmpp is to send one-off messages from
|
A common use case for Slixmpp is to send one-off messages from
|
||||||
time to time. For example, one use case could be sending out a notice when
|
time to time. For example, one use case could be sending out a notice when
|
||||||
|
|||||||
@@ -9,21 +9,20 @@ Glossary
|
|||||||
stream handler
|
stream handler
|
||||||
A callback function that accepts stanza objects pulled directly
|
A callback function that accepts stanza objects pulled directly
|
||||||
from the XML stream. A stream handler is encapsulated in a
|
from the XML stream. A stream handler is encapsulated in a
|
||||||
object that includes a :term:`Matcher` object, and which provides
|
object that includes a :class:`Matcher <.MatcherBase>` object, and
|
||||||
additional semantics. For example, the ``Waiter`` handler wrapper
|
which provides additional semantics. For example, the
|
||||||
blocks thread execution until a matching stanza is received.
|
:class:`.Waiter` handler wrapper blocks thread execution until a
|
||||||
|
matching stanza is received.
|
||||||
|
|
||||||
event handler
|
event handler
|
||||||
A callback function that responds to events raised by
|
A callback function that responds to events raised by
|
||||||
``XMLStream.event``. An event handler may be marked as
|
:meth:`.XMLStream.event`.
|
||||||
threaded, allowing it to execute outside of the main processing
|
|
||||||
loop.
|
|
||||||
|
|
||||||
stanza object
|
stanza object
|
||||||
Informally may refer both to classes which extend ``ElementBase``
|
Informally may refer both to classes which extend :class:`.ElementBase`
|
||||||
or ``StanzaBase``, and to objects of such classes.
|
or :class:`.StanzaBase`, and to objects of such classes.
|
||||||
|
|
||||||
A stanza object is a wrapper for an XML object which exposes ``dict``
|
A stanza object is a wrapper for an XML object which exposes :class:`dict`
|
||||||
like interfaces which may be assigned to, read from, or deleted.
|
like interfaces which may be assigned to, read from, or deleted.
|
||||||
|
|
||||||
stanza plugin
|
stanza plugin
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ item itself, and the JID and node that will own the item.
|
|||||||
In this case, the owning JID and node are provided with the
|
In this case, the owning JID and node are provided with the
|
||||||
parameters ``ijid`` and ``node``.
|
parameters ``ijid`` and ``node``.
|
||||||
|
|
||||||
Peforming Disco Queries
|
Performing Disco Queries
|
||||||
-----------------------
|
-----------------------
|
||||||
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
|
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
|
||||||
and their nodes for disco information. Since these methods are wrappers for
|
and their nodes for disco information. Since these methods are wrappers for
|
||||||
@@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
info = self['xep_0030'].get_info(jid='foo@example.com',
|
info = yield from self['xep_0030'].get_info(jid='foo@example.com',
|
||||||
node='bar',
|
node='bar',
|
||||||
ifrom='baz@mycomponent.example.com',
|
ifrom='baz@mycomponent.example.com',
|
||||||
block=True,
|
timeout=30)
|
||||||
timeout=30)
|
|
||||||
|
|
||||||
items = self['xep_0030'].get_info(jid='foo@example.com',
|
items = self['xep_0030'].get_info(jid='foo@example.com',
|
||||||
node='bar',
|
node='bar',
|
||||||
|
|||||||
@@ -3,38 +3,25 @@ Slixmpp
|
|||||||
|
|
||||||
.. sidebar:: Get the Code
|
.. sidebar:: Get the Code
|
||||||
|
|
||||||
.. code-block:: sh
|
The latest source code for Slixmpp may be found on the `Git repo
|
||||||
|
<http://git.poez.io/slixmpp>`_. ::
|
||||||
|
|
||||||
pip install slixmpp
|
git clone git://git.poez.io/slixmpp
|
||||||
|
|
||||||
The latest source code for Slixmpp may be found on `Github
|
An XMPP chat room is available for discussing and getting help with slixmpp.
|
||||||
<http://github.com/fritzy/Slixmpp>`_. Releases can be found in the
|
|
||||||
``master`` branch, while the latest development version is in the
|
|
||||||
``develop`` branch.
|
|
||||||
|
|
||||||
**Latest Stable Release**
|
|
||||||
- `1.0 <http://github.com/fritzy/Slixmpp/zipball/1.0>`_
|
|
||||||
|
|
||||||
**Develop Releases**
|
|
||||||
- `Latest Develop Version <http://github.com/fritzy/Slixmpp/zipball/develop>`_
|
|
||||||
|
|
||||||
|
|
||||||
A mailing list and XMPP chat room are available for discussing and getting
|
|
||||||
help with Slixmpp.
|
|
||||||
|
|
||||||
**Mailing List**
|
|
||||||
`Slixmpp Discussion on Google Groups <http://groups.google.com/group/slixmpp-discussion>`_
|
|
||||||
|
|
||||||
**Chat**
|
**Chat**
|
||||||
`sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
|
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
|
||||||
|
|
||||||
|
**Reporting bugs**
|
||||||
|
You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
|
||||||
|
|
||||||
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 2.6/3.1+,
|
.. note::
|
||||||
and is featured in examples in
|
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
|
||||||
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_
|
which goal is to use asyncio instead of threads to handle networking. See
|
||||||
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
|
:ref:`differences`.
|
||||||
here from reading the Definitive Guide, please see the notes on updating
|
|
||||||
the examples to the latest version of Slixmpp.
|
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.4+,
|
||||||
|
|
||||||
Slixmpp's design goals and philosphy are:
|
Slixmpp's design goals and philosphy are:
|
||||||
|
|
||||||
@@ -59,15 +46,16 @@ Slixmpp's design goals and philosphy are:
|
|||||||
sensible defaults and appropriate abstractions. XML can be ugly to work
|
sensible defaults and appropriate abstractions. XML can be ugly to work
|
||||||
with, but it doesn't have to be that way.
|
with, but it doesn't have to be that way.
|
||||||
|
|
||||||
|
|
||||||
Here's your first Slixmpp Bot:
|
Here's your first Slixmpp Bot:
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from slixmpp import ClientXMPP
|
from slixmpp import ClientXMPP
|
||||||
from slixmpp.exceptions import IqError, IqTimeout
|
|
||||||
|
|
||||||
|
|
||||||
class EchoBot(ClientXMPP):
|
class EchoBot(ClientXMPP):
|
||||||
@@ -85,27 +73,13 @@ Here's your first Slixmpp Bot:
|
|||||||
# Here's how to access plugins once you've registered them:
|
# Here's how to access plugins once you've registered them:
|
||||||
# self['xep_0030'].add_feature('echo_demo')
|
# self['xep_0030'].add_feature('echo_demo')
|
||||||
|
|
||||||
# If you are working with an OpenFire server, you will
|
|
||||||
# need to use a different SSL version:
|
|
||||||
# import ssl
|
|
||||||
# self.ssl_version = ssl.PROTOCOL_SSLv3
|
|
||||||
|
|
||||||
def session_start(self, event):
|
def session_start(self, event):
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
|
|
||||||
# Most get_*/set_* methods from plugins use Iq stanzas, which
|
# Most get_*/set_* methods from plugins use Iq stanzas, which
|
||||||
# can generate IqError and IqTimeout exceptions
|
# are sent asynchronously. You can almost always provide a
|
||||||
#
|
# callback that will be executed when the reply is received.
|
||||||
# try:
|
|
||||||
# self.get_roster()
|
|
||||||
# except IqError as err:
|
|
||||||
# logging.error('There was an error getting the roster')
|
|
||||||
# logging.error(err.iq['error']['condition'])
|
|
||||||
# self.disconnect()
|
|
||||||
# except IqTimeout:
|
|
||||||
# logging.error('Server is taking too long to respond')
|
|
||||||
# self.disconnect()
|
|
||||||
|
|
||||||
def message(self, msg):
|
def message(self, msg):
|
||||||
if msg['type'] in ('chat', 'normal'):
|
if msg['type'] in ('chat', 'normal'):
|
||||||
@@ -121,9 +95,18 @@ Here's your first Slixmpp Bot:
|
|||||||
|
|
||||||
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
||||||
xmpp.connect()
|
xmpp.connect()
|
||||||
xmpp.process(block=True)
|
xmpp.process()
|
||||||
|
|
||||||
|
|
||||||
|
To read if you come from SleekXMPP
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
differences
|
||||||
|
using_asyncio
|
||||||
|
|
||||||
|
|
||||||
Getting Started (with Examples)
|
Getting Started (with Examples)
|
||||||
-------------------------------
|
-------------------------------
|
||||||
@@ -145,7 +128,6 @@ Tutorials, FAQs, and How To Guides
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
faq
|
|
||||||
xeps
|
xeps
|
||||||
xmpp_tdg
|
xmpp_tdg
|
||||||
howto/stanzas
|
howto/stanzas
|
||||||
@@ -184,9 +166,7 @@ API Reference
|
|||||||
api/xmlstream/handler
|
api/xmlstream/handler
|
||||||
api/xmlstream/matcher
|
api/xmlstream/matcher
|
||||||
api/xmlstream/xmlstream
|
api/xmlstream/xmlstream
|
||||||
api/xmlstream/scheduler
|
|
||||||
api/xmlstream/tostring
|
api/xmlstream/tostring
|
||||||
api/xmlstream/filesocket
|
|
||||||
|
|
||||||
Core Stanzas
|
Core Stanzas
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
@@ -197,8 +177,6 @@ Core Stanzas
|
|||||||
api/stanza/message
|
api/stanza/message
|
||||||
api/stanza/presence
|
api/stanza/presence
|
||||||
api/stanza/iq
|
api/stanza/iq
|
||||||
api/stanza/error
|
|
||||||
api/stanza/stream_error
|
|
||||||
|
|
||||||
Plugins
|
Plugins
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
@@ -220,8 +198,14 @@ Additional Info
|
|||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
* :ref:`search`
|
* :ref:`search`
|
||||||
|
|
||||||
Credits
|
SleekXMPP Credits
|
||||||
-------
|
-----------------
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Those people made SleekXMPP, so you should not bother them if
|
||||||
|
you have an issue with slixmpp. But it’s still fair to credit
|
||||||
|
them for their work.
|
||||||
|
|
||||||
|
|
||||||
**Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_
|
**Main Author:** `Nathan Fritz <http://andyet.net/team/fritzy>`_
|
||||||
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
|
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
|
||||||
|
|||||||
148
docs/using_asyncio.rst
Normal file
148
docs/using_asyncio.rst
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
.. _using_asyncio:
|
||||||
|
|
||||||
|
=============
|
||||||
|
Using asyncio
|
||||||
|
=============
|
||||||
|
|
||||||
|
Block on IQ sending
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:meth:`.Iq.send` now returns a :class:`~.Future` so you can easily block with:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
result = yield from iq.send()
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If the reply is an IQ with an ``error`` type, this will raise an
|
||||||
|
:class:`.IqError`, and if it timeouts, it will raise an
|
||||||
|
:class:`.IqTimeout`. Don't forget to catch it.
|
||||||
|
|
||||||
|
You can still use callbacks instead.
|
||||||
|
|
||||||
|
XEP plugin integration
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The same changes from the SleekXMPP API apply, so you can do:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
iq_info = yield from self.xmpp['xep_0030'].get_info(jid)
|
||||||
|
|
||||||
|
But the following will only return a Future:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
iq_info = self.xmpp['xep_0030'].get_info(jid)
|
||||||
|
|
||||||
|
|
||||||
|
Callbacks, Event Handlers, and Stream Handlers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
IQ callbacks and :term:`Event Handlers <event handler>` can be coroutine
|
||||||
|
functions; in this case, they will be scheduled in the event loop using
|
||||||
|
:meth:`.asyncio.async` and not ran immediately.
|
||||||
|
|
||||||
|
A :class:`.CoroutineCallback` class has been added as well for
|
||||||
|
:term:`Stream Handlers <stream handler>`, which will use
|
||||||
|
:meth:`.asyncio.async` to schedule the callback.
|
||||||
|
|
||||||
|
Running the event loop
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:meth:`.XMLStream.process` is only a thin wrapper on top of
|
||||||
|
``loop.run_forever()`` (if ``timeout`` is provided then it will
|
||||||
|
only run for this amount of time, and if ``forever`` is False it will
|
||||||
|
run until disconnection).
|
||||||
|
|
||||||
|
Therefore you can handle the event loop in any way you like
|
||||||
|
instead of using ``process()``.
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Blocking until the session is established
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
This code blocks until the XMPP session is fully established, which
|
||||||
|
can be useful to make sure external events aren’t triggering XMPP
|
||||||
|
callbacks while everything is not ready.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio, slixmpp
|
||||||
|
|
||||||
|
client = slixmpp.ClientXMPP('jid@example', 'password')
|
||||||
|
client.connected_event = asyncio.Event()
|
||||||
|
callback = lambda _: client.connected_event.set()
|
||||||
|
client.add_event_handler('session_start', callback)
|
||||||
|
client.connect()
|
||||||
|
loop.run_until_complete(event.wait())
|
||||||
|
# do some other stuff before running the event loop, e.g.
|
||||||
|
# loop.run_until_complete(httpserver.init())
|
||||||
|
client.process()
|
||||||
|
|
||||||
|
|
||||||
|
Use with other asyncio-based libraries
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
This code interfaces with aiohttp to retrieve two pages asynchronously
|
||||||
|
when the session is established, and then send the HTML content inside
|
||||||
|
a simple <message>.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio, aiohttp, slixmpp
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def get_pythonorg(event):
|
||||||
|
req = yield from aiohttp.request('get', 'http://www.python.org')
|
||||||
|
text = yield from req.text
|
||||||
|
client.send_message(mto='jid2@example', mbody=text)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def get_asyncioorg(event):
|
||||||
|
req = yield from aiohttp.request('get', 'http://www.asyncio.org')
|
||||||
|
text = yield from req.text
|
||||||
|
client.send_message(mto='jid3@example', mbody=text)
|
||||||
|
|
||||||
|
client = slixmpp.ClientXMPP('jid@example', 'password')
|
||||||
|
client.add_event_handler('session_start', get_pythonorg)
|
||||||
|
client.add_event_handler('session_start', get_asyncioorg)
|
||||||
|
client.connect()
|
||||||
|
client.process()
|
||||||
|
|
||||||
|
|
||||||
|
Blocking Iq
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This client checks (via XEP-0092) the software used by every entity it
|
||||||
|
receives a message from. After this, it sends a message to a specific
|
||||||
|
JID indicating its findings.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import asyncio, slixmpp
|
||||||
|
|
||||||
|
class ExampleClient(slixmpp.ClientXMPP):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
slixmpp.ClientXMPP.__init__(self, *args, **kwargs)
|
||||||
|
self.register_plugin('xep_0092')
|
||||||
|
self.add_event_handler('message', self.on_message)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def on_message(self, event):
|
||||||
|
# You should probably handle IqError and IqTimeout exceptions here
|
||||||
|
# but this is an example.
|
||||||
|
version = yield from self['xep_0092'].get_version(message['from'])
|
||||||
|
text = "%s sent me a message, he runs %s" % (message['from'],
|
||||||
|
version['software_version']['name'])
|
||||||
|
self.send_message(mto='master@example.tld', mbody=text)
|
||||||
|
|
||||||
|
client = ExampleClient('jid@example', 'password')
|
||||||
|
client.connect()
|
||||||
|
client.process()
|
||||||
|
|
||||||
|
|
||||||
@@ -11,20 +11,12 @@
|
|||||||
See the file LICENSE for copying permission.
|
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/Slixmpp']+sys.path
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import unittest
|
|
||||||
import distutils.core
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from glob import glob
|
from os.path import basename, join as pjoin
|
||||||
from os.path import splitext, basename, join as pjoin
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from urllib import urlopen
|
from urllib import urlopen
|
||||||
|
from getpass import getpass
|
||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.plugins.xep_0323.device import Device
|
from slixmpp.plugins.xep_0323.device import Device
|
||||||
@@ -168,9 +160,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
myDevice = TheDevice(args.nodeid);
|
myDevice = TheDevice(args.nodeid);
|
||||||
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
|
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
|
||||||
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
|
myDevice._add_field(name="Temperature", typename="numeric", unit="C")
|
||||||
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
|
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
|
||||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
|
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||||
|
|
||||||
xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10);
|
xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10);
|
||||||
xmpp.beClientOrServer(server=True)
|
xmpp.beClientOrServer(server=True)
|
||||||
@@ -186,5 +178,5 @@ if __name__ == '__main__':
|
|||||||
logging.debug("ready ending")
|
logging.debug("ready ending")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print "noopp didn't happen"
|
print("noopp didn't happen")
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from argparse import ArgumentParser
|
|||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import IqError, IqTimeout
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
|
from slixmpp.xmlstream.asyncio import asyncio
|
||||||
|
|
||||||
|
|
||||||
class Disco(slixmpp.ClientXMPP):
|
class Disco(slixmpp.ClientXMPP):
|
||||||
@@ -53,6 +54,7 @@ class Disco(slixmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
@@ -74,22 +76,16 @@ class Disco(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self.get in self.info_types:
|
if self.get in self.info_types:
|
||||||
# By using block=True, the result stanza will be
|
|
||||||
# returned. Execution will block until the reply is
|
|
||||||
# received. Non-blocking options would be to listen
|
|
||||||
# for the disco_info event, or passing a handler
|
|
||||||
# function using the callback parameter.
|
# function using the callback parameter.
|
||||||
info = self['xep_0030'].get_info(jid=self.target_jid,
|
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
|
||||||
node=self.target_node,
|
node=self.target_node)
|
||||||
block=True)
|
if self.get in self.items_types:
|
||||||
elif self.get in self.items_types:
|
|
||||||
# The same applies from above. Listen for the
|
# The same applies from above. Listen for the
|
||||||
# disco_items event or pass a callback function
|
# disco_items event or pass a callback function
|
||||||
# if you need to process a non-blocking request.
|
# if you need to process a non-blocking request.
|
||||||
items = self['xep_0030'].get_items(jid=self.target_jid,
|
items = yield from self['xep_0030'].get_items(jid=self.target_jid,
|
||||||
node=self.target_node,
|
node=self.target_node)
|
||||||
block=True)
|
if self.get not in self.info_types and self.get not in self.items_types:
|
||||||
else:
|
|
||||||
logging.error("Invalid disco request type.")
|
logging.error("Invalid disco request type.")
|
||||||
return
|
return
|
||||||
except IqError as e:
|
except IqError as e:
|
||||||
@@ -143,7 +139,7 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument("-p", "--password", dest="password",
|
parser.add_argument("-p", "--password", dest="password",
|
||||||
help="password to use")
|
help="password to use")
|
||||||
parser.add_argument("query", choices=["all", "info", "items", "identities", "features"])
|
parser.add_argument("query", choices=["all", "info", "items", "identities", "features"])
|
||||||
parser.add_argument("target-jid")
|
parser.add_argument("target_jid")
|
||||||
parser.add_argument("node", nargs='?')
|
parser.add_argument("node", nargs='?')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -162,4 +158,4 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
xmpp.connect()
|
xmpp.connect()
|
||||||
xmpp.process()
|
xmpp.process(forever=False)
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
import threading
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
|
from slixmpp import asyncio
|
||||||
|
|
||||||
|
|
||||||
FILE_TYPES = {
|
FILE_TYPES = {
|
||||||
@@ -40,8 +40,14 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
self.add_event_handler('avatar_metadata_publish', self.on_avatar)
|
self.add_event_handler('avatar_metadata_publish', self.on_avatar)
|
||||||
|
|
||||||
self.received = set()
|
self.received = set()
|
||||||
self.presences_received = threading.Event()
|
self.presences_received = asyncio.Event()
|
||||||
|
self.roster_received = asyncio.Event()
|
||||||
|
|
||||||
|
def roster_received_cb(self, event):
|
||||||
|
self.roster_received.set()
|
||||||
|
self.presences_received.clear()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
@@ -56,16 +62,20 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
data.
|
data.
|
||||||
"""
|
"""
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
self.get_roster()
|
self.get_roster(callback=self.roster_received_cb)
|
||||||
|
|
||||||
print('Waiting for presence updates...\n')
|
print('Waiting for presence updates...\n')
|
||||||
self.presences_received.wait(15)
|
yield from self.roster_received.wait()
|
||||||
|
print('Roster received')
|
||||||
|
yield from self.presences_received.wait()
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def on_vcard_avatar(self, pres):
|
def on_vcard_avatar(self, pres):
|
||||||
print("Received vCard avatar update from %s" % pres['from'].bare)
|
print("Received vCard avatar update from %s" % pres['from'].bare)
|
||||||
try:
|
try:
|
||||||
result = self['xep_0054'].get_vcard(pres['from'], cached=True)
|
result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
|
||||||
|
timeout=5)
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
print("Error retrieving avatar for %s" % pres['from'])
|
print("Error retrieving avatar for %s" % pres['from'])
|
||||||
return
|
return
|
||||||
@@ -76,16 +86,18 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
pres['from'].bare,
|
pres['from'].bare,
|
||||||
pres['vcard_temp_update']['photo'],
|
pres['vcard_temp_update']['photo'],
|
||||||
filetype)
|
filetype)
|
||||||
with open(filename, 'w+') as img:
|
with open(filename, 'wb+') as img:
|
||||||
img.write(avatar['BINVAL'])
|
img.write(avatar['BINVAL'])
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def on_avatar(self, msg):
|
def on_avatar(self, msg):
|
||||||
print("Received avatar update from %s" % msg['from'])
|
print("Received avatar update from %s" % msg['from'])
|
||||||
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
||||||
for info in metadata['items']:
|
for info in metadata['items']:
|
||||||
if not info['url']:
|
if not info['url']:
|
||||||
try:
|
try:
|
||||||
result = self['xep_0084'].retrieve_avatar(msg['from'], info['id'])
|
result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
|
||||||
|
timeout=5)
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
print("Error retrieving avatar for %s" % msg['from'])
|
print("Error retrieving avatar for %s" % msg['from'])
|
||||||
return
|
return
|
||||||
@@ -94,7 +106,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
filetype = FILE_TYPES.get(metadata['type'], 'png')
|
filetype = FILE_TYPES.get(metadata['type'], 'png')
|
||||||
filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype)
|
filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype)
|
||||||
with open(filename, 'w+') as img:
|
with open(filename, 'wb+') as img:
|
||||||
img.write(avatar['value'])
|
img.write(avatar['value'])
|
||||||
else:
|
else:
|
||||||
# We could retrieve the avatar via HTTP, etc here instead.
|
# We could retrieve the avatar via HTTP, etc here instead.
|
||||||
@@ -105,6 +117,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
Wait to receive updates from all roster contacts.
|
Wait to receive updates from all roster contacts.
|
||||||
"""
|
"""
|
||||||
self.received.add(pres['from'].bare)
|
self.received.add(pres['from'].bare)
|
||||||
|
print((len(self.received), len(self.client_roster.keys())))
|
||||||
if len(self.received) >= len(self.client_roster.keys()):
|
if len(self.received) >= len(self.client_roster.keys()):
|
||||||
self.presences_received.set()
|
self.presences_received.set()
|
||||||
else:
|
else:
|
||||||
|
|||||||
97
examples/http_over_xmpp.py
Normal file
97
examples/http_over_xmpp.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Implementation of HTTP over XMPP transport
|
||||||
|
http://xmpp.org/extensions/xep-0332.html
|
||||||
|
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp import ClientXMPP
|
||||||
|
|
||||||
|
from optparse import OptionParser
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPOverXMPPClient(ClientXMPP):
|
||||||
|
def __init__(self, jid, password):
|
||||||
|
ClientXMPP.__init__(self, jid, password)
|
||||||
|
self.register_plugin('xep_0332') # HTTP over XMPP Transport
|
||||||
|
self.add_event_handler(
|
||||||
|
'session_start', self.session_start, threaded=True
|
||||||
|
)
|
||||||
|
self.add_event_handler('http_request', self.http_request_received)
|
||||||
|
self.add_event_handler('http_response', self.http_response_received)
|
||||||
|
|
||||||
|
def http_request_received(self, iq):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def http_response_received(self, iq):
|
||||||
|
print('HTTP Response Received : %s' % iq)
|
||||||
|
print('From : %s' % iq['from'])
|
||||||
|
print('To : %s' % iq['to'])
|
||||||
|
print('Type : %s' % iq['type'])
|
||||||
|
print('Headers : %s' % iq['resp']['headers'])
|
||||||
|
print('Code : %s' % iq['resp']['code'])
|
||||||
|
print('Message : %s' % iq['resp']['message'])
|
||||||
|
print('Data : %s' % iq['resp']['data'])
|
||||||
|
|
||||||
|
def session_start(self, event):
|
||||||
|
# TODO: Fill in the blanks
|
||||||
|
self['xep_0332'].send_request(
|
||||||
|
to='?', method='?', resource='?', headers={}
|
||||||
|
)
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
#
|
||||||
|
# NOTE: To run this example, fill up the blanks in session_start() and
|
||||||
|
# use the following command.
|
||||||
|
#
|
||||||
|
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
|
||||||
|
#
|
||||||
|
|
||||||
|
parser = OptionParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
parser.add_option(
|
||||||
|
'-v', '--verbose', help='set logging to DEBUG', action='store_const',
|
||||||
|
dest='loglevel', const=logging.DEBUG, default=logging.ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
parser.add_option('-J', '--jid', dest='jid', help='JID')
|
||||||
|
parser.add_option('-P', '--password', dest='password', help='Password')
|
||||||
|
|
||||||
|
# XMPP server ip and port options.
|
||||||
|
parser.add_option(
|
||||||
|
'-i', '--ipaddr', dest='ipaddr',
|
||||||
|
help='IP Address of the XMPP server', default=None
|
||||||
|
)
|
||||||
|
parser.add_option(
|
||||||
|
'-p', '--port', dest='port',
|
||||||
|
help='Port of the XMPP server', default=None
|
||||||
|
)
|
||||||
|
|
||||||
|
opts, args = parser.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=opts.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if opts.jid is None:
|
||||||
|
opts.jid = input('Username: ')
|
||||||
|
if opts.password is None:
|
||||||
|
opts.password = getpass.getpass('Password: ')
|
||||||
|
|
||||||
|
xmpp = HTTPOverXMPPClient(opts.jid, opts.password)
|
||||||
|
xmpp.connect()
|
||||||
|
xmpp.process()
|
||||||
|
|
||||||
@@ -22,13 +22,10 @@ class IBBReceiver(slixmpp.ClientXMPP):
|
|||||||
A basic example of creating and using an in-band bytestream.
|
A basic example of creating and using an in-band bytestream.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, jid, password):
|
def __init__(self, jid, password, filename):
|
||||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
self.register_plugin('xep_0030') # Service Discovery
|
self.file = open(filename, 'wb')
|
||||||
self.register_plugin('xep_0047', {
|
|
||||||
'auto_accept': True
|
|
||||||
}) # In-band Bytestreams
|
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
@@ -39,6 +36,7 @@ class IBBReceiver(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
self.add_event_handler("ibb_stream_start", self.stream_opened)
|
self.add_event_handler("ibb_stream_start", self.stream_opened)
|
||||||
self.add_event_handler("ibb_stream_data", self.stream_data)
|
self.add_event_handler("ibb_stream_data", self.stream_data)
|
||||||
|
self.add_event_handler("ibb_stream_end", self.stream_closed)
|
||||||
|
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
@@ -56,29 +54,16 @@ class IBBReceiver(slixmpp.ClientXMPP):
|
|||||||
self.send_presence()
|
self.send_presence()
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
|
|
||||||
def accept_stream(self, iq):
|
|
||||||
"""
|
|
||||||
Check that it is ok to accept a stream request.
|
|
||||||
|
|
||||||
Controlling stream acceptance can be done via either:
|
|
||||||
- setting 'auto_accept' to False in the plugin
|
|
||||||
configuration. The default is True.
|
|
||||||
- setting 'accept_stream' to a function which accepts
|
|
||||||
an Iq stanza as its argument, like this one.
|
|
||||||
|
|
||||||
The accept_stream function will be used if it exists, and the
|
|
||||||
auto_accept value will be used otherwise.
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def stream_opened(self, stream):
|
def stream_opened(self, stream):
|
||||||
print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid))
|
print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid))
|
||||||
|
|
||||||
# You could run a loop reading from the stream using stream.recv(),
|
def stream_data(self, stream):
|
||||||
# or use the ibb_stream_data event.
|
self.file.write(stream.read())
|
||||||
|
|
||||||
def stream_data(self, event):
|
def stream_closed(self, stream):
|
||||||
print(event['data'])
|
print('Stream closed: %s from %s' % (stream.sid, stream.peer_jid))
|
||||||
|
self.file.close()
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Setup the command line arguments.
|
# Setup the command line arguments.
|
||||||
@@ -97,6 +82,8 @@ if __name__ == '__main__':
|
|||||||
help="JID to use")
|
help="JID to use")
|
||||||
parser.add_argument("-p", "--password", dest="password",
|
parser.add_argument("-p", "--password", dest="password",
|
||||||
help="password to use")
|
help="password to use")
|
||||||
|
parser.add_argument("-o", "--out", dest="filename",
|
||||||
|
help="file to save to")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -108,9 +95,18 @@ if __name__ == '__main__':
|
|||||||
args.jid = input("Username: ")
|
args.jid = input("Username: ")
|
||||||
if args.password is None:
|
if args.password is None:
|
||||||
args.password = getpass("Password: ")
|
args.password = getpass("Password: ")
|
||||||
|
if args.filename is None:
|
||||||
|
args.filename = input("File path: ")
|
||||||
|
|
||||||
xmpp = IBBReceiver(args.jid, args.password)
|
# Setup the IBBReceiver and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = IBBReceiver(args.jid, args.password, args.filename)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0047', {
|
||||||
|
'auto_accept': True
|
||||||
|
}) # In-band Bytestreams
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
xmpp.connect()
|
xmpp.connect()
|
||||||
xmpp.process()
|
xmpp.process(forever=False)
|
||||||
|
|||||||
@@ -9,11 +9,13 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
|
||||||
class IBBSender(slixmpp.ClientXMPP):
|
class IBBSender(slixmpp.ClientXMPP):
|
||||||
@@ -22,11 +24,13 @@ class IBBSender(slixmpp.ClientXMPP):
|
|||||||
A basic example of creating and using an in-band bytestream.
|
A basic example of creating and using an in-band bytestream.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, jid, password, receiver, filename):
|
def __init__(self, jid, password, receiver, filename, use_messages=False):
|
||||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
self.receiver = receiver
|
self.receiver = receiver
|
||||||
self.filename = filename
|
|
||||||
|
self.file = open(filename, 'rb')
|
||||||
|
self.use_messages = use_messages
|
||||||
|
|
||||||
# The session_start event will be triggered when
|
# The session_start event will be triggered when
|
||||||
# the bot establishes its connection with the server
|
# the bot establishes its connection with the server
|
||||||
@@ -35,6 +39,7 @@ class IBBSender(slixmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
@@ -51,15 +56,22 @@ class IBBSender(slixmpp.ClientXMPP):
|
|||||||
self.send_presence()
|
self.send_presence()
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
|
|
||||||
# For the purpose of demonstration, we'll set a very small block
|
try:
|
||||||
# size. The default block size is 4096. We'll also use a window
|
# Open the IBB stream in which to write to.
|
||||||
# allowing sending multiple blocks at a time; in this case, three
|
stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
|
||||||
# block transfers may be in progress at any time.
|
|
||||||
stream = self['xep_0047'].open_stream(self.receiver)
|
|
||||||
|
|
||||||
with open(self.filename) as f:
|
# If you want to send in-memory bytes, use stream.sendall() instead.
|
||||||
data = f.read()
|
yield from stream.sendfile(self.file, timeout=10)
|
||||||
stream.sendall(data)
|
|
||||||
|
# And finally close the stream.
|
||||||
|
yield from stream.close(timeout=10)
|
||||||
|
except (IqError, IqTimeout):
|
||||||
|
print('File transfer errored')
|
||||||
|
else:
|
||||||
|
print('File transfer finished')
|
||||||
|
finally:
|
||||||
|
self.file.close()
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@@ -80,9 +92,11 @@ if __name__ == '__main__':
|
|||||||
parser.add_argument("-p", "--password", dest="password",
|
parser.add_argument("-p", "--password", dest="password",
|
||||||
help="password to use")
|
help="password to use")
|
||||||
parser.add_argument("-r", "--receiver", dest="receiver",
|
parser.add_argument("-r", "--receiver", dest="receiver",
|
||||||
help="JID to use")
|
help="JID of the receiver")
|
||||||
parser.add_argument("-f", "--file", dest="filename",
|
parser.add_argument("-f", "--file", dest="filename",
|
||||||
help="JID to use")
|
help="file to send")
|
||||||
|
parser.add_argument("-m", "--use-messages", action="store_true",
|
||||||
|
help="use messages instead of iqs for file transfer")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -99,16 +113,13 @@ if __name__ == '__main__':
|
|||||||
if args.filename is None:
|
if args.filename is None:
|
||||||
args.filename = input("File path: ")
|
args.filename = input("File path: ")
|
||||||
|
|
||||||
# Setup the EchoBot and register plugins. Note that while plugins may
|
# Setup the IBBSender and register plugins. Note that while plugins may
|
||||||
# have interdependencies, the order in which you register them does
|
# have interdependencies, the order in which you register them does
|
||||||
# not matter.
|
# not matter.
|
||||||
xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename)
|
xmpp = IBBSender(args.jid, args.password, args.receiver, args.filename, args.use_messages)
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
xmpp.register_plugin('xep_0004') # Data Forms
|
|
||||||
xmpp.register_plugin('xep_0047') # In-band Bytestreams
|
xmpp.register_plugin('xep_0047') # In-band Bytestreams
|
||||||
xmpp.register_plugin('xep_0060') # PubSub
|
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
|
||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
xmpp.connect()
|
xmpp.connect()
|
||||||
xmpp.process()
|
xmpp.process(forever=False)
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ def on_session2(event):
|
|||||||
new_xmpp.update_roster(jid,
|
new_xmpp.update_roster(jid,
|
||||||
name = item['name'],
|
name = item['name'],
|
||||||
groups = item['groups'])
|
groups = item['groups'])
|
||||||
new_xmpp.disconnect()
|
new_xmpp.disconnect()
|
||||||
new_xmpp.add_event_handler('session_start', on_session2)
|
new_xmpp.add_event_handler('session_start', on_session2)
|
||||||
|
|
||||||
if new_xmpp.connect():
|
new_xmpp.connect()
|
||||||
new_xmpp.process(block=True)
|
new_xmpp.process()
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
import logging
|
import logging
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
|
from slixmpp import asyncio
|
||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ class PingTest(slixmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
@@ -53,8 +56,8 @@ class PingTest(slixmpp.ClientXMPP):
|
|||||||
self.get_roster()
|
self.get_roster()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rtt = self['xep_0199'].ping(self.pingjid,
|
rtt = yield from self['xep_0199'].ping(self.pingjid,
|
||||||
timeout=10)
|
timeout=10)
|
||||||
logging.info("Success! RTT: %s", rtt)
|
logging.info("Success! RTT: %s", rtt)
|
||||||
except IqError as e:
|
except IqError as e:
|
||||||
logging.info("Error pinging %s: %s",
|
logging.info("Error pinging %s: %s",
|
||||||
@@ -78,8 +81,7 @@ if __name__ == '__main__':
|
|||||||
action="store_const", dest="loglevel",
|
action="store_const", dest="loglevel",
|
||||||
const=logging.DEBUG, default=logging.INFO)
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
parser.add_argument("-t", "--pingto", help="set jid to ping",
|
parser.add_argument("-t", "--pingto", help="set jid to ping",
|
||||||
action="store", type="string", dest="pingjid",
|
dest="pingjid", default=None)
|
||||||
default=None)
|
|
||||||
|
|
||||||
# JID and password options.
|
# JID and password options.
|
||||||
parser.add_argument("-j", "--jid", dest="jid",
|
parser.add_argument("-j", "--jid", dest="jid",
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import logging
|
|||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import slixmpp
|
import slixmpp
|
||||||
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp.xmlstream import ET, tostring
|
from slixmpp.xmlstream import ET, tostring
|
||||||
|
|
||||||
|
|
||||||
class PubsubClient(slixmpp.ClientXMPP):
|
class PubsubClient(slixmpp.ClientXMPP):
|
||||||
|
|
||||||
def __init__(self, jid, password, server,
|
def __init__(self, jid, password, server,
|
||||||
node=None, action='list', data=''):
|
node=None, action='nodes', data=''):
|
||||||
super(PubsubClient, self).__init__(jid, password)
|
super(PubsubClient, self).__init__(jid, password)
|
||||||
|
|
||||||
self.register_plugin('xep_0030')
|
self.register_plugin('xep_0030')
|
||||||
@@ -30,81 +32,83 @@ class PubsubClient(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
self.add_event_handler('session_start', self.start)
|
self.add_event_handler('session_start', self.start)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
getattr(self, self.action)()
|
yield from getattr(self, self.action)()
|
||||||
except:
|
except:
|
||||||
logging.error('Could not execute: %s' % self.action)
|
logging.error('Could not execute: %s', self.action)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
def nodes(self):
|
def nodes(self):
|
||||||
try:
|
try:
|
||||||
result = self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||||
for item in result['disco_items']['items']:
|
for item in result['disco_items']['items']:
|
||||||
print(' - %s' % str(item))
|
logging.info(' - %s', str(item))
|
||||||
except:
|
except XMPPError as error:
|
||||||
logging.error('Could not retrieve node list.')
|
logging.error('Could not retrieve node list: %s', error.format())
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
try:
|
try:
|
||||||
self['xep_0060'].create_node(self.pubsub_server, self.node)
|
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||||
except:
|
logging.info('Created node %s', self.node)
|
||||||
logging.error('Could not create node: %s' % self.node)
|
except XMPPError as error:
|
||||||
|
logging.error('Could not create node %s: %s', self.node, error.format())
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
try:
|
try:
|
||||||
self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||||
print('Deleted node: %s' % self.node)
|
logging.info('Deleted node %s', self.node)
|
||||||
except:
|
except XMPPError as error:
|
||||||
logging.error('Could not delete node: %s' % self.node)
|
logging.error('Could not delete node %s: %s', self.node, error.format())
|
||||||
|
|
||||||
def publish(self):
|
def publish(self):
|
||||||
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
||||||
try:
|
try:
|
||||||
result = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||||
id = result['pubsub']['publish']['item']['id']
|
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
|
||||||
print('Published at item id: %s' % id)
|
except XMPPError as error:
|
||||||
except:
|
logging.error('Could not publish to %s: %s', self.node, error.format())
|
||||||
logging.error('Could not publish to: %s' % self.node)
|
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
try:
|
try:
|
||||||
result = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||||
for item in result['pubsub']['items']['substanzas']:
|
for item in result['pubsub']['items']['substanzas']:
|
||||||
print('Retrieved item %s: %s' % (item['id'], tostring(item['payload'])))
|
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
|
||||||
except:
|
except XMPPError as error:
|
||||||
logging.error('Could not retrieve item %s from node %s' % (self.data, self.node))
|
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
|
||||||
|
|
||||||
def retract(self):
|
def retract(self):
|
||||||
try:
|
try:
|
||||||
result = self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||||
print('Retracted item %s from node %s' % (self.data, self.node))
|
logging.info('Retracted item %s from node %s', self.data, self.node)
|
||||||
except:
|
except XMPPError as error:
|
||||||
logging.error('Could not retract item %s from node %s' % (self.data, self.node))
|
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
|
||||||
|
|
||||||
def purge(self):
|
def purge(self):
|
||||||
try:
|
try:
|
||||||
result = self['xep_0060'].purge(self.pubsub_server, self.node)
|
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||||
print('Purged all items from node %s' % self.node)
|
logging.info('Purged all items from node %s', self.node)
|
||||||
except:
|
except XMPPError as error:
|
||||||
logging.error('Could not purge items from node %s' % self.node)
|
logging.error('Could not purge items from node %s: %s', self.node, error.format())
|
||||||
|
|
||||||
def subscribe(self):
|
def subscribe(self):
|
||||||
try:
|
try:
|
||||||
result = self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||||
print('Subscribed %s to node %s' % (self.boundjid.bare, self.node))
|
subscription = iq['pubsub']['subscription']
|
||||||
except:
|
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
|
||||||
logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node))
|
except XMPPError as error:
|
||||||
|
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||||
|
|
||||||
def unsubscribe(self):
|
def unsubscribe(self):
|
||||||
try:
|
try:
|
||||||
result = self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||||
print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node))
|
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
|
||||||
except:
|
except XMPPError as error:
|
||||||
logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node))
|
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -121,12 +125,12 @@ if __name__ == '__main__':
|
|||||||
action="store_const",
|
action="store_const",
|
||||||
dest="loglevel",
|
dest="loglevel",
|
||||||
const=logging.ERROR,
|
const=logging.ERROR,
|
||||||
default=logging.ERROR)
|
default=logging.INFO)
|
||||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||||
action="store_const",
|
action="store_const",
|
||||||
dest="loglevel",
|
dest="loglevel",
|
||||||
const=logging.DEBUG,
|
const=logging.DEBUG,
|
||||||
default=logging.ERROR)
|
default=logging.INFO)
|
||||||
|
|
||||||
# JID and password options.
|
# JID and password options.
|
||||||
parser.add_argument("-j", "--jid", dest="jid",
|
parser.add_argument("-j", "--jid", dest="jid",
|
||||||
@@ -135,7 +139,7 @@ if __name__ == '__main__':
|
|||||||
help="password to use")
|
help="password to use")
|
||||||
|
|
||||||
parser.add_argument("server")
|
parser.add_argument("server")
|
||||||
parser.add_argument("action", choice=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
|
parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
|
||||||
parser.add_argument("node", nargs='?')
|
parser.add_argument("node", nargs='?')
|
||||||
parser.add_argument("data", nargs='?')
|
parser.add_argument("data", nargs='?')
|
||||||
|
|
||||||
@@ -159,4 +163,4 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
xmpp.connect()
|
xmpp.connect()
|
||||||
xmpp.process()
|
xmpp.process(forever=False)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
|||||||
resp['register']['password'] = self.password
|
resp['register']['password'] = self.password
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp.send()
|
yield from resp.send()
|
||||||
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" %
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
import threading
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import IqError, IqTimeout
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
|
from slixmpp.xmlstream.asyncio import asyncio
|
||||||
|
|
||||||
|
|
||||||
class RosterBrowser(slixmpp.ClientXMPP):
|
class RosterBrowser(slixmpp.ClientXMPP):
|
||||||
@@ -36,8 +36,9 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
|||||||
self.add_event_handler("changed_status", self.wait_for_presences)
|
self.add_event_handler("changed_status", self.wait_for_presences)
|
||||||
|
|
||||||
self.received = set()
|
self.received = set()
|
||||||
self.presences_received = threading.Event()
|
self.presences_received = asyncio.Event()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
@@ -51,17 +52,21 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
|||||||
event does not provide any additional
|
event does not provide any additional
|
||||||
data.
|
data.
|
||||||
"""
|
"""
|
||||||
|
future = asyncio.Future()
|
||||||
|
def callback(result):
|
||||||
|
future.set_result(None)
|
||||||
try:
|
try:
|
||||||
self.get_roster()
|
self.get_roster(callback=callback)
|
||||||
|
yield from future
|
||||||
except IqError as err:
|
except IqError as err:
|
||||||
print('Error: %' % err.iq['error']['condition'])
|
print('Error: %s' % err.iq['error']['condition'])
|
||||||
except IqTimeout:
|
except IqTimeout:
|
||||||
print('Error: Request timed out')
|
print('Error: Request timed out')
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
|
|
||||||
|
|
||||||
print('Waiting for presence updates...\n')
|
print('Waiting for presence updates...\n')
|
||||||
self.presences_received.wait(5)
|
yield from asyncio.sleep(10)
|
||||||
|
|
||||||
print('Roster for %s' % self.boundjid.bare)
|
print('Roster for %s' % self.boundjid.bare)
|
||||||
groups = self.client_roster.groups()
|
groups = self.client_roster.groups()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Boomerang(Endpoint):
|
|||||||
|
|
||||||
@remote
|
@remote
|
||||||
def throw(self):
|
def throw(self):
|
||||||
print "Duck!"
|
print("Duck!")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
90
examples/s5b_transfer/s5b_receiver.py
Executable file
90
examples/s5b_transfer/s5b_receiver.py
Executable file
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from getpass import getpass
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
import slixmpp
|
||||||
|
|
||||||
|
|
||||||
|
class S5BReceiver(slixmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A basic example of creating and using a SOCKS5 bytestream.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password, filename):
|
||||||
|
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
self.file = open(filename, 'wb')
|
||||||
|
|
||||||
|
self.add_event_handler("socks5_connected", self.stream_opened)
|
||||||
|
self.add_event_handler("socks5_data", self.stream_data)
|
||||||
|
self.add_event_handler("socks5_closed", self.stream_closed)
|
||||||
|
|
||||||
|
def stream_opened(self, sid):
|
||||||
|
logging.info('Stream opened. %s', sid)
|
||||||
|
|
||||||
|
def stream_data(self, data):
|
||||||
|
self.file.write(data)
|
||||||
|
|
||||||
|
def stream_closed(self, exception):
|
||||||
|
logging.info('Stream closed. %s', exception)
|
||||||
|
self.file.close()
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
parser = ArgumentParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
|
||||||
|
action="store_const", dest="loglevel",
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
|
||||||
|
action="store_const", dest="loglevel",
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
parser.add_argument("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
parser.add_argument("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
parser.add_argument("-o", "--out", dest="filename",
|
||||||
|
help="file to save to")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=args.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if args.jid is None:
|
||||||
|
args.jid = input("Username: ")
|
||||||
|
if args.password is None:
|
||||||
|
args.password = getpass("Password: ")
|
||||||
|
if args.filename is None:
|
||||||
|
args.filename = input("File path: ")
|
||||||
|
|
||||||
|
# Setup the S5BReceiver and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = S5BReceiver(args.jid, args.password, args.filename)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0065', {
|
||||||
|
'auto_accept': True
|
||||||
|
}) # SOCKS5 Bytestreams
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
xmpp.connect()
|
||||||
|
xmpp.process(forever=False)
|
||||||
124
examples/s5b_transfer/s5b_sender.py
Executable file
124
examples/s5b_transfer/s5b_sender.py
Executable file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from getpass import getpass
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
import slixmpp
|
||||||
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
|
||||||
|
class S5BSender(slixmpp.ClientXMPP):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A basic example of creating and using a SOCKS5 bytestream.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, jid, password, receiver, filename):
|
||||||
|
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||||
|
|
||||||
|
self.receiver = receiver
|
||||||
|
|
||||||
|
self.file = open(filename, 'rb')
|
||||||
|
|
||||||
|
# The session_start event will be triggered when
|
||||||
|
# the bot establishes its connection with the server
|
||||||
|
# and the XML streams are ready for use.
|
||||||
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def start(self, event):
|
||||||
|
"""
|
||||||
|
Process the session_start event.
|
||||||
|
|
||||||
|
Typical actions for the session_start event are
|
||||||
|
requesting the roster and broadcasting an initial
|
||||||
|
presence stanza.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
event -- An empty dictionary. The session_start
|
||||||
|
event does not provide any additional
|
||||||
|
data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Open the S5B stream in which to write to.
|
||||||
|
proxy = yield from self['xep_0065'].handshake(self.receiver)
|
||||||
|
|
||||||
|
# Send the entire file.
|
||||||
|
while True:
|
||||||
|
data = self.file.read(1048576)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
yield from proxy.write(data)
|
||||||
|
|
||||||
|
# And finally close the stream.
|
||||||
|
proxy.transport.write_eof()
|
||||||
|
except (IqError, IqTimeout):
|
||||||
|
print('File transfer errored')
|
||||||
|
else:
|
||||||
|
print('File transfer finished')
|
||||||
|
finally:
|
||||||
|
self.file.close()
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Setup the command line arguments.
|
||||||
|
parser = ArgumentParser()
|
||||||
|
|
||||||
|
# Output verbosity options.
|
||||||
|
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
|
||||||
|
action="store_const", dest="loglevel",
|
||||||
|
const=logging.ERROR, default=logging.INFO)
|
||||||
|
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
|
||||||
|
action="store_const", dest="loglevel",
|
||||||
|
const=logging.DEBUG, default=logging.INFO)
|
||||||
|
|
||||||
|
# JID and password options.
|
||||||
|
parser.add_argument("-j", "--jid", dest="jid",
|
||||||
|
help="JID to use")
|
||||||
|
parser.add_argument("-p", "--password", dest="password",
|
||||||
|
help="password to use")
|
||||||
|
parser.add_argument("-r", "--receiver", dest="receiver",
|
||||||
|
help="JID of the receiver")
|
||||||
|
parser.add_argument("-f", "--file", dest="filename",
|
||||||
|
help="file to send")
|
||||||
|
parser.add_argument("-m", "--use-messages", action="store_true",
|
||||||
|
help="use messages instead of iqs for file transfer")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Setup logging.
|
||||||
|
logging.basicConfig(level=args.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
if args.jid is None:
|
||||||
|
args.jid = input("Username: ")
|
||||||
|
if args.password is None:
|
||||||
|
args.password = getpass("Password: ")
|
||||||
|
if args.receiver is None:
|
||||||
|
args.receiver = input("Receiver: ")
|
||||||
|
if args.filename is None:
|
||||||
|
args.filename = input("File path: ")
|
||||||
|
|
||||||
|
# Setup the S5BSender and register plugins. Note that while plugins may
|
||||||
|
# have interdependencies, the order in which you register them does
|
||||||
|
# not matter.
|
||||||
|
xmpp = S5BSender(args.jid, args.password, args.receiver, args.filename)
|
||||||
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
|
xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams
|
||||||
|
|
||||||
|
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||||
|
xmpp.connect()
|
||||||
|
xmpp.process(forever=False)
|
||||||
@@ -18,7 +18,7 @@ from argparse import ArgumentParser
|
|||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
|
from slixmpp import asyncio
|
||||||
|
|
||||||
class AvatarSetter(slixmpp.ClientXMPP):
|
class AvatarSetter(slixmpp.ClientXMPP):
|
||||||
|
|
||||||
@@ -33,6 +33,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def start(self, event):
|
def start(self, event):
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
@@ -51,7 +52,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
avatar_file = None
|
avatar_file = None
|
||||||
try:
|
try:
|
||||||
avatar_file = open(os.path.expanduser(self.filepath))
|
avatar_file = open(os.path.expanduser(self.filepath), 'rb')
|
||||||
except IOError:
|
except IOError:
|
||||||
print('Could not find file: %s' % self.filepath)
|
print('Could not find file: %s' % self.filepath)
|
||||||
return self.disconnect()
|
return self.disconnect()
|
||||||
@@ -65,32 +66,31 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
|||||||
avatar_file.close()
|
avatar_file.close()
|
||||||
|
|
||||||
used_xep84 = False
|
used_xep84 = False
|
||||||
try:
|
|
||||||
print('Publish XEP-0084 avatar data')
|
|
||||||
self['xep_0084'].publish_avatar(avatar)
|
|
||||||
used_xep84 = True
|
|
||||||
except XMPPError:
|
|
||||||
print('Could not publish XEP-0084 avatar')
|
|
||||||
|
|
||||||
try:
|
print('Publish XEP-0084 avatar data')
|
||||||
print('Update vCard with avatar')
|
result = yield from self['xep_0084'].publish_avatar(avatar)
|
||||||
self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
if isinstance(result, XMPPError):
|
||||||
except XMPPError:
|
print('Could not publish XEP-0084 avatar')
|
||||||
|
else:
|
||||||
|
used_xep84 = True
|
||||||
|
|
||||||
|
print('Update vCard with avatar')
|
||||||
|
result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||||
|
if isinstance(result, XMPPError):
|
||||||
print('Could not set vCard avatar')
|
print('Could not set vCard avatar')
|
||||||
|
|
||||||
if used_xep84:
|
if used_xep84:
|
||||||
try:
|
print('Advertise XEP-0084 avatar metadata')
|
||||||
print('Advertise XEP-0084 avatar metadata')
|
result = yield from self['xep_0084'].publish_avatar_metadata([
|
||||||
self['xep_0084'].publish_avatar_metadata([
|
{'id': avatar_id,
|
||||||
{'id': avatar_id,
|
'type': avatar_type,
|
||||||
'type': avatar_type,
|
'bytes': avatar_bytes}
|
||||||
'bytes': avatar_bytes}
|
# We could advertise multiple avatars to provide
|
||||||
# We could advertise multiple avatars to provide
|
# options in image type, source (HTTP vs pubsub),
|
||||||
# options in image type, source (HTTP vs pubsub),
|
# size, etc.
|
||||||
# size, etc.
|
# {'id': ....}
|
||||||
# {'id': ....}
|
])
|
||||||
])
|
if isinstance(result, XMPPError):
|
||||||
except XMPPError:
|
|
||||||
print('Could not publish XEP-0084 metadata')
|
print('Could not publish XEP-0084 metadata')
|
||||||
|
|
||||||
print('Wait for presence updates to propagate...')
|
print('Wait for presence updates to propagate...')
|
||||||
|
|||||||
11
setup.py
11
setup.py
@@ -13,6 +13,14 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cython.Build import cythonize
|
||||||
|
except ImportError:
|
||||||
|
print('Cython not found, falling back to the slow stringprep module.')
|
||||||
|
ext_modules = None
|
||||||
|
else:
|
||||||
|
ext_modules = cythonize('slixmpp/stringprep.pyx')
|
||||||
|
|
||||||
from run_tests import TestCommand
|
from run_tests import TestCommand
|
||||||
from slixmpp.version import __version__
|
from slixmpp.version import __version__
|
||||||
|
|
||||||
@@ -43,7 +51,8 @@ setup(
|
|||||||
license='MIT',
|
license='MIT',
|
||||||
platforms=['any'],
|
platforms=['any'],
|
||||||
packages=packages,
|
packages=packages,
|
||||||
requires=['aiodns', 'pyasn1', 'pyasn1_modules'],
|
ext_modules=ext_modules,
|
||||||
|
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
|
||||||
classifiers=CLASSIFIERS,
|
classifiers=CLASSIFIERS,
|
||||||
cmdclass={'test': TestCommand}
|
cmdclass={'test': TestCommand}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
asyncio.sslproto._is_sslproto_available=lambda: False
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||||
|
|
||||||
@@ -16,6 +18,7 @@ from slixmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin
|
|||||||
from slixmpp.xmlstream.handler import *
|
from slixmpp.xmlstream.handler import *
|
||||||
from slixmpp.xmlstream import XMLStream
|
from slixmpp.xmlstream import XMLStream
|
||||||
from slixmpp.xmlstream.matcher import *
|
from slixmpp.xmlstream.matcher import *
|
||||||
|
from slixmpp.xmlstream.asyncio import asyncio, future_wrapper
|
||||||
from slixmpp.basexmpp import BaseXMPP
|
from slixmpp.basexmpp import BaseXMPP
|
||||||
from slixmpp.clientxmpp import ClientXMPP
|
from slixmpp.clientxmpp import ClientXMPP
|
||||||
from slixmpp.componentxmpp import ComponentXMPP
|
from slixmpp.componentxmpp import ComponentXMPP
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ from slixmpp.exceptions import IqError, IqTimeout
|
|||||||
from slixmpp.stanza import Message, Presence, Iq, StreamError
|
from slixmpp.stanza import Message, Presence, Iq, StreamError
|
||||||
from slixmpp.stanza.roster import Roster
|
from slixmpp.stanza.roster import Roster
|
||||||
from slixmpp.stanza.nick import Nick
|
from slixmpp.stanza.nick import Nick
|
||||||
from slixmpp.stanza.htmlim import HTMLIM
|
|
||||||
|
|
||||||
from slixmpp.xmlstream import XMLStream, JID
|
from slixmpp.xmlstream import XMLStream, JID
|
||||||
from slixmpp.xmlstream import ET, register_stanza_plugin
|
from slixmpp.xmlstream import ET, register_stanza_plugin
|
||||||
@@ -46,8 +45,8 @@ class BaseXMPP(XMLStream):
|
|||||||
is used during initialization.
|
is used during initialization.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, jid='', default_ns='jabber:client'):
|
def __init__(self, jid='', default_ns='jabber:client', **kwargs):
|
||||||
XMLStream.__init__(self)
|
XMLStream.__init__(self, **kwargs)
|
||||||
|
|
||||||
self.default_ns = default_ns
|
self.default_ns = default_ns
|
||||||
self.stream_ns = 'http://etherx.jabber.org/streams'
|
self.stream_ns = 'http://etherx.jabber.org/streams'
|
||||||
@@ -57,12 +56,12 @@ class BaseXMPP(XMLStream):
|
|||||||
self.stream_id = None
|
self.stream_id = None
|
||||||
|
|
||||||
#: The JabberID (JID) requested for this connection.
|
#: The JabberID (JID) requested for this connection.
|
||||||
self.requested_jid = JID(jid, cache_lock=True)
|
self.requested_jid = JID(jid)
|
||||||
|
|
||||||
#: The JabberID (JID) used by this connection,
|
#: The JabberID (JID) used by this connection,
|
||||||
#: as set after session binding. This may even be a
|
#: as set after session binding. This may even be a
|
||||||
#: different bare JID than what was requested.
|
#: different bare JID than what was requested.
|
||||||
self.boundjid = JID(jid, cache_lock=True)
|
self.boundjid = JID(jid)
|
||||||
|
|
||||||
self._expected_server_name = self.boundjid.host
|
self._expected_server_name = self.boundjid.host
|
||||||
self._redirect_attempts = 0
|
self._redirect_attempts = 0
|
||||||
@@ -143,6 +142,13 @@ class BaseXMPP(XMLStream):
|
|||||||
MatchXPath('{%s}message/{%s}body' % (self.default_ns,
|
MatchXPath('{%s}message/{%s}body' % (self.default_ns,
|
||||||
self.default_ns)),
|
self.default_ns)),
|
||||||
self._handle_message))
|
self._handle_message))
|
||||||
|
|
||||||
|
self.register_handler(
|
||||||
|
Callback('IMError',
|
||||||
|
MatchXPath('{%s}message/{%s}error' % (self.default_ns,
|
||||||
|
self.default_ns)),
|
||||||
|
self._handle_message_error))
|
||||||
|
|
||||||
self.register_handler(
|
self.register_handler(
|
||||||
Callback('Presence',
|
Callback('Presence',
|
||||||
MatchXPath("{%s}presence" % self.default_ns),
|
MatchXPath("{%s}presence" % self.default_ns),
|
||||||
@@ -203,9 +209,9 @@ class BaseXMPP(XMLStream):
|
|||||||
log.warning('Legacy XMPP 0.9 protocol detected.')
|
log.warning('Legacy XMPP 0.9 protocol detected.')
|
||||||
self.event('legacy_protocol')
|
self.event('legacy_protocol')
|
||||||
|
|
||||||
def process(self, timeout=None):
|
def process(self, *, forever=True, timeout=None):
|
||||||
self.init_plugins()
|
self.init_plugins()
|
||||||
XMLStream.process(self, timeout)
|
XMLStream.process(self, forever=forever, timeout=timeout)
|
||||||
|
|
||||||
def init_plugins(self):
|
def init_plugins(self):
|
||||||
for name in self.plugin:
|
for name in self.plugin:
|
||||||
@@ -214,7 +220,7 @@ class BaseXMPP(XMLStream):
|
|||||||
self.plugin[name].post_init()
|
self.plugin[name].post_init()
|
||||||
self.plugin[name].post_inited = True
|
self.plugin[name].post_inited = True
|
||||||
|
|
||||||
def register_plugin(self, plugin, pconfig={}, module=None):
|
def register_plugin(self, plugin, pconfig=None, module=None):
|
||||||
"""Register and configure a plugin for use in this stream.
|
"""Register and configure a plugin for use in this stream.
|
||||||
|
|
||||||
:param plugin: The name of the plugin class. Plugin names must
|
:param plugin: The name of the plugin class. Plugin names must
|
||||||
@@ -631,7 +637,7 @@ class BaseXMPP(XMLStream):
|
|||||||
def set_jid(self, jid):
|
def set_jid(self, jid):
|
||||||
"""Rip a JID apart and claim it as our own."""
|
"""Rip a JID apart and claim it as our own."""
|
||||||
log.debug("setting jid to %s", jid)
|
log.debug("setting jid to %s", jid)
|
||||||
self.boundjid = JID(jid, cache_lock=True)
|
self.boundjid = JID(jid)
|
||||||
|
|
||||||
def getjidresource(self, fulljid):
|
def getjidresource(self, fulljid):
|
||||||
if '/' in fulljid:
|
if '/' in fulljid:
|
||||||
@@ -690,6 +696,12 @@ class BaseXMPP(XMLStream):
|
|||||||
msg['to'] = self.boundjid
|
msg['to'] = self.boundjid
|
||||||
self.event('message', msg)
|
self.event('message', msg)
|
||||||
|
|
||||||
|
def _handle_message_error(self, msg):
|
||||||
|
"""Process incoming message error stanzas."""
|
||||||
|
if not self.is_component and not msg['to'].bare:
|
||||||
|
msg['to'] = self.boundjid
|
||||||
|
self.event('message_error', msg)
|
||||||
|
|
||||||
def _handle_available(self, pres):
|
def _handle_available(self, pres):
|
||||||
self.roster[pres['to']][pres['from']].handle_available(pres)
|
self.roster[pres['to']][pres['from']].handle_available(pres)
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ class ClientXMPP(BaseXMPP):
|
|||||||
|
|
||||||
:param jid: The JID of the XMPP user account.
|
:param jid: The JID of the XMPP user account.
|
||||||
:param password: The password for the XMPP user account.
|
:param password: The password for the XMPP user account.
|
||||||
:param ssl: **Deprecated.**
|
|
||||||
:param plugin_config: A dictionary of plugin configurations.
|
:param plugin_config: A dictionary of plugin configurations.
|
||||||
:param plugin_whitelist: A list of approved plugins that
|
:param plugin_whitelist: A list of approved plugins that
|
||||||
will be loaded when calling
|
will be loaded when calling
|
||||||
@@ -58,9 +57,15 @@ class ClientXMPP(BaseXMPP):
|
|||||||
:param escape_quotes: **Deprecated.**
|
:param escape_quotes: **Deprecated.**
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
|
def __init__(self, jid, password, plugin_config=None,
|
||||||
escape_quotes=True, sasl_mech=None, lang='en'):
|
plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
|
||||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
lang='en', **kwargs):
|
||||||
|
if not plugin_whitelist:
|
||||||
|
plugin_whitelist = []
|
||||||
|
if not plugin_config:
|
||||||
|
plugin_config = {}
|
||||||
|
|
||||||
|
BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs)
|
||||||
|
|
||||||
self.escape_quotes = escape_quotes
|
self.escape_quotes = escape_quotes
|
||||||
self.plugin_config = plugin_config
|
self.plugin_config = plugin_config
|
||||||
@@ -135,8 +140,6 @@ class ClientXMPP(BaseXMPP):
|
|||||||
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
|
|
||||||
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
|
||||||
connection. Defaults to ``True``.
|
connection. Defaults to ``True``.
|
||||||
:param use_ssl: Indicates if the older SSL connection method
|
:param use_ssl: Indicates if the older SSL connection method
|
||||||
|
|||||||
@@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
Defaults to ``False``.
|
Defaults to ``False``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, jid, secret, host=None, port=None,
|
def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
|
||||||
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
|
|
||||||
|
if not plugin_whitelist:
|
||||||
|
plugin_whitelist = []
|
||||||
|
if not plugin_config:
|
||||||
|
plugin_config = {}
|
||||||
|
|
||||||
if use_jc_ns:
|
if use_jc_ns:
|
||||||
default_ns = 'jabber:client'
|
default_ns = 'jabber:client'
|
||||||
else:
|
else:
|
||||||
@@ -55,7 +60,7 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
BaseXMPP.__init__(self, jid, default_ns)
|
BaseXMPP.__init__(self, jid, default_ns)
|
||||||
|
|
||||||
self.auto_authorize = None
|
self.auto_authorize = None
|
||||||
self.stream_header = "<stream:stream %s %s to='%s'>" % (
|
self.stream_header = '<stream:stream %s %s to="%s">' % (
|
||||||
'xmlns="jabber:component:accept"',
|
'xmlns="jabber:component:accept"',
|
||||||
'xmlns:stream="%s"' % self.stream_ns,
|
'xmlns:stream="%s"' % self.stream_ns,
|
||||||
jid)
|
jid)
|
||||||
@@ -68,6 +73,8 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
self.plugin_whitelist = plugin_whitelist
|
self.plugin_whitelist = plugin_whitelist
|
||||||
self.is_component = True
|
self.is_component = True
|
||||||
|
|
||||||
|
self.sessionstarted = False
|
||||||
|
|
||||||
self.register_handler(
|
self.register_handler(
|
||||||
Callback('Handshake',
|
Callback('Handshake',
|
||||||
MatchXPath('{jabber:component:accept}handshake'),
|
MatchXPath('{jabber:component:accept}handshake'),
|
||||||
@@ -75,12 +82,9 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
self.add_event_handler('presence_probe',
|
self.add_event_handler('presence_probe',
|
||||||
self._handle_probe)
|
self._handle_probe)
|
||||||
|
|
||||||
def connect(self, host=None, port=None, use_ssl=False,
|
def connect(self, host=None, port=None, use_ssl=False):
|
||||||
use_tls=False, reattempt=True):
|
|
||||||
"""Connect to the server.
|
"""Connect to the server.
|
||||||
|
|
||||||
Setting ``reattempt`` to ``True`` will cause connection attempts to
|
|
||||||
be made every second until a successful connection is established.
|
|
||||||
|
|
||||||
:param host: The name of the desired server for the connection.
|
:param host: The name of the desired server for the connection.
|
||||||
Defaults to :attr:`server_host`.
|
Defaults to :attr:`server_host`.
|
||||||
@@ -88,11 +92,6 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
Defauts to :attr:`server_port`.
|
Defauts to :attr:`server_port`.
|
||||||
:param use_ssl: Flag indicating if SSL should be used by connecting
|
:param use_ssl: Flag indicating if SSL should be used by connecting
|
||||||
directly to a port using SSL.
|
directly to a port using SSL.
|
||||||
:param use_tls: Flag indicating if TLS should be used, allowing for
|
|
||||||
connecting to a port without using SSL immediately and
|
|
||||||
later upgrading the connection.
|
|
||||||
:param reattempt: Flag indicating if the socket should reconnect
|
|
||||||
after disconnections.
|
|
||||||
"""
|
"""
|
||||||
if host is None:
|
if host is None:
|
||||||
host = self.server_host
|
host = self.server_host
|
||||||
@@ -101,14 +100,9 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
|
|
||||||
self.server_name = self.boundjid.host
|
self.server_name = self.boundjid.host
|
||||||
|
|
||||||
if use_tls:
|
|
||||||
log.info("XEP-0114 components can not use TLS")
|
|
||||||
|
|
||||||
log.debug("Connecting to %s:%s", host, port)
|
log.debug("Connecting to %s:%s", host, port)
|
||||||
return XMLStream.connect(self, host=host, port=port,
|
return XMLStream.connect(self, host=host, port=port,
|
||||||
use_ssl=use_ssl,
|
use_ssl=use_ssl)
|
||||||
use_tls=False,
|
|
||||||
reattempt=reattempt)
|
|
||||||
|
|
||||||
def incoming_filter(self, xml):
|
def incoming_filter(self, xml):
|
||||||
"""
|
"""
|
||||||
@@ -145,7 +139,7 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
:param xml: The reply handshake stanza.
|
:param xml: The reply handshake stanza.
|
||||||
"""
|
"""
|
||||||
self.session_bind_event.set()
|
self.session_bind_event.set()
|
||||||
self.session_started_event.set()
|
self.sessionstarted = True
|
||||||
self.event('session_bind', self.boundjid)
|
self.event('session_bind', self.boundjid)
|
||||||
self.event('session_start')
|
self.event('session_start')
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,18 @@ class XMPPError(Exception):
|
|||||||
self.extension_ns = extension_ns
|
self.extension_ns = extension_ns
|
||||||
self.extension_args = extension_args
|
self.extension_args = extension_args
|
||||||
|
|
||||||
|
def format(self):
|
||||||
|
"""
|
||||||
|
Format the error in a simple user-readable string.
|
||||||
|
"""
|
||||||
|
text = [self.etype, self.condition]
|
||||||
|
if self.text:
|
||||||
|
text.append(self.text)
|
||||||
|
if self.extension:
|
||||||
|
text.append(self.extension)
|
||||||
|
# TODO: handle self.extension_args
|
||||||
|
return ': '.join(text)
|
||||||
|
|
||||||
|
|
||||||
class IqTimeout(XMPPError):
|
class IqTimeout(XMPPError):
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class FeatureBind(BasePlugin):
|
|||||||
iq.send(callback=self._on_bind_response)
|
iq.send(callback=self._on_bind_response)
|
||||||
|
|
||||||
def _on_bind_response(self, response):
|
def _on_bind_response(self, response):
|
||||||
self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
|
self.xmpp.boundjid = JID(response['bind']['jid'])
|
||||||
self.xmpp.bound = True
|
self.xmpp.bound = True
|
||||||
self.xmpp.event('session_bind', self.xmpp.boundjid)
|
self.xmpp.event('session_bind', self.xmpp.boundjid)
|
||||||
self.xmpp.session_bind_event.set()
|
self.xmpp.session_bind_event.set()
|
||||||
|
|||||||
@@ -190,14 +190,14 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
except sasl.SASLCancelled:
|
except sasl.SASLCancelled:
|
||||||
self.attempted_mechs.add(self.mech.name)
|
self.attempted_mechs.add(self.mech.name)
|
||||||
self._send_auth()
|
self._send_auth()
|
||||||
except sasl.SASLFailed:
|
|
||||||
self.attempted_mechs.add(self.mech.name)
|
|
||||||
self._send_auth()
|
|
||||||
except sasl.SASLMutualAuthFailed:
|
except sasl.SASLMutualAuthFailed:
|
||||||
log.error("Mutual authentication failed! " + \
|
log.error("Mutual authentication failed! " + \
|
||||||
"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 sasl.SASLFailed:
|
||||||
|
self.attempted_mechs.add(self.mech.name)
|
||||||
|
self._send_auth()
|
||||||
else:
|
else:
|
||||||
resp.send()
|
resp.send()
|
||||||
|
|
||||||
@@ -210,13 +210,13 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
resp['value'] = self.mech.process(stanza['value'])
|
resp['value'] = self.mech.process(stanza['value'])
|
||||||
except sasl.SASLCancelled:
|
except sasl.SASLCancelled:
|
||||||
self.stanza.Abort(self.xmpp).send()
|
self.stanza.Abort(self.xmpp).send()
|
||||||
except sasl.SASLFailed:
|
|
||||||
self.stanza.Abort(self.xmpp).send()
|
|
||||||
except sasl.SASLMutualAuthFailed:
|
except sasl.SASLMutualAuthFailed:
|
||||||
log.error("Mutual authentication failed! " + \
|
log.error("Mutual authentication failed! " + \
|
||||||
"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 sasl.SASLFailed:
|
||||||
|
self.stanza.Abort(self.xmpp).send()
|
||||||
else:
|
else:
|
||||||
if resp.get_value() == '':
|
if resp.get_value() == '':
|
||||||
resp.del_value()
|
resp.del_value()
|
||||||
|
|||||||
495
slixmpp/jid.py
495
slixmpp/jid.py
@@ -11,24 +11,15 @@
|
|||||||
:license: MIT, see LICENSE for more details
|
:license: MIT, see LICENSE for more details
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import stringprep
|
|
||||||
import threading
|
|
||||||
import encodings.idna
|
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
from slixmpp.util import stringprep_profiles
|
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
#: These characters are not allowed to appear in a JID.
|
HAVE_INET_PTON = hasattr(socket, 'inet_pton')
|
||||||
ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \
|
|
||||||
'\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \
|
|
||||||
'\x1a\x1b\x1c\x1d\x1e\x1f' + \
|
|
||||||
' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f'
|
|
||||||
|
|
||||||
#: The basic regex pattern that a JID must match in order to determine
|
#: The basic regex pattern that a JID must match in order to determine
|
||||||
#: the local, domain, and resource parts. This regex does NOT do any
|
#: the local, domain, and resource parts. This regex does NOT do any
|
||||||
@@ -38,22 +29,8 @@ JID_PATTERN = re.compile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
#: The set of escape sequences for the characters not allowed by nodeprep.
|
#: The set of escape sequences for the characters not allowed by nodeprep.
|
||||||
JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f',
|
JID_ESCAPE_SEQUENCES = {'\\20', '\\22', '\\26', '\\27', '\\2f',
|
||||||
'\\3a', '\\3c', '\\3e', '\\40', '\\5c'])
|
'\\3a', '\\3c', '\\3e', '\\40', '\\5c'}
|
||||||
|
|
||||||
#: A mapping of unallowed characters to their escape sequences. An escape
|
|
||||||
#: sequence for '\' is also included since it must also be escaped in
|
|
||||||
#: certain situations.
|
|
||||||
JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20',
|
|
||||||
'"': '\\22',
|
|
||||||
'&': '\\26',
|
|
||||||
"'": '\\27',
|
|
||||||
'/': '\\2f',
|
|
||||||
':': '\\3a',
|
|
||||||
'<': '\\3c',
|
|
||||||
'>': '\\3e',
|
|
||||||
'@': '\\40',
|
|
||||||
'\\': '\\5c'}
|
|
||||||
|
|
||||||
#: The reverse mapping of escape sequences to their original forms.
|
#: The reverse mapping of escape sequences to their original forms.
|
||||||
JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
|
JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
|
||||||
@@ -67,70 +44,9 @@ JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
|
|||||||
'\\40': '@',
|
'\\40': '@',
|
||||||
'\\5c': '\\'}
|
'\\5c': '\\'}
|
||||||
|
|
||||||
JID_CACHE = OrderedDict()
|
|
||||||
JID_CACHE_LOCK = threading.Lock()
|
|
||||||
JID_CACHE_MAX_SIZE = 1024
|
|
||||||
|
|
||||||
def _cache(key, parts, locked):
|
|
||||||
JID_CACHE[key] = (parts, locked)
|
|
||||||
if len(JID_CACHE) > JID_CACHE_MAX_SIZE:
|
|
||||||
with JID_CACHE_LOCK:
|
|
||||||
while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
|
|
||||||
found = None
|
|
||||||
for key, item in JID_CACHE.items():
|
|
||||||
if not item[1]: # if not locked
|
|
||||||
found = key
|
|
||||||
break
|
|
||||||
if not found: # more than MAX_SIZE locked
|
|
||||||
# warn?
|
|
||||||
break
|
|
||||||
del JID_CACHE[found]
|
|
||||||
|
|
||||||
# pylint: disable=c0103
|
|
||||||
#: The nodeprep profile of stringprep used to validate the local,
|
|
||||||
#: or username, portion of a JID.
|
|
||||||
nodeprep = stringprep_profiles.create(
|
|
||||||
nfkc=True,
|
|
||||||
bidi=True,
|
|
||||||
mappings=[
|
|
||||||
stringprep_profiles.b1_mapping,
|
|
||||||
stringprep.map_table_b2],
|
|
||||||
prohibited=[
|
|
||||||
stringprep.in_table_c11,
|
|
||||||
stringprep.in_table_c12,
|
|
||||||
stringprep.in_table_c21,
|
|
||||||
stringprep.in_table_c22,
|
|
||||||
stringprep.in_table_c3,
|
|
||||||
stringprep.in_table_c4,
|
|
||||||
stringprep.in_table_c5,
|
|
||||||
stringprep.in_table_c6,
|
|
||||||
stringprep.in_table_c7,
|
|
||||||
stringprep.in_table_c8,
|
|
||||||
stringprep.in_table_c9,
|
|
||||||
lambda c: c in ' \'"&/:<>@'],
|
|
||||||
unassigned=[stringprep.in_table_a1])
|
|
||||||
|
|
||||||
# pylint: disable=c0103
|
|
||||||
#: The resourceprep profile of stringprep, which is used to validate
|
|
||||||
#: the resource portion of a JID.
|
|
||||||
resourceprep = stringprep_profiles.create(
|
|
||||||
nfkc=True,
|
|
||||||
bidi=True,
|
|
||||||
mappings=[stringprep_profiles.b1_mapping],
|
|
||||||
prohibited=[
|
|
||||||
stringprep.in_table_c12,
|
|
||||||
stringprep.in_table_c21,
|
|
||||||
stringprep.in_table_c22,
|
|
||||||
stringprep.in_table_c3,
|
|
||||||
stringprep.in_table_c4,
|
|
||||||
stringprep.in_table_c5,
|
|
||||||
stringprep.in_table_c6,
|
|
||||||
stringprep.in_table_c7,
|
|
||||||
stringprep.in_table_c8,
|
|
||||||
stringprep.in_table_c9],
|
|
||||||
unassigned=[stringprep.in_table_a1])
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Find the best cache size for a standard usage.
|
||||||
|
@lru_cache(maxsize=1024)
|
||||||
def _parse_jid(data):
|
def _parse_jid(data):
|
||||||
"""
|
"""
|
||||||
Parse string data into the node, domain, and resource
|
Parse string data into the node, domain, and resource
|
||||||
@@ -162,17 +78,19 @@ def _validate_node(node):
|
|||||||
|
|
||||||
:returns: The local portion of a JID, as validated by nodeprep.
|
:returns: The local portion of a JID, as validated by nodeprep.
|
||||||
"""
|
"""
|
||||||
try:
|
if node is None:
|
||||||
if node is not None:
|
return None
|
||||||
node = nodeprep(node)
|
|
||||||
|
|
||||||
if not node:
|
try:
|
||||||
raise InvalidJID('Localpart must not be 0 bytes')
|
node = nodeprep(node)
|
||||||
if len(node) > 1023:
|
except StringprepError:
|
||||||
raise InvalidJID('Localpart must be less than 1024 bytes')
|
raise InvalidJID('Nodeprep failed')
|
||||||
return node
|
|
||||||
except stringprep_profiles.StringPrepError:
|
if not node:
|
||||||
raise InvalidJID('Invalid local part')
|
raise InvalidJID('Localpart must not be 0 bytes')
|
||||||
|
if len(node) > 1023:
|
||||||
|
raise InvalidJID('Localpart must be less than 1024 bytes')
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
def _validate_domain(domain):
|
def _validate_domain(domain):
|
||||||
@@ -199,10 +117,10 @@ def _validate_domain(domain):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Check if this is an IPv6 address
|
# Check if this is an IPv6 address
|
||||||
if not ip_addr and hasattr(socket, 'inet_pton'):
|
if not ip_addr and HAVE_INET_PTON and domain[0] == '[' and domain[-1] == ']':
|
||||||
try:
|
try:
|
||||||
socket.inet_pton(socket.AF_INET6, domain.strip('[]'))
|
ip = domain[1:-1]
|
||||||
domain = '[%s]' % domain.strip('[]')
|
socket.inet_pton(socket.AF_INET6, ip)
|
||||||
ip_addr = True
|
ip_addr = True
|
||||||
except (socket.error, ValueError):
|
except (socket.error, ValueError):
|
||||||
pass
|
pass
|
||||||
@@ -213,31 +131,19 @@ def _validate_domain(domain):
|
|||||||
if domain and domain[-1] == '.':
|
if domain and domain[-1] == '.':
|
||||||
domain = domain[:-1]
|
domain = domain[:-1]
|
||||||
|
|
||||||
domain_parts = []
|
try:
|
||||||
|
domain = idna(domain)
|
||||||
|
except StringprepError:
|
||||||
|
raise InvalidJID('idna validation failed')
|
||||||
|
|
||||||
|
if ':' in domain:
|
||||||
|
raise InvalidJID('Domain containing a port')
|
||||||
for label in domain.split('.'):
|
for label in domain.split('.'):
|
||||||
try:
|
if not label:
|
||||||
label = encodings.idna.nameprep(label)
|
raise InvalidJID('Domain containing too many dots')
|
||||||
encodings.idna.ToASCII(label)
|
|
||||||
pass_nameprep = True
|
|
||||||
except UnicodeError:
|
|
||||||
pass_nameprep = False
|
|
||||||
|
|
||||||
if not pass_nameprep:
|
|
||||||
raise InvalidJID('Could not encode domain as ASCII')
|
|
||||||
|
|
||||||
if label.startswith('xn--'):
|
|
||||||
label = encodings.idna.ToUnicode(label)
|
|
||||||
|
|
||||||
for char in label:
|
|
||||||
if char in ILLEGAL_CHARS:
|
|
||||||
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 -')
|
||||||
|
|
||||||
domain_parts.append(label)
|
|
||||||
domain = '.'.join(domain_parts)
|
|
||||||
|
|
||||||
if not domain:
|
if not domain:
|
||||||
raise InvalidJID('Domain must not be 0 bytes')
|
raise InvalidJID('Domain must not be 0 bytes')
|
||||||
if len(domain) > 1023:
|
if len(domain) > 1023:
|
||||||
@@ -253,42 +159,19 @@ def _validate_resource(resource):
|
|||||||
|
|
||||||
:returns: The local portion of a JID, as validated by resourceprep.
|
:returns: The local portion of a JID, as validated by resourceprep.
|
||||||
"""
|
"""
|
||||||
|
if resource is None:
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if resource is not None:
|
resource = resourceprep(resource)
|
||||||
resource = resourceprep(resource)
|
except StringprepError:
|
||||||
|
raise InvalidJID('Resourceprep failed')
|
||||||
|
|
||||||
if not resource:
|
if not resource:
|
||||||
raise InvalidJID('Resource must not be 0 bytes')
|
raise InvalidJID('Resource must not be 0 bytes')
|
||||||
if len(resource) > 1023:
|
if len(resource) > 1023:
|
||||||
raise InvalidJID('Resource must be less than 1024 bytes')
|
raise InvalidJID('Resource must be less than 1024 bytes')
|
||||||
return resource
|
return resource
|
||||||
except stringprep_profiles.StringPrepError:
|
|
||||||
raise InvalidJID('Invalid resource')
|
|
||||||
|
|
||||||
|
|
||||||
def _escape_node(node):
|
|
||||||
"""Escape the local portion of a JID."""
|
|
||||||
result = []
|
|
||||||
|
|
||||||
for i, char in enumerate(node):
|
|
||||||
if char == '\\':
|
|
||||||
if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES:
|
|
||||||
result.append('\\5c')
|
|
||||||
continue
|
|
||||||
result.append(char)
|
|
||||||
|
|
||||||
for i, char in enumerate(result):
|
|
||||||
if char != '\\':
|
|
||||||
result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char)
|
|
||||||
|
|
||||||
escaped = ''.join(result)
|
|
||||||
|
|
||||||
if escaped.startswith('\\20') or escaped.endswith('\\20'):
|
|
||||||
raise InvalidJID('Escaped local part starts or ends with "\\20"')
|
|
||||||
|
|
||||||
_validate_node(escaped)
|
|
||||||
|
|
||||||
return escaped
|
|
||||||
|
|
||||||
|
|
||||||
def _unescape_node(node):
|
def _unescape_node(node):
|
||||||
@@ -313,9 +196,7 @@ def _unescape_node(node):
|
|||||||
seq = seq[1:]
|
seq = seq[1:]
|
||||||
else:
|
else:
|
||||||
unescaped.append(char)
|
unescaped.append(char)
|
||||||
unescaped = ''.join(unescaped)
|
return ''.join(unescaped)
|
||||||
|
|
||||||
return unescaped
|
|
||||||
|
|
||||||
|
|
||||||
def _format_jid(local=None, domain=None, resource=None):
|
def _format_jid(local=None, domain=None, resource=None):
|
||||||
@@ -328,12 +209,12 @@ def _format_jid(local=None, domain=None, resource=None):
|
|||||||
:return: A full or bare JID string.
|
:return: A full or bare JID string.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
if local:
|
if local is not None:
|
||||||
result.append(local)
|
result.append(local)
|
||||||
result.append('@')
|
result.append('@')
|
||||||
if domain:
|
if domain is not None:
|
||||||
result.append(domain)
|
result.append(domain)
|
||||||
if resource:
|
if resource is not None:
|
||||||
result.append('/')
|
result.append('/')
|
||||||
result.append(resource)
|
result.append(resource)
|
||||||
return ''.join(result)
|
return ''.join(result)
|
||||||
@@ -349,47 +230,47 @@ class InvalidJID(ValueError):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=R0903
|
# pylint: disable=R0903
|
||||||
class UnescapedJID(object):
|
class UnescapedJID:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
.. versionadded:: 1.1.10
|
.. versionadded:: 1.1.10
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, local, domain, resource):
|
__slots__ = ('_node', '_domain', '_resource')
|
||||||
self._jid = (local, domain, resource)
|
|
||||||
|
|
||||||
# pylint: disable=R0911
|
def __init__(self, node, domain, resource):
|
||||||
def __getattr__(self, name):
|
self._node = node
|
||||||
|
self._domain = domain
|
||||||
|
self._resource = resource
|
||||||
|
|
||||||
|
def __getattribute__(self, name):
|
||||||
"""Retrieve the given JID component.
|
"""Retrieve the given JID component.
|
||||||
|
|
||||||
:param name: one of: user, server, domain, resource,
|
:param name: one of: user, server, domain, resource,
|
||||||
full, or bare.
|
full, or bare.
|
||||||
"""
|
"""
|
||||||
if name == 'resource':
|
if name == 'resource':
|
||||||
return self._jid[2] or ''
|
return self._resource or ''
|
||||||
elif name in ('user', 'username', 'local', 'node'):
|
if name in ('user', 'username', 'local', 'node'):
|
||||||
return self._jid[0] or ''
|
return self._node or ''
|
||||||
elif name in ('server', 'domain', 'host'):
|
if name in ('server', 'domain', 'host'):
|
||||||
return self._jid[1] or ''
|
return self._domain or ''
|
||||||
elif name in ('full', 'jid'):
|
if name in ('full', 'jid'):
|
||||||
return _format_jid(*self._jid)
|
return _format_jid(self._node, self._domain, self._resource)
|
||||||
elif name == 'bare':
|
if name == 'bare':
|
||||||
return _format_jid(self._jid[0], self._jid[1])
|
return _format_jid(self._node, self._domain)
|
||||||
elif name == '_jid':
|
return object.__getattribute__(self, name)
|
||||||
return getattr(super(JID, self), '_jid')
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Use the full JID as the string value."""
|
"""Use the full JID as the string value."""
|
||||||
return _format_jid(*self._jid)
|
return _format_jid(self._node, self._domain, self._resource)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""Use the full JID as the representation."""
|
"""Use the full JID as the representation."""
|
||||||
return self.__str__()
|
return _format_jid(self._node, self._domain, self._resource)
|
||||||
|
|
||||||
|
|
||||||
class JID(object):
|
class JID:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A representation of a Jabber ID, or JID.
|
A representation of a Jabber ID, or JID.
|
||||||
@@ -401,13 +282,13 @@ class JID(object):
|
|||||||
The JID is a full JID otherwise.
|
The JID is a full JID otherwise.
|
||||||
|
|
||||||
**JID Properties:**
|
**JID Properties:**
|
||||||
:jid: Alias for ``full``.
|
|
||||||
:full: The string value of the full JID.
|
:full: The string value of the full JID.
|
||||||
|
:jid: Alias for ``full``.
|
||||||
:bare: The string value of the bare JID.
|
:bare: The string value of the bare JID.
|
||||||
:user: The username portion of the JID.
|
:node: The node portion of the JID.
|
||||||
:username: Alias for ``user``.
|
:user: Alias for ``node``.
|
||||||
:local: Alias for ``user``.
|
:local: Alias for ``node``.
|
||||||
:node: Alias for ``user``.
|
:username: Alias for ``node``.
|
||||||
:domain: The domain name portion of the JID.
|
:domain: The domain name portion of the JID.
|
||||||
:server: Alias for ``domain``.
|
:server: Alias for ``domain``.
|
||||||
:host: Alias for ``domain``.
|
:host: Alias for ``domain``.
|
||||||
@@ -415,67 +296,23 @@ class JID(object):
|
|||||||
|
|
||||||
:param string jid:
|
:param string jid:
|
||||||
A string of the form ``'[user@]domain[/resource]'``.
|
A string of the form ``'[user@]domain[/resource]'``.
|
||||||
:param string local:
|
|
||||||
Optional. Specify the local, or username, portion
|
|
||||||
of the JID. If provided, it will override the local
|
|
||||||
value provided by the `jid` parameter. The given
|
|
||||||
local value will also be escaped if necessary.
|
|
||||||
:param string domain:
|
|
||||||
Optional. Specify the domain of the JID. If
|
|
||||||
provided, it will override the domain given by
|
|
||||||
the `jid` parameter.
|
|
||||||
:param string resource:
|
|
||||||
Optional. Specify the resource value of the JID.
|
|
||||||
If provided, it will override the domain given
|
|
||||||
by the `jid` parameter.
|
|
||||||
|
|
||||||
:raises InvalidJID:
|
:raises InvalidJID:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=W0212
|
__slots__ = ('_node', '_domain', '_resource')
|
||||||
def __init__(self, jid=None, **kwargs):
|
|
||||||
locked = kwargs.get('cache_lock', False)
|
|
||||||
in_local = kwargs.get('local', None)
|
|
||||||
in_domain = kwargs.get('domain', None)
|
|
||||||
in_resource = kwargs.get('resource', None)
|
|
||||||
parts = None
|
|
||||||
if in_local or in_domain or in_resource:
|
|
||||||
parts = (in_local, in_domain, in_resource)
|
|
||||||
|
|
||||||
# only check cache if there is a jid string, or parts, not if there
|
def __init__(self, jid=None):
|
||||||
# are both
|
if not jid:
|
||||||
self._jid = None
|
self._node = None
|
||||||
key = None
|
self._domain = None
|
||||||
if (jid is not None) and (parts is None):
|
self._resource = None
|
||||||
if isinstance(jid, JID):
|
elif not isinstance(jid, JID):
|
||||||
# it's already good to go, and there are no additions
|
self._node, self._domain, self._resource = _parse_jid(jid)
|
||||||
self._jid = jid._jid
|
else:
|
||||||
return
|
self._node = jid._node
|
||||||
key = jid
|
self._domain = jid._domain
|
||||||
self._jid, locked = JID_CACHE.get(jid, (None, locked))
|
self._resource = jid._resource
|
||||||
elif jid is None and parts is not None:
|
|
||||||
key = parts
|
|
||||||
self._jid, locked = JID_CACHE.get(parts, (None, locked))
|
|
||||||
if not self._jid:
|
|
||||||
if not jid:
|
|
||||||
parsed_jid = (None, None, None)
|
|
||||||
elif not isinstance(jid, JID):
|
|
||||||
parsed_jid = _parse_jid(jid)
|
|
||||||
else:
|
|
||||||
parsed_jid = jid._jid
|
|
||||||
|
|
||||||
local, domain, resource = parsed_jid
|
|
||||||
|
|
||||||
if 'local' in kwargs:
|
|
||||||
local = _escape_node(in_local)
|
|
||||||
if 'domain' in kwargs:
|
|
||||||
domain = _validate_domain(in_domain)
|
|
||||||
if 'resource' in kwargs:
|
|
||||||
resource = _validate_resource(in_resource)
|
|
||||||
|
|
||||||
self._jid = (local, domain, resource)
|
|
||||||
if key:
|
|
||||||
_cache(key, self._jid, locked)
|
|
||||||
|
|
||||||
def unescape(self):
|
def unescape(self):
|
||||||
"""Return an unescaped JID object.
|
"""Return an unescaped JID object.
|
||||||
@@ -488,151 +325,125 @@ class JID(object):
|
|||||||
|
|
||||||
.. versionadded:: 1.1.10
|
.. versionadded:: 1.1.10
|
||||||
"""
|
"""
|
||||||
return UnescapedJID(_unescape_node(self._jid[0]),
|
return UnescapedJID(_unescape_node(self._node),
|
||||||
self._jid[1],
|
self._domain,
|
||||||
self._jid[2])
|
self._resource)
|
||||||
|
|
||||||
def regenerate(self):
|
|
||||||
"""No-op
|
|
||||||
|
|
||||||
.. deprecated:: 1.1.10
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def reset(self, data):
|
|
||||||
"""Start fresh from a new JID string.
|
|
||||||
|
|
||||||
:param string data: A string of the form ``'[user@]domain[/resource]'``.
|
|
||||||
|
|
||||||
.. deprecated:: 1.1.10
|
|
||||||
"""
|
|
||||||
self._jid = JID(data)._jid
|
|
||||||
|
|
||||||
@property
|
|
||||||
def resource(self):
|
|
||||||
return self._jid[2] or ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user(self):
|
|
||||||
return self._jid[0] or ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def local(self):
|
|
||||||
return self._jid[0] or ''
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def node(self):
|
def node(self):
|
||||||
return self._jid[0] or ''
|
return self._node or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self):
|
||||||
|
return self._node or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def local(self):
|
||||||
|
return self._node or ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def username(self):
|
def username(self):
|
||||||
return self._jid[0] or ''
|
return self._node or ''
|
||||||
|
|
||||||
@property
|
|
||||||
def bare(self):
|
|
||||||
return _format_jid(self._jid[0], self._jid[1])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def server(self):
|
|
||||||
return self._jid[1] or ''
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def domain(self):
|
def domain(self):
|
||||||
return self._jid[1] or ''
|
return self._domain or ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def server(self):
|
||||||
|
return self._domain or ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self):
|
def host(self):
|
||||||
return self._jid[1] or ''
|
return self._domain or ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full(self):
|
def resource(self):
|
||||||
return _format_jid(*self._jid)
|
return self._resource or ''
|
||||||
|
|
||||||
@property
|
|
||||||
def jid(self):
|
|
||||||
return _format_jid(*self._jid)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bare(self):
|
def bare(self):
|
||||||
return _format_jid(self._jid[0], self._jid[1])
|
return _format_jid(self._node, self._domain)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full(self):
|
||||||
|
return _format_jid(self._node, self._domain, self._resource)
|
||||||
|
|
||||||
@resource.setter
|
@property
|
||||||
def resource(self, value):
|
def jid(self):
|
||||||
self._jid = JID(self, resource=value)._jid
|
return _format_jid(self._node, self._domain, self._resource)
|
||||||
|
|
||||||
@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
|
@node.setter
|
||||||
def node(self, value):
|
def node(self, value):
|
||||||
self._jid = JID(self, local=value)._jid
|
self._node = _validate_node(value)
|
||||||
|
|
||||||
@server.setter
|
@user.setter
|
||||||
def server(self, value):
|
def user(self, value):
|
||||||
self._jid = JID(self, domain=value)._jid
|
self._node = _validate_node(value)
|
||||||
|
|
||||||
|
@local.setter
|
||||||
|
def local(self, value):
|
||||||
|
self._node = _validate_node(value)
|
||||||
|
|
||||||
|
@username.setter
|
||||||
|
def username(self, value):
|
||||||
|
self._node = _validate_node(value)
|
||||||
|
|
||||||
@domain.setter
|
@domain.setter
|
||||||
def domain(self, value):
|
def domain(self, value):
|
||||||
self._jid = JID(self, domain=value)._jid
|
self._domain = _validate_domain(value)
|
||||||
|
|
||||||
|
@server.setter
|
||||||
|
def server(self, value):
|
||||||
|
self._domain = _validate_domain(value)
|
||||||
|
|
||||||
@host.setter
|
@host.setter
|
||||||
def host(self, value):
|
def host(self, value):
|
||||||
self._jid = JID(self, domain=value)._jid
|
self._domain = _validate_domain(value)
|
||||||
|
|
||||||
@full.setter
|
|
||||||
def full(self, value):
|
|
||||||
self._jid = JID(value)._jid
|
|
||||||
|
|
||||||
@jid.setter
|
|
||||||
def jid(self, value):
|
|
||||||
self._jid = JID(value)._jid
|
|
||||||
|
|
||||||
@bare.setter
|
@bare.setter
|
||||||
def bare(self, value):
|
def bare(self, value):
|
||||||
parsed = JID(value)._jid
|
node, domain, resource = _parse_jid(value)
|
||||||
self._jid = (parsed[0], parsed[1], self._jid[2])
|
assert not resource
|
||||||
|
self._node = node
|
||||||
|
self._domain = domain
|
||||||
|
|
||||||
|
@resource.setter
|
||||||
|
def resource(self, value):
|
||||||
|
self._resource = _validate_resource(value)
|
||||||
|
|
||||||
|
@full.setter
|
||||||
|
def full(self, value):
|
||||||
|
self._node, self._domain, self._resource = _parse_jid(value)
|
||||||
|
|
||||||
|
@jid.setter
|
||||||
|
def jid(self, value):
|
||||||
|
self._node, self._domain, self._resource = _parse_jid(value)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Use the full JID as the string value."""
|
"""Use the full JID as the string value."""
|
||||||
return _format_jid(*self._jid)
|
return _format_jid(self._node, self._domain, self._resource)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""Use the full JID as the representation."""
|
"""Use the full JID as the representation."""
|
||||||
return self.__str__()
|
return _format_jid(self._node, self._domain, self._resource)
|
||||||
|
|
||||||
# pylint: disable=W0212
|
# pylint: disable=W0212
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Two JIDs are equal if they have the same full JID value."""
|
"""Two JIDs are equal if they have the same full JID value."""
|
||||||
if isinstance(other, UnescapedJID):
|
if isinstance(other, UnescapedJID):
|
||||||
return False
|
return False
|
||||||
|
if not isinstance(other, JID):
|
||||||
|
other = JID(other)
|
||||||
|
|
||||||
other = JID(other)
|
return (self._node == other._node and
|
||||||
return self._jid == other._jid
|
self._domain == other._domain and
|
||||||
|
self._resource == other._resource)
|
||||||
|
|
||||||
# pylint: disable=W0212
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
"""Two JIDs are considered unequal if they are not equal."""
|
"""Two JIDs are considered unequal if they are not equal."""
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
"""Hash a JID based on the string version of its full JID."""
|
"""Hash a JID based on the string version of its full JID."""
|
||||||
return hash(self.__str__())
|
return hash(_format_jid(self._node, self._domain, self._resource))
|
||||||
|
|
||||||
def __copy__(self):
|
|
||||||
"""Generate a duplicate JID."""
|
|
||||||
return JID(self)
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
|
||||||
"""Generate a duplicate JID."""
|
|
||||||
return JID(deepcopy(str(self), memo))
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ __all__ = [
|
|||||||
'xep_0108', # User Activity
|
'xep_0108', # User Activity
|
||||||
'xep_0115', # Entity Capabilities
|
'xep_0115', # Entity Capabilities
|
||||||
'xep_0118', # User Tune
|
'xep_0118', # User Tune
|
||||||
|
'xep_0122', # Data Forms Validation
|
||||||
'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
|
||||||
@@ -83,4 +84,5 @@ __all__ = [
|
|||||||
'xep_0319', # Last User Interaction in Presence
|
'xep_0319', # Last User Interaction in Presence
|
||||||
'xep_0323', # IoT Systems Sensor Data
|
'xep_0323', # IoT Systems Sensor Data
|
||||||
'xep_0325', # IoT Systems Control
|
'xep_0325', # IoT Systems Control
|
||||||
|
'xep_0332', # HTTP Over XMPP Transport
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ class PluginManager(object):
|
|||||||
:param dict config: Optional settings dictionary for
|
:param dict config: Optional settings dictionary for
|
||||||
configuring plugin behaviour.
|
configuring plugin behaviour.
|
||||||
"""
|
"""
|
||||||
top_level = False
|
|
||||||
if enabled is None:
|
if enabled is None:
|
||||||
enabled = set()
|
enabled = set()
|
||||||
|
|
||||||
@@ -166,14 +165,14 @@ class PluginManager(object):
|
|||||||
self.enable(dep, enabled=enabled)
|
self.enable(dep, enabled=enabled)
|
||||||
plugin._init()
|
plugin._init()
|
||||||
|
|
||||||
if top_level:
|
for name in enabled:
|
||||||
for name in enabled:
|
if hasattr(self._plugins[name], 'old_style'):
|
||||||
if hasattr(self.plugins[name], 'old_style'):
|
# Older style plugins require post_init()
|
||||||
# Older style plugins require post_init()
|
# to run just before stream processing begins,
|
||||||
# to run just before stream processing begins,
|
# so we don't call it here.
|
||||||
# so we don't call it here.
|
pass
|
||||||
pass
|
else:
|
||||||
self.plugins[name].post_init()
|
self._plugins[name].post_init()
|
||||||
|
|
||||||
def enable_all(self, names=None, config=None):
|
def enable_all(self, names=None, config=None):
|
||||||
"""Enable all registered plugins.
|
"""Enable all registered plugins.
|
||||||
|
|||||||
47
slixmpp/plugins/google/auth/stanza.py
Normal file
47
slixmpp/plugins/google/auth/stanza.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.xmlstream import ElementBase, ET
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleAuth(ElementBase):
|
||||||
|
name = 'auth'
|
||||||
|
namespace = 'http://www.google.com/talk/protocol/auth'
|
||||||
|
plugin_attrib = 'google'
|
||||||
|
interfaces = 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('')
|
||||||
|
|
||||||
|
def get_client_uses_full_bind_result(self):
|
||||||
|
return self.parent()._get_attr(self.discovery_attr) == 'true'
|
||||||
|
|
||||||
|
def set_client_uses_full_bind_result(self, value):
|
||||||
|
if value in (True, 'true'):
|
||||||
|
self.parent()._set_attr(self.discovery_attr, 'true')
|
||||||
|
else:
|
||||||
|
self.parent()._del_attr(self.discovery_attr)
|
||||||
|
|
||||||
|
def del_client_uses_full_bind_result(self):
|
||||||
|
self.parent()._del_attr(self.discovery_attr)
|
||||||
|
|
||||||
|
def get_service(self):
|
||||||
|
return self.parent()._get_attr(self.service_attr, '')
|
||||||
|
|
||||||
|
def set_service(self, value):
|
||||||
|
if value:
|
||||||
|
self.parent()._set_attr(self.service_attr, value)
|
||||||
|
else:
|
||||||
|
self.parent()._del_attr(self.service_attr)
|
||||||
|
|
||||||
|
def del_service(self):
|
||||||
|
self.parent()._del_attr(self.service_attr)
|
||||||
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from slixmpp.stanza import Iq
|
||||||
|
from slixmpp.xmlstream.handler import Callback
|
||||||
|
from slixmpp.xmlstream.matcher import MatchXPath
|
||||||
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
|
from slixmpp.plugins import BasePlugin
|
||||||
|
from slixmpp.plugins.google.gmail import stanza
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Gmail(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Google: Gmail Notifications
|
||||||
|
|
||||||
|
Also see <https://developers.google.com/talk/jep_extensions/gmail>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'gmail'
|
||||||
|
description = 'Google: Gmail Notifications'
|
||||||
|
dependencies = set()
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, stanza.GmailQuery)
|
||||||
|
register_stanza_plugin(Iq, stanza.MailBox)
|
||||||
|
register_stanza_plugin(Iq, stanza.NewMail)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Gmail New Mail',
|
||||||
|
MatchXPath('{%s}iq/{%s}%s' % (
|
||||||
|
self.xmpp.default_ns,
|
||||||
|
stanza.NewMail.namespace,
|
||||||
|
stanza.NewMail.name)),
|
||||||
|
self._handle_new_mail))
|
||||||
|
|
||||||
|
self._last_result_time = None
|
||||||
|
self._last_result_tid = None
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Gmail New Mail')
|
||||||
|
|
||||||
|
def _handle_new_mail(self, iq):
|
||||||
|
log.info('Gmail: New email!')
|
||||||
|
iq.reply().send()
|
||||||
|
self.xmpp.event('gmail_notification')
|
||||||
|
|
||||||
|
def check(self, timeout=None, callback=None):
|
||||||
|
last_time = self._last_result_time
|
||||||
|
last_tid = self._last_result_tid
|
||||||
|
|
||||||
|
callback = lambda iq: self._update_last_results(iq, callback)
|
||||||
|
|
||||||
|
return self.search(newer_time=last_time,
|
||||||
|
newer_tid=last_tid,
|
||||||
|
timeout=timeout,
|
||||||
|
callback=callback)
|
||||||
|
|
||||||
|
def _update_last_results(self, iq, callback=None):
|
||||||
|
self._last_result_time = iq['gmail_messages']['result_time']
|
||||||
|
threads = iq['gmail_messages']['threads']
|
||||||
|
if threads:
|
||||||
|
self._last_result_tid = threads[0]['tid']
|
||||||
|
if callback:
|
||||||
|
callback(iq)
|
||||||
|
|
||||||
|
def search(self, query=None, newer_time=None, newer_tid=None,
|
||||||
|
timeout=None, callback=None):
|
||||||
|
if not query:
|
||||||
|
log.info('Gmail: Checking for new email')
|
||||||
|
else:
|
||||||
|
log.info('Gmail: Searching for emails matching: "%s"', query)
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq['to'] = self.xmpp.boundjid.bare
|
||||||
|
iq['gmail']['search'] = query
|
||||||
|
iq['gmail']['newer_than_time'] = newer_time
|
||||||
|
iq['gmail']['newer_than_tid'] = newer_tid
|
||||||
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.jid import JID
|
||||||
|
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class NoSave(ElementBase):
|
||||||
|
name = 'x'
|
||||||
|
namespace = 'google:nosave'
|
||||||
|
plugin_attrib = 'google_nosave'
|
||||||
|
interfaces = 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, value):
|
||||||
|
self._set_attr('source', str(value))
|
||||||
|
|
||||||
|
|
||||||
|
register_stanza_plugin(NoSaveQuery, Item)
|
||||||
63
slixmpp/plugins/google/settings/settings.py
Normal file
63
slixmpp/plugins/google/settings/settings.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.stanza import Iq
|
||||||
|
from slixmpp.xmlstream.handler import Callback
|
||||||
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
|
from slixmpp.plugins import BasePlugin
|
||||||
|
from slixmpp.plugins.google.settings import stanza
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleSettings(BasePlugin):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Google: Gmail Notifications
|
||||||
|
|
||||||
|
Also see <https://developers.google.com/talk/jep_extensions/usersettings>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'google_settings'
|
||||||
|
description = 'Google: User Settings'
|
||||||
|
dependencies = set()
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, stanza.UserSettings)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Google Settings',
|
||||||
|
StanzaPath('iq@type=set/google_settings'),
|
||||||
|
self._handle_settings_change))
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.remove_handler('Google Settings')
|
||||||
|
|
||||||
|
def get(self, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'get'
|
||||||
|
iq.enable('google_settings')
|
||||||
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def update(self, settings, timeout=None, callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['type'] = 'set'
|
||||||
|
iq.enable('google_settings')
|
||||||
|
|
||||||
|
for setting, value in settings.items():
|
||||||
|
iq['google_settings'][setting] = value
|
||||||
|
|
||||||
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def _handle_settings_change(self, iq):
|
||||||
|
reply = self.xmpp.Iq()
|
||||||
|
reply['type'] = 'result'
|
||||||
|
reply['id'] = iq['id']
|
||||||
|
reply['to'] = iq['from']
|
||||||
|
reply.send()
|
||||||
|
self.xmpp.event('google_settings_change', iq)
|
||||||
@@ -13,8 +13,9 @@ class FormField(ElementBase):
|
|||||||
namespace = 'jabber:x:data'
|
namespace = 'jabber:x:data'
|
||||||
name = 'field'
|
name = 'field'
|
||||||
plugin_attrib = 'field'
|
plugin_attrib = 'field'
|
||||||
|
plugin_multi_attrib = 'fields'
|
||||||
interfaces = set(('answer', 'desc', 'required', 'value',
|
interfaces = set(('answer', 'desc', 'required', 'value',
|
||||||
'options', 'label', 'type', 'var'))
|
'label', 'type', 'var'))
|
||||||
sub_interfaces = set(('desc',))
|
sub_interfaces = set(('desc',))
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
@@ -165,6 +166,7 @@ class FieldOption(ElementBase):
|
|||||||
plugin_attrib = 'option'
|
plugin_attrib = 'option'
|
||||||
interfaces = set(('label', 'value'))
|
interfaces = set(('label', 'value'))
|
||||||
sub_interfaces = set(('value',))
|
sub_interfaces = set(('value',))
|
||||||
|
plugin_multi_attrib = 'options'
|
||||||
|
|
||||||
|
|
||||||
FormField.addOption = FormField.add_option
|
FormField.addOption = FormField.add_option
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import copy
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from slixmpp.thirdparty import OrderedSet
|
||||||
|
|
||||||
from slixmpp.xmlstream import ElementBase, ET
|
from slixmpp.xmlstream import ElementBase, ET
|
||||||
from slixmpp.plugins.xep_0004.stanza import FormField
|
from slixmpp.plugins.xep_0004.stanza import FormField
|
||||||
@@ -22,8 +23,7 @@ class Form(ElementBase):
|
|||||||
namespace = 'jabber:x:data'
|
namespace = 'jabber:x:data'
|
||||||
name = 'x'
|
name = 'x'
|
||||||
plugin_attrib = 'form'
|
plugin_attrib = 'form'
|
||||||
interfaces = set(('fields', 'instructions', 'items',
|
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
|
||||||
'reported', 'title', 'type', 'values'))
|
|
||||||
sub_interfaces = set(('title',))
|
sub_interfaces = set(('title',))
|
||||||
form_types = set(('cancel', 'form', 'result', 'submit'))
|
form_types = set(('cancel', 'form', 'result', 'submit'))
|
||||||
|
|
||||||
@@ -43,12 +43,12 @@ class Form(ElementBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def field(self):
|
def field(self):
|
||||||
return self['fields']
|
return self.get_fields()
|
||||||
|
|
||||||
def set_type(self, ftype):
|
def set_type(self, ftype):
|
||||||
self._set_attr('type', ftype)
|
self._set_attr('type', ftype)
|
||||||
if ftype == 'submit':
|
if ftype == 'submit':
|
||||||
fields = self['fields']
|
fields = self.get_fields()
|
||||||
for var in fields:
|
for var in fields:
|
||||||
field = fields[var]
|
field = fields[var]
|
||||||
del field['type']
|
del field['type']
|
||||||
@@ -74,7 +74,8 @@ class Form(ElementBase):
|
|||||||
field['desc'] = desc
|
field['desc'] = desc
|
||||||
field['required'] = required
|
field['required'] = required
|
||||||
if options is not None:
|
if options is not None:
|
||||||
field['options'] = options
|
for option in options:
|
||||||
|
field.add_option(**option)
|
||||||
else:
|
else:
|
||||||
del field['type']
|
del field['type']
|
||||||
self.append(field)
|
self.append(field)
|
||||||
@@ -151,7 +152,6 @@ class Form(ElementBase):
|
|||||||
return fields
|
return fields
|
||||||
|
|
||||||
def get_instructions(self):
|
def get_instructions(self):
|
||||||
instructions = ''
|
|
||||||
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
|
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
|
||||||
return "\n".join([instXML.text for instXML in instsXML])
|
return "\n".join([instXML.text for instXML in instsXML])
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ class Form(ElementBase):
|
|||||||
def get_reported(self):
|
def get_reported(self):
|
||||||
fields = OrderedDict()
|
fields = OrderedDict()
|
||||||
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
|
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
|
||||||
FormField.namespace))
|
FormField.namespace))
|
||||||
for field in xml:
|
for field in xml:
|
||||||
field = FormField(xml=field)
|
field = FormField(xml=field)
|
||||||
fields[field['var']] = field
|
fields[field['var']] = field
|
||||||
@@ -178,7 +178,7 @@ class Form(ElementBase):
|
|||||||
|
|
||||||
def get_values(self):
|
def get_values(self):
|
||||||
values = OrderedDict()
|
values = OrderedDict()
|
||||||
fields = self['fields']
|
fields = self.get_fields()
|
||||||
for var in fields:
|
for var in fields:
|
||||||
values[var] = fields[var]['value']
|
values[var] = fields[var]['value']
|
||||||
return values
|
return values
|
||||||
@@ -195,7 +195,14 @@ class Form(ElementBase):
|
|||||||
fields = fields.items()
|
fields = fields.items()
|
||||||
for var, field in fields:
|
for var, field in fields:
|
||||||
field['var'] = var
|
field['var'] = var
|
||||||
self.add_field(**field)
|
self.add_field(
|
||||||
|
var = field.get('var'),
|
||||||
|
label = field.get('label'),
|
||||||
|
desc = field.get('desc'),
|
||||||
|
required = field.get('required'),
|
||||||
|
value = field.get('value'),
|
||||||
|
options = field.get('options'),
|
||||||
|
type = field.get('type'))
|
||||||
|
|
||||||
def set_instructions(self, instructions):
|
def set_instructions(self, instructions):
|
||||||
del self['instructions']
|
del self['instructions']
|
||||||
@@ -213,17 +220,33 @@ class Form(ElementBase):
|
|||||||
self.add_item(item)
|
self.add_item(item)
|
||||||
|
|
||||||
def set_reported(self, reported):
|
def set_reported(self, reported):
|
||||||
|
"""
|
||||||
|
This either needs a dictionary or dictionaries or a dictionary of form fields.
|
||||||
|
:param reported:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
for var in reported:
|
for var in reported:
|
||||||
field = reported[var]
|
field = reported[var]
|
||||||
field['var'] = var
|
|
||||||
self.add_reported(var, **field)
|
if isinstance(field, dict):
|
||||||
|
self.add_reported(**field)
|
||||||
|
else:
|
||||||
|
reported = self.xml.find('{%s}reported' % self.namespace)
|
||||||
|
if reported is None:
|
||||||
|
reported = ET.Element('{%s}reported' % self.namespace)
|
||||||
|
self.xml.append(reported)
|
||||||
|
|
||||||
|
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
||||||
|
reported.append(fieldXML)
|
||||||
|
new_field = FormField(xml=fieldXML)
|
||||||
|
new_field.values = field.values
|
||||||
|
|
||||||
def set_values(self, values):
|
def set_values(self, values):
|
||||||
fields = self['fields']
|
fields = self.get_fields()
|
||||||
for field in values:
|
for field in values:
|
||||||
if field not in fields:
|
if field not in self.get_fields():
|
||||||
fields[field] = self.add_field(var=field)
|
fields[field] = self.add_field(var=field)
|
||||||
fields[field]['value'] = values[field]
|
self.get_fields()[field]['value'] = values[field]
|
||||||
|
|
||||||
def merge(self, other):
|
def merge(self, other):
|
||||||
new = copy.copy(self)
|
new = copy.copy(self)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from binding import py2xml, xml2py, xml2fault, fault2xml
|
from slixmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
import abc
|
import abc
|
||||||
import inspect
|
import inspect
|
||||||
@@ -18,6 +18,38 @@ import traceback
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def _isstr(obj):
|
||||||
|
return isinstance(obj, str)
|
||||||
|
|
||||||
|
|
||||||
|
# Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3.
|
||||||
|
# This decorator is copied from 'six' (https://bitbucket.org/gutworth/six):
|
||||||
|
#
|
||||||
|
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
def _add_metaclass(metaclass):
|
||||||
|
def wrapper(cls):
|
||||||
|
orig_vars = cls.__dict__.copy()
|
||||||
|
slots = orig_vars.get('__slots__')
|
||||||
|
if slots is not None:
|
||||||
|
if isinstance(slots, str):
|
||||||
|
slots = [slots]
|
||||||
|
for slots_var in slots:
|
||||||
|
orig_vars.pop(slots_var)
|
||||||
|
orig_vars.pop('__dict__', None)
|
||||||
|
orig_vars.pop('__weakref__', None)
|
||||||
|
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
def _intercept(method, name, public):
|
def _intercept(method, name, public):
|
||||||
def _resolver(instance, *args, **kwargs):
|
def _resolver(instance, *args, **kwargs):
|
||||||
log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
|
log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
|
||||||
@@ -68,7 +100,7 @@ def remote(function_argument, public = True):
|
|||||||
if hasattr(function_argument, '__call__'):
|
if hasattr(function_argument, '__call__'):
|
||||||
return _intercept(function_argument, None, public)
|
return _intercept(function_argument, None, public)
|
||||||
else:
|
else:
|
||||||
if not isinstance(function_argument, basestring):
|
if not _isstr(function_argument):
|
||||||
if not isinstance(function_argument, bool):
|
if not isinstance(function_argument, bool):
|
||||||
raise Exception('Expected an RPC method name or visibility modifier!')
|
raise Exception('Expected an RPC method name or visibility modifier!')
|
||||||
else:
|
else:
|
||||||
@@ -222,12 +254,11 @@ class TimeoutException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@_add_metaclass(abc.ABCMeta)
|
||||||
class Callback(object):
|
class Callback(object):
|
||||||
'''
|
'''
|
||||||
A base class for callback handlers.
|
A base class for callback handlers.
|
||||||
'''
|
'''
|
||||||
__metaclass__ = abc.ABCMeta
|
|
||||||
|
|
||||||
|
|
||||||
@abc.abstractproperty
|
@abc.abstractproperty
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
@@ -291,7 +322,7 @@ class Future(Callback):
|
|||||||
self._event.set()
|
self._event.set()
|
||||||
|
|
||||||
|
|
||||||
|
@_add_metaclass(abc.ABCMeta)
|
||||||
class Endpoint(object):
|
class Endpoint(object):
|
||||||
'''
|
'''
|
||||||
The Endpoint class is an abstract base class for all objects
|
The Endpoint class is an abstract base class for all objects
|
||||||
@@ -303,8 +334,6 @@ class Endpoint(object):
|
|||||||
which specifies which object an RPC call refers to. It is the
|
which specifies which object an RPC call refers to. It is the
|
||||||
first part in a RPC method name '<fqn>.<method>'.
|
first part in a RPC method name '<fqn>.<method>'.
|
||||||
'''
|
'''
|
||||||
__metaclass__ = abc.ABCMeta
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, session, target_jid):
|
def __init__(self, session, target_jid):
|
||||||
'''
|
'''
|
||||||
@@ -491,7 +520,7 @@ class RemoteSession(object):
|
|||||||
|
|
||||||
def _find_key(self, dict, value):
|
def _find_key(self, dict, value):
|
||||||
"""return the key of dictionary dic given the value"""
|
"""return the key of dictionary dic given the value"""
|
||||||
search = [k for k, v in dict.iteritems() if v == value]
|
search = [k for k, v in dict.items() if v == value]
|
||||||
if len(search) == 0:
|
if len(search) == 0:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@@ -547,7 +576,7 @@ class RemoteSession(object):
|
|||||||
result = handler_cls(*args, **kwargs)
|
result = handler_cls(*args, **kwargs)
|
||||||
Endpoint.__init__(result, self, self._client.boundjid.full)
|
Endpoint.__init__(result, self, self._client.boundjid.full)
|
||||||
method_dict = result.get_methods()
|
method_dict = result.get_methods()
|
||||||
for method_name, method in method_dict.iteritems():
|
for method_name, method in method_dict.items():
|
||||||
#!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
|
#!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
|
||||||
self._register_call(result.FQN(), method, method_name)
|
self._register_call(result.FQN(), method, method_name)
|
||||||
self._register_acl(result.FQN(), acl)
|
self._register_acl(result.FQN(), acl)
|
||||||
@@ -569,11 +598,11 @@ class RemoteSession(object):
|
|||||||
self._register_callback(pid, callback)
|
self._register_callback(pid, callback)
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
def close(self):
|
def close(self, wait=False):
|
||||||
'''
|
'''
|
||||||
Closes this session.
|
Closes this session.
|
||||||
'''
|
'''
|
||||||
self._client.disconnect(False)
|
self._client.disconnect(wait=wait)
|
||||||
self._session_close_callback()
|
self._session_close_callback()
|
||||||
|
|
||||||
def _on_jabber_rpc_method_call(self, iq):
|
def _on_jabber_rpc_method_call(self, iq):
|
||||||
@@ -697,7 +726,8 @@ class Remote(object):
|
|||||||
if(client.boundjid.bare in cls._sessions):
|
if(client.boundjid.bare in cls._sessions):
|
||||||
raise RemoteException("There already is a session associated with these credentials!")
|
raise RemoteException("There already is a session associated with these credentials!")
|
||||||
else:
|
else:
|
||||||
cls._sessions[client.boundjid.bare] = client;
|
cls._sessions[client.boundjid.bare] = client
|
||||||
|
|
||||||
def _session_close_callback():
|
def _session_close_callback():
|
||||||
with Remote._lock:
|
with Remote._lock:
|
||||||
del cls._sessions[client.boundjid.bare]
|
del cls._sessions[client.boundjid.bare]
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ class XEP_0009(BasePlugin):
|
|||||||
|
|
||||||
def _item_not_found(self, iq):
|
def _item_not_found(self, iq):
|
||||||
payload = iq.get_payload()
|
payload = iq.get_payload()
|
||||||
iq.reply().error().set_payload(payload)
|
iq = iq.reply()
|
||||||
|
iq.error().set_payload(payload)
|
||||||
iq['error']['code'] = '404'
|
iq['error']['code'] = '404'
|
||||||
iq['error']['type'] = 'cancel'
|
iq['error']['type'] = 'cancel'
|
||||||
iq['error']['condition'] = 'item-not-found'
|
iq['error']['condition'] = 'item-not-found'
|
||||||
@@ -101,7 +102,8 @@ class XEP_0009(BasePlugin):
|
|||||||
|
|
||||||
def _undefined_condition(self, iq):
|
def _undefined_condition(self, iq):
|
||||||
payload = iq.get_payload()
|
payload = iq.get_payload()
|
||||||
iq.reply().error().set_payload(payload)
|
iq = iq.reply()
|
||||||
|
iq.error().set_payload(payload)
|
||||||
iq['error']['code'] = '500'
|
iq['error']['code'] = '500'
|
||||||
iq['error']['type'] = 'cancel'
|
iq['error']['type'] = 'cancel'
|
||||||
iq['error']['condition'] = 'undefined-condition'
|
iq['error']['condition'] = 'undefined-condition'
|
||||||
@@ -109,7 +111,8 @@ class XEP_0009(BasePlugin):
|
|||||||
|
|
||||||
def _forbidden(self, iq):
|
def _forbidden(self, iq):
|
||||||
payload = iq.get_payload()
|
payload = iq.get_payload()
|
||||||
iq.reply().error().set_payload(payload)
|
iq = iq.reply()
|
||||||
|
iq.error().set_payload(payload)
|
||||||
iq['error']['code'] = '403'
|
iq['error']['code'] = '403'
|
||||||
iq['error']['type'] = 'auth'
|
iq['error']['type'] = 'auth'
|
||||||
iq['error']['condition'] = 'forbidden'
|
iq['error']['condition'] = 'forbidden'
|
||||||
@@ -117,7 +120,8 @@ class XEP_0009(BasePlugin):
|
|||||||
|
|
||||||
def _recipient_unvailable(self, iq):
|
def _recipient_unvailable(self, iq):
|
||||||
payload = iq.get_payload()
|
payload = iq.get_payload()
|
||||||
iq.reply().error().set_payload(payload)
|
iq = iq.reply()
|
||||||
|
error().set_payload(payload)
|
||||||
iq['error']['code'] = '404'
|
iq['error']['code'] = '404'
|
||||||
iq['error']['type'] = 'wait'
|
iq['error']['type'] = 'wait'
|
||||||
iq['error']['condition'] = 'recipient-unavailable'
|
iq['error']['condition'] = 'recipient-unavailable'
|
||||||
@@ -216,3 +220,4 @@ class XEP_0009(BasePlugin):
|
|||||||
def _extract_method(self, stanza):
|
def _extract_method(self, stanza):
|
||||||
xml = ET.fromstring("%s" % stanza)
|
xml = ET.fromstring("%s" % stanza)
|
||||||
return xml.find("./methodCall/methodName").text
|
return xml.find("./methodCall/methodName").text
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import logging
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from slixmpp.plugins import BasePlugin, register_plugin
|
from slixmpp.plugins import BasePlugin, register_plugin
|
||||||
from slixmpp import Iq
|
from slixmpp import future_wrapper, Iq
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp.xmlstream import JID, register_stanza_plugin
|
from slixmpp.xmlstream import JID, register_stanza_plugin
|
||||||
from slixmpp.xmlstream.handler import Callback
|
from slixmpp.xmlstream.handler import Callback
|
||||||
@@ -76,6 +76,7 @@ class XEP_0012(BasePlugin):
|
|||||||
def del_last_activity(self, jid):
|
def del_last_activity(self, jid):
|
||||||
self.api['del_last_activity'](jid)
|
self.api['del_last_activity'](jid)
|
||||||
|
|
||||||
|
@future_wrapper
|
||||||
def get_last_activity(self, jid, local=False, ifrom=None, timeout=None,
|
def get_last_activity(self, jid, local=False, ifrom=None, timeout=None,
|
||||||
callback=None, timeout_callback=None):
|
callback=None, timeout_callback=None):
|
||||||
if jid is not None and not isinstance(jid, JID):
|
if jid is not None and not isinstance(jid, JID):
|
||||||
@@ -132,8 +133,7 @@ class XEP_0012(BasePlugin):
|
|||||||
if not isinstance(iq, Iq):
|
if not isinstance(iq, Iq):
|
||||||
reply = self.xmpp.Iq()
|
reply = self.xmpp.Iq()
|
||||||
else:
|
else:
|
||||||
iq.reply()
|
reply = iq.reply()
|
||||||
reply = iq
|
|
||||||
|
|
||||||
if jid not in self._last_activities:
|
if jid not in self._last_activities:
|
||||||
raise XMPPError('service-unavailable')
|
raise XMPPError('service-unavailable')
|
||||||
|
|||||||
@@ -67,8 +67,7 @@ class XEP_0027(BasePlugin):
|
|||||||
register_stanza_plugin(Message, Encrypted)
|
register_stanza_plugin(Message, Encrypted)
|
||||||
|
|
||||||
self.xmpp.add_event_handler('unverified_signed_presence',
|
self.xmpp.add_event_handler('unverified_signed_presence',
|
||||||
self._handle_unverified_signed_presence,
|
self._handle_unverified_signed_presence)
|
||||||
threaded=True)
|
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
Callback('Signed Presence',
|
Callback('Signed Presence',
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from slixmpp import Iq
|
from slixmpp import Iq
|
||||||
|
from slixmpp import future_wrapper
|
||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
from slixmpp.xmlstream.handler import Callback
|
from slixmpp.xmlstream.handler import Callback
|
||||||
from slixmpp.xmlstream.matcher import StanzaPath
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
@@ -122,6 +123,12 @@ class XEP_0030(BasePlugin):
|
|||||||
for op in self._disco_ops:
|
for op in self._disco_ops:
|
||||||
self.api.register(getattr(self.static, op), op, default=True)
|
self.api.register(getattr(self.static, op), op, default=True)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.add_feature('http://jabber.org/protocol/disco#info')
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.del_feature('http://jabber.org/protocol/disco#info')
|
||||||
|
|
||||||
def _add_disco_op(self, op, default_handler):
|
def _add_disco_op(self, op, default_handler):
|
||||||
self.api.register(default_handler, op)
|
self.api.register(default_handler, op)
|
||||||
self.api.register_default(default_handler, op)
|
self.api.register_default(default_handler, op)
|
||||||
@@ -288,6 +295,7 @@ class XEP_0030(BasePlugin):
|
|||||||
'cached': cached}
|
'cached': cached}
|
||||||
return self.api['has_identity'](jid, node, ifrom, data)
|
return self.api['has_identity'](jid, node, ifrom, data)
|
||||||
|
|
||||||
|
@future_wrapper
|
||||||
def get_info(self, jid=None, node=None, local=None,
|
def get_info(self, jid=None, node=None, local=None,
|
||||||
cached=None, **kwargs):
|
cached=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -362,9 +370,9 @@ class XEP_0030(BasePlugin):
|
|||||||
iq['to'] = jid
|
iq['to'] = jid
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['disco_info']['node'] = node if node else ''
|
iq['disco_info']['node'] = node if node else ''
|
||||||
iq.send(timeout=kwargs.get('timeout', None),
|
return iq.send(timeout=kwargs.get('timeout', None),
|
||||||
callback=kwargs.get('callback', None),
|
callback=kwargs.get('callback', None),
|
||||||
timeout_callback=kwargs.get('timeout_callback', None))
|
timeout_callback=kwargs.get('timeout_callback', None))
|
||||||
|
|
||||||
def set_info(self, jid=None, node=None, info=None):
|
def set_info(self, jid=None, node=None, info=None):
|
||||||
"""
|
"""
|
||||||
@@ -375,6 +383,7 @@ class XEP_0030(BasePlugin):
|
|||||||
info = info['disco_info']
|
info = info['disco_info']
|
||||||
self.api['set_info'](jid, node, None, info)
|
self.api['set_info'](jid, node, None, info)
|
||||||
|
|
||||||
|
@future_wrapper
|
||||||
def get_items(self, jid=None, node=None, local=False, **kwargs):
|
def get_items(self, jid=None, node=None, local=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Retrieve the disco#items results from a given JID/node combination.
|
Retrieve the disco#items results from a given JID/node combination.
|
||||||
@@ -423,9 +432,9 @@ class XEP_0030(BasePlugin):
|
|||||||
raise NotImplementedError("XEP 0059 has not yet been fixed")
|
raise NotImplementedError("XEP 0059 has not yet been fixed")
|
||||||
return self.xmpp['xep_0059'].iterate(iq, 'disco_items')
|
return self.xmpp['xep_0059'].iterate(iq, 'disco_items')
|
||||||
else:
|
else:
|
||||||
iq.send(timeout=kwargs.get('timeout', None),
|
return iq.send(timeout=kwargs.get('timeout', None),
|
||||||
callback=kwargs.get('callback', None),
|
callback=kwargs.get('callback', None),
|
||||||
timeout_callback=kwargs.get('timeout_callback', None))
|
timeout_callback=kwargs.get('timeout_callback', None))
|
||||||
|
|
||||||
def set_items(self, jid=None, node=None, **kwargs):
|
def set_items(self, jid=None, node=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -600,7 +609,7 @@ class XEP_0030(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
self.api['del_features'](jid, node, None, kwargs)
|
self.api['del_features'](jid, node, None, kwargs)
|
||||||
|
|
||||||
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}):
|
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None):
|
||||||
"""
|
"""
|
||||||
Execute the most specific node handler for the given
|
Execute the most specific node handler for the given
|
||||||
JID/node combination.
|
JID/node combination.
|
||||||
@@ -611,6 +620,9 @@ class XEP_0030(BasePlugin):
|
|||||||
node -- The node requested.
|
node -- The node requested.
|
||||||
data -- Optional, custom data to pass to the handler.
|
data -- Optional, custom data to pass to the handler.
|
||||||
"""
|
"""
|
||||||
|
if not data:
|
||||||
|
data = {}
|
||||||
|
|
||||||
return self.api[htype](jid, node, ifrom, data)
|
return self.api[htype](jid, node, ifrom, data)
|
||||||
|
|
||||||
def _handle_disco_info(self, iq):
|
def _handle_disco_info(self, iq):
|
||||||
@@ -634,7 +646,7 @@ class XEP_0030(BasePlugin):
|
|||||||
info['id'] = iq['id']
|
info['id'] = iq['id']
|
||||||
info.send()
|
info.send()
|
||||||
else:
|
else:
|
||||||
iq.reply()
|
iq = iq.reply()
|
||||||
if info:
|
if info:
|
||||||
info = self._fix_default_info(info)
|
info = self._fix_default_info(info)
|
||||||
iq.set_payload(info.xml)
|
iq.set_payload(info.xml)
|
||||||
@@ -674,7 +686,7 @@ class XEP_0030(BasePlugin):
|
|||||||
if isinstance(items, Iq):
|
if isinstance(items, Iq):
|
||||||
items.send()
|
items.send()
|
||||||
else:
|
else:
|
||||||
iq.reply()
|
iq = iq.reply()
|
||||||
if items:
|
if items:
|
||||||
iq.set_payload(items.xml)
|
iq.set_payload(items.xml)
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class DiscoInfo(ElementBase):
|
|||||||
id_xml.attrib['{%s}lang' % self.xml_ns] = lang
|
id_xml.attrib['{%s}lang' % self.xml_ns] = lang
|
||||||
if name:
|
if name:
|
||||||
id_xml.attrib['name'] = name
|
id_xml.attrib['name'] = name
|
||||||
self.xml.append(id_xml)
|
self.xml.insert(0, id_xml)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,12 @@ class XEP_0045(BasePlugin):
|
|||||||
'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))
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp.plugin['xep_0030'].del_feature(feature='http://jabber.org/protocol/muc')
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/muc')
|
||||||
|
|
||||||
def handle_groupchat_invite(self, inv):
|
def handle_groupchat_invite(self, inv):
|
||||||
""" Handle an invite into a muc.
|
""" Handle an invite into a muc.
|
||||||
"""
|
"""
|
||||||
@@ -397,6 +403,16 @@ class XEP_0045(BasePlugin):
|
|||||||
return None
|
return None
|
||||||
return self.rooms[room].keys()
|
return self.rooms[room].keys()
|
||||||
|
|
||||||
|
def getUsersByAffiliation(cls, room, affiliation='member', ifrom=None):
|
||||||
|
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||||
|
raise TypeError
|
||||||
|
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
||||||
|
item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation})
|
||||||
|
query.append(item)
|
||||||
|
iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get')
|
||||||
|
iq.append(query)
|
||||||
|
return iq.send()
|
||||||
|
|
||||||
|
|
||||||
xep_0045 = XEP_0045
|
xep_0045 = XEP_0045
|
||||||
register_plugin(XEP_0045)
|
register_plugin(XEP_0045)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import asyncio
|
||||||
import uuid
|
import uuid
|
||||||
import logging
|
import logging
|
||||||
import threading
|
|
||||||
|
|
||||||
from slixmpp import Message, Iq
|
from slixmpp import Message, Iq
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
@@ -23,17 +23,11 @@ class XEP_0047(BasePlugin):
|
|||||||
default_config = {
|
default_config = {
|
||||||
'block_size': 4096,
|
'block_size': 4096,
|
||||||
'max_block_size': 8192,
|
'max_block_size': 8192,
|
||||||
'window_size': 1,
|
|
||||||
'auto_accept': False,
|
'auto_accept': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self._streams = {}
|
self._streams = {}
|
||||||
self._pending_streams = {}
|
|
||||||
self._pending_lock = threading.Lock()
|
|
||||||
self._stream_lock = threading.Lock()
|
|
||||||
|
|
||||||
self._preauthed_sids_lock = threading.Lock()
|
|
||||||
self._preauthed_sids = {}
|
self._preauthed_sids = {}
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Open)
|
register_stanza_plugin(Iq, Open)
|
||||||
@@ -85,9 +79,8 @@ class XEP_0047(BasePlugin):
|
|||||||
self._streams[(jid, sid, peer_jid)] = stream
|
self._streams[(jid, sid, peer_jid)] = stream
|
||||||
|
|
||||||
def _del_stream(self, jid, sid, peer_jid, data):
|
def _del_stream(self, jid, sid, peer_jid, data):
|
||||||
with self._stream_lock:
|
if (jid, sid, peer_jid) in self._streams:
|
||||||
if (jid, sid, peer_jid) in self._streams:
|
del self._streams[(jid, sid, peer_jid)]
|
||||||
del self._streams[(jid, sid, peer_jid)]
|
|
||||||
|
|
||||||
def _accept_stream(self, iq):
|
def _accept_stream(self, iq):
|
||||||
receiver = iq['to']
|
receiver = iq['to']
|
||||||
@@ -100,23 +93,20 @@ class XEP_0047(BasePlugin):
|
|||||||
|
|
||||||
def _authorized(self, jid, sid, ifrom, 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:
|
return True
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _authorized_sid(self, jid, sid, ifrom, iq):
|
def _authorized_sid(self, jid, sid, ifrom, iq):
|
||||||
with self._preauthed_sids_lock:
|
if (jid, sid, ifrom) in self._preauthed_sids:
|
||||||
if (jid, sid, ifrom) in self._preauthed_sids:
|
del self._preauthed_sids[(jid, sid, ifrom)]
|
||||||
del self._preauthed_sids[(jid, sid, ifrom)]
|
return True
|
||||||
return True
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
def _preauthorize_sid(self, jid, sid, ifrom, data):
|
def _preauthorize_sid(self, jid, sid, ifrom, data):
|
||||||
with self._preauthed_sids_lock:
|
self._preauthed_sids[(jid, sid, ifrom)] = True
|
||||||
self._preauthed_sids[(jid, sid, ifrom)] = True
|
|
||||||
|
|
||||||
def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False,
|
def open_stream(self, jid, block_size=None, sid=None, use_messages=False,
|
||||||
ifrom=None, block=True, timeout=None, callback=None):
|
ifrom=None, 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:
|
if block_size is None:
|
||||||
@@ -128,48 +118,28 @@ class XEP_0047(BasePlugin):
|
|||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq['ibb_open']['block_size'] = block_size
|
iq['ibb_open']['block_size'] = block_size
|
||||||
iq['ibb_open']['sid'] = sid
|
iq['ibb_open']['sid'] = sid
|
||||||
iq['ibb_open']['stanza'] = 'iq'
|
iq['ibb_open']['stanza'] = 'message' if use_messages else 'iq'
|
||||||
|
|
||||||
stream = IBBytestream(self.xmpp, sid, block_size,
|
stream = IBBytestream(self.xmpp, sid, block_size,
|
||||||
iq['from'], iq['to'], window,
|
iq['from'], iq['to'], use_messages)
|
||||||
use_messages)
|
|
||||||
|
|
||||||
with self._stream_lock:
|
stream_future = asyncio.Future()
|
||||||
self._pending_streams[iq['id']] = stream
|
|
||||||
|
|
||||||
self._pending_streams[iq['id']] = stream
|
def _handle_opened_stream(iq):
|
||||||
|
log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from'])
|
||||||
if block:
|
stream.self_jid = iq['to']
|
||||||
resp = iq.send(timeout=timeout)
|
stream.peer_jid = iq['from']
|
||||||
self._handle_opened_stream(resp)
|
stream.stream_started = True
|
||||||
return stream
|
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
|
||||||
else:
|
stream_future.set_result(stream)
|
||||||
cb = None
|
|
||||||
if callback is not None:
|
if callback is not None:
|
||||||
def chained(resp):
|
callback(stream)
|
||||||
self._handle_opened_stream(resp)
|
self.xmpp.event('ibb_stream_start', stream)
|
||||||
callback(resp)
|
self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream)
|
||||||
cb = chained
|
|
||||||
else:
|
|
||||||
cb = self._handle_opened_stream
|
|
||||||
return iq.send(block=block, timeout=timeout, callback=cb)
|
|
||||||
|
|
||||||
def _handle_opened_stream(self, iq):
|
iq.send(timeout=timeout, callback=_handle_opened_stream)
|
||||||
if iq['type'] == 'result':
|
|
||||||
with self._stream_lock:
|
|
||||||
stream = self._pending_streams.get(iq['id'], None)
|
|
||||||
if stream is not None:
|
|
||||||
log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from'])
|
|
||||||
stream.self_jid = iq['to']
|
|
||||||
stream.peer_jid = iq['from']
|
|
||||||
stream.stream_started.set()
|
|
||||||
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:
|
return stream_future
|
||||||
if iq['id'] in self._pending_streams:
|
|
||||||
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']
|
||||||
@@ -181,18 +151,16 @@ class XEP_0047(BasePlugin):
|
|||||||
raise XMPPError(etype='modify', condition='bad-request')
|
raise XMPPError(etype='modify', condition='bad-request')
|
||||||
|
|
||||||
if not self._accept_stream(iq):
|
if not self._accept_stream(iq):
|
||||||
raise XMPPError(etype='modify', condition='not-acceptable')
|
raise XMPPError(etype='cancel', 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['to'], iq['from'],
|
iq['to'], iq['from'])
|
||||||
self.window_size)
|
stream.stream_started = True
|
||||||
stream.stream_started.set()
|
|
||||||
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
|
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
|
||||||
iq.reply()
|
iq.reply().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)
|
self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class Open(ElementBase):
|
|||||||
interfaces = set(('block_size', 'sid', 'stanza'))
|
interfaces = set(('block_size', 'sid', 'stanza'))
|
||||||
|
|
||||||
def get_block_size(self):
|
def get_block_size(self):
|
||||||
return int(self._get_attr('block-size'))
|
return int(self._get_attr('block-size', '0'))
|
||||||
|
|
||||||
def set_block_size(self, value):
|
def set_block_size(self, value):
|
||||||
self._set_attr('block-size', str(value))
|
self._set_attr('block-size', str(value))
|
||||||
@@ -47,7 +47,10 @@ class Data(ElementBase):
|
|||||||
self._set_attr('seq', str(value))
|
self._set_attr('seq', str(value))
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
b64_data = self.xml.text.strip()
|
text = self.xml.text
|
||||||
|
if not text:
|
||||||
|
raise XMPPError('not-acceptable', 'IBB data element is empty.')
|
||||||
|
b64_data = text.strip()
|
||||||
if VALID_B64.match(b64_data).group() == b64_data:
|
if VALID_B64.match(b64_data).group() == b64_data:
|
||||||
return from_b64(b64_data)
|
return from_b64(b64_data)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import asyncio
|
||||||
import socket
|
import socket
|
||||||
import threading
|
|
||||||
import logging
|
import logging
|
||||||
from queue import Queue
|
|
||||||
|
|
||||||
from slixmpp.stanza import Iq
|
from slixmpp.stanza import Iq
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
@@ -12,11 +11,10 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class IBBytestream(object):
|
class IBBytestream(object):
|
||||||
|
|
||||||
def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False):
|
def __init__(self, xmpp, sid, block_size, jid, peer, 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.use_messages = use_messages
|
self.use_messages = use_messages
|
||||||
|
|
||||||
if jid is None:
|
if jid is None:
|
||||||
@@ -27,29 +25,20 @@ class IBBytestream(object):
|
|||||||
self.send_seq = -1
|
self.send_seq = -1
|
||||||
self.recv_seq = -1
|
self.recv_seq = -1
|
||||||
|
|
||||||
self._send_seq_lock = threading.Lock()
|
self.stream_started = False
|
||||||
self._recv_seq_lock = threading.Lock()
|
self.stream_in_closed = False
|
||||||
|
self.stream_out_closed = False
|
||||||
|
|
||||||
self.stream_started = threading.Event()
|
self.recv_queue = asyncio.Queue()
|
||||||
self.stream_in_closed = threading.Event()
|
|
||||||
self.stream_out_closed = threading.Event()
|
|
||||||
|
|
||||||
self.recv_queue = Queue()
|
@asyncio.coroutine
|
||||||
|
def send(self, data, timeout=None):
|
||||||
self.send_window = threading.BoundedSemaphore(value=self.window_size)
|
if not self.stream_started or self.stream_out_closed:
|
||||||
self.window_ids = set()
|
|
||||||
self.window_empty = threading.Event()
|
|
||||||
self.window_empty.set()
|
|
||||||
|
|
||||||
def send(self, data):
|
|
||||||
if not self.stream_started.is_set() or \
|
|
||||||
self.stream_out_closed.is_set():
|
|
||||||
raise socket.error
|
raise socket.error
|
||||||
data = data[0:self.block_size]
|
if len(data) > self.block_size:
|
||||||
self.send_window.acquire()
|
data = data[:self.block_size]
|
||||||
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
|
|
||||||
if self.use_messages:
|
if self.use_messages:
|
||||||
msg = self.xmpp.Message()
|
msg = self.xmpp.Message()
|
||||||
msg['to'] = self.peer_jid
|
msg['to'] = self.peer_jid
|
||||||
@@ -59,7 +48,6 @@ class IBBytestream(object):
|
|||||||
msg['ibb_data']['seq'] = seq
|
msg['ibb_data']['seq'] = seq
|
||||||
msg['ibb_data']['data'] = data
|
msg['ibb_data']['data'] = data
|
||||||
msg.send()
|
msg.send()
|
||||||
self.send_window.release()
|
|
||||||
else:
|
else:
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
@@ -68,74 +56,66 @@ class IBBytestream(object):
|
|||||||
iq['ibb_data']['sid'] = self.sid
|
iq['ibb_data']['sid'] = self.sid
|
||||||
iq['ibb_data']['seq'] = seq
|
iq['ibb_data']['seq'] = seq
|
||||||
iq['ibb_data']['data'] = data
|
iq['ibb_data']['data'] = data
|
||||||
self.window_empty.clear()
|
yield from iq.send(timeout=timeout)
|
||||||
self.window_ids.add(iq['id'])
|
|
||||||
iq.send(block=False, callback=self._recv_ack)
|
|
||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
def sendall(self, data):
|
@asyncio.coroutine
|
||||||
|
def sendall(self, data, timeout=None):
|
||||||
sent_len = 0
|
sent_len = 0
|
||||||
while sent_len < len(data):
|
while sent_len < len(data):
|
||||||
sent_len += self.send(data[sent_len:])
|
sent_len += yield from self.send(data[sent_len:self.block_size], timeout=timeout)
|
||||||
|
|
||||||
def _recv_ack(self, iq):
|
@asyncio.coroutine
|
||||||
self.window_ids.remove(iq['id'])
|
def sendfile(self, file, timeout=None):
|
||||||
if not self.window_ids:
|
while True:
|
||||||
self.window_empty.set()
|
data = file.read(self.block_size)
|
||||||
self.send_window.release()
|
if not data:
|
||||||
if iq['type'] == 'error':
|
break
|
||||||
self.close()
|
yield from self.send(data, timeout=timeout)
|
||||||
|
|
||||||
def _recv_data(self, stanza):
|
def _recv_data(self, stanza):
|
||||||
with self._recv_seq_lock:
|
new_seq = stanza['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 = stanza['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_nowait(data)
|
||||||
self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data})
|
self.xmpp.event('ibb_stream_data', self)
|
||||||
|
|
||||||
if isinstance(stanza, Iq):
|
if isinstance(stanza, Iq):
|
||||||
stanza.reply()
|
stanza.reply().send()
|
||||||
stanza.send()
|
|
||||||
|
|
||||||
def recv(self, *args, **kwargs):
|
def recv(self, *args, **kwargs):
|
||||||
return self.read(block=True)
|
return self.read()
|
||||||
|
|
||||||
def read(self, block=True, timeout=None, **kwargs):
|
def read(self):
|
||||||
if not self.stream_started.is_set() or \
|
if not self.stream_started or self.stream_in_closed:
|
||||||
self.stream_in_closed.is_set():
|
|
||||||
raise socket.error
|
raise socket.error
|
||||||
if timeout is not None:
|
return self.recv_queue.get_nowait()
|
||||||
block = True
|
|
||||||
try:
|
|
||||||
return self.recv_queue.get(block, timeout)
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def close(self):
|
def close(self, timeout=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['to'] = self.peer_jid
|
iq['to'] = self.peer_jid
|
||||||
iq['from'] = self.self_jid
|
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 = True
|
||||||
iq.send(block=False,
|
def _close_stream(_):
|
||||||
callback=lambda x: self.stream_in_closed.set())
|
self.stream_in_closed = True
|
||||||
|
future = iq.send(timeout=timeout, callback=_close_stream)
|
||||||
self.xmpp.event('ibb_stream_end', self)
|
self.xmpp.event('ibb_stream_end', self)
|
||||||
|
return future
|
||||||
|
|
||||||
def _closed(self, iq):
|
def _closed(self, iq):
|
||||||
self.stream_in_closed.set()
|
self.stream_in_closed = True
|
||||||
self.stream_out_closed.set()
|
self.stream_out_closed = True
|
||||||
iq.reply()
|
iq.reply().send()
|
||||||
iq.send()
|
|
||||||
self.xmpp.event('ibb_stream_end', self)
|
self.xmpp.event('ibb_stream_end', self)
|
||||||
|
|
||||||
def makefile(self, *args, **kwargs):
|
def makefile(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from slixmpp import Iq
|
from slixmpp import Iq
|
||||||
from slixmpp.exceptions import IqError
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp.xmlstream.handler import Callback
|
from slixmpp.xmlstream.handler import Callback
|
||||||
from slixmpp.xmlstream.matcher import StanzaPath
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
from slixmpp.xmlstream import register_stanza_plugin, JID
|
from slixmpp.xmlstream import register_stanza_plugin, JID
|
||||||
@@ -37,10 +37,6 @@ class XEP_0050(BasePlugin):
|
|||||||
|
|
||||||
Also see <http://xmpp.org/extensions/xep-0050.html>
|
Also see <http://xmpp.org/extensions/xep-0050.html>
|
||||||
|
|
||||||
Configuration Values:
|
|
||||||
threaded -- Indicates if command events should be threaded.
|
|
||||||
Defaults to True.
|
|
||||||
|
|
||||||
Events:
|
Events:
|
||||||
command_execute -- Received a command with action="execute"
|
command_execute -- Received a command with action="execute"
|
||||||
command_next -- Received a command with action="next"
|
command_next -- Received a command with action="next"
|
||||||
@@ -48,8 +44,6 @@ class XEP_0050(BasePlugin):
|
|||||||
command_cancel -- Received a command with action="cancel"
|
command_cancel -- Received a command with action="cancel"
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
threaded -- Indicates if command events should be threaded.
|
|
||||||
Defaults to True.
|
|
||||||
commands -- A dictionary mapping JID/node pairs to command
|
commands -- A dictionary mapping JID/node pairs to command
|
||||||
names and handlers.
|
names and handlers.
|
||||||
sessions -- A dictionary or equivalent backend mapping
|
sessions -- A dictionary or equivalent backend mapping
|
||||||
@@ -83,7 +77,6 @@ class XEP_0050(BasePlugin):
|
|||||||
dependencies = set(['xep_0030', 'xep_0004'])
|
dependencies = set(['xep_0030', 'xep_0004'])
|
||||||
stanza = stanza
|
stanza = stanza
|
||||||
default_config = {
|
default_config = {
|
||||||
'threaded': True,
|
|
||||||
'session_db': None
|
'session_db': None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +94,7 @@ class XEP_0050(BasePlugin):
|
|||||||
self._handle_command))
|
self._handle_command))
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Command)
|
register_stanza_plugin(Iq, Command)
|
||||||
register_stanza_plugin(Command, Form)
|
register_stanza_plugin(Command, Form, iterable=True)
|
||||||
|
|
||||||
self.xmpp.add_event_handler('command_execute',
|
self.xmpp.add_event_handler('command_execute',
|
||||||
self._handle_command_start)
|
self._handle_command_start)
|
||||||
@@ -223,6 +216,7 @@ class XEP_0050(BasePlugin):
|
|||||||
name, handler = self.commands.get(key, ('Not found', None))
|
name, handler = self.commands.get(key, ('Not found', None))
|
||||||
if not handler:
|
if not handler:
|
||||||
log.debug('Command not found: %s, %s', key, self.commands)
|
log.debug('Command not found: %s, %s', key, self.commands)
|
||||||
|
raise XMPPError('item-not-found')
|
||||||
|
|
||||||
payload = []
|
payload = []
|
||||||
for stanza in iq['command']['substanzas']:
|
for stanza in iq['command']['substanzas']:
|
||||||
@@ -339,7 +333,7 @@ class XEP_0050(BasePlugin):
|
|||||||
for item in payload:
|
for item in payload:
|
||||||
register_stanza_plugin(Command, item.__class__, iterable=True)
|
register_stanza_plugin(Command, item.__class__, iterable=True)
|
||||||
|
|
||||||
iq.reply()
|
iq = iq.reply()
|
||||||
iq['command']['node'] = session['node']
|
iq['command']['node'] = session['node']
|
||||||
iq['command']['sessionid'] = session['id']
|
iq['command']['sessionid'] = session['id']
|
||||||
|
|
||||||
@@ -382,7 +376,7 @@ class XEP_0050(BasePlugin):
|
|||||||
if handler:
|
if handler:
|
||||||
handler(iq, session)
|
handler(iq, session)
|
||||||
del self.sessions[sessionid]
|
del self.sessions[sessionid]
|
||||||
iq.reply()
|
iq = iq.reply()
|
||||||
iq['command']['node'] = node
|
iq['command']['node'] = node
|
||||||
iq['command']['sessionid'] = sessionid
|
iq['command']['sessionid'] = sessionid
|
||||||
iq['command']['status'] = 'canceled'
|
iq['command']['status'] = 'canceled'
|
||||||
@@ -421,12 +415,26 @@ class XEP_0050(BasePlugin):
|
|||||||
|
|
||||||
del self.sessions[sessionid]
|
del self.sessions[sessionid]
|
||||||
|
|
||||||
iq.reply()
|
payload = session['payload']
|
||||||
|
if payload is None:
|
||||||
|
payload = []
|
||||||
|
if not isinstance(payload, list):
|
||||||
|
payload = [payload]
|
||||||
|
|
||||||
|
for item in payload:
|
||||||
|
register_stanza_plugin(Command, item.__class__, iterable=True)
|
||||||
|
|
||||||
|
iq = iq.reply()
|
||||||
|
|
||||||
iq['command']['node'] = node
|
iq['command']['node'] = node
|
||||||
iq['command']['sessionid'] = sessionid
|
iq['command']['sessionid'] = sessionid
|
||||||
iq['command']['actions'] = []
|
iq['command']['actions'] = []
|
||||||
iq['command']['status'] = 'completed'
|
iq['command']['status'] = 'completed'
|
||||||
iq['command']['notes'] = session['notes']
|
iq['command']['notes'] = session['notes']
|
||||||
|
|
||||||
|
for item in payload:
|
||||||
|
iq['command'].append(item)
|
||||||
|
|
||||||
iq.send()
|
iq.send()
|
||||||
else:
|
else:
|
||||||
raise XMPPError('item-not-found')
|
raise XMPPError('item-not-found')
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ class Telephone(ElementBase):
|
|||||||
|
|
||||||
def setup(self, xml=None):
|
def setup(self, xml=None):
|
||||||
super(Telephone, self).setup(xml=xml)
|
super(Telephone, self).setup(xml=xml)
|
||||||
self._set_sub_text('NUMBER', '', keep=True)
|
## this blanks out numbers received from server
|
||||||
|
##self._set_sub_text('NUMBER', '', keep=True)
|
||||||
|
|
||||||
def set_number(self, value):
|
def set_number(self, value):
|
||||||
self._set_sub_text('NUMBER', value, keep=True)
|
self._set_sub_text('NUMBER', value, keep=True)
|
||||||
@@ -324,7 +325,10 @@ class Birthday(ElementBase):
|
|||||||
def get_bday(self):
|
def get_bday(self):
|
||||||
if not self.xml.text:
|
if not self.xml.text:
|
||||||
return None
|
return None
|
||||||
return xep_0082.parse(self.xml.text)
|
try:
|
||||||
|
return xep_0082.parse(self.xml.text)
|
||||||
|
except ValueError:
|
||||||
|
return self.xml.text
|
||||||
|
|
||||||
|
|
||||||
class Rev(ElementBase):
|
class Rev(ElementBase):
|
||||||
@@ -343,7 +347,10 @@ class Rev(ElementBase):
|
|||||||
def get_rev(self):
|
def get_rev(self):
|
||||||
if not self.xml.text:
|
if not self.xml.text:
|
||||||
return None
|
return None
|
||||||
return xep_0082.parse(self.xml.text)
|
try:
|
||||||
|
return xep_0082.parse(self.xml.text)
|
||||||
|
except ValueError:
|
||||||
|
return self.xml.text
|
||||||
|
|
||||||
|
|
||||||
class Title(ElementBase):
|
class Title(ElementBase):
|
||||||
@@ -523,8 +530,11 @@ class TimeZone(ElementBase):
|
|||||||
def get_tz(self):
|
def get_tz(self):
|
||||||
if not self.xml.text:
|
if not self.xml.text:
|
||||||
return xep_0082.tzutc()
|
return xep_0082.tzutc()
|
||||||
time = xep_0082.parse('00:00:00%s' % self.xml.text)
|
try:
|
||||||
return time.tzinfo
|
time = xep_0082.parse('00:00:00%s' % self.xml.text)
|
||||||
|
return time.tzinfo
|
||||||
|
except ValueError:
|
||||||
|
return self.xml.text
|
||||||
|
|
||||||
|
|
||||||
register_stanza_plugin(VCardTemp, Name)
|
register_stanza_plugin(VCardTemp, Name)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from slixmpp.xmlstream.handler import Callback
|
|||||||
from slixmpp.xmlstream.matcher import StanzaPath
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
from slixmpp.plugins.xep_0054 import VCardTemp, stanza
|
from slixmpp.plugins.xep_0054 import VCardTemp, stanza
|
||||||
|
from slixmpp import future_wrapper
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -59,8 +60,9 @@ class XEP_0054(BasePlugin):
|
|||||||
def make_vcard(self):
|
def make_vcard(self):
|
||||||
return VCardTemp()
|
return VCardTemp()
|
||||||
|
|
||||||
|
@future_wrapper
|
||||||
def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
|
def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
|
||||||
block=True, callback=None, timeout=None):
|
callback=None, timeout=None, timeout_callback=None):
|
||||||
if local is None:
|
if local is None:
|
||||||
if jid is not None and not isinstance(jid, JID):
|
if jid is not None and not isinstance(jid, JID):
|
||||||
jid = JID(jid)
|
jid = JID(jid)
|
||||||
@@ -99,14 +101,12 @@ class XEP_0054(BasePlugin):
|
|||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq.enable('vcard_temp')
|
iq.enable('vcard_temp')
|
||||||
|
|
||||||
vcard = iq.send(block=block, callback=callback, timeout=timeout)
|
return iq.send(callback=callback, timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
if block:
|
@future_wrapper
|
||||||
self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp'])
|
def publish_vcard(self, vcard=None, jid=None, ifrom=None,
|
||||||
return vcard
|
callback=None, timeout=None, timeout_callback=None):
|
||||||
|
|
||||||
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
|
|
||||||
callback=None, timeout=None):
|
|
||||||
self.api['set_vcard'](jid, None, ifrom, vcard)
|
self.api['set_vcard'](jid, None, ifrom, vcard)
|
||||||
if self.xmpp.is_component:
|
if self.xmpp.is_component:
|
||||||
return
|
return
|
||||||
@@ -116,7 +116,8 @@ class XEP_0054(BasePlugin):
|
|||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq.append(vcard)
|
iq.append(vcard)
|
||||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
return iq.send(callback=callback, timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def _handle_get_vcard(self, iq):
|
def _handle_get_vcard(self, iq):
|
||||||
if iq['type'] == 'result':
|
if iq['type'] == 'result':
|
||||||
@@ -127,7 +128,7 @@ class XEP_0054(BasePlugin):
|
|||||||
if isinstance(vcard, Iq):
|
if isinstance(vcard, Iq):
|
||||||
vcard.send()
|
vcard.send()
|
||||||
else:
|
else:
|
||||||
iq.reply()
|
iq = iq.reply()
|
||||||
iq.append(vcard)
|
iq.append(vcard)
|
||||||
iq.send()
|
iq.send()
|
||||||
elif iq['type'] == 'set':
|
elif iq['type'] == 'set':
|
||||||
|
|||||||
@@ -260,12 +260,12 @@ class XEP_0060(BasePlugin):
|
|||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
jid -- The pubsub service JID.
|
jid -- The pubsub service JID.
|
||||||
node -- The node to subscribe to.
|
node -- The node to unsubscribe from.
|
||||||
subid -- The specific subscription, if multiple subscriptions
|
subid -- The specific subscription, if multiple subscriptions
|
||||||
exist for this JID/node combination.
|
exist for this JID/node combination.
|
||||||
bare -- Indicates if the subscribee is a bare or full JID.
|
bare -- Indicates if the subscribee is a bare or full JID.
|
||||||
Defaults to True for a bare JID.
|
Defaults to True for a bare JID.
|
||||||
subscribee -- The JID that is subscribing to the node.
|
subscribee -- The JID that is unsubscribing from the node.
|
||||||
ifrom -- Specify the sender's JID.
|
ifrom -- Specify the sender's JID.
|
||||||
timeout -- The length of time (in seconds) to wait for a
|
timeout -- The length of time (in seconds) to wait for a
|
||||||
response before exiting the send call if blocking
|
response before exiting the send call if blocking
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from slixmpp.plugins.base import register_plugin
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from slixmpp.plugins.xep_0065.socks5 import Socks5Protocol
|
||||||
from slixmpp.plugins.xep_0065.stanza import Socks5
|
from slixmpp.plugins.xep_0065.stanza import Socks5
|
||||||
from slixmpp.plugins.xep_0065.proxy import XEP_0065
|
from slixmpp.plugins.xep_0065.proxy import XEP_0065
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import threading
|
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from slixmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5
|
|
||||||
|
|
||||||
from slixmpp.stanza import Iq
|
from slixmpp.stanza import Iq
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp.xmlstream import register_stanza_plugin
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
@@ -14,7 +12,7 @@ from slixmpp.xmlstream.handler import Callback
|
|||||||
from slixmpp.xmlstream.matcher import StanzaPath
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
from slixmpp.plugins.base import BasePlugin
|
from slixmpp.plugins.base import BasePlugin
|
||||||
|
|
||||||
from slixmpp.plugins.xep_0065 import stanza, Socks5
|
from slixmpp.plugins.xep_0065 import stanza, Socks5, Socks5Protocol
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -23,7 +21,7 @@ log = logging.getLogger(__name__)
|
|||||||
class XEP_0065(BasePlugin):
|
class XEP_0065(BasePlugin):
|
||||||
|
|
||||||
name = 'xep_0065'
|
name = 'xep_0065'
|
||||||
description = "Socks5 Bytestreams"
|
description = "XEP-0065: SOCKS5 Bytestreams"
|
||||||
dependencies = set(['xep_0030'])
|
dependencies = set(['xep_0030'])
|
||||||
default_config = {
|
default_config = {
|
||||||
'auto_accept': False
|
'auto_accept': False
|
||||||
@@ -34,9 +32,6 @@ class XEP_0065(BasePlugin):
|
|||||||
|
|
||||||
self._proxies = {}
|
self._proxies = {}
|
||||||
self._sessions = {}
|
self._sessions = {}
|
||||||
self._sessions_lock = threading.Lock()
|
|
||||||
|
|
||||||
self._preauthed_sids_lock = threading.Lock()
|
|
||||||
self._preauthed_sids = {}
|
self._preauthed_sids = {}
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
@@ -65,34 +60,34 @@ class XEP_0065(BasePlugin):
|
|||||||
connection.
|
connection.
|
||||||
"""
|
"""
|
||||||
if not self._proxies:
|
if not self._proxies:
|
||||||
self._proxies = self.discover_proxies()
|
self._proxies = yield from self.discover_proxies()
|
||||||
|
|
||||||
if sid is None:
|
if sid is None:
|
||||||
sid = uuid4().hex
|
sid = uuid4().hex
|
||||||
|
|
||||||
used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
|
used = yield from self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
|
||||||
proxy = used['socks']['streamhost_used']['jid']
|
proxy = used['socks']['streamhost_used']['jid']
|
||||||
|
|
||||||
if proxy not in self._proxies:
|
if proxy not in self._proxies:
|
||||||
log.warning('Received unknown SOCKS5 proxy: %s', proxy)
|
log.warning('Received unknown SOCKS5 proxy: %s', proxy)
|
||||||
return
|
return
|
||||||
|
|
||||||
with self._sessions_lock:
|
try:
|
||||||
self._sessions[sid] = self._connect_proxy(
|
self._sessions[sid] = (yield from self._connect_proxy(
|
||||||
sid,
|
self._get_dest_sha1(sid, self.xmpp.boundjid, to),
|
||||||
self.xmpp.boundjid,
|
|
||||||
to,
|
|
||||||
self._proxies[proxy][0],
|
self._proxies[proxy][0],
|
||||||
self._proxies[proxy][1],
|
self._proxies[proxy][1]))[1]
|
||||||
peer=to)
|
except socket.error:
|
||||||
|
return None
|
||||||
|
addr, port = yield from self._sessions[sid].connected
|
||||||
|
|
||||||
# Request that the proxy activate the session with the target.
|
# Request that the proxy activate the session with the target.
|
||||||
self.activate(proxy, sid, to, timeout=timeout)
|
yield from self.activate(proxy, sid, to, timeout=timeout)
|
||||||
socket = self.get_socket(sid)
|
sock = self.get_socket(sid)
|
||||||
self.xmpp.event('stream:%s:%s' % (sid, to), socket)
|
self.xmpp.event('stream:%s:%s' % (sid, to), sock)
|
||||||
return socket
|
return sock
|
||||||
|
|
||||||
def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None):
|
def request_stream(self, to, sid=None, ifrom=None, timeout=None, callback=None):
|
||||||
if sid is None:
|
if sid is None:
|
||||||
sid = uuid4().hex
|
sid = uuid4().hex
|
||||||
|
|
||||||
@@ -107,7 +102,7 @@ class XEP_0065(BasePlugin):
|
|||||||
iq['socks']['sid'] = sid
|
iq['socks']['sid'] = sid
|
||||||
for proxy, (host, port) in self._proxies.items():
|
for proxy, (host, port) in self._proxies.items():
|
||||||
iq['socks'].add_streamhost(proxy, host, port)
|
iq['socks'].add_streamhost(proxy, host, port)
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|
||||||
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
|
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
|
||||||
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
|
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
|
||||||
@@ -119,11 +114,16 @@ class XEP_0065(BasePlugin):
|
|||||||
|
|
||||||
discovered = set()
|
discovered = set()
|
||||||
|
|
||||||
disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
|
disco_items = yield from self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
|
||||||
|
disco_items = {item[0] for item in disco_items['disco_items']['items']}
|
||||||
|
|
||||||
for item in disco_items['disco_items']['items']:
|
disco_info_futures = {}
|
||||||
|
for item in disco_items:
|
||||||
|
disco_info_futures[item] = self.xmpp['xep_0030'].get_info(item, timeout=timeout)
|
||||||
|
|
||||||
|
for item in disco_items:
|
||||||
try:
|
try:
|
||||||
disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout)
|
disco_info = yield from disco_info_futures[item]
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -135,7 +135,7 @@ class XEP_0065(BasePlugin):
|
|||||||
|
|
||||||
for jid in discovered:
|
for jid in discovered:
|
||||||
try:
|
try:
|
||||||
addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
|
addr = yield from self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
|
||||||
self._proxies[jid] = (addr['socks']['streamhost']['host'],
|
self._proxies[jid] = (addr['socks']['streamhost']['host'],
|
||||||
addr['socks']['streamhost']['port'])
|
addr['socks']['streamhost']['port'])
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
@@ -143,11 +143,20 @@ class XEP_0065(BasePlugin):
|
|||||||
|
|
||||||
return self._proxies
|
return self._proxies
|
||||||
|
|
||||||
def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None):
|
def get_network_address(self, proxy, ifrom=None, timeout=None, callback=None):
|
||||||
"""Get the network information of a proxy."""
|
"""Get the network information of a proxy."""
|
||||||
iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom)
|
iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom)
|
||||||
iq.enable('socks')
|
iq.enable('socks')
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|
||||||
|
def _get_dest_sha1(self, sid, requester, target):
|
||||||
|
# The hostname MUST be SHA1(SID + Requester JID + Target JID)
|
||||||
|
# where the output is hexadecimal-encoded (not binary).
|
||||||
|
digest = sha1()
|
||||||
|
digest.update(sid.encode('utf8'))
|
||||||
|
digest.update(str(requester).encode('utf8'))
|
||||||
|
digest.update(str(target).encode('utf8'))
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
def _handle_streamhost(self, iq):
|
def _handle_streamhost(self, iq):
|
||||||
"""Handle incoming SOCKS5 session request."""
|
"""Handle incoming SOCKS5 session request."""
|
||||||
@@ -159,40 +168,59 @@ class XEP_0065(BasePlugin):
|
|||||||
raise XMPPError(etype='modify', condition='not-acceptable')
|
raise XMPPError(etype='modify', condition='not-acceptable')
|
||||||
|
|
||||||
streamhosts = iq['socks']['streamhosts']
|
streamhosts = iq['socks']['streamhosts']
|
||||||
conn = None
|
requester = iq['from']
|
||||||
used_streamhost = None
|
target = iq['to']
|
||||||
|
|
||||||
sender = iq['from']
|
dest = self._get_dest_sha1(sid, requester, target)
|
||||||
|
|
||||||
|
proxy_futures = []
|
||||||
for streamhost in streamhosts:
|
for streamhost in streamhosts:
|
||||||
try:
|
proxy_futures.append(self._connect_proxy(
|
||||||
conn = self._connect_proxy(sid,
|
dest,
|
||||||
sender,
|
|
||||||
self.xmpp.boundjid,
|
|
||||||
streamhost['host'],
|
streamhost['host'],
|
||||||
streamhost['port'],
|
streamhost['port']))
|
||||||
peer=sender)
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def gather(futures, iq, streamhosts):
|
||||||
|
proxies = yield from asyncio.gather(*futures, return_exceptions=True)
|
||||||
|
for streamhost, proxy in zip(streamhosts, proxies):
|
||||||
|
if isinstance(proxy, ValueError):
|
||||||
|
continue
|
||||||
|
elif isinstance(proxy, socket.error):
|
||||||
|
log.error('Socket error while connecting to the proxy.')
|
||||||
|
continue
|
||||||
|
proxy = proxy[1]
|
||||||
|
# TODO: what if the future never happens?
|
||||||
|
try:
|
||||||
|
addr, port = yield from proxy.connected
|
||||||
|
except socket.error:
|
||||||
|
log.exception('Socket error while connecting to the proxy.')
|
||||||
|
continue
|
||||||
|
# TODO: make a better choice than just the first working one.
|
||||||
used_streamhost = streamhost['jid']
|
used_streamhost = streamhost['jid']
|
||||||
|
conn = proxy
|
||||||
break
|
break
|
||||||
except socket.error:
|
else:
|
||||||
continue
|
raise XMPPError(etype='cancel', condition='item-not-found')
|
||||||
else:
|
|
||||||
raise XMPPError(etype='cancel', condition='item-not-found')
|
|
||||||
|
|
||||||
iq.reply()
|
# TODO: close properly the connection to the other proxies.
|
||||||
with self._sessions_lock:
|
|
||||||
|
iq = iq.reply()
|
||||||
self._sessions[sid] = conn
|
self._sessions[sid] = conn
|
||||||
iq['socks']['sid'] = sid
|
iq['socks']['sid'] = sid
|
||||||
iq['socks']['streamhost_used']['jid'] = used_streamhost
|
iq['socks']['streamhost_used']['jid'] = used_streamhost
|
||||||
iq.send()
|
iq.send()
|
||||||
self.xmpp.event('socks5_stream', conn)
|
self.xmpp.event('socks5_stream', conn)
|
||||||
self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn)
|
self.xmpp.event('stream:%s:%s' % (sid, requester), conn)
|
||||||
|
|
||||||
def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None):
|
asyncio.async(gather(proxy_futures, iq, streamhosts))
|
||||||
|
|
||||||
|
def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None):
|
||||||
"""Activate the socks5 session that has been negotiated."""
|
"""Activate the socks5 session that has been negotiated."""
|
||||||
iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom)
|
iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom)
|
||||||
iq['socks']['sid'] = sid
|
iq['socks']['sid'] = sid
|
||||||
iq['socks']['activate'] = target
|
iq['socks']['activate'] = target
|
||||||
iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|
||||||
def deactivate(self, sid):
|
def deactivate(self, sid):
|
||||||
"""Closes the proxy socket associated with this SID."""
|
"""Closes the proxy socket associated with this SID."""
|
||||||
@@ -204,66 +232,27 @@ class XEP_0065(BasePlugin):
|
|||||||
except socket.error:
|
except socket.error:
|
||||||
pass
|
pass
|
||||||
# Though this should not be neccessary remove the closed session anyway
|
# Though this should not be neccessary remove the closed session anyway
|
||||||
with self._sessions_lock:
|
if sid in self._sessions:
|
||||||
if sid in self._sessions:
|
log.warn(('SOCKS5 session with sid = "%s" was not ' +
|
||||||
log.warn(('SOCKS5 session with sid = "%s" was not ' +
|
'removed from _sessions by sock.close()') % sid)
|
||||||
'removed from _sessions by sock.close()') % sid)
|
del self._sessions[sid]
|
||||||
del self._sessions[sid]
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Closes all proxy sockets."""
|
"""Closes all proxy sockets."""
|
||||||
for sid, sock in self._sessions.items():
|
for sid, sock in self._sessions.items():
|
||||||
sock.close()
|
sock.close()
|
||||||
with self._sessions_lock:
|
self._sessions = {}
|
||||||
self._sessions = {}
|
|
||||||
|
|
||||||
def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None):
|
def _connect_proxy(self, dest, proxy, proxy_port):
|
||||||
""" Establishes a connection between the client and the server-side
|
""" Returns a future to a connection between the client and the server-side
|
||||||
Socks5 proxy.
|
Socks5 proxy.
|
||||||
|
|
||||||
sid : The StreamID. <str>
|
dest : The SHA-1 of (SID + Requester JID + Target JID), in hex. <str>
|
||||||
requester : The JID of the requester. <str>
|
host : The hostname or the IP of the proxy. <str>
|
||||||
target : The JID of the target. <str>
|
port : The port of the proxy. <str> or <int>
|
||||||
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,
|
factory = lambda: Socks5Protocol(dest, 0, self.xmpp.event)
|
||||||
# the Proxy class accepts the proxy_port argument as a string
|
return self.xmpp.loop.create_connection(factory, proxy, proxy_port)
|
||||||
# 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):
|
def _accept_stream(self, iq):
|
||||||
receiver = iq['to']
|
receiver = iq['to']
|
||||||
@@ -278,15 +267,13 @@ class XEP_0065(BasePlugin):
|
|||||||
return self.auto_accept
|
return self.auto_accept
|
||||||
|
|
||||||
def _authorized_sid(self, jid, sid, ifrom, iq):
|
def _authorized_sid(self, jid, sid, ifrom, iq):
|
||||||
with self._preauthed_sids_lock:
|
log.debug('>>> authed sids: %s', self._preauthed_sids)
|
||||||
log.debug('>>> authed sids: %s', self._preauthed_sids)
|
log.debug('>>> lookup: %s %s %s', jid, sid, ifrom)
|
||||||
log.debug('>>> lookup: %s %s %s', jid, sid, ifrom)
|
if (jid, sid, ifrom) in self._preauthed_sids:
|
||||||
if (jid, sid, ifrom) in self._preauthed_sids:
|
del self._preauthed_sids[(jid, sid, ifrom)]
|
||||||
del self._preauthed_sids[(jid, sid, ifrom)]
|
return True
|
||||||
return True
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
def _preauthorize_sid(self, jid, sid, ifrom, data):
|
def _preauthorize_sid(self, jid, sid, ifrom, data):
|
||||||
log.debug('>>>> %s %s %s %s', 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
|
||||||
self._preauthed_sids[(jid, sid, ifrom)] = True
|
|
||||||
|
|||||||
265
slixmpp/plugins/xep_0065/socks5.py
Normal file
265
slixmpp/plugins/xep_0065/socks5.py
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
'''Pure asyncio implementation of RFC 1928 - SOCKS Protocol Version 5.'''
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import enum
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from slixmpp.stringprep import punycode, StringprepError
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolMismatch(Exception):
|
||||||
|
'''We only implement SOCKS5, no other version or protocol.'''
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolError(Exception):
|
||||||
|
'''Some protocol error.'''
|
||||||
|
|
||||||
|
|
||||||
|
class MethodMismatch(Exception):
|
||||||
|
'''The server answered with a method we didn’t ask for.'''
|
||||||
|
|
||||||
|
|
||||||
|
class MethodUnacceptable(Exception):
|
||||||
|
'''None of our methods is supported by the server.'''
|
||||||
|
|
||||||
|
|
||||||
|
class AddressTypeUnacceptable(Exception):
|
||||||
|
'''The address type (ATYP) field isn’t one of IPv4, IPv6 or domain name.'''
|
||||||
|
|
||||||
|
|
||||||
|
class ReplyError(Exception):
|
||||||
|
'''The server answered with an error.'''
|
||||||
|
|
||||||
|
possible_values = (
|
||||||
|
"succeeded",
|
||||||
|
"general SOCKS server failure",
|
||||||
|
"connection not allowed by ruleset",
|
||||||
|
"Network unreachable",
|
||||||
|
"Host unreachable",
|
||||||
|
"Connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"Command not supported",
|
||||||
|
"Address type not supported",
|
||||||
|
"Unknown error")
|
||||||
|
|
||||||
|
def __init__(self, result):
|
||||||
|
if result < 9:
|
||||||
|
Exception.__init__(self, self.possible_values[result])
|
||||||
|
else:
|
||||||
|
Exception.__init__(self, self.possible_values[9])
|
||||||
|
|
||||||
|
|
||||||
|
class Method(enum.IntEnum):
|
||||||
|
'''Known methods for a SOCKS5 session.'''
|
||||||
|
none = 0
|
||||||
|
gssapi = 1
|
||||||
|
password = 2
|
||||||
|
# Methods 3 to 127 are reserved by IANA.
|
||||||
|
# Methods 128 to 254 are reserved for private use.
|
||||||
|
unacceptable = 255
|
||||||
|
not_yet_selected = -1
|
||||||
|
|
||||||
|
|
||||||
|
class Command(enum.IntEnum):
|
||||||
|
'''Existing commands for requests.'''
|
||||||
|
connect = 1
|
||||||
|
bind = 2
|
||||||
|
udp_associate = 3
|
||||||
|
|
||||||
|
|
||||||
|
class AddressType(enum.IntEnum):
|
||||||
|
'''Existing address types.'''
|
||||||
|
ipv4 = 1
|
||||||
|
domain = 3
|
||||||
|
ipv6 = 4
|
||||||
|
|
||||||
|
|
||||||
|
class Socks5Protocol(asyncio.Protocol):
|
||||||
|
'''This implements SOCKS5 as an asyncio protocol.'''
|
||||||
|
|
||||||
|
def __init__(self, dest_addr, dest_port, event):
|
||||||
|
self.methods = {Method.none}
|
||||||
|
self.selected_method = Method.not_yet_selected
|
||||||
|
self.transport = None
|
||||||
|
self.dest = (dest_addr, dest_port)
|
||||||
|
self.connected = asyncio.Future()
|
||||||
|
self.event = event
|
||||||
|
self.paused = asyncio.Future()
|
||||||
|
self.paused.set_result(None)
|
||||||
|
|
||||||
|
def register_method(self, method):
|
||||||
|
'''Register a SOCKS5 method.'''
|
||||||
|
self.methods.add(method)
|
||||||
|
|
||||||
|
def unregister_method(self, method):
|
||||||
|
'''Unregister a SOCKS5 method.'''
|
||||||
|
self.methods.remove(method)
|
||||||
|
|
||||||
|
def connection_made(self, transport):
|
||||||
|
'''Called when the connection to the SOCKS5 server is established.'''
|
||||||
|
|
||||||
|
log.debug('SOCKS5 connection established.')
|
||||||
|
|
||||||
|
self.transport = transport
|
||||||
|
self._send_methods()
|
||||||
|
|
||||||
|
def data_received(self, data):
|
||||||
|
'''Called when we received some data from the SOCKS5 server.'''
|
||||||
|
|
||||||
|
log.debug('SOCKS5 message received.')
|
||||||
|
|
||||||
|
# If we are already connected, this is a data packet.
|
||||||
|
if self.connected.done():
|
||||||
|
return self.event('socks5_data', data)
|
||||||
|
|
||||||
|
# Every SOCKS5 message starts with the protocol version.
|
||||||
|
if data[0] != 5:
|
||||||
|
raise ProtocolMismatch()
|
||||||
|
|
||||||
|
# Then select the correct handler for the data we just received.
|
||||||
|
if self.selected_method == Method.not_yet_selected:
|
||||||
|
self._handle_method(data)
|
||||||
|
else:
|
||||||
|
self._handle_connect(data)
|
||||||
|
|
||||||
|
def connection_lost(self, exc):
|
||||||
|
log.debug('SOCKS5 connection closed.')
|
||||||
|
self.event('socks5_closed', exc)
|
||||||
|
|
||||||
|
def pause_writing(self):
|
||||||
|
self.paused = asyncio.Future()
|
||||||
|
|
||||||
|
def resume_writing(self):
|
||||||
|
self.paused.set_result(None)
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
yield from self.paused
|
||||||
|
self.transport.write(data)
|
||||||
|
|
||||||
|
def _send_methods(self):
|
||||||
|
'''Send the methods request, first thing a client should do.'''
|
||||||
|
|
||||||
|
# Create the buffer for our request.
|
||||||
|
request = bytearray(len(self.methods) + 2)
|
||||||
|
|
||||||
|
# Protocol version.
|
||||||
|
request[0] = 5
|
||||||
|
|
||||||
|
# Number of methods to send.
|
||||||
|
request[1] = len(self.methods)
|
||||||
|
|
||||||
|
# List every method we support.
|
||||||
|
for i, method in enumerate(self.methods):
|
||||||
|
request[i + 2] = method
|
||||||
|
|
||||||
|
# Send the request.
|
||||||
|
self.transport.write(request)
|
||||||
|
|
||||||
|
def _send_request(self, command):
|
||||||
|
'''Send a request, should be done after having negociated a method.'''
|
||||||
|
|
||||||
|
# Encode the destination address to embed it in our request.
|
||||||
|
# We need to do that first because its length is variable.
|
||||||
|
address, port = self.dest
|
||||||
|
addr = self._encode_addr(address)
|
||||||
|
|
||||||
|
# Create the buffer for our request.
|
||||||
|
request = bytearray(5 + len(addr))
|
||||||
|
|
||||||
|
# Protocol version.
|
||||||
|
request[0] = 5
|
||||||
|
|
||||||
|
# Specify the command we want to use.
|
||||||
|
request[1] = command
|
||||||
|
|
||||||
|
# request[2] is reserved, keeping it at 0.
|
||||||
|
|
||||||
|
# Add our destination address and port.
|
||||||
|
request[3:3+len(addr)] = addr
|
||||||
|
request[-2:] = struct.pack('>H', port)
|
||||||
|
|
||||||
|
# Send the request.
|
||||||
|
log.debug('SOCKS5 message sent.')
|
||||||
|
self.transport.write(request)
|
||||||
|
|
||||||
|
def _handle_method(self, data):
|
||||||
|
'''Handle a method reply from the server.'''
|
||||||
|
|
||||||
|
if len(data) != 2:
|
||||||
|
raise ProtocolError()
|
||||||
|
selected_method = data[1]
|
||||||
|
if selected_method not in self.methods:
|
||||||
|
raise MethodMismatch()
|
||||||
|
if selected_method == Method.unacceptable:
|
||||||
|
raise MethodUnacceptable()
|
||||||
|
self.selected_method = selected_method
|
||||||
|
self._send_request(Command.connect)
|
||||||
|
|
||||||
|
def _handle_connect(self, data):
|
||||||
|
'''Handle a connect reply from the server.'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
addr, port = self._parse_result(data)
|
||||||
|
except ReplyError as exception:
|
||||||
|
self.connected.set_exception(exception)
|
||||||
|
self.connected.set_result((addr, port))
|
||||||
|
self.event('socks5_connected', (addr, port))
|
||||||
|
|
||||||
|
def _parse_result(self, data):
|
||||||
|
'''Parse a reply from the server.'''
|
||||||
|
|
||||||
|
result = data[1]
|
||||||
|
if result != 0:
|
||||||
|
raise ReplyError(result)
|
||||||
|
addr = self._parse_addr(data[3:-2])
|
||||||
|
port = struct.unpack('>H', data[-2:])[0]
|
||||||
|
return (addr, port)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_addr(addr):
|
||||||
|
'''Parse an address (IP or domain) from a bytestream.'''
|
||||||
|
|
||||||
|
addr_type = addr[0]
|
||||||
|
if addr_type == AddressType.ipv6:
|
||||||
|
try:
|
||||||
|
return socket.inet_ntop(socket.AF_INET6, addr[1:])
|
||||||
|
except ValueError as e:
|
||||||
|
raise AddressTypeUnacceptable(e)
|
||||||
|
if addr_type == AddressType.ipv4:
|
||||||
|
try:
|
||||||
|
return socket.inet_ntop(socket.AF_INET, addr[1:])
|
||||||
|
except ValueError as e:
|
||||||
|
raise AddressTypeUnacceptable(e)
|
||||||
|
if addr_type == AddressType.domain:
|
||||||
|
length = addr[1]
|
||||||
|
address = addr[2:]
|
||||||
|
if length != len(address):
|
||||||
|
raise Exception('Size mismatch')
|
||||||
|
return address.decode()
|
||||||
|
raise AddressTypeUnacceptable(addr_type)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _encode_addr(addr):
|
||||||
|
'''Encode an address (IP or domain) into a bytestream.'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
ipv6 = socket.inet_pton(socket.AF_INET6, addr)
|
||||||
|
return b'\x04' + ipv6
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
ipv4 = socket.inet_aton(addr)
|
||||||
|
return b'\x01' + ipv4
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
domain = punycode(addr)
|
||||||
|
return b'\x03' + bytes([len(domain)]) + domain
|
||||||
|
except StringprepError:
|
||||||
|
pass
|
||||||
|
raise Exception('Err…')
|
||||||
@@ -81,26 +81,25 @@ class XEP_0077(BasePlugin):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_registration(self, jid=None, ifrom=None, block=True,
|
def get_registration(self, jid=None, ifrom=None,
|
||||||
timeout=None, callback=None):
|
timeout=None, callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['to'] = jid
|
iq['to'] = jid
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq.enable('register')
|
iq.enable('register')
|
||||||
return iq.send(block=block, timeout=timeout,
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
callback=callback)
|
|
||||||
|
|
||||||
def cancel_registration(self, jid=None, ifrom=None, block=True,
|
def cancel_registration(self, jid=None, ifrom=None,
|
||||||
timeout=None, callback=None):
|
timeout=None, callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['to'] = jid
|
iq['to'] = jid
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq['register']['remove'] = True
|
iq['register']['remove'] = True
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|
||||||
def change_password(self, password, jid=None, ifrom=None, block=True,
|
def change_password(self, password, jid=None, ifrom=None,
|
||||||
timeout=None, callback=None):
|
timeout=None, callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
@@ -112,4 +111,4 @@ class XEP_0077(BasePlugin):
|
|||||||
else:
|
else:
|
||||||
iq['register']['username'] = self.xmpp.boundjid.user
|
iq['register']['username'] = self.xmpp.boundjid.user
|
||||||
iq['register']['password'] = password
|
iq['register']['password'] = password
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|||||||
@@ -128,9 +128,8 @@ class XEP_0078(BasePlugin):
|
|||||||
|
|
||||||
self.xmpp.authenticated = True
|
self.xmpp.authenticated = True
|
||||||
|
|
||||||
self.xmpp.boundjid = JID(self.xmpp.requested_jid,
|
self.xmpp.boundjid = JID(self.xmpp.requested_jid)
|
||||||
resource=resource,
|
self.xmpp.boundjid.resource = resource
|
||||||
cache_lock=True)
|
|
||||||
self.xmpp.event('session_bind', self.xmpp.boundjid)
|
self.xmpp.event('session_bind', self.xmpp.boundjid)
|
||||||
|
|
||||||
log.debug("Established Session")
|
log.debug("Established Session")
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ class XEP_0080(BasePlugin):
|
|||||||
|
|
||||||
options -- Optional form of publish options.
|
options -- Optional form of publish options.
|
||||||
ifrom -- Specify the sender's JID.
|
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
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
before exiting the send call if blocking is used.
|
before exiting the send call if blocking is used.
|
||||||
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
@@ -86,10 +84,10 @@ class XEP_0080(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
options = kwargs.get('options', None)
|
options = kwargs.get('options', None)
|
||||||
ifrom = kwargs.get('ifrom', None)
|
ifrom = kwargs.get('ifrom', None)
|
||||||
block = kwargs.get('block', None)
|
|
||||||
callback = kwargs.get('callback', None)
|
callback = kwargs.get('callback', None)
|
||||||
timeout = kwargs.get('timeout', None)
|
timeout = kwargs.get('timeout', None)
|
||||||
for param in ('ifrom', 'block', 'callback', 'timeout', 'options'):
|
timeout_callback = kwargs.get('timeout_callback', None)
|
||||||
|
for param in ('ifrom', 'block', 'callback', 'timeout', 'options', 'timeout_callback'):
|
||||||
if param in kwargs:
|
if param in kwargs:
|
||||||
del kwargs[param]
|
del kwargs[param]
|
||||||
|
|
||||||
@@ -99,18 +97,16 @@ class XEP_0080(BasePlugin):
|
|||||||
return self.xmpp['xep_0163'].publish(geoloc,
|
return self.xmpp['xep_0163'].publish(geoloc,
|
||||||
options=options,
|
options=options,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
|
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Clear existing user location information to stop notifications.
|
Clear existing user location information to stop notifications.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
ifrom -- Specify the sender's JID.
|
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
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
before exiting the send call if blocking is used.
|
before exiting the send call if blocking is used.
|
||||||
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
@@ -120,6 +116,6 @@ class XEP_0080(BasePlugin):
|
|||||||
geoloc = Geoloc()
|
geoloc = Geoloc()
|
||||||
return self.xmpp['xep_0163'].publish(geoloc,
|
return self.xmpp['xep_0163'].publish(geoloc,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=None)
|
||||||
|
|||||||
@@ -44,28 +44,29 @@ class XEP_0084(BasePlugin):
|
|||||||
def generate_id(self, data):
|
def generate_id(self, data):
|
||||||
return hashlib.sha1(data).hexdigest()
|
return hashlib.sha1(data).hexdigest()
|
||||||
|
|
||||||
def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True,
|
def retrieve_avatar(self, jid, id, url=None, ifrom=None,
|
||||||
callback=None, timeout=None):
|
callback=None, timeout=None, timeout_callback=None):
|
||||||
return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,
|
return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def publish_avatar(self, data, ifrom=None, block=True, callback=None,
|
def publish_avatar(self, data, ifrom=None, callback=None,
|
||||||
timeout=None):
|
timeout=None, timeout_callback=None):
|
||||||
payload = Data()
|
payload = Data()
|
||||||
payload['value'] = data
|
payload['value'] = data
|
||||||
return self.xmpp['xep_0163'].publish(payload,
|
return self.xmpp['xep_0163'].publish(payload,
|
||||||
id=self.generate_id(data),
|
id=self.generate_id(data),
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def publish_avatar_metadata(self, items=None, pointers=None,
|
def publish_avatar_metadata(self, items=None, pointers=None,
|
||||||
ifrom=None, block=True,
|
ifrom=None,
|
||||||
callback=None, timeout=None):
|
callback=None, timeout=None,
|
||||||
|
timeout_callback=None):
|
||||||
metadata = MetaData()
|
metadata = MetaData()
|
||||||
if items is None:
|
if items is None:
|
||||||
items = []
|
items = []
|
||||||
@@ -84,18 +85,16 @@ class XEP_0084(BasePlugin):
|
|||||||
return self.xmpp['xep_0163'].publish(metadata,
|
return self.xmpp['xep_0163'].publish(metadata,
|
||||||
id=info['id'],
|
id=info['id'],
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
|
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Clear existing avatar metadata information to stop notifications.
|
Clear existing avatar metadata information to stop notifications.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
ifrom -- Specify the sender's JID.
|
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
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
before exiting the send call if blocking is used.
|
before exiting the send call if blocking is used.
|
||||||
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
@@ -106,6 +105,6 @@ class XEP_0084(BasePlugin):
|
|||||||
return self.xmpp['xep_0163'].publish(metadata,
|
return self.xmpp['xep_0163'].publish(metadata,
|
||||||
node=MetaData.namespace,
|
node=MetaData.namespace,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ class Data(ElementBase):
|
|||||||
def get_value(self):
|
def get_value(self):
|
||||||
if self.xml.text:
|
if self.xml.text:
|
||||||
return b64decode(bytes(self.xml.text))
|
return b64decode(bytes(self.xml.text))
|
||||||
return ''
|
return b''
|
||||||
|
|
||||||
def set_value(self, value):
|
def set_value(self, value):
|
||||||
if value:
|
if value:
|
||||||
self.xml.text = b64encode(bytes(value))
|
self.xml.text = b64encode(bytes(value)).decode()
|
||||||
else:
|
else:
|
||||||
self.xml.text = ''
|
self.xml.text = ''
|
||||||
|
|
||||||
|
|||||||
@@ -64,13 +64,14 @@ class XEP_0092(BasePlugin):
|
|||||||
Arguments:
|
Arguments:
|
||||||
iq -- The Iq stanza containing the software version query.
|
iq -- The Iq stanza containing the software version query.
|
||||||
"""
|
"""
|
||||||
iq.reply()
|
iq = iq.reply()
|
||||||
iq['software_version']['name'] = self.software_name
|
iq['software_version']['name'] = self.software_name
|
||||||
iq['software_version']['version'] = self.version
|
iq['software_version']['version'] = self.version
|
||||||
iq['software_version']['os'] = self.os
|
iq['software_version']['os'] = self.os
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None):
|
def get_version(self, jid, ifrom=None, timeout=None, callback=None,
|
||||||
|
timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Retrieve the software version of a remote agent.
|
Retrieve the software version of a remote agent.
|
||||||
|
|
||||||
@@ -82,4 +83,5 @@ 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)
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|||||||
@@ -182,7 +182,6 @@ class XEP_0095(BasePlugin):
|
|||||||
if stream_handler:
|
if stream_handler:
|
||||||
self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid),
|
self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid),
|
||||||
stream_handler,
|
stream_handler,
|
||||||
threaded=True,
|
|
||||||
disposable=True)
|
disposable=True)
|
||||||
return iq.send()
|
return iq.send()
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class XEP_0096(BasePlugin):
|
|||||||
data['size'] = size
|
data['size'] = size
|
||||||
data['date'] = date
|
data['date'] = date
|
||||||
data['desc'] = desc
|
data['desc'] = desc
|
||||||
|
data['hash'] = hash
|
||||||
if allow_ranged:
|
if allow_ranged:
|
||||||
data.enable('range')
|
data.enable('range')
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from slixmpp.stanza import StreamFeatures, Presence, Iq
|
|||||||
from slixmpp.xmlstream import register_stanza_plugin, JID
|
from slixmpp.xmlstream import register_stanza_plugin, JID
|
||||||
from slixmpp.xmlstream.handler import Callback
|
from slixmpp.xmlstream.handler import Callback
|
||||||
from slixmpp.xmlstream.matcher import StanzaPath
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from slixmpp import asyncio
|
||||||
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
|
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
|
||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
from slixmpp.plugins.xep_0115 import stanza, StaticCaps
|
from slixmpp.plugins.xep_0115 import stanza, StaticCaps
|
||||||
@@ -131,6 +132,7 @@ class XEP_0115(BasePlugin):
|
|||||||
|
|
||||||
self.xmpp.event('entity_caps', p)
|
self.xmpp.event('entity_caps', p)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
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: %s, %s, %s",
|
log.debug("Received unsupported legacy caps: %s, %s, %s",
|
||||||
@@ -162,7 +164,8 @@ class XEP_0115(BasePlugin):
|
|||||||
log.debug("New caps verification string: %s", ver)
|
log.debug("New caps verification string: %s", ver)
|
||||||
try:
|
try:
|
||||||
node = '%s#%s' % (pres['caps']['node'], ver)
|
node = '%s#%s' % (pres['caps']['node'], ver)
|
||||||
caps = self.xmpp['xep_0030'].get_info(pres['from'], node)
|
caps = yield from self.xmpp['xep_0030'].get_info(pres['from'], node,
|
||||||
|
coroutine=True)
|
||||||
|
|
||||||
if isinstance(caps, Iq):
|
if isinstance(caps, Iq):
|
||||||
caps = caps['disco_info']
|
caps = caps['disco_info']
|
||||||
@@ -277,9 +280,10 @@ 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')
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def update_caps(self, jid=None, node=None, preserve=False):
|
def update_caps(self, jid=None, node=None, preserve=False):
|
||||||
try:
|
try:
|
||||||
info = self.xmpp['xep_0030'].get_info(jid, node, local=True)
|
info = yield from self.xmpp['xep_0030'].get_info(jid, node, local=True)
|
||||||
if isinstance(info, Iq):
|
if isinstance(info, Iq):
|
||||||
info = info['disco_info']
|
info = info['disco_info']
|
||||||
ver = self.generate_verstring(info, self.hash)
|
ver = self.generate_verstring(info, self.hash)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class XEP_0118(BasePlugin):
|
|||||||
|
|
||||||
def publish_tune(self, artist=None, length=None, rating=None, source=None,
|
def publish_tune(self, artist=None, length=None, rating=None, source=None,
|
||||||
title=None, track=None, uri=None, options=None,
|
title=None, track=None, uri=None, options=None,
|
||||||
ifrom=None, block=True, callback=None, timeout=None):
|
ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Publish the user's current tune.
|
Publish the user's current tune.
|
||||||
|
|
||||||
@@ -49,8 +49,6 @@ class XEP_0118(BasePlugin):
|
|||||||
uri -- A URL to more information about the song.
|
uri -- A URL to more information about the song.
|
||||||
options -- Optional form of publish options.
|
options -- Optional form of publish options.
|
||||||
ifrom -- Specify the sender's JID.
|
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
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
before exiting the send call if blocking is used.
|
before exiting the send call if blocking is used.
|
||||||
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
@@ -69,18 +67,16 @@ class XEP_0118(BasePlugin):
|
|||||||
node=UserTune.namespace,
|
node=UserTune.namespace,
|
||||||
options=options,
|
options=options,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
|
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Clear existing user tune information to stop notifications.
|
Clear existing user tune information to stop notifications.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
ifrom -- Specify the sender's JID.
|
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
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
before exiting the send call if blocking is used.
|
before exiting the send call if blocking is used.
|
||||||
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
@@ -91,6 +87,6 @@ class XEP_0118(BasePlugin):
|
|||||||
return self.xmpp['xep_0163'].publish(tune,
|
return self.xmpp['xep_0163'].publish(tune,
|
||||||
node=UserTune.namespace,
|
node=UserTune.namespace,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|||||||
11
slixmpp/plugins/xep_0122/__init__.py
Normal file
11
slixmpp/plugins/xep_0122/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
from slixmpp.plugins.xep_0122.stanza import FormValidation
|
||||||
|
from slixmpp.plugins.xep_0122.data_validation import XEP_0122
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0122)
|
||||||
|
|
||||||
|
|
||||||
|
# Retain some backwards compatibility
|
||||||
|
xep_0122 = XEP_0122
|
||||||
19
slixmpp/plugins/xep_0122/data_validation.py
Normal file
19
slixmpp/plugins/xep_0122/data_validation.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
|
from slixmpp.plugins import BasePlugin
|
||||||
|
from slixmpp.plugins.xep_0004 import stanza
|
||||||
|
from slixmpp.plugins.xep_0004.stanza import FormField
|
||||||
|
from slixmpp.plugins.xep_0122.stanza import FormValidation
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0122(BasePlugin):
|
||||||
|
"""
|
||||||
|
XEP-0122: Data Forms
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'xep_0122'
|
||||||
|
description = 'XEP-0122: Data Forms Validation'
|
||||||
|
dependencies = set(['xep_0004'])
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(FormField, FormValidation)
|
||||||
93
slixmpp/plugins/xep_0122/stanza.py
Normal file
93
slixmpp/plugins/xep_0122/stanza.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
from slixmpp.xmlstream import ElementBase, ET
|
||||||
|
|
||||||
|
|
||||||
|
class FormValidation(ElementBase):
|
||||||
|
"""
|
||||||
|
Validation values for form fields.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
<field var='evt.date' type='text-single' label='Event Date/Time'>
|
||||||
|
<validate xmlns='http://jabber.org/protocol/xdata-validate'
|
||||||
|
datatype='xs:dateTime'/>
|
||||||
|
<value>2003-10-06T11:22:00-07:00</value>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
Questions:
|
||||||
|
Should this look at the datatype value and convert the range values as appropriate?
|
||||||
|
Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype?
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = 'http://jabber.org/protocol/xdata-validate'
|
||||||
|
name = 'validate'
|
||||||
|
plugin_attrib = 'validate'
|
||||||
|
interfaces = {'datatype', 'basic', 'open', 'range', 'regex', }
|
||||||
|
sub_interfaces = {'basic', 'open', 'range', 'regex', }
|
||||||
|
plugin_attrib_map = {}
|
||||||
|
plugin_tag_map = {}
|
||||||
|
|
||||||
|
def _add_field(self, name):
|
||||||
|
self.remove_all()
|
||||||
|
item_xml = ET.Element('{%s}%s' % (self.namespace, name))
|
||||||
|
self.xml.append(item_xml)
|
||||||
|
return item_xml
|
||||||
|
|
||||||
|
def set_basic(self, value):
|
||||||
|
if value:
|
||||||
|
self._add_field('basic')
|
||||||
|
else:
|
||||||
|
del self['basic']
|
||||||
|
|
||||||
|
def set_open(self, value):
|
||||||
|
if value:
|
||||||
|
self._add_field('open')
|
||||||
|
else:
|
||||||
|
del self['open']
|
||||||
|
|
||||||
|
def set_regex(self, regex):
|
||||||
|
if regex:
|
||||||
|
_regex = self._add_field('regex')
|
||||||
|
_regex.text = regex
|
||||||
|
else:
|
||||||
|
del self['regex']
|
||||||
|
|
||||||
|
def set_range(self, value, minimum=None, maximum=None):
|
||||||
|
if value:
|
||||||
|
_range = self._add_field('range')
|
||||||
|
_range.attrib['min'] = str(minimum)
|
||||||
|
_range.attrib['max'] = str(maximum)
|
||||||
|
else:
|
||||||
|
del self['range']
|
||||||
|
|
||||||
|
def remove_all(self, except_tag=None):
|
||||||
|
for a in self.sub_interfaces:
|
||||||
|
if a != except_tag:
|
||||||
|
del self[a]
|
||||||
|
|
||||||
|
def get_basic(self):
|
||||||
|
present = self.xml.find('{%s}basic' % self.namespace)
|
||||||
|
return present is not None
|
||||||
|
|
||||||
|
def get_open(self):
|
||||||
|
present = self.xml.find('{%s}open' % self.namespace)
|
||||||
|
return present is not None
|
||||||
|
|
||||||
|
def get_regex(self):
|
||||||
|
present = self.xml.find('{%s}regex' % self.namespace)
|
||||||
|
if present is not None:
|
||||||
|
return present.text
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_range(self):
|
||||||
|
present = self.xml.find('{%s}range' % self.namespace)
|
||||||
|
if present is not None:
|
||||||
|
attributes = present.attrib
|
||||||
|
return_value = dict()
|
||||||
|
if 'min' in attributes:
|
||||||
|
return_value['minimum'] = attributes['min']
|
||||||
|
if 'max' in attributes:
|
||||||
|
return_value['maximum'] = attributes['max']
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
return False
|
||||||
@@ -35,15 +35,14 @@ class XEP_0133(BasePlugin):
|
|||||||
|
|
||||||
|
|
||||||
def create_command(name):
|
def create_command(name):
|
||||||
def admin_command(self, jid=None, session=None, ifrom=None, block=False):
|
def admin_command(self, jid=None, session=None, ifrom=None):
|
||||||
if jid is None:
|
if jid is None:
|
||||||
jid = self.xmpp.boundjid.server
|
jid = self.xmpp.boundjid.server
|
||||||
self.xmpp['xep_0050'].start_command(
|
self.xmpp['xep_0050'].start_command(
|
||||||
jid=jid,
|
jid=jid,
|
||||||
node='http://jabber.org/protocol/admin#%s' % name,
|
node='http://jabber.org/protocol/admin#%s' % name,
|
||||||
session=session,
|
session=session,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom)
|
||||||
block=block)
|
|
||||||
return admin_command
|
return admin_command
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
145
slixmpp/plugins/xep_0138.py
Normal file
145
slixmpp/plugins/xep_0138.py
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
"""
|
||||||
|
slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2011 Nathanael C. Fritz
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import zlib
|
||||||
|
|
||||||
|
|
||||||
|
from slixmpp.stanza import StreamFeatures
|
||||||
|
from slixmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase
|
||||||
|
from slixmpp.xmlstream.matcher import *
|
||||||
|
from slixmpp.xmlstream.handler import *
|
||||||
|
from slixmpp.plugins import BasePlugin, register_plugin
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Compression(ElementBase):
|
||||||
|
name = 'compression'
|
||||||
|
namespace = 'http://jabber.org/features/compress'
|
||||||
|
interfaces = set(('methods',))
|
||||||
|
plugin_attrib = 'compression'
|
||||||
|
plugin_tag_map = {}
|
||||||
|
plugin_attrib_map = {}
|
||||||
|
|
||||||
|
def get_methods(self):
|
||||||
|
methods = []
|
||||||
|
for method in self.xml.findall('{%s}method' % self.namespace):
|
||||||
|
methods.append(method.text)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
|
||||||
|
class Compress(StanzaBase):
|
||||||
|
name = 'compress'
|
||||||
|
namespace = 'http://jabber.org/protocol/compress'
|
||||||
|
interfaces = set(('method',))
|
||||||
|
sub_interfaces = interfaces
|
||||||
|
plugin_attrib = 'compress'
|
||||||
|
plugin_tag_map = {}
|
||||||
|
plugin_attrib_map = {}
|
||||||
|
|
||||||
|
def setup(self, xml):
|
||||||
|
StanzaBase.setup(self, xml)
|
||||||
|
self.xml.tag = self.tag_name()
|
||||||
|
|
||||||
|
|
||||||
|
class Compressed(StanzaBase):
|
||||||
|
name = 'compressed'
|
||||||
|
namespace = 'http://jabber.org/protocol/compress'
|
||||||
|
interfaces = set()
|
||||||
|
plugin_tag_map = {}
|
||||||
|
plugin_attrib_map = {}
|
||||||
|
|
||||||
|
def setup(self, xml):
|
||||||
|
StanzaBase.setup(self, xml)
|
||||||
|
self.xml.tag = self.tag_name()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ZlibSocket(object):
|
||||||
|
|
||||||
|
def __init__(self, socketobj):
|
||||||
|
self.__socket = socketobj
|
||||||
|
self.compressor = zlib.compressobj()
|
||||||
|
self.decompressor = zlib.decompressobj(zlib.MAX_WBITS)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self.__socket, name)
|
||||||
|
|
||||||
|
def send(self, data):
|
||||||
|
sentlen = len(data)
|
||||||
|
data = self.compressor.compress(data)
|
||||||
|
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
|
||||||
|
log.debug(b'>>> (compressed)' + (data.encode("hex")))
|
||||||
|
#return self.__socket.send(data)
|
||||||
|
sentactuallen = self.__socket.send(data)
|
||||||
|
assert(sentactuallen == len(data))
|
||||||
|
|
||||||
|
return sentlen
|
||||||
|
|
||||||
|
def recv(self, *args, **kwargs):
|
||||||
|
data = self.__socket.recv(*args, **kwargs)
|
||||||
|
log.debug(b'<<< (compressed)' + data.encode("hex"))
|
||||||
|
return self.decompressor.decompress(self.decompressor.unconsumed_tail + data)
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0138(BasePlugin):
|
||||||
|
"""
|
||||||
|
XEP-0138: Compression
|
||||||
|
"""
|
||||||
|
name = "xep_0138"
|
||||||
|
description = "XEP-0138: Compression"
|
||||||
|
dependencies = set(["xep_0030"])
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
self.xep = '0138'
|
||||||
|
self.description = 'Stream Compression (Generic)'
|
||||||
|
|
||||||
|
self.compression_methods = {'zlib': True}
|
||||||
|
|
||||||
|
register_stanza_plugin(StreamFeatures, Compression)
|
||||||
|
self.xmpp.register_stanza(Compress)
|
||||||
|
self.xmpp.register_stanza(Compressed)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('Compressed',
|
||||||
|
StanzaPath('compressed'),
|
||||||
|
self._handle_compressed,
|
||||||
|
instream=True))
|
||||||
|
|
||||||
|
self.xmpp.register_feature('compression',
|
||||||
|
self._handle_compression,
|
||||||
|
restart=True,
|
||||||
|
order=self.config.get('order', 5))
|
||||||
|
|
||||||
|
def register_compression_method(self, name, handler):
|
||||||
|
self.compression_methods[name] = handler
|
||||||
|
|
||||||
|
def _handle_compression(self, features):
|
||||||
|
for method in features['compression']['methods']:
|
||||||
|
if method in self.compression_methods:
|
||||||
|
log.info('Attempting to use %s compression' % method)
|
||||||
|
c = Compress(self.xmpp)
|
||||||
|
c['method'] = method
|
||||||
|
c.send(now=True)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _handle_compressed(self, stanza):
|
||||||
|
self.xmpp.features.add('compression')
|
||||||
|
log.debug('Stream Compressed!')
|
||||||
|
compressed_socket = ZlibSocket(self.xmpp.socket)
|
||||||
|
self.xmpp.set_socket(compressed_socket)
|
||||||
|
raise RestartStream()
|
||||||
|
|
||||||
|
def _handle_failure(self, stanza):
|
||||||
|
pass
|
||||||
|
|
||||||
|
xep_0138 = XEP_0138
|
||||||
|
register_plugin(XEP_0138)
|
||||||
@@ -33,8 +33,9 @@ class XEP_0152(BasePlugin):
|
|||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.xmpp['xep_0163'].register_pep('reachability', Reachability)
|
self.xmpp['xep_0163'].register_pep('reachability', Reachability)
|
||||||
|
|
||||||
def publish_reachability(self, addresses, options=None,
|
def publish_reachability(self, addresses, options=None, ifrom=None,
|
||||||
ifrom=None, block=True, callback=None, timeout=None):
|
callback=None, timeout=None,
|
||||||
|
timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Publish alternative addresses where the user can be reached.
|
Publish alternative addresses where the user can be reached.
|
||||||
|
|
||||||
@@ -43,8 +44,6 @@ class XEP_0152(BasePlugin):
|
|||||||
optional description for each address.
|
optional description for each address.
|
||||||
options -- Optional form of publish options.
|
options -- Optional form of publish options.
|
||||||
ifrom -- Specify the sender's JID.
|
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
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
before exiting the send call if blocking is used.
|
before exiting the send call if blocking is used.
|
||||||
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
@@ -66,18 +65,16 @@ class XEP_0152(BasePlugin):
|
|||||||
node=Reachability.namespace,
|
node=Reachability.namespace,
|
||||||
options=options,
|
options=options,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
|
def stop(self, ifrom=None, callback=None, timeout=None, timeout_callback=None):
|
||||||
"""
|
"""
|
||||||
Clear existing user activity information to stop notifications.
|
Clear existing user activity information to stop notifications.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
ifrom -- Specify the sender's JID.
|
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
|
timeout -- The length of time (in seconds) to wait for a response
|
||||||
before exiting the send call if blocking is used.
|
before exiting the send call if blocking is used.
|
||||||
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
Defaults to slixmpp.xmlstream.RESPONSE_TIMEOUT
|
||||||
@@ -88,6 +85,6 @@ class XEP_0152(BasePlugin):
|
|||||||
return self.xmpp['xep_0163'].publish(reach,
|
return self.xmpp['xep_0163'].publish(reach,
|
||||||
node=Reachability.namespace,
|
node=Reachability.namespace,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
|
||||||
callback=callback,
|
callback=callback,
|
||||||
timeout=timeout)
|
timeout=timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import threading
|
|
||||||
|
|
||||||
from slixmpp.stanza import Presence
|
from slixmpp.stanza import Presence
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError, IqTimeout
|
||||||
from slixmpp.xmlstream import register_stanza_plugin
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
from slixmpp.plugins.base import BasePlugin
|
from slixmpp.plugins.base import BasePlugin
|
||||||
from slixmpp.plugins.xep_0153 import stanza, VCardTempUpdate
|
from slixmpp.plugins.xep_0153 import stanza, VCardTempUpdate
|
||||||
|
from slixmpp import asyncio, future_wrapper
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -30,8 +30,6 @@ class XEP_0153(BasePlugin):
|
|||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self._hashes = {}
|
self._hashes = {}
|
||||||
|
|
||||||
self._allow_advertising = threading.Event()
|
|
||||||
|
|
||||||
register_stanza_plugin(Presence, VCardTempUpdate)
|
register_stanza_plugin(Presence, VCardTempUpdate)
|
||||||
|
|
||||||
self.xmpp.add_filter('out', self._update_presence)
|
self.xmpp.add_filter('out', self._update_presence)
|
||||||
@@ -59,36 +57,62 @@ class XEP_0153(BasePlugin):
|
|||||||
self.xmpp.del_event_handler('presence_chat', self._recv_presence)
|
self.xmpp.del_event_handler('presence_chat', self._recv_presence)
|
||||||
self.xmpp.del_event_handler('presence_away', self._recv_presence)
|
self.xmpp.del_event_handler('presence_away', self._recv_presence)
|
||||||
|
|
||||||
def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
|
@future_wrapper
|
||||||
timeout=None, callback=None):
|
def set_avatar(self, jid=None, avatar=None, mtype=None, timeout=None,
|
||||||
|
callback=None, timeout_callback=None):
|
||||||
if jid is None:
|
if jid is None:
|
||||||
jid = self.xmpp.boundjid.bare
|
jid = self.xmpp.boundjid.bare
|
||||||
|
|
||||||
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
|
future = asyncio.Future()
|
||||||
vcard = vcard['vcard_temp']
|
|
||||||
vcard['PHOTO']['TYPE'] = mtype
|
|
||||||
vcard['PHOTO']['BINVAL'] = avatar
|
|
||||||
|
|
||||||
self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard)
|
def propagate_timeout_exception(fut):
|
||||||
|
try:
|
||||||
|
fut.done()
|
||||||
|
except IqTimeout as e:
|
||||||
|
future.set_exception(e)
|
||||||
|
|
||||||
self.api['reset_hash'](jid)
|
def custom_callback(result):
|
||||||
self.xmpp.roster[jid].send_last_presence()
|
vcard = result['vcard_temp']
|
||||||
|
vcard['PHOTO']['TYPE'] = mtype
|
||||||
|
vcard['PHOTO']['BINVAL'] = avatar
|
||||||
|
|
||||||
|
new_future = self.xmpp['xep_0054'].publish_vcard(jid=jid,
|
||||||
|
vcard=vcard,
|
||||||
|
timeout=timeout,
|
||||||
|
callback=next_callback,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
new_future.add_done_callback(propagate_timeout_exception)
|
||||||
|
|
||||||
|
def next_callback(result):
|
||||||
|
if result['type'] == 'error':
|
||||||
|
future.set_exception(result)
|
||||||
|
else:
|
||||||
|
self.api['reset_hash'](jid)
|
||||||
|
self.xmpp.roster[jid].send_last_presence()
|
||||||
|
|
||||||
|
future.set_result(result)
|
||||||
|
|
||||||
|
first_future = self.xmpp['xep_0054'].get_vcard(jid, cached=False, timeout=timeout,
|
||||||
|
callback=custom_callback,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
first_future.add_done_callback(propagate_timeout_exception)
|
||||||
|
return future
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def _start(self, event):
|
def _start(self, event):
|
||||||
try:
|
try:
|
||||||
vcard = self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
|
vcard = yield from self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
|
||||||
data = vcard['vcard_temp']['PHOTO']['BINVAL']
|
data = vcard['vcard_temp']['PHOTO']['BINVAL']
|
||||||
if not data:
|
if not data:
|
||||||
new_hash = ''
|
new_hash = ''
|
||||||
else:
|
else:
|
||||||
new_hash = hashlib.sha1(data).hexdigest()
|
new_hash = hashlib.sha1(data).hexdigest()
|
||||||
self.api['set_hash'](self.xmpp.boundjid, args=new_hash)
|
self.api['set_hash'](self.xmpp.boundjid, args=new_hash)
|
||||||
self._allow_advertising.set()
|
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
log.debug('Could not retrieve vCard for %s' % self.xmpp.boundjid.bare)
|
log.debug('Could not retrieve vCard for %s', self.xmpp.boundjid.bare)
|
||||||
|
|
||||||
def _end(self, event):
|
def _end(self, event):
|
||||||
self._allow_advertising.clear()
|
pass
|
||||||
|
|
||||||
def _update_presence(self, stanza):
|
def _update_presence(self, stanza):
|
||||||
if not isinstance(stanza, Presence):
|
if not isinstance(stanza, Presence):
|
||||||
@@ -110,9 +134,10 @@ class XEP_0153(BasePlugin):
|
|||||||
if own_jid:
|
if own_jid:
|
||||||
self.xmpp.roster[jid].send_last_presence()
|
self.xmpp.roster[jid].send_last_presence()
|
||||||
|
|
||||||
try:
|
def callback(iq):
|
||||||
iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
|
if iq['type'] == 'error':
|
||||||
|
log.debug('Could not retrieve vCard for %s', jid)
|
||||||
|
return
|
||||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||||
if not data:
|
if not data:
|
||||||
new_hash = ''
|
new_hash = ''
|
||||||
@@ -120,8 +145,9 @@ class XEP_0153(BasePlugin):
|
|||||||
new_hash = hashlib.sha1(data).hexdigest()
|
new_hash = hashlib.sha1(data).hexdigest()
|
||||||
|
|
||||||
self.api['set_hash'](jid, args=new_hash)
|
self.api['set_hash'](jid, args=new_hash)
|
||||||
except XMPPError:
|
|
||||||
log.debug('Could not retrieve vCard for %s' % jid)
|
self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom,
|
||||||
|
callback=callback)
|
||||||
|
|
||||||
def _recv_presence(self, pres):
|
def _recv_presence(self, pres):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from slixmpp import asyncio
|
||||||
from slixmpp.xmlstream import register_stanza_plugin
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
from slixmpp.plugins.base import BasePlugin, register_plugin
|
from slixmpp.plugins.base import BasePlugin, register_plugin
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ class XEP_0163(BasePlugin):
|
|||||||
for ns in namespace:
|
for ns in namespace:
|
||||||
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
|
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
|
||||||
jid=jid)
|
jid=jid)
|
||||||
self.xmpp['xep_0115'].update_caps(jid)
|
asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
|
||||||
|
|
||||||
def remove_interest(self, namespace, jid=None):
|
def remove_interest(self, namespace, jid=None):
|
||||||
"""
|
"""
|
||||||
@@ -80,7 +81,7 @@ class XEP_0163(BasePlugin):
|
|||||||
for ns in namespace:
|
for ns in namespace:
|
||||||
self.xmpp['xep_0030'].del_feature(jid=jid,
|
self.xmpp['xep_0030'].del_feature(jid=jid,
|
||||||
feature='%s+notify' % namespace)
|
feature='%s+notify' % namespace)
|
||||||
self.xmpp['xep_0115'].update_caps(jid)
|
asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
|
||||||
|
|
||||||
def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
|
def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
|
||||||
timeout_callback=None, callback=None, timeout=None):
|
timeout_callback=None, callback=None, timeout=None):
|
||||||
|
|||||||
@@ -67,9 +67,7 @@ class XEP_0184(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
ack = self.xmpp.Message()
|
ack = self.xmpp.Message()
|
||||||
ack['to'] = msg['from']
|
ack['to'] = msg['from']
|
||||||
ack['from'] = msg['to']
|
|
||||||
ack['receipt'] = msg['id']
|
ack['receipt'] = msg['id']
|
||||||
ack['id'] = msg['id']
|
|
||||||
ack.send()
|
ack.send()
|
||||||
|
|
||||||
def _handle_receipt_received(self, msg):
|
def _handle_receipt_received(self, msg):
|
||||||
|
|||||||
@@ -27,18 +27,18 @@ class XEP_0186(BasePlugin):
|
|||||||
register_stanza_plugin(Iq, Visible)
|
register_stanza_plugin(Iq, Visible)
|
||||||
register_stanza_plugin(Iq, Invisible)
|
register_stanza_plugin(Iq, Invisible)
|
||||||
|
|
||||||
def set_invisible(self, ifrom=None, block=True, callback=None,
|
def set_invisible(self, ifrom=None, callback=None,
|
||||||
timeout=None):
|
timeout=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq.enable('invisible')
|
iq.enable('invisible')
|
||||||
iq.send(block=block, callback=callback, timeout=timeout)
|
iq.send(callback=callback, timeout=timeout)
|
||||||
|
|
||||||
def set_visible(self, ifrom=None, block=True, callback=None,
|
def set_visible(self, ifrom=None, callback=None,
|
||||||
timeout=None):
|
timeout=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq.enable('visible')
|
iq.enable('visible')
|
||||||
iq.send(block=block, callback=callback, timeout=timeout)
|
iq.send(callback=callback, timeout=timeout)
|
||||||
|
|||||||
@@ -45,14 +45,17 @@ class XEP_0191(BasePlugin):
|
|||||||
self.xmpp.remove_handler('Blocked Contact')
|
self.xmpp.remove_handler('Blocked Contact')
|
||||||
self.xmpp.remove_handler('Unblocked Contact')
|
self.xmpp.remove_handler('Unblocked Contact')
|
||||||
|
|
||||||
def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None):
|
def get_blocked(self, ifrom=None, timeout=None, callback=None,
|
||||||
|
timeout_callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq.enable('blocklist')
|
iq.enable('blocklist')
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def block(self, jids, ifrom=None, block=True, timeout=None, callback=None):
|
def block(self, jids, ifrom=None, timeout=None, callback=None,
|
||||||
|
timeout_callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
@@ -61,9 +64,11 @@ class XEP_0191(BasePlugin):
|
|||||||
jids = [jids]
|
jids = [jids]
|
||||||
|
|
||||||
iq['block']['items'] = jids
|
iq['block']['items'] = jids
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def unblock(self, jids=None, ifrom=None, block=True, timeout=None, callback=None):
|
def unblock(self, jids=None, ifrom=None, timeout=None, callback=None,
|
||||||
|
timeout_callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
@@ -74,7 +79,8 @@ class XEP_0191(BasePlugin):
|
|||||||
jids = [jids]
|
jids = [jids]
|
||||||
|
|
||||||
iq['unblock']['items'] = jids
|
iq['unblock']['items'] = jids
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
def _handle_blocked(self, iq):
|
def _handle_blocked(self, iq):
|
||||||
self.xmpp.event('blocked', iq)
|
self.xmpp.event('blocked', iq)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import logging
|
|||||||
|
|
||||||
from slixmpp.jid import JID
|
from slixmpp.jid import JID
|
||||||
from slixmpp.stanza import Iq
|
from slixmpp.stanza import Iq
|
||||||
|
from slixmpp import asyncio
|
||||||
from slixmpp.exceptions import IqError, IqTimeout
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
from slixmpp.xmlstream import register_stanza_plugin
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
from slixmpp.xmlstream.matcher import StanzaPath
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
@@ -71,8 +72,7 @@ class XEP_0199(BasePlugin):
|
|||||||
|
|
||||||
if self.keepalive:
|
if self.keepalive:
|
||||||
self.xmpp.add_event_handler('session_start',
|
self.xmpp.add_event_handler('session_start',
|
||||||
self.enable_keepalive,
|
self.enable_keepalive)
|
||||||
threaded=True)
|
|
||||||
self.xmpp.add_event_handler('session_end',
|
self.xmpp.add_event_handler('session_end',
|
||||||
self.disable_keepalive)
|
self.disable_keepalive)
|
||||||
|
|
||||||
@@ -129,8 +129,7 @@ class XEP_0199(BasePlugin):
|
|||||||
timeout -- Time in seconds to wait for a response.
|
timeout -- Time in seconds to wait for a response.
|
||||||
Defaults to self.timeout.
|
Defaults to self.timeout.
|
||||||
callback -- Optional handler to execute when a pong
|
callback -- Optional handler to execute when a pong
|
||||||
is received. Useful in conjunction with
|
is received.
|
||||||
the option block=False.
|
|
||||||
"""
|
"""
|
||||||
if not timeout:
|
if not timeout:
|
||||||
timeout = self.timeout
|
timeout = self.timeout
|
||||||
@@ -144,8 +143,10 @@ class XEP_0199(BasePlugin):
|
|||||||
return iq.send(timeout=timeout, callback=callback,
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
timeout_callback=timeout_callback)
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
def ping(self, jid=None, ifrom=None, timeout=None):
|
def ping(self, jid=None, ifrom=None, timeout=None):
|
||||||
"""Send a ping request and calculate RTT.
|
"""Send a ping request and calculate RTT.
|
||||||
|
This is a coroutine.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
jid -- The JID that will receive the ping.
|
jid -- The JID that will receive the ping.
|
||||||
@@ -171,7 +172,8 @@ class XEP_0199(BasePlugin):
|
|||||||
|
|
||||||
log.debug('Pinging %s' % jid)
|
log.debug('Pinging %s' % jid)
|
||||||
try:
|
try:
|
||||||
self.send_ping(jid, ifrom=ifrom, timeout=timeout)
|
yield from self.send_ping(jid, ifrom=ifrom, timeout=timeout,
|
||||||
|
coroutine=True)
|
||||||
except IqError as e:
|
except IqError as e:
|
||||||
if own_host:
|
if own_host:
|
||||||
rtt = time.time() - start
|
rtt = time.time() - start
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user