Update the documentation and examples
- update most of the examples with slixmpp - change the help channels pointed out in the doc - add a page listing differences from slixmpp and how to use asyncio nicely with slixmpp - fix some in-code rst documentation
This commit is contained in:
parent
e112e86475
commit
c66a4d4097
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
|
||||
--------
|
||||
.. module:: slixmpp.xmlstream.handler.callback
|
||||
.. module:: slixmpp.xmlstream.handler
|
||||
|
||||
.. autoclass:: Callback
|
||||
:members:
|
||||
|
||||
CoroutineCallback
|
||||
-----------------
|
||||
|
||||
.. autoclass:: CoroutineCallback
|
||||
:members:
|
||||
|
||||
Waiter
|
||||
------
|
||||
.. module:: slixmpp.xmlstream.handler.waiter
|
||||
|
||||
.. autoclass:: Waiter
|
||||
:members:
|
||||
|
@ -1,7 +1,7 @@
|
||||
Jabber IDs (JID)
|
||||
=================
|
||||
|
||||
.. module:: slixmpp.xmlstream.jid
|
||||
.. module:: slixmpp.jid
|
||||
|
||||
.. autoclass:: JID
|
||||
: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>`
|
||||
or XML chunks easier, :term:`stanza objects <stanza object>` can be
|
||||
composed in two ways: as iterable child objects or as plugins. Iterable
|
||||
child stanzas, or :term:`substanzas`, are accessible through a special
|
||||
``'substanzas'`` interface. This option is useful for stanzas which
|
||||
child stanzas, or :term:`substanzas <substanza>`, are accessible through a
|
||||
special ``'substanzas'`` interface. This option is useful for stanzas which
|
||||
may contain more than one of the same kind of element. When there is
|
||||
only one child element, the plugin method is more useful. For plugins,
|
||||
a parent stanza object delegates one of its XML child elements to the
|
||||
|
@ -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
|
||||
it to the terminal, the ``jabber:client`` namespace will appear.
|
||||
|
||||
.. autofunction:: tostring
|
||||
.. autofunction:: slixmpp.xmlstream.tostring
|
||||
|
||||
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
|
||||
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
|
||||
which can be triggered either manually or on a timed schedule.
|
||||
|
||||
The Main Threads
|
||||
~~~~~~~~~~~~~~~~
|
||||
:class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances run using at
|
||||
least three background threads: the send thread, the read thread, and the
|
||||
scheduler thread. The send thread is in charge of monitoring the send queue
|
||||
and writing text to the outgoing XML stream. The read thread pulls text off
|
||||
of the incoming XML stream and stores the results in an event queue. The
|
||||
scheduler thread is used to emit events after a given period of time.
|
||||
The event loop
|
||||
~~~~~~~~~~~~~~
|
||||
:class:`~slixmpp.xmlstream.xmlstream.XMLStream` instances inherit the
|
||||
:class:`asyncio.BaseProtocol` class, and therefore do not have to handle
|
||||
reads and writes directly, but receive data through
|
||||
:meth:`~slixmpp.xmlstream.xmlstream.XMLStream.data_received` and write
|
||||
data in the socket transport.
|
||||
|
||||
Additionally, the main event processing loop may be executed in its
|
||||
own thread if Slixmpp is being used in the background for another
|
||||
application.
|
||||
Upon receiving data, :term:`stream handlers <stream handler>` are run
|
||||
immediately, except if they are coroutines, in which case they are
|
||||
scheduled using :meth:`asyncio.async`.
|
||||
|
||||
Short-lived threads may also be spawned as requested for threaded
|
||||
:term:`event handlers <event handler>`.
|
||||
:term:`Event handlers <event handler>` (which are called inside
|
||||
:term:`stream handlers <stream handler>`) work the same way.
|
||||
|
||||
How XML Text is Turned into Action
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -53,7 +52,7 @@ when this bit of XML is received (with an assumed namespace of
|
||||
</message>
|
||||
|
||||
|
||||
1. **Convert XML strings into objects.**
|
||||
#. **Convert XML strings into objects.**
|
||||
|
||||
Incoming text is parsed and converted into XML objects (using
|
||||
ElementTree) which are then wrapped into what are referred to as
|
||||
@ -66,65 +65,43 @@ when this bit of XML is received (with an assumed namespace of
|
||||
``{jabber:client}message`` is associated with the class
|
||||
: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
|
||||
with the registered callback handlers. For each match, a copy of the
|
||||
:term:`stanza object` is paired with a reference to the handler and
|
||||
placed into the event queue.
|
||||
with the registered callback handlers.
|
||||
|
||||
Our :class:`~slixmpp.stanza.Message` object is thus paired with the message stanza handler
|
||||
:meth:`BaseXMPP._handle_message` to create the tuple::
|
||||
Each handler matching our :term:`stanza object` is then added to a list.
|
||||
|
||||
('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
|
||||
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**
|
||||
#. **Raise Custom Events**
|
||||
|
||||
Since a :term:`stream handler` shouldn't block, if extensive processing
|
||||
for a stanza is required (such as needing to send and receive an
|
||||
:class:`~slixmpp.stanza.Iq` stanza), then custom events must be used.
|
||||
These events are not explicitly tied to the incoming XML stream and may
|
||||
be raised at any time. Importantly, these events may be handled in their
|
||||
own thread.
|
||||
be raised at any time.
|
||||
|
||||
When the event is raised, a copy of the stanza is created for each
|
||||
handler registered for the event. In contrast to :term:`stream handlers
|
||||
<stream handler>`, these functions are referred to as :term:`event
|
||||
handlers <event handler>`. Each stanza/handler pair is then put into the
|
||||
event queue.
|
||||
In contrast to :term:`stream handlers <stream handler>`, these functions
|
||||
are referred to as :term:`event handlers <event handler>`.
|
||||
|
||||
The code for :meth:`BaseXMPP._handle_message` follows this pattern, and
|
||||
raises a ``'message'`` event::
|
||||
raises a ``'message'`` event
|
||||
|
||||
self.event('message', msg)
|
||||
.. code-block:: python
|
||||
|
||||
The event call then places the message object back into the event queue
|
||||
paired with an :term:`event handler`::
|
||||
self.event('message', msg)
|
||||
|
||||
('event', 'message', msg_copy1, custom_event_handler_1)
|
||||
('event', 'message', msg_copy2, custom_evetn_handler_2)
|
||||
#. **Process Custom Events**
|
||||
|
||||
5. **Process Custom Events**
|
||||
|
||||
The stanza and :term:`event handler` are then pulled from the event
|
||||
queue, and the handler is executed, passing the stanza as its only
|
||||
argument. If the handler was registered as threaded, then a new thread
|
||||
will be spawned for it.
|
||||
The :term:`event handlers <event handler>` are then executed, passing
|
||||
the stanza as the only argument.
|
||||
|
||||
.. note::
|
||||
Events may be raised without needing :term:`stanza objects <stanza object>`.
|
||||
@ -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
|
||||
custom handler in order to do awesome stuff::
|
||||
|
||||
msg.reply()
|
||||
msg['body'] = "Hey! This is awesome!"
|
||||
msg.send()
|
||||
reply = msg.reply()
|
||||
reply['body'] = "Hey! This is awesome!"
|
||||
reply.send()
|
||||
|
||||
|
||||
.. index:: BaseXMPP, XMLStream
|
||||
|
@ -105,7 +105,7 @@ html_theme = 'haiku'
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
html_title = 'Slixmpp'
|
||||
html_title = 'slixmpp'
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
html_short_title = '%s Documentation' % release
|
||||
@ -219,4 +219,4 @@ man_pages = [
|
||||
[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']):
|
||||
# Successful registration
|
||||
self.xmpp.event('registered_user', iq)
|
||||
iq.reply().set_payload(iq['register'].xml)
|
||||
iq.send()
|
||||
reply = iq.reply()
|
||||
reply.set_payload(iq['register'].xml)
|
||||
reply.send()
|
||||
else:
|
||||
# Conflicting registration
|
||||
self._sendError(iq, '409', 'cancel', 'conflict',
|
||||
@ -666,14 +667,16 @@ with some additional registration fields implemented.
|
||||
# Add a blank field
|
||||
reg.addField(field)
|
||||
|
||||
iq.reply().set_payload(reg.xml)
|
||||
iq.send()
|
||||
reply = iq.reply()
|
||||
reply.set_payload(reg.xml)
|
||||
reply.send()
|
||||
|
||||
def _sendError(self, iq, code, error_type, name, text=''):
|
||||
iq.reply().set_payload(iq['register'].xml)
|
||||
iq.error()
|
||||
iq['error']['code'] = code
|
||||
iq['error']['type'] = error_type
|
||||
iq['error']['condition'] = name
|
||||
iq['error']['text'] = text
|
||||
iq.send()
|
||||
reply = iq.reply()
|
||||
reply.set_payload(iq['register'].xml)
|
||||
reply.error()
|
||||
reply['error']['code'] = code
|
||||
reply['error']['type'] = error_type
|
||||
reply['error']['condition'] = name
|
||||
reply['error']['text'] = text
|
||||
reply.send()
|
||||
|
47
docs/differences.rst
Normal file
47
docs/differences.rst
Normal file
@ -0,0 +1,47 @@
|
||||
.. _differences:
|
||||
|
||||
Differences from SleekXMPP
|
||||
==========================
|
||||
|
||||
**Python 3.4+ only**
|
||||
slixmpp will only work on python 3.4 and above.
|
||||
|
||||
**Stanza copies**
|
||||
The same stanza object is given through all the handlers; a handler that
|
||||
edits the stanza object should make its own copy.
|
||||
|
||||
**Replies**
|
||||
Because stanzas are not copied anymore,
|
||||
:meth:`Stanza.reply() <.StanzaBase.reply>` calls
|
||||
(for :class:`IQs <.Iq>`, :class:`Messages <.Message>`, etc)
|
||||
now return a new object instead of editing the stanza object
|
||||
in-place.
|
||||
|
||||
**Block and threaded arguments**
|
||||
All the functions that had a ``threaded=`` or ``block=`` argument
|
||||
do not have it anymore. Also, :meth:`.Iq.send` **does not block
|
||||
anymore**.
|
||||
|
||||
**Coroutine facilities**
|
||||
**See** :ref:`using_asyncio`
|
||||
|
||||
If an event handler is a coroutine, it will be called asynchronously
|
||||
in the event loop instead of inside the event caller.
|
||||
|
||||
A CoroutineCallback class has been added to create coroutine stream
|
||||
handlers, which will be also handled in the event loop.
|
||||
|
||||
The :class:`~.slixmpp.stanza.Iq` object’s :meth:`~.slixmpp.stanza.Iq.send`
|
||||
method now takes a *coroutine* parameter which, if set to ``True``,
|
||||
will return a coroutine which will (asyncio-)block until the reply
|
||||
is received.
|
||||
|
||||
Many plugins (WIP) calls which retrieve information also accept this
|
||||
``coroutine`` parameter.
|
||||
|
||||
**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.
|
@ -7,19 +7,11 @@ Create and Run a Server Component
|
||||
.. note::
|
||||
|
||||
If you have any issues working through this quickstart guide
|
||||
or the other tutorials here, please either send a message to the
|
||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
||||
or join the chat room at `sleek@conference.jabber.org
|
||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
||||
join the chat room at `slixmpp@muc.poez.io
|
||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||
|
||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip``
|
||||
or ``easy_install``.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install slixmpp # Or: easy_install slixmpp
|
||||
|
||||
with `Git <http://git.poez.io/slixmpp>`_.
|
||||
|
||||
Many XMPP applications eventually graduate to requiring to run as a server
|
||||
component in order to meet scalability requirements. To demonstrate how to
|
||||
|
@ -7,19 +7,11 @@ Slixmpp Quickstart - Echo Bot
|
||||
.. note::
|
||||
|
||||
If you have any issues working through this quickstart guide
|
||||
or the other tutorials here, please either send a message to the
|
||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
||||
or join the chat room at `sleek@conference.jabber.org
|
||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
||||
join the chat room at `slixmpp@muc.poez.io
|
||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||
|
||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip``
|
||||
or ``easy_install``.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install slixmpp # Or: easy_install slixmpp
|
||||
|
||||
with `Git <http://git.poez.io/slixmpp>`_.
|
||||
|
||||
As a basic starting project, we will create an echo bot which will reply to any
|
||||
messages sent to it. We will also go through adding some basic command line configuration
|
||||
@ -44,6 +36,7 @@ To get started, here is a brief outline of the structure that the final project
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
import logging
|
||||
import getpass
|
||||
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'''
|
||||
|
||||
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
|
||||
--------------------------
|
||||
|
||||
@ -313,9 +288,9 @@ the ``EchoBot.__init__`` method instead.
|
||||
xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||
|
||||
Now we're ready to connect and begin echoing messages. If you have the package
|
||||
``dnspython`` installed, then the :meth:`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
|
||||
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
|
||||
to :meth:`slixmpp.clientxmpp.ClientXMPP`.
|
||||
|
||||
@ -330,22 +305,6 @@ to :meth:`slixmpp.clientxmpp.ClientXMPP`.
|
||||
else:
|
||||
print('Unable to connect')
|
||||
|
||||
.. note::
|
||||
|
||||
For Google Talk users withouth ``dnspython`` installed, the above code
|
||||
should look like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# .. option parsing & echo bot configuration
|
||||
|
||||
if xmpp.connect(('talk.google.com', 5222)):
|
||||
xmpp.process(block=True)
|
||||
else:
|
||||
print('Unable to connect')
|
||||
|
||||
To begin responding to messages, you'll see we called :meth:`slixmpp.basexmpp.BaseXMPP.process`
|
||||
which will start the event handling, send queue, and XML reader threads. It will also call
|
||||
the :meth:`slixmpp.plugins.base.BasePlugin.post_init` method on all registered plugins. By
|
||||
|
@ -7,19 +7,11 @@ Mulit-User Chat (MUC) Bot
|
||||
.. note::
|
||||
|
||||
If you have any issues working through this quickstart guide
|
||||
or the other tutorials here, please either send a message to the
|
||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
||||
or join the chat room at `sleek@conference.jabber.org
|
||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
||||
join the chat room at `slixmpp@muc.poez.io
|
||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||
|
||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||
from `Github <http://github.com/fritzy/Slixmpp>`_, or installing it using ``pip``
|
||||
or ``easy_install``.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install slixmpp # Or: easy_install slixmpp
|
||||
|
||||
from `Git <http://git.poez.io/slixmpp>`_.
|
||||
|
||||
Now that you've got the basic gist of using Slixmpp by following the
|
||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
||||
|
@ -7,10 +7,8 @@ Enable HTTP Proxy Support
|
||||
.. note::
|
||||
|
||||
If you have any issues working through this quickstart guide
|
||||
or the other tutorials here, please either send a message to the
|
||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
||||
or join the chat room at `sleek@conference.jabber.org
|
||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
||||
join the chat room at `slixmpp@muc.poez.io
|
||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||
|
||||
In some instances, you may wish to route XMPP traffic through
|
||||
an HTTP proxy, probably to get around restrictive firewalls.
|
||||
|
@ -4,10 +4,8 @@ Sign in, Send a Message, and Disconnect
|
||||
.. note::
|
||||
|
||||
If you have any issues working through this quickstart guide
|
||||
or the other tutorials here, please either send a message to the
|
||||
`mailing list <http://groups.google.com/group/slixmpp-discussion>`_
|
||||
or join the chat room at `sleek@conference.jabber.org
|
||||
<xmpp:sleek@conference.jabber.org?join>`_.
|
||||
join the chat room at `slixmpp@muc.poez.io
|
||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||
|
||||
A common use case for Slixmpp is to send one-off messages from
|
||||
time to time. For example, one use case could be sending out a notice when
|
||||
|
@ -9,21 +9,20 @@ Glossary
|
||||
stream handler
|
||||
A callback function that accepts stanza objects pulled directly
|
||||
from the XML stream. A stream handler is encapsulated in a
|
||||
object that includes a :term:`Matcher` object, and which provides
|
||||
additional semantics. For example, the ``Waiter`` handler wrapper
|
||||
blocks thread execution until a matching stanza is received.
|
||||
object that includes a :class:`Matcher <.MatcherBase>` object, and
|
||||
which provides additional semantics. For example, the
|
||||
:class:`.Waiter` handler wrapper blocks thread execution until a
|
||||
matching stanza is received.
|
||||
|
||||
event handler
|
||||
A callback function that responds to events raised by
|
||||
``XMLStream.event``. An event handler may be marked as
|
||||
threaded, allowing it to execute outside of the main processing
|
||||
loop.
|
||||
:meth:`.XMLStream.event`.
|
||||
|
||||
stanza object
|
||||
Informally may refer both to classes which extend ``ElementBase``
|
||||
or ``StanzaBase``, and to objects of such classes.
|
||||
Informally may refer both to classes which extend :class:`.ElementBase`
|
||||
or :class:`.StanzaBase`, and to objects of such classes.
|
||||
|
||||
A stanza object is a wrapper for an XML object which exposes ``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.
|
||||
|
||||
stanza plugin
|
||||
|
@ -3,38 +3,25 @@ Slixmpp
|
||||
|
||||
.. 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
|
||||
<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>`_
|
||||
An XMPP chat room is available for discussing and getting help with slixmpp.
|
||||
|
||||
**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+,
|
||||
and is featured in examples in
|
||||
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_
|
||||
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
|
||||
here from reading the Definitive Guide, please see the notes on updating
|
||||
the examples to the latest version of Slixmpp.
|
||||
.. note::
|
||||
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
|
||||
which goal is to use asyncio instead of threads to handle networking. See
|
||||
:ref:`differences`.
|
||||
|
||||
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.4+,
|
||||
|
||||
Slixmpp's design goals and philosphy are:
|
||||
|
||||
@ -59,11 +46,13 @@ Slixmpp's design goals and philosphy are:
|
||||
sensible defaults and appropriate abstractions. XML can be ugly to work
|
||||
with, but it doesn't have to be that way.
|
||||
|
||||
|
||||
Here's your first Slixmpp Bot:
|
||||
--------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from slixmpp import ClientXMPP
|
||||
@ -85,27 +74,13 @@ Here's your first Slixmpp Bot:
|
||||
# Here's how to access plugins once you've registered them:
|
||||
# self['xep_0030'].add_feature('echo_demo')
|
||||
|
||||
# If you are working with an OpenFire server, you will
|
||||
# need to use a different SSL version:
|
||||
# import ssl
|
||||
# self.ssl_version = ssl.PROTOCOL_SSLv3
|
||||
|
||||
def session_start(self, event):
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
|
||||
# Most get_*/set_* methods from plugins use Iq stanzas, which
|
||||
# can generate IqError and IqTimeout exceptions
|
||||
#
|
||||
# try:
|
||||
# self.get_roster()
|
||||
# except IqError as err:
|
||||
# logging.error('There was an error getting the roster')
|
||||
# logging.error(err.iq['error']['condition'])
|
||||
# self.disconnect()
|
||||
# except IqTimeout:
|
||||
# logging.error('Server is taking too long to respond')
|
||||
# self.disconnect()
|
||||
# are sent asynchronously. You can almost always provide a
|
||||
# callback that will be executed when the reply is received.
|
||||
|
||||
def message(self, msg):
|
||||
if msg['type'] in ('chat', 'normal'):
|
||||
@ -121,9 +96,18 @@ Here's your first Slixmpp Bot:
|
||||
|
||||
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
||||
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)
|
||||
-------------------------------
|
||||
@ -145,7 +129,6 @@ Tutorials, FAQs, and How To Guides
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
faq
|
||||
xeps
|
||||
xmpp_tdg
|
||||
howto/stanzas
|
||||
@ -184,9 +167,7 @@ API Reference
|
||||
api/xmlstream/handler
|
||||
api/xmlstream/matcher
|
||||
api/xmlstream/xmlstream
|
||||
api/xmlstream/scheduler
|
||||
api/xmlstream/tostring
|
||||
api/xmlstream/filesocket
|
||||
|
||||
Core Stanzas
|
||||
~~~~~~~~~~~~
|
||||
@ -197,8 +178,6 @@ Core Stanzas
|
||||
api/stanza/message
|
||||
api/stanza/presence
|
||||
api/stanza/iq
|
||||
api/stanza/error
|
||||
api/stanza/stream_error
|
||||
|
||||
Plugins
|
||||
~~~~~~~
|
||||
@ -220,8 +199,14 @@ Additional Info
|
||||
* :ref:`modindex`
|
||||
* :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>`_
|
||||
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
|
||||
|
125
docs/using_asyncio.rst
Normal file
125
docs/using_asyncio.rst
Normal file
@ -0,0 +1,125 @@
|
||||
.. _using_asyncio:
|
||||
|
||||
=============
|
||||
Using asyncio
|
||||
=============
|
||||
|
||||
Block on IQ sending
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:meth:`.Iq.send` now accepts a ``coroutine`` parameter which, if ``True``,
|
||||
will return a coroutine waiting for the IQ reply to be received.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
result = yield from iq.send(coroutine=True)
|
||||
|
||||
XEP plugin integration
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Many XEP plugins have been modified to accept this ``coroutine`` parameter as
|
||||
well, so you can do things like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
iq_info = yield from self.xmpp['xep_0030'].get_info(jid, coroutine=True)
|
||||
|
||||
|
||||
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).
|
||||
|
||||
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 event: 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'],
|
||||
coroutine=True)
|
||||
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.
|
||||
"""
|
||||
|
||||
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 unittest
|
||||
import distutils.core
|
||||
import datetime
|
||||
|
||||
from glob import glob
|
||||
from os.path import splitext, basename, join as pjoin
|
||||
from os.path import basename, join as pjoin
|
||||
from argparse import ArgumentParser
|
||||
from urllib import urlopen
|
||||
from getpass import getpass
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.plugins.xep_0323.device import Device
|
||||
@ -186,5 +178,5 @@ if __name__ == '__main__':
|
||||
logging.debug("ready ending")
|
||||
|
||||
else:
|
||||
print "noopp didn't happen"
|
||||
print("noopp didn't happen")
|
||||
|
||||
|
@ -15,6 +15,7 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp.xmlstream.asyncio import asyncio
|
||||
|
||||
|
||||
class Disco(slixmpp.ClientXMPP):
|
||||
@ -53,6 +54,7 @@ class Disco(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
@ -79,17 +81,17 @@ class Disco(slixmpp.ClientXMPP):
|
||||
# received. Non-blocking options would be to listen
|
||||
# for the disco_info event, or passing a handler
|
||||
# function using the callback parameter.
|
||||
info = self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
block=True)
|
||||
elif self.get in self.items_types:
|
||||
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
coroutine=True)
|
||||
if self.get in self.items_types:
|
||||
# The same applies from above. Listen for the
|
||||
# disco_items event or pass a callback function
|
||||
# if you need to process a non-blocking request.
|
||||
items = self['xep_0030'].get_items(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
block=True)
|
||||
else:
|
||||
items = yield from self['xep_0030'].get_items(jid=self.target_jid,
|
||||
node=self.target_node,
|
||||
coroutine=True)
|
||||
if self.get not in self.info_types and self.get not in self.items_types:
|
||||
logging.error("Invalid disco request type.")
|
||||
return
|
||||
except IqError as e:
|
||||
@ -143,7 +145,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
parser.add_argument("query", choices=["all", "info", "items", "identities", "features"])
|
||||
parser.add_argument("target-jid")
|
||||
parser.add_argument("target_jid")
|
||||
parser.add_argument("node", nargs='?')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
@ -11,11 +11,11 @@
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
import threading
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp import asyncio
|
||||
|
||||
|
||||
FILE_TYPES = {
|
||||
@ -40,8 +40,14 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
self.add_event_handler('avatar_metadata_publish', self.on_avatar)
|
||||
|
||||
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):
|
||||
"""
|
||||
Process the session_start event.
|
||||
@ -56,16 +62,20 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
data.
|
||||
"""
|
||||
self.send_presence()
|
||||
self.get_roster()
|
||||
self.get_roster(callback=self.roster_received_cb)
|
||||
|
||||
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()
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_vcard_avatar(self, pres):
|
||||
print("Received vCard avatar update from %s" % pres['from'].bare)
|
||||
try:
|
||||
result = self['xep_0054'].get_vcard(pres['from'], cached=True)
|
||||
result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
|
||||
coroutine=True, timeout=5)
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % pres['from'])
|
||||
return
|
||||
@ -76,16 +86,18 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
pres['from'].bare,
|
||||
pres['vcard_temp_update']['photo'],
|
||||
filetype)
|
||||
with open(filename, 'w+') as img:
|
||||
with open(filename, 'wb+') as img:
|
||||
img.write(avatar['BINVAL'])
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_avatar(self, msg):
|
||||
print("Received avatar update from %s" % msg['from'])
|
||||
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
||||
for info in metadata['items']:
|
||||
if not info['url']:
|
||||
try:
|
||||
result = self['xep_0084'].retrieve_avatar(msg['from'], info['id'])
|
||||
result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
|
||||
coroutine=True, timeout=5)
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % msg['from'])
|
||||
return
|
||||
@ -94,7 +106,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
|
||||
filetype = FILE_TYPES.get(metadata['type'], 'png')
|
||||
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'])
|
||||
else:
|
||||
# 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.
|
||||
"""
|
||||
self.received.add(pres['from'].bare)
|
||||
print((len(self.received), len(self.client_roster.keys())))
|
||||
if len(self.received) >= len(self.client_roster.keys()):
|
||||
self.presences_received.set()
|
||||
else:
|
||||
|
@ -103,5 +103,5 @@ def on_session2(event):
|
||||
new_xmpp.disconnect()
|
||||
new_xmpp.add_event_handler('session_start', on_session2)
|
||||
|
||||
if new_xmpp.connect():
|
||||
new_xmpp.process(block=True)
|
||||
new_xmpp.connect()
|
||||
new_xmpp.process()
|
||||
|
@ -12,6 +12,8 @@
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp import asyncio
|
||||
|
||||
import slixmpp
|
||||
|
||||
@ -36,6 +38,7 @@ class PingTest(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
@ -53,8 +56,8 @@ class PingTest(slixmpp.ClientXMPP):
|
||||
self.get_roster()
|
||||
|
||||
try:
|
||||
rtt = self['xep_0199'].ping(self.pingjid,
|
||||
timeout=10)
|
||||
rtt = yield from self['xep_0199'].ping(self.pingjid,
|
||||
timeout=10)
|
||||
logging.info("Success! RTT: %s", rtt)
|
||||
except IqError as e:
|
||||
logging.info("Error pinging %s: %s",
|
||||
@ -78,8 +81,7 @@ if __name__ == '__main__':
|
||||
action="store_const", dest="loglevel",
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
parser.add_argument("-t", "--pingto", help="set jid to ping",
|
||||
action="store", type="string", dest="pingjid",
|
||||
default=None)
|
||||
dest="pingjid", default=None)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
|
@ -7,7 +7,13 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.xmlstream import ET, tostring
|
||||
from slixmpp.xmlstream.asyncio import asyncio
|
||||
|
||||
def make_callback():
|
||||
future = asyncio.Future()
|
||||
def callback(result):
|
||||
future.set_result(result)
|
||||
return future, callback
|
||||
|
||||
class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
@ -41,8 +47,10 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
self.disconnect()
|
||||
|
||||
def nodes(self):
|
||||
future, callback = make_callback()
|
||||
try:
|
||||
result = self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||
self['xep_0060'].get_nodes(self.pubsub_server, self.node, callback=callback)
|
||||
result = yield from future
|
||||
for item in result['disco_items']['items']:
|
||||
print(' - %s' % str(item))
|
||||
except:
|
||||
@ -63,16 +71,20 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
def publish(self):
|
||||
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
||||
future, callback = make_callback()
|
||||
try:
|
||||
result = self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||
self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload, callback=callback)
|
||||
result = yield from future
|
||||
id = result['pubsub']['publish']['item']['id']
|
||||
print('Published at item id: %s' % id)
|
||||
except:
|
||||
logging.error('Could not publish to: %s' % self.node)
|
||||
|
||||
def get(self):
|
||||
future, callback = make_callback()
|
||||
try:
|
||||
result = self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||
self['xep_0060'].get_item(self.pubsub_server, self.node, self.data, callback=callback)
|
||||
result = yield from future
|
||||
for item in result['pubsub']['items']['substanzas']:
|
||||
print('Retrieved item %s: %s' % (item['id'], tostring(item['payload'])))
|
||||
except:
|
||||
@ -80,28 +92,28 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
def retract(self):
|
||||
try:
|
||||
result = self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
print('Retracted item %s from node %s' % (self.data, self.node))
|
||||
except:
|
||||
logging.error('Could not retract item %s from node %s' % (self.data, self.node))
|
||||
|
||||
def purge(self):
|
||||
try:
|
||||
result = self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
print('Purged all items from node %s' % self.node)
|
||||
except:
|
||||
logging.error('Could not purge items from node %s' % self.node)
|
||||
|
||||
def subscribe(self):
|
||||
try:
|
||||
result = self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
print('Subscribed %s to node %s' % (self.boundjid.bare, self.node))
|
||||
except:
|
||||
logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node))
|
||||
|
||||
def unsubscribe(self):
|
||||
try:
|
||||
result = self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node))
|
||||
except:
|
||||
logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node))
|
||||
|
@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
||||
resp['register']['password'] = self.password
|
||||
|
||||
try:
|
||||
resp.send()
|
||||
yield from resp.send_coroutine()
|
||||
logging.info("Account created for %s!" % self.boundjid)
|
||||
except IqError as e:
|
||||
logging.error("Could not register account: %s" %
|
||||
|
@ -11,11 +11,11 @@
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
import threading
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp.xmlstream.asyncio import asyncio
|
||||
|
||||
|
||||
class RosterBrowser(slixmpp.ClientXMPP):
|
||||
@ -36,8 +36,9 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
self.add_event_handler("changed_status", self.wait_for_presences)
|
||||
|
||||
self.received = set()
|
||||
self.presences_received = threading.Event()
|
||||
self.presences_received = asyncio.Event()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
@ -51,8 +52,12 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
future = asyncio.Future()
|
||||
def callback(result):
|
||||
future.set_result(None)
|
||||
try:
|
||||
self.get_roster()
|
||||
self.get_roster(callback=callback)
|
||||
yield from future
|
||||
except IqError as err:
|
||||
print('Error: %' % err.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
@ -61,7 +66,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
|
||||
|
||||
print('Waiting for presence updates...\n')
|
||||
self.presences_received.wait(5)
|
||||
yield from asyncio.sleep(10)
|
||||
|
||||
print('Roster for %s' % self.boundjid.bare)
|
||||
groups = self.client_roster.groups()
|
||||
|
@ -20,7 +20,7 @@ class Boomerang(Endpoint):
|
||||
|
||||
@remote
|
||||
def throw(self):
|
||||
print "Duck!"
|
||||
print("Duck!")
|
||||
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
|
||||
from slixmpp import asyncio
|
||||
|
||||
class AvatarSetter(slixmpp.ClientXMPP):
|
||||
|
||||
@ -33,6 +33,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
|
||||
self.filepath = filepath
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
@ -51,7 +52,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
|
||||
avatar_file = None
|
||||
try:
|
||||
avatar_file = open(os.path.expanduser(self.filepath))
|
||||
avatar_file = open(os.path.expanduser(self.filepath), 'rb')
|
||||
except IOError:
|
||||
print('Could not find file: %s' % self.filepath)
|
||||
return self.disconnect()
|
||||
@ -65,32 +66,31 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
avatar_file.close()
|
||||
|
||||
used_xep84 = False
|
||||
try:
|
||||
print('Publish XEP-0084 avatar data')
|
||||
self['xep_0084'].publish_avatar(avatar)
|
||||
used_xep84 = True
|
||||
except XMPPError:
|
||||
print('Could not publish XEP-0084 avatar')
|
||||
|
||||
try:
|
||||
print('Update vCard with avatar')
|
||||
self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||
except XMPPError:
|
||||
print('Publish XEP-0084 avatar data')
|
||||
result = yield from self['xep_0084'].publish_avatar(avatar, coroutine=True)
|
||||
if isinstance(result, XMPPError):
|
||||
print('Could not publish XEP-0084 avatar')
|
||||
else:
|
||||
used_xep84 = True
|
||||
|
||||
print('Update vCard with avatar')
|
||||
result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type, coroutine=True)
|
||||
if isinstance(result, XMPPError):
|
||||
print('Could not set vCard avatar')
|
||||
|
||||
if used_xep84:
|
||||
try:
|
||||
print('Advertise XEP-0084 avatar metadata')
|
||||
self['xep_0084'].publish_avatar_metadata([
|
||||
{'id': avatar_id,
|
||||
'type': avatar_type,
|
||||
'bytes': avatar_bytes}
|
||||
# We could advertise multiple avatars to provide
|
||||
# options in image type, source (HTTP vs pubsub),
|
||||
# size, etc.
|
||||
# {'id': ....}
|
||||
])
|
||||
except XMPPError:
|
||||
print('Advertise XEP-0084 avatar metadata')
|
||||
result = yield from self['xep_0084'].publish_avatar_metadata([
|
||||
{'id': avatar_id,
|
||||
'type': avatar_type,
|
||||
'bytes': avatar_bytes}
|
||||
# We could advertise multiple avatars to provide
|
||||
# options in image type, source (HTTP vs pubsub),
|
||||
# size, etc.
|
||||
# {'id': ....}
|
||||
], coroutine=True)
|
||||
if isinstance(result, XMPPError):
|
||||
print('Could not publish XEP-0084 metadata')
|
||||
|
||||
print('Wait for presence updates to propagate...')
|
||||
|
@ -32,6 +32,9 @@ class Iq(RootStanza):
|
||||
as a carrier stanza for an application-specific protocol instead.
|
||||
|
||||
Example <iq> Stanzas:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<iq to="user@example.com" type="get" id="314">
|
||||
<query xmlns="http://jabber.org/protocol/disco#items" />
|
||||
</iq>
|
||||
@ -47,20 +50,9 @@ class Iq(RootStanza):
|
||||
</iq>
|
||||
|
||||
Stanza Interface:
|
||||
query -- The namespace of the <query> element if one exists.
|
||||
|
||||
- **query**: The namespace of the <query> element if one exists.
|
||||
Attributes:
|
||||
types -- May be one of: get, set, result, or error.
|
||||
|
||||
Methods:
|
||||
__init__ -- Overrides StanzaBase.__init__.
|
||||
unhandled -- Send error if there are no handlers.
|
||||
set_payload -- Overrides StanzaBase.set_payload.
|
||||
set_query -- Add or modify a <query> element.
|
||||
get_query -- Return the namespace of the <query> element.
|
||||
del_query -- Remove the <query> element.
|
||||
reply -- Overrides StanzaBase.reply
|
||||
send -- Overrides StanzaBase.send
|
||||
- **types**: May be one of: get, set, result, or error.
|
||||
"""
|
||||
|
||||
namespace = 'jabber:client'
|
||||
@ -98,8 +90,9 @@ class Iq(RootStanza):
|
||||
"""
|
||||
Set the XML contents of the <iq> stanza.
|
||||
|
||||
Arguments:
|
||||
value -- An XML object to use as the <iq> stanza's contents
|
||||
:param value: An XML object or a list of XML objects to use as the <iq>
|
||||
stanza's contents
|
||||
:type value: list or XML object
|
||||
"""
|
||||
self.clear()
|
||||
StanzaBase.set_payload(self, value)
|
||||
@ -111,8 +104,7 @@ class Iq(RootStanza):
|
||||
|
||||
Query elements are differentiated by their namespace.
|
||||
|
||||
Arguments:
|
||||
value -- The namespace of the <query> element.
|
||||
:param str value: The namespace of the <query> element.
|
||||
"""
|
||||
query = self.xml.find("{%s}query" % value)
|
||||
if query is None and value:
|
||||
@ -126,7 +118,9 @@ class Iq(RootStanza):
|
||||
return self
|
||||
|
||||
def get_query(self):
|
||||
"""Return the namespace of the <query> element."""
|
||||
"""Return the namespace of the <query> element.
|
||||
|
||||
:rtype: str"""
|
||||
for child in self.xml:
|
||||
if child.tag.endswith('query'):
|
||||
ns = child.tag.split('}')[0]
|
||||
@ -144,16 +138,15 @@ class Iq(RootStanza):
|
||||
|
||||
def reply(self, clear=True):
|
||||
"""
|
||||
Send a reply <iq> stanza.
|
||||
Create a new <iq> stanza replying to ``self``.
|
||||
|
||||
Overrides StanzaBase.reply
|
||||
|
||||
Sets the 'type' to 'result' in addition to the default
|
||||
StanzaBase.reply behavior.
|
||||
|
||||
Arguments:
|
||||
clear -- Indicates if existing content should be
|
||||
removed before replying. Defaults to True.
|
||||
:param bool clear: Indicates if existing content should be
|
||||
removed before replying. Defaults to True.
|
||||
"""
|
||||
new_iq = StanzaBase.reply(self, clear=clear)
|
||||
new_iq['type'] = 'result'
|
||||
@ -168,10 +161,8 @@ class Iq(RootStanza):
|
||||
|
||||
Overrides StanzaBase.send
|
||||
|
||||
Arguments:
|
||||
|
||||
timeout -- The length of time (in seconds) to wait for a
|
||||
response before an IqTimeout is raised
|
||||
:param int timeout: The length of time (in seconds) to wait for a
|
||||
response before an IqTimeout is raised
|
||||
"""
|
||||
|
||||
future = asyncio.Future()
|
||||
@ -216,20 +207,19 @@ class Iq(RootStanza):
|
||||
|
||||
Overrides StanzaBase.send
|
||||
|
||||
Arguments:
|
||||
|
||||
callback -- Optional reference to a stream handler
|
||||
function. Will be executed when a reply stanza is
|
||||
received.
|
||||
timeout -- The length of time (in seconds) to wait for a
|
||||
response before the timeout_callback is called,
|
||||
instead of the regular callback
|
||||
timeout_callback -- Optional reference to a stream handler
|
||||
function. Will be executed when the timeout expires
|
||||
before a response has been received with the
|
||||
originally-sent IQ stanza.
|
||||
coroutine -- This function will return a coroutine if this argument
|
||||
is True.
|
||||
:param function callback: Optional reference to a stream handler
|
||||
function. Will be executed when a reply
|
||||
stanza is received.
|
||||
:param int timeout: The length of time (in seconds) to wait for a
|
||||
response before the timeout_callback is called,
|
||||
instead of the regular callback
|
||||
:param function timeout_callback: Optional reference to a stream handler
|
||||
function. Will be executed when the
|
||||
timeout expires before a response has
|
||||
been received for the originally-sent
|
||||
IQ stanza.
|
||||
:param bool coroutine: This function will return a coroutine if this
|
||||
argument is True.
|
||||
"""
|
||||
if self.stream.session_bind_event.is_set():
|
||||
matcher = MatchIDSender({
|
||||
|
@ -23,6 +23,9 @@ class Message(RootStanza):
|
||||
an error response.
|
||||
|
||||
Example <message> stanzas:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<message to="user1@example.com" from="user2@example.com">
|
||||
<body>Hi!</body>
|
||||
</message>
|
||||
@ -32,26 +35,13 @@ class Message(RootStanza):
|
||||
</message>
|
||||
|
||||
Stanza Interface:
|
||||
body -- The main contents of the message.
|
||||
subject -- An optional description of the message's contents.
|
||||
mucroom -- (Read-only) The name of the MUC room that sent the message.
|
||||
mucnick -- (Read-only) The MUC nickname of message's sender.
|
||||
- **body**: The main contents of the message.
|
||||
- **subject**: An optional description of the message's contents.
|
||||
- **mucroom**: (Read-only) The name of the MUC room that sent the message.
|
||||
- **mucnick**: (Read-only) The MUC nickname of message's sender.
|
||||
|
||||
Attributes:
|
||||
types -- May be one of: normal, chat, headline, groupchat, or error.
|
||||
|
||||
Methods:
|
||||
setup -- Overrides StanzaBase.setup.
|
||||
chat -- Set the message type to 'chat'.
|
||||
normal -- Set the message type to 'normal'.
|
||||
reply -- Overrides StanzaBase.reply
|
||||
get_type -- Overrides StanzaBase interface
|
||||
get_mucroom -- Return the name of the MUC room of the message.
|
||||
set_mucroom -- Dummy method to prevent assignment.
|
||||
del_mucroom -- Dummy method to prevent deletion.
|
||||
get_mucnick -- Return the MUC nickname of the message's sender.
|
||||
set_mucnick -- Dummy method to prevent assignment.
|
||||
del_mucnick -- Dummy method to prevent deletion.
|
||||
- **types**: May be one of: normal, chat, headline, groupchat, or error.
|
||||
"""
|
||||
|
||||
name = 'message'
|
||||
@ -81,18 +71,25 @@ class Message(RootStanza):
|
||||
Overrides default stanza interface behavior.
|
||||
|
||||
Returns 'normal' if no type attribute is present.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self._get_attr('type', 'normal')
|
||||
|
||||
def get_parent_thread(self):
|
||||
"""Return the message thread's parent thread."""
|
||||
"""Return the message thread's parent thread.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
thread = self.xml.find('{%s}thread' % self.namespace)
|
||||
if thread is not None:
|
||||
return thread.attrib.get('parent', '')
|
||||
return ''
|
||||
|
||||
def set_parent_thread(self, value):
|
||||
"""Add or change the message thread's parent thread."""
|
||||
"""Add or change the message thread's parent thread.
|
||||
|
||||
:param str value: identifier of the thread"""
|
||||
thread = self.xml.find('{%s}thread' % self.namespace)
|
||||
if value:
|
||||
if thread is None:
|
||||
@ -128,10 +125,11 @@ class Message(RootStanza):
|
||||
Sets proper 'to' attribute if the message is from a MUC, and
|
||||
adds a message body if one is given.
|
||||
|
||||
Arguments:
|
||||
body -- Optional text content for the message.
|
||||
clear -- Indicates if existing content should be removed
|
||||
before replying. Defaults to True.
|
||||
:param str body: Optional text content for the message.
|
||||
:param bool clear: Indicates if existing content should be removed
|
||||
before replying. Defaults to True.
|
||||
|
||||
:rtype: :class:`~.Message`
|
||||
"""
|
||||
new_message = StanzaBase.reply(self, clear)
|
||||
|
||||
@ -152,6 +150,8 @@ class Message(RootStanza):
|
||||
Return the name of the MUC room where the message originated.
|
||||
|
||||
Read-only stanza interface.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
if self['type'] == 'groupchat':
|
||||
return self['from'].bare
|
||||
@ -163,6 +163,8 @@ class Message(RootStanza):
|
||||
Return the nickname of the MUC user that sent the message.
|
||||
|
||||
Read-only stanza interface.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
if self['type'] == 'groupchat':
|
||||
return self['from'].resource
|
||||
|
@ -27,6 +27,9 @@ class Presence(RootStanza):
|
||||
to help keep the network running smoothly.
|
||||
|
||||
Example <presence> stanzas:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<presence />
|
||||
|
||||
<presence from="user@example.com">
|
||||
@ -40,24 +43,14 @@ class Presence(RootStanza):
|
||||
<presence to="user@otherhost.com" type="subscribe" />
|
||||
|
||||
Stanza Interface:
|
||||
priority -- A value used by servers to determine message routing.
|
||||
show -- The type of status, such as away or available for chat.
|
||||
status -- Custom, human readable status message.
|
||||
- **priority**: A value used by servers to determine message routing.
|
||||
- **show**: The type of status, such as away or available for chat.
|
||||
- **status**: Custom, human readable status message.
|
||||
|
||||
Attributes:
|
||||
types -- One of: available, unavailable, error, probe,
|
||||
subscribe, subscribed, unsubscribe,
|
||||
and unsubscribed.
|
||||
showtypes -- One of: away, chat, dnd, and xa.
|
||||
|
||||
Methods:
|
||||
setup -- Overrides StanzaBase.setup
|
||||
reply -- Overrides StanzaBase.reply
|
||||
set_show -- Set the value of the <show> element.
|
||||
get_type -- Get the value of the type attribute or <show> element.
|
||||
set_type -- Set the value of the type attribute or <show> element.
|
||||
get_priority -- Get the value of the <priority> element.
|
||||
set_priority -- Set the value of the <priority> element.
|
||||
- **types**: One of: available, unavailable, error, probe,
|
||||
subscribe, subscribed, unsubscribe, and unsubscribed.
|
||||
- **showtypes**: One of: away, chat, dnd, and xa.
|
||||
"""
|
||||
|
||||
name = 'presence'
|
||||
@ -93,8 +86,7 @@ class Presence(RootStanza):
|
||||
"""
|
||||
Set the value of the <show> element.
|
||||
|
||||
Arguments:
|
||||
show -- Must be one of: away, chat, dnd, or xa.
|
||||
:param str show: Must be one of: away, chat, dnd, or xa.
|
||||
"""
|
||||
if show is None:
|
||||
self._del_sub('show')
|
||||
@ -119,8 +111,7 @@ class Presence(RootStanza):
|
||||
Set the type attribute's value, and the <show> element
|
||||
if applicable.
|
||||
|
||||
Arguments:
|
||||
value -- Must be in either self.types or self.showtypes.
|
||||
:param str value: Must be in either self.types or self.showtypes.
|
||||
"""
|
||||
if value in self.types:
|
||||
self['show'] = None
|
||||
@ -146,14 +137,15 @@ class Presence(RootStanza):
|
||||
Bot clients should typically use a priority of 0 if the same
|
||||
JID is used elsewhere by a human-interacting client.
|
||||
|
||||
Arguments:
|
||||
value -- An integer value greater than or equal to 0.
|
||||
:param int value: An integer value greater than or equal to 0.
|
||||
"""
|
||||
self._set_sub_text('priority', text=str(value))
|
||||
|
||||
def get_priority(self):
|
||||
"""
|
||||
Return the value of the <presence> element as an integer.
|
||||
|
||||
:rtype: int
|
||||
"""
|
||||
p = self._get_sub_text('priority')
|
||||
if not p:
|
||||
@ -166,13 +158,12 @@ class Presence(RootStanza):
|
||||
|
||||
def reply(self, clear=True):
|
||||
"""
|
||||
Set the appropriate presence reply type.
|
||||
Create a new reply <presence/> stanza from ``self``.
|
||||
|
||||
Overrides StanzaBase.reply.
|
||||
|
||||
Arguments:
|
||||
clear -- Indicates if the stanza contents should be removed
|
||||
before replying. Defaults to True.
|
||||
:param bool clear: Indicates if the stanza contents should be removed
|
||||
before replying. Defaults to True.
|
||||
"""
|
||||
new_presence = StanzaBase.reply(self, clear)
|
||||
if self['type'] == 'unsubscribe':
|
||||
|
@ -1474,7 +1474,7 @@ class StanzaBase(ElementBase):
|
||||
|
||||
Only type values contained in :attr:`types` are accepted.
|
||||
|
||||
:param string value: One of the values contained in :attr:`types`
|
||||
:param str value: One of the values contained in :attr:`types`
|
||||
"""
|
||||
if value in self.types:
|
||||
self.xml.attrib['type'] = value
|
||||
@ -1499,8 +1499,8 @@ class StanzaBase(ElementBase):
|
||||
def set_from(self, value):
|
||||
"""Set the 'from' attribute of the stanza.
|
||||
|
||||
Arguments:
|
||||
from -- A string or JID object representing the sender's JID.
|
||||
:param from: A string or JID object representing the sender's JID.
|
||||
:type from: str or :class:`.JID`
|
||||
"""
|
||||
return self._set_attr('from', str(value))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user