Compare commits

..

1 Commits

Author SHA1 Message Date
mathieui
4512248901 Merge branch 'develop' of https://github.com/fritzy/SleekXMPP into sleek-merge
Conflicts:
	README.rst
	examples/IoT_TestDevice.py
	examples/disco_browser.py
	setup.py
	sleekxmpp/jid.py
	sleekxmpp/plugins/google/auth/stanza.py
	sleekxmpp/plugins/google/gmail/notifications.py
	sleekxmpp/plugins/google/nosave/stanza.py
	sleekxmpp/plugins/google/settings/settings.py
	sleekxmpp/thirdparty/__init__.py
	sleekxmpp/thirdparty/socks.py
	sleekxmpp/thirdparty/statemachine.py
	sleekxmpp/util/__init__.py
	sleekxmpp/xmlstream/xmlstream.py
	slixmpp/basexmpp.py
	slixmpp/plugins/xep_0004/stanza/form.py
	slixmpp/plugins/xep_0009/rpc.py
	slixmpp/plugins/xep_0050/adhoc.py
	slixmpp/plugins/xep_0065/proxy.py
	slixmpp/plugins/xep_0084/stanza.py
	slixmpp/plugins/xep_0202/time.py
	slixmpp/plugins/xep_0323/sensordata.py
	slixmpp/plugins/xep_0325/control.py
	slixmpp/plugins/xep_0325/stanza/control.py
	slixmpp/roster/single.py
	slixmpp/stanza/atom.py
	slixmpp/stanza/rootstanza.py
	slixmpp/test/slixtest.py
	slixmpp/util/sasl/mechanisms.py
	slixmpp/version.py
	slixmpp/xmlstream/stanzabase.py
	tests/test_stanza_xep_0323.py
	tests/test_stanza_xep_0325.py
	tests/test_stream_xep_0323.py
	tests/test_stream_xep_0325.py
2015-09-23 23:15:09 +02:00
351 changed files with 2210 additions and 5154 deletions

View File

@@ -1,13 +0,0 @@
################ Please use Gitlab instead of Github ###################################
Hello, thank you for contributing to slixmpp!
Youre about to open a pull request on github. However this github repository is not the official place for contributions on slixmpp.
Please open your merge request on https://lab.louiz.org/poezio/slixmpp/
You should be able to log in there with your github credentials, clone the slixmpp repository in your namespace, push your existing pull request into a new branch, and then open a merge request with one click, within 3 minutes.
This will help us review your contribution, avoid spreading things everywhere and it will even run the tests automatically with your changes.
Thank you.

View File

@@ -1,21 +0,0 @@
stages:
- test
- trigger
test:
stage: test
tags:
- docker
image: ubuntu:latest
script:
- apt update
- apt install -y python3 cython3 gpg
- ./run_tests.py
trigger_poezio:
stage: trigger
tags:
- docker
image: appropriate/curl:latest
script:
- curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline

View File

@@ -1,7 +1,10 @@
language: python language: python
python: python:
- "3.7" - "2.6"
- "3.8-dev" - "2.7"
- "3.2"
- "3.3"
- "3.4"
install: install:
- "pip install ." - "pip install ."
script: testall.py script: testall.py

View File

@@ -1,14 +0,0 @@
Contributing to the Slixmpp project
===================================
To contribute, the preferred way is to commit your changes on some
publicly-available git repository (on a fork `on github
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
notify the developers with either:
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
- a pull request on github
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
Even though Slixmpps github repository is just a read-only mirror, we can
still be notified of the pull requests and fetch your mirror manually to
integrate your changes.

View File

@@ -1,7 +1,6 @@
Pre-requisites: Pre-requisites:
- Python 3.7+ - Python 3.4
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
- GnuPG, for testing
Install: Install:
> python3 setup.py install > python3 setup.py install

25
LICENSE
View File

@@ -167,3 +167,28 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
socksipy: A Python SOCKS client module.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copyright 2006 Dan-Haim. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.

View File

@@ -1,22 +1,13 @@
Slixmpp Slixmpp
######### #########
Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of
SleekXMPP. SleekXMPP.
Slixmpp's goals is to only rewrite the core of the library (the low level 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.
Building
--------
Slixmpp can make use of cython to improve performance on critical modules.
To do that, **cython3** is necessary along with **libidn** headers.
Otherwise, no compilation is needed. Building is done by running setup.py::
python3 setup.py build_ext --inplace
Documentation and Testing Documentation 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``.
@@ -36,7 +27,7 @@ The Slixmpp Boilerplate
------------------------- -------------------------
Projects using Slixmpp tend to follow a basic pattern for setting up client/component Projects using Slixmpp tend to follow a basic pattern for setting up client/component
connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp
based project. See the documentation or examples directory for more detailed archetypes for based project. See the documetation or examples directory for more detailed archetypes for
Slixmpp projects:: Slixmpp projects::
import logging import logging
@@ -102,18 +93,8 @@ Slixmpp projects::
Slixmpp Credits Slixmpp Credits
--------------- ---------------
**Maintainers:** **Maintainer of the slixmpp fork:** Florent Le Coz
- Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_), `louiz@louiz.org <xmpp:louiz@louiz.org?message>`_,
- Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_),
**Contributors:**
- Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_)
- Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_)
- Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_)
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
- Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
Credits (SleekXMPP) Credits (SleekXMPP)
------------------- -------------------

View File

@@ -408,3 +408,24 @@ div.viewcode-block:target {
margin: -1px -12px; margin: -1px -12px;
padding: 0 12px; padding: 0 12px;
} }
#from_andyet {
-webkit-box-shadow: #CCC 0px 0px 3px;
background: rgba(255, 255, 255, 1);
bottom: 0px;
right: 17px;
padding: 3px 10px;
position: fixed;
}
#from_andyet h2 {
background-image: url("images/from_&yet.png");
background-repeat: no-repeat;
height: 29px;
line-height: 0;
text-indent: -9999em;
width: 79px;
margin-top: 0;
margin: 0px;
padding: 0px;
}

View File

@@ -65,5 +65,6 @@
<div class="bottomnav"> <div class="bottomnav">
{{ nav() }} {{ nav() }}
</div> </div>
<a id="from_andyet" href="http://andyet.net"><h2>From &amp;yet</h2></a>
{% endblock %} {% endblock %}

View File

@@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace
prefixes:: prefixes::
>>> from slixmpp.xmlstream.tostring import tostring >>> from slixmpp.xmlstream.tostring import tostring
>>> from xml.etree import ElementTree as ET >>> from xml.etree import cElementTree as ET
>>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>') >>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
>>> ET.tostring(xml) >>> ET.tostring(xml)
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>' '<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'

View File

@@ -12,17 +12,12 @@
# serve to show the default. # serve to show the default.
import sys, os import sys, os
import datetime
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..')) sys.path.insert(0, os.path.abspath('..'))
# get version automagically from source tree
from slixmpp.version import __version__ as version
release = ".".join(version.split(".")[0:2])
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
@@ -46,18 +41,16 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'Slixmpp' project = u'Slixmpp'
year = datetime.datetime.now().year copyright = u'2011, Nathan Fritz, Lance Stout'
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year)
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# auto imported from code!
# The short X.Y version. # The short X.Y version.
# version = '1.4' version = '1.0'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
# release = '1.4.0' release = '1.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@@ -163,7 +163,7 @@ behaviour:
namespace = 'jabber:iq:register' namespace = 'jabber:iq:register'
name = 'query' name = 'query'
plugin_attrib = 'register' plugin_attrib = 'register'
interfaces = {'username', 'password', 'registered', 'remove'} interfaces = set(('username', 'password', 'registered', 'remove'))
sub_interfaces = interfaces sub_interfaces = interfaces
def getRegistered(self): def getRegistered(self):
@@ -535,10 +535,10 @@ with some additional registration fields implemented.
namespace = 'jabber:iq:register' namespace = 'jabber:iq:register'
name = 'query' name = 'query'
plugin_attrib = 'register' plugin_attrib = 'register'
interfaces = {'username', 'password', 'email', 'nick', 'name', interfaces = set(('username', 'password', 'email', 'nick', 'name',
'first', 'last', 'address', 'city', 'state', 'zip', 'first', 'last', 'address', 'city', 'state', 'zip',
'phone', 'url', 'date', 'misc', 'text', 'key', 'phone', 'url', 'date', 'misc', 'text', 'key',
'registered', 'remove', 'instructions'} 'registered', 'remove', 'instructions'))
sub_interfaces = interfaces sub_interfaces = interfaces
def getRegistered(self): def getRegistered(self):

View File

@@ -3,9 +3,8 @@
Differences from SleekXMPP Differences from SleekXMPP
========================== ==========================
**Python 3.7+ only** **Python 3.4+ only**
slixmpp will work on python 3.7 and above. It may work with previous slixmpp will only work on python 3.4 and above.
versions but we provide no guarantees.
**Stanza copies** **Stanza copies**
The same stanza object is given through all the handlers; a handler that The same stanza object is given through all the handlers; a handler that

View File

@@ -259,8 +259,8 @@ Event Index
Signal that a connection to the XMPP server has been lost and the current Signal that a connection to the XMPP server has been lost and the current
stream session has ended. Currently equivalent to :term:`disconnected`, but stream session has ended. Currently equivalent to :term:`disconnected`, but
implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_ future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
distinguish between the two events. will distinguish the two events.
Plugins that maintain session-based state should clear themselves when Plugins that maintain session-based state should clear themselves when
this event is fired. this event is fired.

View File

@@ -11,7 +11,7 @@ Create and Run a Server Component
<xmpp:slixmpp@muc.poez.io?join>`_. <xmpp:slixmpp@muc.poez.io?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
with `Git <https://lab.louiz.org/poezio/slixmpp>`_. with `Git <http://git.poez.io/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

View File

@@ -11,7 +11,7 @@ Slixmpp Quickstart - Echo Bot
<xmpp:slixmpp@muc.poez.io?join>`_. <xmpp:slixmpp@muc.poez.io?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
with `Git <https://lab.louiz.org/poezio/slixmpp>`_. with `Git <http://git.poez.io/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
@@ -70,7 +70,7 @@ as well.
class EchoBot(slixmpp.ClientXMPP): class EchoBot(slixmpp.ClientXMPP):
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super(EchoBot, self).__init__(jid, password)
Handling Session Start Handling Session Start
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
@@ -83,7 +83,7 @@ started. To do that, we will register an event handler for the :term:`session_st
.. code-block:: python .. code-block:: python
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super(EchoBot, self).__init__(jid, password)
self.add_event_handler('session_start', self.start) self.add_event_handler('session_start', self.start)
@@ -153,7 +153,7 @@ whenever a messsage is received.
.. code-block:: python .. code-block:: python
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super(EchoBot, self).__init__(jid, password)
self.add_event_handler('session_start', self.start) self.add_event_handler('session_start', self.start)
self.add_event_handler('message', self.message) self.add_event_handler('message', self.message)
@@ -329,7 +329,7 @@ The Final Product
----------------- -----------------
Here then is what the final result should look like after working through the guide above. The code Here then is what the final result should look like after working through the guide above. The code
can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_. can also be found in the Slixmpp `examples directory <http://github.com/fritzy/Slixmpp/tree/master/examples>`_.
.. compound:: .. compound::

View File

@@ -1,7 +1,7 @@
.. _mucbot: .. _mucbot:
========================= =========================
Multi-User Chat (MUC) Bot Mulit-User Chat (MUC) Bot
========================= =========================
.. note:: .. note::
@@ -11,7 +11,7 @@ Multi-User Chat (MUC) Bot
<xmpp:slixmpp@muc.poez.io?join>`_. <xmpp:slixmpp@muc.poez.io?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 `Git <https://lab.louiz.org/poezio/slixmpp>`_. from `Git <http://git.poez.io/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
@@ -63,13 +63,13 @@ has been established:
def start(self, event): def start(self, event):
self.get_roster() self.get_roster()
self.send_presence() self.send_presence()
self.plugin['xep_0045'].join_muc(self.room, self.plugin['xep_0045'].joinMUC(self.room,
self.nick, self.nick,
wait=True) wait=True)
Note that as in :ref:`echobot`, we need to include send an initial presence and request Note that as in :ref:`echobot`, we need to include send an initial presence and request
the roster. Next, we want to join the group chat, so we call the the roster. Next, we want to join the group chat, so we call the
``join_muc`` method of the MUC plugin. ``joinMUC`` method of the MUC plugin.
.. note:: .. note::

View File

@@ -24,7 +24,7 @@ for the JID that will receive our message, and the string content of the message
class SendMsgBot(slixmpp.ClientXMPP): class SendMsgBot(slixmpp.ClientXMPP):
def __init__(self, jid, password, recipient, msg): def __init__(self, jid, password, recipient, msg):
super().__init__(jid, password) super(SendMsgBot, self).__init__(jid, password)
self.recipient = recipient self.recipient = recipient
self.msg = msg self.msg = msg
@@ -47,11 +47,11 @@ the roster. Next, we want to send our message, and to do that we will use :meth:
self.send_message(mto=self.recipient, mbody=self.msg) self.send_message(mto=self.recipient, mbody=self.msg)
Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`. Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`.
Now, sent stanzas are placed in a queue to pass them to the send thread. Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` by default will wait for an :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
acknowledgement from the server for at least `2.0` seconds. This time is configurable with for the client to disconnect before the send queue is processed and the message is actually
the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect sent on the wire. To ensure that our message is processed, we use
<slixmpp.xmlstream.XMLStream.disconnect>` will not close the connection gracefully. :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`.
.. code-block:: python .. code-block:: python
@@ -61,12 +61,12 @@ the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect
self.send_message(mto=self.recipient, mbody=self.msg) self.send_message(mto=self.recipient, mbody=self.msg)
self.disconnect() self.disconnect(wait=True)
.. warning:: .. warning::
If you happen to be adding stanzas to the send queue faster than the send thread If you happen to be adding stanzas to the send queue faster than the send thread
can process them, then :meth:`disconnect() <slixmpp.xmlstream.XMLStream.disconnect>` can process them, then :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`
will block and not disconnect. will block and not disconnect.
Final Product Final Product

View File

@@ -61,7 +61,7 @@ operation using these stanzas without doing any complex operations such as
checking an ACL, etc. checking an ACL, etc.
You may find it necessary at some point to revert a particular node or JID to You may find it necessary at some point to revert a particular node or JID to
using the default, static handlers. To do so, use the method ``restore_defaults()``. using the default, static handlers. To do so, use the method ``make_static()``.
You may also elect to only convert a given set of actions instead. You may also elect to only convert a given set of actions instead.
Creating a Node Handler Creating a Node Handler
@@ -162,7 +162,7 @@ item itself, and the JID and node that will own the item.
parameters ``ijid`` and ``node``. parameters ``ijid`` and ``node``.
Performing 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
sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()`` sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``

View File

@@ -4,9 +4,9 @@ Slixmpp
.. sidebar:: Get the Code .. sidebar:: Get the Code
The latest source code for Slixmpp may be found on the `Git repo The latest source code for Slixmpp may be found on the `Git repo
<https://lab.louiz.org/poezio/slixmpp>`_. :: <http://git.poez.io/slixmpp>`_. ::
git clone https://lab.louiz.org/poezio/slixmpp git clone git://git.poez.io/slixmpp
An XMPP chat room is available for discussing and getting help with slixmpp. An XMPP chat room is available for discussing and getting help with slixmpp.
@@ -14,14 +14,14 @@ Slixmpp
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_ `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
**Reporting bugs** **Reporting bugs**
You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues. You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
.. note:: .. note::
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_ 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 which goal is to use asyncio instead of threads to handle networking. See
:ref:`differences`. :ref:`differences`.
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+, 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:

View File

@@ -33,7 +33,7 @@ class CommandBot(slixmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -47,7 +47,7 @@ class CommandBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
# We add the command after session_start has fired # We add the command after session_start has fired
# to ensure that the correct full JID is used. # to ensure that the correct full JID is used.
@@ -68,7 +68,7 @@ class CommandBot(slixmpp.ClientXMPP):
session. Additional, custom data may be saved session. Additional, custom data may be saved
here to persist across handler callbacks. here to persist across handler callbacks.
""" """
form = self['xep_0004'].make_form('form', 'Greeting') form = self['xep_0004'].makeForm('form', 'Greeting')
form['instructions'] = 'Send a custom greeting to a JID' form['instructions'] = 'Send a custom greeting to a JID'
form.addField(var='greeting', form.addField(var='greeting',
ftype='text-single', ftype='text-single',

View File

@@ -37,7 +37,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.message) self.add_event_handler("message", self.message)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -51,7 +51,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
# We first create a session dictionary containing: # We first create a session dictionary containing:
# 'next' -- the handler to execute on a successful response # 'next' -- the handler to execute on a successful response
@@ -94,7 +94,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
# label="Your greeting" /> # label="Your greeting" />
# </x> # </x>
form = self['xep_0004'].make_form(ftype='submit') form = self['xep_0004'].makeForm(ftype='submit')
form.addField(var='greeting', form.addField(var='greeting',
value=session['greeting']) value=session['greeting'])
@@ -176,4 +176,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(forever=False) xmpp.process()

View File

@@ -30,7 +30,7 @@ class AdminCommands(slixmpp.ClientXMPP):
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -44,7 +44,7 @@ class AdminCommands(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
def command_success(iq, session): def command_success(iq, session):
print('Command completed') print('Command completed')

View File

@@ -1,99 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2015 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
log = logging.getLogger(__name__)
class AnswerConfirm(slixmpp.ClientXMPP):
"""
A basic client demonstrating how to confirm or deny an HTTP request.
"""
def __init__(self, jid, password, trusted):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.add_event_handler("http_confirm", self.confirm)
self.add_event_handler("session_start", self.start)
def start(self, *args):
self.make_presence().send()
def prompt(self, stanza):
confirm = stanza['confirm']
print('Received confirm request %s from %s to access %s using '
'method %s' % (
confirm['id'], stanza['from'], confirm['url'],
confirm['method'])
)
result = input("Do you accept (y/N)? ")
return 'y' == result.lower()
def confirm(self, stanza):
if self.prompt(stanza):
reply = stanza.reply()
else:
reply = stanza.reply()
reply.enable('error')
reply['error']['type'] = 'auth'
reply['error']['code'] = '401'
reply['error']['condition'] = 'not-authorized'
reply.append(stanza['confirm'])
reply.send()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
parser.add_argument("-q","--quiet", help="set logging to ERROR",
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
# Other options.
parser.add_argument("-t", "--trusted", nargs='*',
help="List of trusted JIDs")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
xmpp = AnswerConfirm(args.jid, args.password, args.trusted)
xmpp.register_plugin('xep_0070')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process()

View File

@@ -1,124 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2015 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import sys
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError, IqError
from slixmpp import asyncio
log = logging.getLogger(__name__)
class AskConfirm(slixmpp.ClientXMPP):
"""
A basic client asking an entity if they confirm the access to an HTTP URL.
"""
def __init__(self, jid, password, recipient, id, url, method):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.recipient = recipient
self.id = id
self.url = url
self.method = method
# Will be used to set the proper exit code.
self.confirmed = asyncio.Future()
self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.start)
self.add_event_handler("http_confirm_message", self.confirm)
def confirm(self, message):
print(message)
if message['confirm']['id'] == self.id:
if message['type'] == 'error':
self.confirmed.set_result(False)
else:
self.confirmed.set_result(True)
async def start(self, event):
log.info('Sending confirm request %s to %s who wants to access %s using '
'method %s...' % (self.id, self.recipient, self.url, self.method))
try:
confirmed = await self['xep_0070'].ask_confirm(self.recipient,
id=self.id,
url=self.url,
method=self.method,
message='Plz say yes or no for {method} {url} ({id}).')
if isinstance(confirmed, slixmpp.Message):
confirmed = await self.confirmed
else:
confirmed = True
except IqError:
confirmed = False
if confirmed:
print('Confirmed')
else:
print('Denied')
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
parser.add_argument("-q","--quiet", help="set logging to ERROR",
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
# Other options.
parser.add_argument("-r", "--recipient", required=True,
help="Recipient JID")
parser.add_argument("-i", "--id", required=True,
help="id TODO")
parser.add_argument("-u", "--url", required=True,
help="URL the user tried to access")
parser.add_argument("-m", "--method", required=True,
help="HTTP method used")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id,
args.url, args.method)
xmpp.register_plugin('xep_0070')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)
sys.exit(0 if xmpp.confirmed else 1)

View File

@@ -50,7 +50,7 @@ class ActionBot(slixmpp.ClientXMPP):
register_stanza_plugin(Iq, Action) register_stanza_plugin(Iq, Action)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -73,7 +73,7 @@ class ActionBot(slixmpp.ClientXMPP):
""" """
self.event('custom_action', iq) self.event('custom_action', iq)
async def _handle_action_event(self, iq): def _handle_action_event(self, iq):
""" """
Respond to the custom action event. Respond to the custom action event.
""" """
@@ -82,20 +82,17 @@ class ActionBot(slixmpp.ClientXMPP):
if method == 'is_prime' and param == '2': if method == 'is_prime' and param == '2':
print("got message: %s" % iq) print("got message: %s" % iq)
rep = iq.reply() iq.reply()
rep['action']['status'] = 'done' iq['action']['status'] = 'done'
await rep.send() iq.send()
elif method == 'bye': elif method == 'bye':
print("got message: %s" % iq) print("got message: %s" % iq)
rep = iq.reply()
rep['action']['status'] = 'done'
await rep.send()
self.disconnect() self.disconnect()
else: else:
print("got message: %s" % iq) print("got message: %s" % iq)
rep = iq.reply() iq.reply()
rep['action']['status'] = 'error' iq['action']['status'] = 'error'
await rep.send() iq.send()
if __name__ == '__main__': if __name__ == '__main__':
# Setup the command line arguments. # Setup the command line arguments.

View File

@@ -43,7 +43,7 @@ class ActionUserBot(slixmpp.ClientXMPP):
register_stanza_plugin(Iq, Action) register_stanza_plugin(Iq, Action)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -57,11 +57,11 @@ class ActionUserBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
await self.send_custom_iq() self.send_custom_iq()
async def send_custom_iq(self): def send_custom_iq(self):
"""Create and send two custom actions. """Create and send two custom actions.
If the first action was successful, then send If the first action was successful, then send
@@ -74,14 +74,14 @@ class ActionUserBot(slixmpp.ClientXMPP):
iq['action']['param'] = '2' iq['action']['param'] = '2'
try: try:
resp = await iq.send() resp = iq.send()
if resp['action']['status'] == 'done': if resp['action']['status'] == 'done':
#sending bye #sending bye
iq2 = self.Iq() iq2 = self.Iq()
iq2['to'] = self.action_provider iq2['to'] = self.action_provider
iq2['type'] = 'set' iq2['type'] = 'set'
iq2['action']['method'] = 'bye' iq2['action']['method'] = 'bye'
await iq2.send() iq2.send(block=False)
self.disconnect() self.disconnect()
except XMPPError: except XMPPError:

View File

@@ -41,7 +41,7 @@ class Action(ElementBase):
#: del action['status'] #: del action['status']
#: #:
#: to set, get, or remove its values. #: to set, get, or remove its values.
interfaces = {'method', 'param', 'status'} interfaces = set(('method', 'param', 'status'))
#: By default, values in the `interfaces` set are mapped to #: By default, values in the `interfaces` set are mapped to
#: attribute values. This can be changed such that an interface #: attribute values. This can be changed such that an interface

View File

@@ -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,7 +54,8 @@ class Disco(slixmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
async def start(self, event): @asyncio.coroutine
def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -69,19 +71,19 @@ class Disco(slixmpp.ClientXMPP):
event does not provide any additional event does not provide any additional
data. data.
""" """
await self.get_roster() self.get_roster()
self.send_presence() self.send_presence()
try: try:
if self.get in self.info_types: if self.get in self.info_types:
# function using the callback parameter. # function using the callback parameter.
info = await 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)
if self.get in self.items_types: if 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 = await 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)
if self.get not in self.info_types and self.get not in self.items_types: if self.get not in self.info_types and self.get not in self.items_types:
logging.error("Invalid disco request type.") logging.error("Invalid disco request type.")

View File

@@ -47,7 +47,8 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.roster_received.set() self.roster_received.set()
self.presences_received.clear() self.presences_received.clear()
async def start(self, event): @asyncio.coroutine
def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -64,15 +65,16 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.get_roster(callback=self.roster_received_cb) self.get_roster(callback=self.roster_received_cb)
print('Waiting for presence updates...\n') print('Waiting for presence updates...\n')
await self.roster_received.wait() yield from self.roster_received.wait()
print('Roster received') print('Roster received')
await self.presences_received.wait() yield from self.presences_received.wait()
self.disconnect() self.disconnect()
async def on_vcard_avatar(self, pres): @asyncio.coroutine
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 = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True, result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
timeout=5) timeout=5)
except XMPPError: except XMPPError:
print("Error retrieving avatar for %s" % pres['from']) print("Error retrieving avatar for %s" % pres['from'])
@@ -87,13 +89,14 @@ class AvatarDownloader(slixmpp.ClientXMPP):
with open(filename, 'wb+') as img: with open(filename, 'wb+') as img:
img.write(avatar['BINVAL']) img.write(avatar['BINVAL'])
async def on_avatar(self, msg): @asyncio.coroutine
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 = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'], result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
timeout=5) timeout=5)
except XMPPError: except XMPPError:
print("Error retrieving avatar for %s" % msg['from']) print("Error retrieving avatar for %s" % msg['from'])
@@ -159,4 +162,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(forever=False) xmpp.process()

View File

@@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP):
# MUC messages and error messages. # MUC messages and error messages.
self.add_event_handler("message", self.message) self.add_event_handler("message", self.message)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
def message(self, msg): def message(self, msg):
""" """

View File

@@ -55,10 +55,10 @@ class GTalkBot(slixmpp.ClientXMPP):
cert.verify('talk.google.com', der_cert) cert.verify('talk.google.com', der_cert)
logging.debug("CERT: Found GTalk certificate") logging.debug("CERT: Found GTalk certificate")
except cert.CertificateError as err: except cert.CertificateError as err:
logging.error(err.message) log.error(err.message)
self.disconnect() self.disconnect(send_close=False)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -72,7 +72,7 @@ class GTalkBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
def message(self, msg): def message(self, msg):
""" """

View File

@@ -13,7 +13,7 @@
from slixmpp import ClientXMPP from slixmpp import ClientXMPP
from argparse import ArgumentParser from optparse import OptionParser
import logging import logging
import getpass import getpass
@@ -23,7 +23,7 @@ class HTTPOverXMPPClient(ClientXMPP):
ClientXMPP.__init__(self, jid, password) ClientXMPP.__init__(self, jid, password)
self.register_plugin('xep_0332') # HTTP over XMPP Transport self.register_plugin('xep_0332') # HTTP over XMPP Transport
self.add_event_handler( self.add_event_handler(
'session_start', self.session_start 'session_start', self.session_start, threaded=True
) )
self.add_event_handler('http_request', self.http_request_received) self.add_event_handler('http_request', self.http_request_received)
self.add_event_handler('http_response', self.http_response_received) self.add_event_handler('http_response', self.http_response_received)
@@ -58,40 +58,40 @@ if __name__ == '__main__':
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v] # ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
# #
parser = ArgumentParser() parser = OptionParser()
# Output verbosity options. # Output verbosity options.
parser.add_argument( parser.add_option(
'-v', '--verbose', help='set logging to DEBUG', action='store_const', '-v', '--verbose', help='set logging to DEBUG', action='store_const',
dest='loglevel', const=logging.DEBUG, default=logging.ERROR dest='loglevel', const=logging.DEBUG, default=logging.ERROR
) )
# JID and password options. # JID and password options.
parser.add_argument('-J', '--jid', dest='jid', help='JID') parser.add_option('-J', '--jid', dest='jid', help='JID')
parser.add_argument('-P', '--password', dest='password', help='Password') parser.add_option('-P', '--password', dest='password', help='Password')
# XMPP server ip and port options. # XMPP server ip and port options.
parser.add_argument( parser.add_option(
'-i', '--ipaddr', dest='ipaddr', '-i', '--ipaddr', dest='ipaddr',
help='IP Address of the XMPP server', default=None help='IP Address of the XMPP server', default=None
) )
parser.add_argument( parser.add_option(
'-p', '--port', dest='port', '-p', '--port', dest='port',
help='Port of the XMPP server', default=None help='Port of the XMPP server', default=None
) )
args = parser.parse_args() opts, args = parser.parse_args()
# Setup logging. # Setup logging.
logging.basicConfig(level=args.loglevel, logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s') format='%(levelname)-8s %(message)s')
if args.jid is None: if opts.jid is None:
args.jid = input('Username: ') opts.jid = input('Username: ')
if args.password is None: if opts.password is None:
args.password = getpass.getpass('Password: ') opts.password = getpass.getpass('Password: ')
xmpp = HTTPOverXMPPClient(args.jid, args.password) xmpp = HTTPOverXMPPClient(opts.jid, opts.password)
xmpp.connect() xmpp.connect()
xmpp.process() xmpp.process()

View File

@@ -1,96 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
log = logging.getLogger(__name__)
class HttpUpload(slixmpp.ClientXMPP):
"""
A basic client asking an entity if they confirm the access to an HTTP URL.
"""
def __init__(self, jid, password, recipient, filename, domain=None):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.recipient = recipient
self.filename = filename
self.domain = domain
self.add_event_handler("session_start", self.start)
async def start(self, event):
log.info('Uploading file %s...', self.filename)
def timeout_callback(arg):
raise TimeoutError("could not send message in time")
url = await self['xep_0363'].upload_file(
self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback)
log.info('Upload success!')
log.info('Sending file to %s', self.recipient)
html = '<body xmlns="http://www.w3.org/1999/xhtml"><a href="%s">%s</a></body>' % (url, url)
self.send_message(self.recipient, url, mhtml=html)
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
parser.add_argument("-q","--quiet", help="set logging to ERROR",
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
# Other options.
parser.add_argument("-r", "--recipient", required=True,
help="Recipient JID")
parser.add_argument("-f", "--file", required=True,
help="File to send")
parser.add_argument("--domain",
help="Domain to use for HTTP File Upload (leave out for your own servers)")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain)
xmpp.register_plugin('xep_0071')
xmpp.register_plugin('xep_0128')
xmpp.register_plugin('xep_0363')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)

View File

@@ -9,6 +9,7 @@
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
@@ -38,7 +39,8 @@ class IBBSender(slixmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
async def start(self, event): @asyncio.coroutine
def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -56,13 +58,13 @@ class IBBSender(slixmpp.ClientXMPP):
try: try:
# Open the IBB stream in which to write to. # Open the IBB stream in which to write to.
stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages) stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
# If you want to send in-memory bytes, use stream.sendall() instead. # If you want to send in-memory bytes, use stream.sendall() instead.
await stream.sendfile(self.file, timeout=10) yield from stream.sendfile(self.file, timeout=10)
# And finally close the stream. # And finally close the stream.
await stream.close(timeout=10) yield from stream.close(timeout=10)
except (IqError, IqTimeout): except (IqError, IqTimeout):
print('File transfer errored') print('File transfer errored')
else: else:

View File

@@ -1,97 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2017 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
log = logging.getLogger(__name__)
class MAM(slixmpp.ClientXMPP):
"""
A basic client fetching mam archive messages
"""
def __init__(self, jid, password, remote_jid, start):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.remote_jid = remote_jid
self.start_date = start
self.add_event_handler("session_start", self.start)
async def start(self, *args):
"""
Fetch mam results for the specified JID.
Use RSM to paginate the results.
"""
results = self.plugin['xep_0313'].retrieve(jid=self.remote_jid, iterator=True, rsm={'max': 10}, start=self.start_date)
page = 1
async for rsm in results:
print('Page %d' % page)
for msg in rsm['mam']['results']:
forwarded = msg['mam_result']['forwarded']
timestamp = forwarded['delay']['stamp']
message = forwarded['stanza']
print('[%s] %s: %s' % (timestamp, message['from'], message['body']))
page += 1
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
parser.add_argument("-q","--quiet", help="set logging to ERROR",
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
# Other options
parser.add_argument("-r", "--remote-jid", dest="remote_jid",
help="Remote JID")
parser.add_argument("--start", help="Start date", default='2017-09-20T12:00:00Z')
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
if args.remote_jid is None:
args.remote_jid = input("Remote JID: ")
if args.start is None:
args.start = input("Start time: ")
xmpp = MAM(args.jid, args.password, args.remote_jid, args.start)
xmpp.register_plugin('xep_0313')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)

View File

@@ -1,120 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp.plugins.xep_0394 import stanza as markup_stanza
class EchoBot(slixmpp.ClientXMPP):
"""
A simple Slixmpp bot that will echo messages it
receives, along with a short thank you message.
"""
def __init__(self, jid, password):
slixmpp.ClientXMPP.__init__(self, jid, password)
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can initialize
# our roster.
self.add_event_handler("session_start", self.start)
# The message event is triggered whenever a message
# stanza is received. Be aware that that includes
# MUC messages and error messages.
self.add_event_handler("message", self.message)
async def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an initial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.send_presence()
await self.get_roster()
def message(self, msg):
"""
Process incoming message stanzas. Be aware that this also
includes MUC messages and error messages. It is usually
a good idea to check the messages's type before processing
or sending replies.
Arguments:
msg -- The received message stanza. See the documentation
for stanza objects and the Message stanza to see
how it may be used.
"""
body = msg['body']
new_body = self['xep_0394'].to_plain_text(body, msg['markup'])
xhtml = self['xep_0394'].to_xhtml_im(body, msg['markup'])
print('Plain text:', new_body)
print('XHTML-IM:', xhtml['body'])
message = msg.reply()
message['body'] = new_body
message['html']['body'] = xhtml['body']
self.send(message)
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser(description=EchoBot.__doc__)
# Output verbosity options.
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
action="store_const", dest="loglevel",
const=logging.ERROR, default=logging.INFO)
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
action="store_const", dest="loglevel",
const=logging.DEBUG, default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
# Setup the EchoBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = EchoBot(args.jid, args.password)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0199') # XMPP Ping
xmpp.register_plugin('xep_0394') # Message Markup
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process()

View File

@@ -104,4 +104,4 @@ def on_session2(event):
new_xmpp.add_event_handler('session_start', on_session2) new_xmpp.add_event_handler('session_start', on_session2)
new_xmpp.connect() new_xmpp.connect()
new_xmpp.process(forever=False) new_xmpp.process()

View File

@@ -52,7 +52,7 @@ class MUCBot(slixmpp.ClientXMPP):
self.muc_online) self.muc_online)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -65,13 +65,13 @@ class MUCBot(slixmpp.ClientXMPP):
event does not provide any additional event does not provide any additional
data. data.
""" """
await self.get_roster() self.get_roster()
self.send_presence() self.send_presence()
self.plugin['xep_0045'].join_muc(self.room, self.plugin['xep_0045'].joinMUC(self.room,
self.nick, self.nick,
# If a room password is needed, use: # If a room password is needed, use:
# password=the_room_password, # password=the_room_password,
wait=True) wait=True)
def muc_message(self, msg): def muc_message(self, msg):
""" """

View File

@@ -13,6 +13,7 @@ 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.exceptions import IqError, IqTimeout
from slixmpp import asyncio
import slixmpp import slixmpp
@@ -37,7 +38,8 @@ class PingTest(slixmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
async def start(self, event): @asyncio.coroutine
def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -51,10 +53,10 @@ class PingTest(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
try: try:
rtt = await 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:
@@ -109,4 +111,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(forever=False) xmpp.process()

View File

@@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP):
# MUC messages and error messages. # MUC messages and error messages.
self.add_event_handler("message", self.message) self.add_event_handler("message", self.message)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
def message(self, msg): def message(self, msg):
""" """

View File

@@ -5,6 +5,7 @@ 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.exceptions import XMPPError
from slixmpp.xmlstream import ET, tostring from slixmpp.xmlstream import ET, tostring
@@ -14,13 +15,13 @@ class PubsubClient(slixmpp.ClientXMPP):
def __init__(self, jid, password, server, def __init__(self, jid, password, server,
node=None, action='nodes', data=''): node=None, action='nodes', data=''):
super().__init__(jid, password) super(PubsubClient, self).__init__(jid, password)
self.register_plugin('xep_0030') self.register_plugin('xep_0030')
self.register_plugin('xep_0059') self.register_plugin('xep_0059')
self.register_plugin('xep_0060') self.register_plugin('xep_0060')
self.actions = ['nodes', 'create', 'delete', 'get_configure', self.actions = ['nodes', 'create', 'delete',
'publish', 'get', 'retract', 'publish', 'get', 'retract',
'purge', 'subscribe', 'unsubscribe'] 'purge', 'subscribe', 'unsubscribe']
@@ -31,86 +32,80 @@ class PubsubClient(slixmpp.ClientXMPP):
self.add_event_handler('session_start', self.start) self.add_event_handler('session_start', self.start)
async def start(self, event): @asyncio.coroutine
await self.get_roster() def start(self, event):
self.get_roster()
self.send_presence() self.send_presence()
try: try:
await getattr(self, self.action)() yield from getattr(self, self.action)()
except: except:
logging.exception('Could not execute %s:', self.action) logging.error('Could not execute: %s', self.action)
self.disconnect() self.disconnect()
async def nodes(self): def nodes(self):
try: try:
result = await 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']:
logging.info(' - %s', str(item)) logging.info(' - %s', str(item))
except XMPPError as error: except XMPPError as error:
logging.error('Could not retrieve node list: %s', error.format()) logging.error('Could not retrieve node list: %s', error.format())
async def create(self): def create(self):
try: try:
await self['xep_0060'].create_node(self.pubsub_server, self.node) yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
logging.info('Created node %s', self.node) logging.info('Created node %s', self.node)
except XMPPError as error: except XMPPError as error:
logging.error('Could not create node %s: %s', self.node, error.format()) logging.error('Could not create node %s: %s', self.node, error.format())
async def delete(self): def delete(self):
try: try:
await self['xep_0060'].delete_node(self.pubsub_server, self.node) yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
logging.info('Deleted node %s', self.node) logging.info('Deleted node %s', self.node)
except XMPPError as error: except XMPPError as error:
logging.error('Could not delete node %s: %s', self.node, error.format()) logging.error('Could not delete node %s: %s', self.node, error.format())
async def get_configure(self): def publish(self):
try:
configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node)
logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form'])
except XMPPError as error:
logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format())
async def publish(self):
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data) payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
try: try:
result = await 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)
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id']) logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
except XMPPError as error: except XMPPError as error:
logging.error('Could not publish to %s: %s', self.node, error.format()) logging.error('Could not publish to %s: %s', self.node, error.format())
async def get(self): def get(self):
try: try:
result = await 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']:
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload'])) logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
except XMPPError as error: except XMPPError as error:
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format()) logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
async def retract(self): def retract(self):
try: try:
await self['xep_0060'].retract(self.pubsub_server, self.node, self.data) yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
logging.info('Retracted item %s from node %s', self.data, self.node) logging.info('Retracted item %s from node %s', self.data, self.node)
except XMPPError as error: except XMPPError as error:
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format()) logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
async def purge(self): def purge(self):
try: try:
await self['xep_0060'].purge(self.pubsub_server, self.node) yield from self['xep_0060'].purge(self.pubsub_server, self.node)
logging.info('Purged all items from node %s', self.node) logging.info('Purged all items from node %s', self.node)
except XMPPError as error: except XMPPError as error:
logging.error('Could not purge items from node %s: %s', self.node, error.format()) logging.error('Could not purge items from node %s: %s', self.node, error.format())
async def subscribe(self): def subscribe(self):
try: try:
iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node) iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
subscription = iq['pubsub']['subscription'] subscription = iq['pubsub']['subscription']
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node']) logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
except XMPPError as error: except XMPPError as error:
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format()) logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
async def unsubscribe(self): def unsubscribe(self):
try: try:
await self['xep_0060'].unsubscribe(self.pubsub_server, self.node) yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node) logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
except XMPPError as error: except XMPPError as error:
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format()) logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
@@ -123,7 +118,7 @@ if __name__ == '__main__':
parser = ArgumentParser() parser = ArgumentParser()
parser.version = '%%prog 0.1' parser.version = '%%prog 0.1'
parser.usage = "Usage: %%prog [options] <jid> " + \ parser.usage = "Usage: %%prog [options] <jid> " + \
'nodes|create|delete|get_configure|purge|subscribe|unsubscribe|publish|retract|get' + \ 'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \
' [<node> <data>]' ' [<node> <data>]'
parser.add_argument("-q","--quiet", help="set logging to ERROR", parser.add_argument("-q","--quiet", help="set logging to ERROR",
@@ -144,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", choices=["nodes", "create", "delete", "get_configure", "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='?')

View File

@@ -14,7 +14,7 @@ from slixmpp.xmlstream.handler import Callback
class PubsubEvents(slixmpp.ClientXMPP): class PubsubEvents(slixmpp.ClientXMPP):
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super(PubsubEvents, self).__init__(jid, password)
self.register_plugin('xep_0030') self.register_plugin('xep_0030')
self.register_plugin('xep_0059') self.register_plugin('xep_0059')
@@ -38,8 +38,8 @@ class PubsubEvents(slixmpp.ClientXMPP):
# self.add_event_handler('event_prefix_purge', handler) # self.add_event_handler('event_prefix_purge', handler)
# self.add_event_handler('event_prefix_delete', handler) # self.add_event_handler('event_prefix_delete', handler)
async def start(self, event): def start(self, event):
await self.get_roster() self.get_roster()
self.send_presence() self.send_presence()
def _publish(self, msg): def _publish(self, msg):

View File

@@ -47,7 +47,7 @@ class RegisterBot(slixmpp.ClientXMPP):
# for data forms and OOB links that will make that easier. # for data forms and OOB links that will make that easier.
self.add_event_handler("register", self.register) self.add_event_handler("register", self.register)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -61,12 +61,12 @@ class RegisterBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
# We're only concerned about registering, so nothing more to do here. # We're only concerned about registering, so nothing more to do here.
self.disconnect() self.disconnect()
async def register(self, iq): def register(self, iq):
""" """
Fill out and submit a registration form. Fill out and submit a registration form.
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
resp['register']['password'] = self.password resp['register']['password'] = self.password
try: try:
await 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" %

View File

@@ -38,7 +38,8 @@ class RosterBrowser(slixmpp.ClientXMPP):
self.received = set() self.received = set()
self.presences_received = asyncio.Event() self.presences_received = asyncio.Event()
async def start(self, event): @asyncio.coroutine
def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -51,8 +52,12 @@ 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:
await self.get_roster() self.get_roster(callback=callback)
yield from future
except IqError as err: except IqError as err:
print('Error: %s' % err.iq['error']['condition']) print('Error: %s' % err.iq['error']['condition'])
except IqTimeout: except IqTimeout:
@@ -61,7 +66,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
print('Waiting for presence updates...\n') print('Waiting for presence updates...\n')
await asyncio.sleep(10) 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()
@@ -134,4 +139,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(forever=False) xmpp.process()

View File

@@ -9,6 +9,7 @@
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

View File

@@ -9,6 +9,7 @@
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
@@ -35,7 +36,8 @@ class S5BSender(slixmpp.ClientXMPP):
# and the XML streams are ready for use. # and the XML streams are ready for use.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
async def start(self, event): @asyncio.coroutine
def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -51,14 +53,14 @@ class S5BSender(slixmpp.ClientXMPP):
try: try:
# Open the S5B stream in which to write to. # Open the S5B stream in which to write to.
proxy = await self['xep_0065'].handshake(self.receiver) proxy = yield from self['xep_0065'].handshake(self.receiver)
# Send the entire file. # Send the entire file.
while True: while True:
data = self.file.read(1048576) data = self.file.read(1048576)
if not data: if not data:
break break
await proxy.write(data) yield from proxy.write(data)
# And finally close the stream. # And finally close the stream.
proxy.transport.write_eof() proxy.transport.write_eof()

View File

@@ -38,7 +38,7 @@ class SendMsgBot(slixmpp.ClientXMPP):
# our roster. # our roster.
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -52,7 +52,7 @@ class SendMsgBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
self.send_message(mto=self.recipient, self.send_message(mto=self.recipient,
mbody=self.msg, mbody=self.msg,
@@ -107,4 +107,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(forever=False) xmpp.process()

View File

@@ -18,6 +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):
@@ -32,7 +33,8 @@ class AvatarSetter(slixmpp.ClientXMPP):
self.filepath = filepath self.filepath = filepath
async def start(self, event): @asyncio.coroutine
def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -46,7 +48,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
avatar_file = None avatar_file = None
try: try:
@@ -66,20 +68,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
used_xep84 = False used_xep84 = False
print('Publish XEP-0084 avatar data') print('Publish XEP-0084 avatar data')
result = await self['xep_0084'].publish_avatar(avatar) result = yield from self['xep_0084'].publish_avatar(avatar)
if isinstance(result, XMPPError): if isinstance(result, XMPPError):
print('Could not publish XEP-0084 avatar') print('Could not publish XEP-0084 avatar')
else: else:
used_xep84 = True used_xep84 = True
print('Update vCard with avatar') print('Update vCard with avatar')
result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
if isinstance(result, XMPPError): if isinstance(result, XMPPError):
print('Could not set vCard avatar') print('Could not set vCard avatar')
if used_xep84: if used_xep84:
print('Advertise XEP-0084 avatar metadata') print('Advertise XEP-0084 avatar metadata')
result = await self['xep_0084'].publish_avatar_metadata([ result = yield from self['xep_0084'].publish_avatar_metadata([
{'id': avatar_id, {'id': avatar_id,
'type': avatar_type, 'type': avatar_type,
'bytes': avatar_bytes} 'bytes': avatar_bytes}
@@ -137,4 +139,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(forever=False) xmpp.process()

View File

@@ -60,7 +60,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP):
# MUC messages and error messages. # MUC messages and error messages.
self.add_event_handler("message", self.message) self.add_event_handler("message", self.message)
async def start(self, event): def start(self, event):
""" """
Process the session_start event. Process the session_start event.
@@ -74,7 +74,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP):
data. data.
""" """
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
def message(self, msg): def message(self, msg):
""" """

View File

@@ -22,7 +22,7 @@ from slixmpp import ClientXMPP
class LocationBot(ClientXMPP): class LocationBot(ClientXMPP):
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super(LocationBot, self).__init__(jid, password)
self.add_event_handler('session_start', self.start) self.add_event_handler('session_start', self.start)
self.add_event_handler('user_location_publish', self.add_event_handler('user_location_publish',
@@ -38,9 +38,9 @@ class LocationBot(ClientXMPP):
self.current_tune = None self.current_tune = None
async def start(self, event): def start(self, event):
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
self['xep_0115'].update_caps() self['xep_0115'].update_caps()
print("Using freegeoip.net to get geolocation.") print("Using freegeoip.net to get geolocation.")

View File

@@ -17,7 +17,7 @@ from slixmpp import ClientXMPP
class TuneBot(ClientXMPP): class TuneBot(ClientXMPP):
def __init__(self, jid, password): def __init__(self, jid, password):
super().__init__(jid, password) super(TuneBot, self).__init__(jid, password)
# Check for the current song every 5 seconds. # Check for the current song every 5 seconds.
self.schedule('Check Current Tune', 5, self._update_tune, repeat=True) self.schedule('Check Current Tune', 5, self._update_tune, repeat=True)
@@ -35,9 +35,9 @@ class TuneBot(ClientXMPP):
self.current_tune = None self.current_tune = None
async def start(self, event): def start(self, event):
self.send_presence() self.send_presence()
await self.get_roster() self.get_roster()
self['xep_0115'].update_caps() self['xep_0115'].update_caps()
def _update_tune(self): def _update_tune(self):

View File

@@ -7,20 +7,26 @@
# This software is licensed as described in the README.rst and LICENSE # This software is licensed as described in the README.rst and LICENSE
# file, which you should have received as part of this distribution. # file, which you should have received as part of this distribution.
import os
from pathlib import Path from pathlib import Path
from subprocess import call, DEVNULL, check_output, CalledProcessError
from tempfile import TemporaryFile
try: try:
from setuptools import setup from setuptools import setup
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__
VERSION = __version__ VERSION = __version__
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).') DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, '
'Google Talk, etc).')
with open('README.rst', encoding='utf8') as readme: with open('README.rst', encoding='utf8') as readme:
LONG_DESCRIPTION = readme.read() LONG_DESCRIPTION = readme.read()
@@ -28,48 +34,12 @@ CLASSIFIERS = [
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.8',
'Topic :: Internet :: XMPP',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
] ]
packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')] packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')]
def check_include(library_name, header):
command = [os.environ.get('PKG_CONFIG', 'pkg-config'), '--cflags', library_name]
try:
cflags = check_output(command).decode('utf-8').split()
except FileNotFoundError:
print('pkg-config not found.')
return False
except CalledProcessError:
# pkg-config already prints the missing libraries on stderr.
return False
command = [os.environ.get('CC', 'cc')] + cflags + ['-E', '-']
with TemporaryFile('w+') as c_file:
c_file.write('#include <%s>' % header)
c_file.seek(0)
try:
return call(command, stdin=c_file, stdout=DEVNULL, stderr=DEVNULL) == 0
except FileNotFoundError:
print('%s headers not found.' % library_name)
return False
HAS_PYTHON_HEADERS = check_include('python3', 'Python.h')
HAS_STRINGPREP_HEADERS = check_include('libidn', 'stringprep.h')
ext_modules = None
if HAS_PYTHON_HEADERS and HAS_STRINGPREP_HEADERS:
try:
from Cython.Build import cythonize
except ImportError:
print('Cython not found, falling back to the slow stringprep module.')
else:
ext_modules = cythonize('slixmpp/stringprep.pyx')
else:
print('Falling back to the slow stringprep module.')
setup( setup(
name="slixmpp", name="slixmpp",
version=VERSION, version=VERSION,
@@ -77,12 +47,12 @@ setup(
long_description=LONG_DESCRIPTION, long_description=LONG_DESCRIPTION,
author='Florent Le Coz', author='Florent Le Coz',
author_email='louiz@louiz.org', author_email='louiz@louiz.org',
url='https://lab.louiz.org/poezio/slixmpp', url='https://dev.louiz.org/projects/slixmpp',
license='MIT', license='MIT',
platforms=['any'], platforms=['any'],
packages=packages, packages=packages,
ext_modules=ext_modules, ext_modules=ext_modules,
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'], install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
classifiers=CLASSIFIERS, classifiers=CLASSIFIERS,
cmdclass={'test': TestCommand} cmdclass={'test': TestCommand}
) )

View File

@@ -6,13 +6,11 @@
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())
import asyncio
# Required for python < 3.7 to use the old ssl implementation
# and manage to do starttls as an unintended side effect
asyncio.sslproto._is_sslproto_available = lambda: False
from slixmpp.stanza import Message, Presence, Iq from slixmpp.stanza import Message, Presence, Iq
from slixmpp.jid import JID, InvalidJID from slixmpp.jid import JID, InvalidJID

View File

@@ -12,8 +12,8 @@
:license: MIT, see LICENSE for more details :license: MIT, see LICENSE for more details
""" """
import asyncio
import logging import logging
import threading
from slixmpp import plugins, roster, stanza from slixmpp import plugins, roster, stanza
from slixmpp.api import APIRegistry from slixmpp.api import APIRegistry
@@ -21,6 +21,7 @@ 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.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
@@ -69,7 +70,7 @@ class BaseXMPP(XMLStream):
#: redirections that will be followed before quitting. #: redirections that will be followed before quitting.
self.max_redirects = 5 self.max_redirects = 5
self.session_bind_event = asyncio.Event() self.session_bind_event = threading.Event()
#: A dictionary mapping plugin names to plugins. #: A dictionary mapping plugin names to plugins.
self.plugin = PluginManager(self) self.plugin = PluginManager(self)
@@ -104,15 +105,12 @@ class BaseXMPP(XMLStream):
#: :attr:`use_message_ids` to `True` will assign all outgoing #: :attr:`use_message_ids` to `True` will assign all outgoing
#: messages an ID. Some plugin features require enabling #: messages an ID. Some plugin features require enabling
#: this option. #: this option.
self.use_message_ids = True self.use_message_ids = False
#: Presence updates may optionally be tagged with ID values. #: Presence updates may optionally be tagged with ID values.
#: Setting :attr:`use_message_ids` to `True` will assign all #: Setting :attr:`use_message_ids` to `True` will assign all
#: outgoing messages an ID. #: outgoing messages an ID.
self.use_presence_ids = True self.use_presence_ids = False
#: XEP-0359 <origin-id/> tag that gets added to <message/> stanzas.
self.use_origin_id = True
#: The API registry is a way to process callbacks based on #: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is #: JID+node combinations. Each callback in the registry is
@@ -196,6 +194,7 @@ class BaseXMPP(XMLStream):
# Initialize a few default stanza plugins. # Initialize a few default stanza plugins.
register_stanza_plugin(Iq, Roster) register_stanza_plugin(Iq, Roster)
register_stanza_plugin(Message, Nick)
def start_stream_handler(self, xml): def start_stream_handler(self, xml):
"""Save the stream ID once the streams have been established. """Save the stream ID once the streams have been established.
@@ -688,6 +687,7 @@ class BaseXMPP(XMLStream):
self.address = (host, port) self.address = (host, port)
self.default_domain = host self.default_domain = host
self.dns_records = None self.dns_records = None
self.reconnect_delay = None
self.reconnect() self.reconnect()
def _handle_message(self, msg): def _handle_message(self, msg):
@@ -753,9 +753,6 @@ class BaseXMPP(XMLStream):
Update the roster with presence information. Update the roster with presence information.
""" """
if self.roster[presence['from']].ignore_updates:
return
if not self.is_component and not presence['to'].bare: if not self.is_component and not presence['to'].bare:
presence['to'] = self.boundjid presence['to'] = self.boundjid

View File

@@ -12,16 +12,14 @@
:license: MIT, see LICENSE for more details :license: MIT, see LICENSE for more details
""" """
import asyncio
import logging import logging
from slixmpp.jid import JID
from slixmpp.stanza import StreamFeatures from slixmpp.stanza import StreamFeatures
from slixmpp.basexmpp import BaseXMPP from slixmpp.basexmpp import BaseXMPP
from slixmpp.exceptions import XMPPError from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream import XMLStream from slixmpp.xmlstream import XMLStream
from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath
from slixmpp.xmlstream.handler import Callback, CoroutineCallback from slixmpp.xmlstream.handler import Callback
# Flag indicating if DNS SRV records are available for use. # Flag indicating if DNS SRV records are available for use.
try: try:
@@ -106,24 +104,13 @@ class ClientXMPP(BaseXMPP):
self.register_stanza(StreamFeatures) self.register_stanza(StreamFeatures)
self.register_handler( self.register_handler(
CoroutineCallback('Stream Features', Callback('Stream Features',
MatchXPath('{%s}features' % self.stream_ns), MatchXPath('{%s}features' % self.stream_ns),
self._handle_stream_features)) self._handle_stream_features))
def roster_push_filter(iq):
from_ = iq['from']
if from_ and from_ != JID('') and from_ != self.boundjid.bare:
reply = iq.reply()
reply['type'] = 'error'
reply['error']['type'] = 'cancel'
reply['error']['code'] = 503
reply['error']['condition'] = 'service-unavailable'
reply.send()
return
self.event('roster_update', iq)
self.register_handler( self.register_handler(
Callback('Roster Update', Callback('Roster Update',
StanzaPath('iq@type=set/roster'), StanzaPath('iq@type=set/roster'),
roster_push_filter)) lambda iq: self.event('roster_update', iq)))
# Setup default stream features # Setup default stream features
self.register_plugin('feature_starttls') self.register_plugin('feature_starttls')
@@ -153,11 +140,8 @@ 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 force_starttls: Indicates that negotiation should be aborted :param use_tls: Indicates if TLS should be used for the
if the server does not advertise support for connection. Defaults to ``True``.
STARTTLS. Defaults to ``True``.
:param disable_starttls: Disables TLS for the connection.
Defaults to ``False``.
:param use_ssl: Indicates if the older SSL connection method :param use_ssl: Indicates if the older SSL connection method
should be used. Defaults to ``False``. should be used. Defaults to ``False``.
""" """
@@ -255,7 +239,7 @@ class ClientXMPP(BaseXMPP):
orig_cb(resp) orig_cb(resp)
callback = wrapped callback = wrapped
return iq.send(callback, timeout, timeout_callback) iq.send(callback, timeout, timeout_callback)
def _reset_connection_state(self, event=None): def _reset_connection_state(self, event=None):
#TODO: Use stream state here #TODO: Use stream state here
@@ -265,7 +249,7 @@ class ClientXMPP(BaseXMPP):
self.bindfail = False self.bindfail = False
self.features = set() self.features = set()
async def _handle_stream_features(self, features): def _handle_stream_features(self, features):
"""Process the received stream features. """Process the received stream features.
:param features: The features stanza. :param features: The features stanza.
@@ -273,11 +257,7 @@ class ClientXMPP(BaseXMPP):
for order, name in self._stream_feature_order: for order, name in self._stream_feature_order:
if name in features['features']: if name in features['features']:
handler, restart = self._stream_feature_handlers[name] handler, restart = self._stream_feature_handlers[name]
if asyncio.iscoroutinefunction(handler): if handler(features) and restart:
result = await handler(features)
else:
result = handler(features)
if result and restart:
# Don't continue if the feature requires # Don't continue if the feature requires
# restarting the XML stream. # restarting the XML stream.
return True return True

View File

@@ -77,7 +77,7 @@ class IqTimeout(XMPPError):
""" """
def __init__(self, iq): def __init__(self, iq):
super().__init__( super(IqTimeout, self).__init__(
condition='remote-server-timeout', condition='remote-server-timeout',
etype='cancel') etype='cancel')
@@ -94,7 +94,7 @@ class IqError(XMPPError):
""" """
def __init__(self, iq): def __init__(self, iq):
super().__init__( super(IqError, self).__init__(
condition=iq['error']['condition'], condition=iq['error']['condition'],
text=iq['error']['text'], text=iq['error']['text'],
etype=iq['error']['type']) etype=iq['error']['type'])

View File

@@ -13,3 +13,7 @@ from slixmpp.features.feature_bind.stanza import Bind
register_plugin(FeatureBind) register_plugin(FeatureBind)
# Retain some backwards compatibility
feature_bind = FeatureBind

View File

@@ -6,7 +6,6 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import asyncio
import logging import logging
from slixmpp.jid import JID from slixmpp.jid import JID
@@ -35,7 +34,7 @@ class FeatureBind(BasePlugin):
register_stanza_plugin(Iq, stanza.Bind) register_stanza_plugin(Iq, stanza.Bind)
register_stanza_plugin(StreamFeatures, stanza.Bind) register_stanza_plugin(StreamFeatures, stanza.Bind)
async def _handle_bind_resource(self, features): def _handle_bind_resource(self, features):
""" """
Handle requesting a specific resource. Handle requesting a specific resource.
@@ -50,7 +49,7 @@ class FeatureBind(BasePlugin):
if self.xmpp.requested_jid.resource: if self.xmpp.requested_jid.resource:
iq['bind']['resource'] = self.xmpp.requested_jid.resource iq['bind']['resource'] = self.xmpp.requested_jid.resource
await 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']) self.xmpp.boundjid = JID(response['bind']['jid'])

View File

@@ -16,6 +16,6 @@ class Bind(ElementBase):
name = 'bind' name = 'bind'
namespace = 'urn:ietf:params:xml:ns:xmpp-bind' namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
interfaces = {'resource', 'jid'} interfaces = set(('resource', 'jid'))
sub_interfaces = interfaces sub_interfaces = interfaces
plugin_attrib = 'bind' plugin_attrib = 'bind'

View File

@@ -16,3 +16,7 @@ from slixmpp.features.feature_mechanisms.stanza import Failure
register_plugin(FeatureMechanisms) register_plugin(FeatureMechanisms)
# Retain some backwards compatibility
feature_mechanisms = FeatureMechanisms

View File

@@ -49,7 +49,7 @@ class FeatureMechanisms(BasePlugin):
if self.security_callback is None: if self.security_callback is None:
self.security_callback = self._default_security self.security_callback = self._default_security
creds = self.sasl_callback({'username'}, set()) creds = self.sasl_callback(set(['username']), set())
if not self.use_mech and not creds['username']: if not self.use_mech and not creds['username']:
self.use_mech = 'ANONYMOUS' self.use_mech = 'ANONYMOUS'
@@ -97,9 +97,12 @@ class FeatureMechanisms(BasePlugin):
jid = self.xmpp.requested_jid.bare jid = self.xmpp.requested_jid.bare
result[value] = creds.get('email', jid) result[value] = creds.get('email', jid)
elif value == 'channel_binding': elif value == 'channel_binding':
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)): if hasattr(self.xmpp.socket, 'get_channel_binding'):
result[value] = self.xmpp.socket.get_channel_binding() result[value] = self.xmpp.socket.get_channel_binding()
else: else:
log.debug("Channel binding not supported.")
log.debug("Use Python 3.3+ for channel binding and " + \
"SCRAM-SHA-1-PLUS support")
result[value] = None result[value] = None
elif value == 'host': elif value == 'host':
result[value] = creds.get('host', self.xmpp.requested_jid.domain) result[value] = creds.get('host', self.xmpp.requested_jid.domain)
@@ -119,7 +122,7 @@ class FeatureMechanisms(BasePlugin):
if value == 'encrypted': if value == 'encrypted':
if 'starttls' in self.xmpp.features: if 'starttls' in self.xmpp.features:
result[value] = True result[value] = True
elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)): elif isinstance(self.xmpp.socket, ssl.SSLSocket):
result[value] = True result[value] = True
else: else:
result[value] = False result[value] = False

View File

@@ -19,12 +19,12 @@ class Auth(StanzaBase):
name = 'auth' name = 'auth'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = {'mechanism', 'value'} interfaces = set(('mechanism', 'value'))
plugin_attrib = name plugin_attrib = name
#: Some SASL mechs require sending values as is, #: Some SASL mechs require sending values as is,
#: without converting base64. #: without converting base64.
plain_mechs = {'X-MESSENGER-OAUTH2'} plain_mechs = set(['X-MESSENGER-OAUTH2'])
def setup(self, xml): def setup(self, xml):
StanzaBase.setup(self, xml) StanzaBase.setup(self, xml)

View File

@@ -19,7 +19,7 @@ class Challenge(StanzaBase):
name = 'challenge' name = 'challenge'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = {'value'} interfaces = set(('value',))
plugin_attrib = name plugin_attrib = name
def setup(self, xml): def setup(self, xml):

View File

@@ -16,14 +16,13 @@ class Failure(StanzaBase):
name = 'failure' name = 'failure'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = {'condition', 'text'} interfaces = set(('condition', 'text'))
plugin_attrib = name plugin_attrib = name
sub_interfaces = {'text'} sub_interfaces = set(('text',))
conditions = {'aborted', 'account-disabled', 'credentials-expired', conditions = set(('aborted', 'account-disabled', 'credentials-expired',
'encryption-required', 'incorrect-encoding', 'encryption-required', 'incorrect-encoding', 'invalid-authzid',
'invalid-authzid', 'invalid-mechanism', 'malformed-request', 'invalid-mechanism', 'malformed-request', 'mechansism-too-weak',
'mechansism-too-weak', 'not-authorized', 'not-authorized', 'temporary-auth-failure'))
'temporary-auth-failure'}
def setup(self, xml=None): def setup(self, xml=None):
""" """

View File

@@ -16,7 +16,7 @@ class Mechanisms(ElementBase):
name = 'mechanisms' name = 'mechanisms'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = {'mechanisms', 'required'} interfaces = set(('mechanisms', 'required'))
plugin_attrib = name plugin_attrib = name
is_extension = True is_extension = True
@@ -29,7 +29,7 @@ class Mechanisms(ElementBase):
""" """
""" """
results = [] results = []
mechs = self.xml.findall('{%s}mechanism' % self.namespace) mechs = self.findall('{%s}mechanism' % self.namespace)
if mechs: if mechs:
for mech in mechs: for mech in mechs:
results.append(mech.text) results.append(mech.text)
@@ -47,7 +47,7 @@ class Mechanisms(ElementBase):
def del_mechanisms(self): def del_mechanisms(self):
""" """
""" """
mechs = self.xml.findall('{%s}mechanism' % self.namespace) mechs = self.findall('{%s}mechanism' % self.namespace)
if mechs: if mechs:
for mech in mechs: for mech in mechs:
self.xml.remove(mech) self.xml.remove(mech)

View File

@@ -19,7 +19,7 @@ class Response(StanzaBase):
name = 'response' name = 'response'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = {'value'} interfaces = set(('value',))
plugin_attrib = name plugin_attrib = name
def setup(self, xml): def setup(self, xml):

View File

@@ -18,7 +18,7 @@ class Success(StanzaBase):
name = 'success' name = 'success'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl' namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = {'value'} interfaces = set(['value'])
plugin_attrib = name plugin_attrib = name
def setup(self, xml): def setup(self, xml):

View File

@@ -13,3 +13,7 @@ from slixmpp.features.feature_rosterver.stanza import RosterVer
register_plugin(FeatureRosterVer) register_plugin(FeatureRosterVer)
# Retain some backwards compatibility
feature_rosterver = FeatureRosterVer

View File

@@ -13,3 +13,7 @@ from slixmpp.features.feature_session.stanza import Session
register_plugin(FeatureSession) register_plugin(FeatureSession)
# Retain some backwards compatibility
feature_session = FeatureSession

View File

@@ -6,7 +6,6 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import asyncio
import logging import logging
from slixmpp.stanza import Iq, StreamFeatures from slixmpp.stanza import Iq, StreamFeatures
@@ -35,22 +34,17 @@ class FeatureSession(BasePlugin):
register_stanza_plugin(Iq, stanza.Session) register_stanza_plugin(Iq, stanza.Session)
register_stanza_plugin(StreamFeatures, stanza.Session) register_stanza_plugin(StreamFeatures, stanza.Session)
async def _handle_start_session(self, features): def _handle_start_session(self, features):
""" """
Handle the start of the session. Handle the start of the session.
Arguments: Arguments:
feature -- The stream features element. feature -- The stream features element.
""" """
if features['session']['optional']:
self.xmpp.sessionstarted = True
self.xmpp.event('session_start')
return
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'set' iq['type'] = 'set'
iq.enable('session') iq.enable('session')
await iq.send(callback=self._on_start_session_response) iq.send(callback=self._on_start_session_response)
def _on_start_session_response(self, response): def _on_start_session_response(self, response):
self.xmpp.features.add('session') self.xmpp.features.add('session')

View File

@@ -6,7 +6,7 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
from slixmpp.xmlstream import ElementBase, ET from slixmpp.xmlstream import ElementBase
class Session(ElementBase): class Session(ElementBase):
@@ -16,19 +16,5 @@ class Session(ElementBase):
name = 'session' name = 'session'
namespace = 'urn:ietf:params:xml:ns:xmpp-session' namespace = 'urn:ietf:params:xml:ns:xmpp-session'
interfaces = {'optional'} interfaces = set()
plugin_attrib = 'session' plugin_attrib = 'session'
def get_optional(self):
return self.xml.find('{%s}optional' % self.namespace) is not None
def set_optional(self, value):
if value:
optional = ET.Element('{%s}optional' % self.namespace)
self.xml.append(optional)
else:
self.del_optional()
def del_optional(self):
optional = self.xml.find('{%s}optional' % self.namespace)
self.xml.remove(optional)

View File

@@ -13,3 +13,7 @@ from slixmpp.features.feature_starttls.stanza import *
register_plugin(FeatureSTARTTLS) register_plugin(FeatureSTARTTLS)
# Retain some backwards compatibility
feature_starttls = FeatureSTARTTLS

View File

@@ -16,7 +16,7 @@ class STARTTLS(ElementBase):
name = 'starttls' name = 'starttls'
namespace = 'urn:ietf:params:xml:ns:xmpp-tls' namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
interfaces = {'required'} interfaces = set(('required',))
plugin_attrib = name plugin_attrib = name
def get_required(self): def get_required(self):

View File

@@ -12,7 +12,7 @@ from slixmpp.stanza import StreamFeatures
from slixmpp.xmlstream import register_stanza_plugin from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream.matcher import MatchXPath from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import CoroutineCallback from slixmpp.xmlstream.handler import Callback
from slixmpp.features.feature_starttls import stanza from slixmpp.features.feature_starttls import stanza
@@ -28,7 +28,7 @@ class FeatureSTARTTLS(BasePlugin):
def plugin_init(self): def plugin_init(self):
self.xmpp.register_handler( self.xmpp.register_handler(
CoroutineCallback('STARTTLS Proceed', Callback('STARTTLS Proceed',
MatchXPath(stanza.Proceed.tag_name()), MatchXPath(stanza.Proceed.tag_name()),
self._handle_starttls_proceed, self._handle_starttls_proceed,
instream=True)) instream=True))
@@ -58,8 +58,8 @@ class FeatureSTARTTLS(BasePlugin):
self.xmpp.send(features['starttls']) self.xmpp.send(features['starttls'])
return True return True
async def _handle_starttls_proceed(self, proceed): def _handle_starttls_proceed(self, proceed):
"""Restart the XML stream when TLS is accepted.""" """Restart the XML stream when TLS is accepted."""
log.debug("Starting TLS") log.debug("Starting TLS")
if await self.xmpp.start_tls(): if self.xmpp.start_tls():
self.xmpp.features.add('starttls') self.xmpp.features.add('starttls')

View File

@@ -16,7 +16,6 @@ import socket
from copy import deepcopy from copy import deepcopy
from functools import lru_cache from functools import lru_cache
from typing import Optional
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
@@ -72,7 +71,7 @@ def _parse_jid(data):
return node, domain, resource return node, domain, resource
def _validate_node(node: Optional[str]): def _validate_node(node):
"""Validate the local, or username, portion of a JID. """Validate the local, or username, portion of a JID.
:raises InvalidJID: :raises InvalidJID:
@@ -80,7 +79,7 @@ def _validate_node(node: Optional[str]):
:returns: The local portion of a JID, as validated by nodeprep. :returns: The local portion of a JID, as validated by nodeprep.
""" """
if node is None: if node is None:
return '' return None
try: try:
node = nodeprep(node) node = nodeprep(node)
@@ -94,7 +93,7 @@ def _validate_node(node: Optional[str]):
return node return node
def _validate_domain(domain: str): def _validate_domain(domain):
"""Validate the domain portion of a JID. """Validate the domain portion of a JID.
IP literal addresses are left as-is, if valid. Domain names IP literal addresses are left as-is, if valid. Domain names
@@ -153,7 +152,7 @@ def _validate_domain(domain: str):
return domain return domain
def _validate_resource(resource: Optional[str]): def _validate_resource(resource):
"""Validate the resource portion of a JID. """Validate the resource portion of a JID.
:raises InvalidJID: :raises InvalidJID:
@@ -161,7 +160,7 @@ def _validate_resource(resource: Optional[str]):
: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: if resource is None:
return '' return None
try: try:
resource = resourceprep(resource) resource = resourceprep(resource)
@@ -175,7 +174,7 @@ def _validate_resource(resource: Optional[str]):
return resource return resource
def _unescape_node(node: str): def _unescape_node(node):
"""Unescape a local portion of a JID. """Unescape a local portion of a JID.
.. note:: .. note::
@@ -200,11 +199,7 @@ def _unescape_node(node: str):
return ''.join(unescaped) return ''.join(unescaped)
def _format_jid( def _format_jid(local=None, domain=None, resource=None):
local: Optional[str] = None,
domain: Optional[str] = None,
resource: Optional[str] = None,
):
"""Format the given JID components into a full or bare JID. """Format the given JID components into a full or bare JID.
:param string local: Optional. The local portion of the JID. :param string local: Optional. The local portion of the JID.
@@ -213,15 +208,16 @@ def _format_jid(
:return: A full or bare JID string. :return: A full or bare JID string.
""" """
if domain is None: result = []
return ''
if local is not None: if local is not None:
result = local + '@' + domain result.append(local)
else: result.append('@')
result = domain if domain is not None:
result.append(domain)
if resource is not None: if resource is not None:
result += '/' + resource result.append('/')
return result result.append(resource)
return ''.join(result)
class InvalidJID(ValueError): class InvalidJID(ValueError):
@@ -242,17 +238,12 @@ class UnescapedJID:
__slots__ = ('_node', '_domain', '_resource') __slots__ = ('_node', '_domain', '_resource')
def __init__( def __init__(self, node, domain, resource):
self,
node: Optional[str],
domain: Optional[str],
resource: Optional[str],
):
self._node = node self._node = node
self._domain = domain self._domain = domain
self._resource = resource self._resource = resource
def __getattribute__(self, name: str): 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,
@@ -309,23 +300,19 @@ class JID:
:raises InvalidJID: :raises InvalidJID:
""" """
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full') __slots__ = ('_node', '_domain', '_resource')
def __init__(self, jid: Optional[str] = None): def __init__(self, jid=None):
if not jid: if not jid:
self._node = '' self._node = None
self._domain = '' self._domain = None
self._resource = '' self._resource = None
self._bare = ''
self._full = ''
return
elif not isinstance(jid, JID): elif not isinstance(jid, JID):
self._node, self._domain, self._resource = _parse_jid(jid) self._node, self._domain, self._resource = _parse_jid(jid)
else: else:
self._node = jid._node self._node = jid._node
self._domain = jid._domain self._domain = jid._domain
self._resource = jid._resource self._resource = jid._resource
self._update_bare_full()
def unescape(self): def unescape(self):
"""Return an unescaped JID object. """Return an unescaped JID object.
@@ -342,80 +329,104 @@ class JID:
self._domain, self._domain,
self._resource) self._resource)
def _update_bare_full(self):
"""Format the given JID into a bare and a full JID.
"""
self._bare = (self._node + '@' + self._domain
if self._node
else self._domain)
self._full = (self._bare + '/' + self._resource
if self._resource
else self._bare)
@property @property
def node(self): def node(self):
return self._node return self._node or ''
@property
def user(self):
return self._node or ''
@property
def local(self):
return self._node or ''
@property
def username(self):
return self._node or ''
@property @property
def domain(self): def domain(self):
return self._domain return self._domain or ''
@property
def server(self):
return self._domain or ''
@property
def host(self):
return self._domain or ''
@property @property
def resource(self): def resource(self):
return self._resource return self._resource or ''
@property @property
def bare(self): def bare(self):
return self._bare return _format_jid(self._node, self._domain)
@property @property
def full(self): def full(self):
return self._full return _format_jid(self._node, self._domain, self._resource)
@property
def jid(self):
return _format_jid(self._node, self._domain, self._resource)
@node.setter @node.setter
def node(self, value: str): def node(self, value):
self._node = _validate_node(value)
@user.setter
def user(self, value):
self._node = _validate_node(value)
@local.setter
def local(self, value):
self._node = _validate_node(value)
@username.setter
def username(self, value):
self._node = _validate_node(value) self._node = _validate_node(value)
self._update_bare_full()
@domain.setter @domain.setter
def domain(self, value: str): def domain(self, value):
self._domain = _validate_domain(value)
@server.setter
def server(self, value):
self._domain = _validate_domain(value)
@host.setter
def host(self, value):
self._domain = _validate_domain(value) self._domain = _validate_domain(value)
self._update_bare_full()
@bare.setter @bare.setter
def bare(self, value: str): def bare(self, value):
node, domain, resource = _parse_jid(value) node, domain, resource = _parse_jid(value)
assert not resource assert not resource
self._node = node self._node = node
self._domain = domain self._domain = domain
self._update_bare_full()
@resource.setter @resource.setter
def resource(self, value: str): def resource(self, value):
self._resource = _validate_resource(value) self._resource = _validate_resource(value)
self._update_bare_full()
@full.setter @full.setter
def full(self, value: str): def full(self, value):
self._node, self._domain, self._resource = _parse_jid(value) self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full()
user = node @jid.setter
local = node def jid(self, value):
username = node self._node, self._domain, self._resource = _parse_jid(value)
server = domain
host = domain
jid = full
def __str__(self): def __str__(self):
"""Use the full JID as the string value.""" """Use the full JID as the string value."""
return self._full 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._full return _format_jid(self._node, self._domain, self._resource)
# pylint: disable=W0212 # pylint: disable=W0212
def __eq__(self, other): def __eq__(self, other):
@@ -423,10 +434,7 @@ class JID:
if isinstance(other, UnescapedJID): if isinstance(other, UnescapedJID):
return False return False
if not isinstance(other, JID): if not isinstance(other, JID):
try: other = JID(other)
other = JID(other)
except InvalidJID:
return NotImplemented
return (self._node == other._node and return (self._node == other._node and
self._domain == other._domain and self._domain == other._domain and
@@ -438,4 +446,4 @@ class JID:
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._full) return hash(_format_jid(self._node, self._domain, self._resource))

View File

@@ -85,6 +85,4 @@ __all__ = [
'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 'xep_0332', # HTTP Over XMPP Transport
'protoxep_reactions', # https://dino.im/xeps/reactions.html
'protoxep_occupantid', # https://dino.im/xeps/occupant-id.html
] ]

View File

@@ -308,7 +308,7 @@ class BasePlugin(object):
if key in self.default_config: if key in self.default_config:
self.config[key] = value self.config[key] = value
else: else:
super().__setattr__(key, value) super(BasePlugin, self).__setattr__(key, value)
def _init(self): def _init(self):
"""Initialize plugin state, such as registering event handlers. """Initialize plugin state, such as registering event handlers.

View File

@@ -21,15 +21,15 @@ class GmailQuery(ElementBase):
namespace = 'google:mail:notify' namespace = 'google:mail:notify'
name = 'query' name = 'query'
plugin_attrib = 'gmail' plugin_attrib = 'gmail'
interfaces = {'newer-than-time', 'newer-than-tid', 'q', 'search'} interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
def get_search(self): def getSearch(self):
return self['q'] return self['q']
def set_search(self, search): def setSearch(self, search):
self['q'] = search self['q'] = search
def del_search(self): def delSearch(self):
del self['q'] del self['q']
@@ -37,20 +37,20 @@ class MailBox(ElementBase):
namespace = 'google:mail:notify' namespace = 'google:mail:notify'
name = 'mailbox' name = 'mailbox'
plugin_attrib = 'mailbox' plugin_attrib = 'mailbox'
interfaces = {'result-time', 'total-matched', 'total-estimate', interfaces = set(('result-time', 'total-matched', 'total-estimate',
'url', 'threads', 'matched', 'estimate'} 'url', 'threads', 'matched', 'estimate'))
def get_threads(self): def getThreads(self):
threads = [] threads = []
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
MailThread.name)): MailThread.name)):
threads.append(MailThread(xml=threadXML, parent=None)) threads.append(MailThread(xml=threadXML, parent=None))
return threads return threads
def get_matched(self): def getMatched(self):
return self['total-matched'] return self['total-matched']
def get_estimate(self): def getEstimate(self):
return self['total-estimate'] == '1' return self['total-estimate'] == '1'
@@ -58,11 +58,11 @@ class MailThread(ElementBase):
namespace = 'google:mail:notify' namespace = 'google:mail:notify'
name = 'mail-thread-info' name = 'mail-thread-info'
plugin_attrib = 'thread' plugin_attrib = 'thread'
interfaces = {'tid', 'participation', 'messages', 'date', interfaces = set(('tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'} 'senders', 'url', 'labels', 'subject', 'snippet'))
sub_interfaces = {'labels', 'subject', 'snippet'} sub_interfaces = set(('labels', 'subject', 'snippet'))
def get_senders(self): def getSenders(self):
senders = [] senders = []
sendersXML = self.xml.find('{%s}senders' % self.namespace) sendersXML = self.xml.find('{%s}senders' % self.namespace)
if sendersXML is not None: if sendersXML is not None:
@@ -75,12 +75,12 @@ class MailSender(ElementBase):
namespace = 'google:mail:notify' namespace = 'google:mail:notify'
name = 'sender' name = 'sender'
plugin_attrib = 'sender' plugin_attrib = 'sender'
interfaces = {'address', 'name', 'originator', 'unread'} interfaces = set(('address', 'name', 'originator', 'unread'))
def get_originator(self): def getOriginator(self):
return self.xml.attrib.get('originator', '0') == '1' return self.xml.attrib.get('originator', '0') == '1'
def get_unread(self): def getUnread(self):
return self.xml.attrib.get('unread', '0') == '1' return self.xml.attrib.get('unread', '0') == '1'

View File

@@ -1,47 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin, BasePlugin
from slixmpp.plugins.google.gmail import Gmail
from slixmpp.plugins.google.auth import GoogleAuth
from slixmpp.plugins.google.settings import GoogleSettings
from slixmpp.plugins.google.nosave import GoogleNoSave
class Google(BasePlugin):
"""
Google: Custom GTalk Features
Also see: <https://developers.google.com/talk/jep_extensions/extensions>
"""
name = 'google'
description = 'Google: Custom GTalk Features'
dependencies = set([
'gmail',
'google_settings',
'google_nosave',
'google_auth'
])
def __getitem__(self, attr):
if attr in ('settings', 'nosave', 'auth'):
return self.xmpp['google_%s' % attr]
elif attr == 'gmail':
return self.xmpp['gmail']
else:
raise KeyError(attr)
register_plugin(Gmail)
register_plugin(GoogleAuth)
register_plugin(GoogleSettings)
register_plugin(GoogleNoSave)
register_plugin(Google)

View File

@@ -1,10 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.google.auth import stanza
from slixmpp.plugins.google.auth.auth import GoogleAuth

View File

@@ -1,47 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.plugins.google.auth import stanza
class GoogleAuth(BasePlugin):
"""
Google: Auth Extensions (JID Domain Discovery, OAuth2)
Also see:
<https://developers.google.com/talk/jep_extensions/jid_domain_change>
<https://developers.google.com/talk/jep_extensions/oauth>
"""
name = 'google_auth'
description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)'
dependencies = set(['feature_mechanisms'])
stanza = stanza
def plugin_init(self):
self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga'
register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth,
stanza.GoogleAuth)
self.xmpp.add_filter('out', self._auth)
def plugin_end(self):
self.xmpp.del_filter('out', self._auth)
def _auth(self, stanza):
if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth):
stanza.stream = self.xmpp
stanza['google']['client_uses_full_bind_result'] = True
if stanza['mechanism'] == 'X-OAUTH2':
stanza['google']['service'] = 'oauth2'
print(stanza)
return stanza

View File

@@ -13,7 +13,7 @@ class GoogleAuth(ElementBase):
name = 'auth' name = 'auth'
namespace = 'http://www.google.com/talk/protocol/auth' namespace = 'http://www.google.com/talk/protocol/auth'
plugin_attrib = 'google' plugin_attrib = 'google'
interfaces = {'client_uses_full_bind_result', 'service'} interfaces = set(['client_uses_full_bind_result', 'service'])
discovery_attr= '{%s}client-uses-full-bind-result' % namespace discovery_attr= '{%s}client-uses-full-bind-result' % namespace
service_attr= '{%s}service' % namespace service_attr= '{%s}service' % namespace

View File

@@ -1,10 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.google.gmail import stanza
from slixmpp.plugins.google.gmail.notifications import Gmail

View File

@@ -1,101 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
class GmailQuery(ElementBase):
namespace = 'google:mail:notify'
name = 'query'
plugin_attrib = 'gmail'
interfaces = set(['newer_than_time', 'newer_than_tid', 'search'])
def get_search(self):
return self._get_attr('q', '')
def set_search(self, search):
self._set_attr('q', search)
def del_search(self):
self._del_attr('q')
def get_newer_than_time(self):
return self._get_attr('newer-than-time', '')
def set_newer_than_time(self, value):
self._set_attr('newer-than-time', value)
def del_newer_than_time(self):
self._del_attr('newer-than-time')
def get_newer_than_tid(self):
return self._get_attr('newer-than-tid', '')
def set_newer_than_tid(self, value):
self._set_attr('newer-than-tid', value)
def del_newer_than_tid(self):
self._del_attr('newer-than-tid')
class MailBox(ElementBase):
namespace = 'google:mail:notify'
name = 'mailbox'
plugin_attrib = 'gmail_messages'
interfaces = set(['result_time', 'url', 'matched', 'estimate'])
def get_matched(self):
return self._get_attr('total-matched', '')
def get_estimate(self):
return self._get_attr('total-estimate', '') == '1'
def get_result_time(self):
return self._get_attr('result-time', '')
class MailThread(ElementBase):
namespace = 'google:mail:notify'
name = 'mail-thread-info'
plugin_attrib = 'thread'
plugin_multi_attrib = 'threads'
interfaces = set(['tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'])
sub_interfaces = set(['labels', 'subject', 'snippet'])
def get_senders(self):
result = []
senders = self.xml.findall('{%s}senders/{%s}sender' % (
self.namespace, self.namespace))
for sender in senders:
result.append(MailSender(xml=sender))
return result
class MailSender(ElementBase):
namespace = 'google:mail:notify'
name = 'sender'
plugin_attrib = name
interfaces = set(['address', 'name', 'originator', 'unread'])
def get_originator(self):
return self.xml.attrib.get('originator', '0') == '1'
def get_unread(self):
return self.xml.attrib.get('unread', '0') == '1'
class NewMail(ElementBase):
namespace = 'google:mail:notify'
name = 'new-mail'
plugin_attrib = 'gmail_notification'
register_stanza_plugin(MailBox, MailThread, iterable=True)

View File

@@ -1,10 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.google.nosave import stanza
from slixmpp.plugins.google.nosave.nosave import GoogleNoSave

View File

@@ -1,78 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.stanza import Iq, Message
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.plugins.google.nosave import stanza
class GoogleNoSave(BasePlugin):
"""
Google: Off the Record Chats
NOTE: This is NOT an encryption method.
Also see <https://developers.google.com/talk/jep_extensions/otr>.
"""
name = 'google_nosave'
description = 'Google: Off the Record Chats'
dependencies = set(['google_settings'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.NoSave)
register_stanza_plugin(Iq, stanza.NoSaveQuery)
self.xmpp.register_handler(
Callback('Google Nosave',
StanzaPath('iq@type=set/google_nosave'),
self._handle_nosave_change))
def plugin_end(self):
self.xmpp.remove_handler('Google Nosave')
def enable(self, jid=None, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': False},
timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = True
return iq.send(timeout=timeout, callback=callback)
def disable(self, jid=None, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': True},
timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = False
return iq.send(timeout=timeout, callback=callback)
def get(self, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('google_nosave')
return iq.send(timeout=timeout, callback=callback)
def _handle_nosave_change(self, iq):
reply = self.xmpp.Iq()
reply['type'] = 'result'
reply['id'] = iq['id']
reply['to'] = iq['from']
reply.send()
self.xmpp.event('google_nosave_change', iq)

View File

@@ -14,7 +14,7 @@ class NoSave(ElementBase):
name = 'x' name = 'x'
namespace = 'google:nosave' namespace = 'google:nosave'
plugin_attrib = 'google_nosave' plugin_attrib = 'google_nosave'
interfaces = {'value'} interfaces = set(['value'])
def get_value(self): def get_value(self):
return self._get_attr('value', '') == 'enabled' return self._get_attr('value', '') == 'enabled'
@@ -35,7 +35,7 @@ class Item(ElementBase):
namespace = 'google:nosave' namespace = 'google:nosave'
plugin_attrib = 'item' plugin_attrib = 'item'
plugin_multi_attrib = 'items' plugin_multi_attrib = 'items'
interfaces = {'jid', 'source', 'value'} interfaces = set(['jid', 'source', 'value'])
def get_value(self): def get_value(self):
return self._get_attr('value', '') == 'enabled' return self._get_attr('value', '') == 'enabled'

View File

@@ -1,10 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.google.settings import stanza
from slixmpp.plugins.google.settings.settings import GoogleSettings

View File

@@ -1,110 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ET, ElementBase
class UserSettings(ElementBase):
name = 'usersetting'
namespace = 'google:setting'
plugin_attrib = 'google_settings'
interfaces = set(['auto_accept_suggestions',
'mail_notifications',
'archiving_enabled',
'gmail',
'email_verified',
'domain_privacy_notice',
'display_name'])
def _get_setting(self, setting):
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
if xml is not None:
return xml.attrib.get('value', '') == 'true'
return False
def _set_setting(self, setting, value):
self._del_setting(setting)
if value in (True, False):
xml = ET.Element('{%s}%s' % (self.namespace, setting))
xml.attrib['value'] = 'true' if value else 'false'
self.xml.append(xml)
def _del_setting(self, setting):
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
if xml is not None:
self.xml.remove(xml)
def get_display_name(self):
xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname'))
if xml is not None:
return xml.attrib.get('value', '')
return ''
def set_display_name(self, value):
self._del_setting(setting)
if value:
xml = ET.Element('{%s}%s' % (self.namespace, 'displayname'))
xml.attrib['value'] = value
self.xml.append(xml)
def del_display_name(self):
self._del_setting('displayname')
def get_auto_accept_suggestions(self):
return self._get_setting('autoacceptsuggestions')
def get_mail_notifications(self):
return self._get_setting('mailnotifications')
def get_archiving_enabled(self):
return self._get_setting('archivingenabled')
def get_gmail(self):
return self._get_setting('gmail')
def get_email_verified(self):
return self._get_setting('emailverified')
def get_domain_privacy_notice(self):
return self._get_setting('domainprivacynotice')
def set_auto_accept_suggestions(self, value):
self._set_setting('autoacceptsuggestions', value)
def set_mail_notifications(self, value):
self._set_setting('mailnotifications', value)
def set_archiving_enabled(self, value):
self._set_setting('archivingenabled', value)
def set_gmail(self, value):
self._set_setting('gmail', value)
def set_email_verified(self, value):
self._set_setting('emailverified', value)
def set_domain_privacy_notice(self, value):
self._set_setting('domainprivacynotice', value)
def del_auto_accept_suggestions(self):
self._del_setting('autoacceptsuggestions')
def del_mail_notifications(self):
self._del_setting('mailnotifications')
def del_archiving_enabled(self):
self._del_setting('archivingenabled')
def del_gmail(self):
self._del_setting('gmail')
def del_email_verified(self):
self._del_setting('emailverified')
def del_domain_privacy_notice(self):
self._del_setting('domainprivacynotice')

View File

@@ -1,12 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.protoxep_occupantid.occupantid import XEP_OccupantID
from slixmpp.plugins.protoxep_occupantid.stanza import OccupantID
register_plugin(XEP_OccupantID)

View File

@@ -1,23 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins import BasePlugin
from slixmpp.stanza import Message, Presence
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins.protoxep_occupantid import stanza
class XEP_OccupantID(BasePlugin):
name = 'protoxep_occupantid'
description = 'XEP-XXXX: Anonymous unique occupant identifiers for MUCs'
dependencies = set()
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.OccupantID)
register_stanza_plugin(Presence, stanza.OccupantID)

View File

@@ -1,16 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase
class OccupantID(ElementBase):
name = 'occupant-id'
plugin_attrib = 'occupant-id'
namespace = 'urn:xmpp:occupant-id:0'
interfaces = {'id'}

View File

@@ -1,11 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.protoxep_reactions.reactions import XEP_Reactions
register_plugin(XEP_Reactions)

View File

@@ -1,54 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from typing import Iterable
from slixmpp.plugins import BasePlugin
from slixmpp.stanza import Message
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.matcher import MatchXMLMask
from slixmpp.xmlstream.handler import Callback
from slixmpp.plugins.protoxep_reactions import stanza
class XEP_Reactions(BasePlugin):
name = 'protoxep_reactions'
description = 'XEP-XXXX: Message Reactions'
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):
self.xmpp.register_handler(
Callback(
'Reaction received',
MatchXMLMask('<message><reactions xmlns="urn:xmpp:reactions:0"/></message>'),
self._handle_reactions,
)
)
self.xmpp['xep_0030'].add_feature('urn:xmpp:reactions:0')
register_stanza_plugin(Message, stanza.Reactions)
def plugin_end(self):
self.xmpp.remove_handler('Reaction received')
self.xmpp['xep_0030'].remove_feature('urn:xmpp:reactions:0')
def _handle_reactions(self, message: Message):
self.xmpp.event('reactions', message)
@staticmethod
def set_reactions(message: Message, to_id: str, reactions: Iterable[str]):
"""
Add reactions to a Message object.
"""
reactions_stanza = stanza.Reactions()
reactions_stanza['to'] = to_id
for reaction in reactions:
reaction_stanza = stanza.Reaction()
reaction_stanza['value'] = reaction
reactions_stanza.append(reaction_stanza)
message.append(reactions_stanza)

View File

@@ -1,31 +0,0 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2019 Mathieu Pasquet
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
class Reactions(ElementBase):
name = 'reactions'
plugin_attrib = 'reactions'
namespace = 'urn:xmpp:reactions:0'
interfaces = {'to'}
class Reaction(ElementBase):
name = 'reaction'
namespace = 'urn:xmpp:reactions:0'
interfaces = {'value'}
def get_value(self) -> str:
return self.xml.text
def set_value(self, value: str):
self.xml.text = value
register_stanza_plugin(Reactions, Reaction, iterable=True)

View File

@@ -14,3 +14,9 @@ from slixmpp.plugins.xep_0004.dataforms import XEP_0004
register_plugin(XEP_0004) register_plugin(XEP_0004)
# Retain some backwards compatibility
xep_0004 = XEP_0004
xep_0004.makeForm = xep_0004.make_form
xep_0004.buildForm = xep_0004.build_form

View File

@@ -23,7 +23,7 @@ class XEP_0004(BasePlugin):
name = 'xep_0004' name = 'xep_0004'
description = 'XEP-0004: Data Forms' description = 'XEP-0004: Data Forms'
dependencies = {'xep_0030'} dependencies = set(['xep_0030'])
stanza = stanza stanza = stanza
def plugin_init(self): def plugin_init(self):

View File

@@ -14,21 +14,21 @@ class FormField(ElementBase):
name = 'field' name = 'field'
plugin_attrib = 'field' plugin_attrib = 'field'
plugin_multi_attrib = 'fields' plugin_multi_attrib = 'fields'
interfaces = {'answer', 'desc', 'required', 'value', interfaces = set(('answer', 'desc', 'required', 'value',
'label', 'type', 'var'} 'label', 'type', 'var'))
sub_interfaces = {'desc'} sub_interfaces = set(('desc',))
plugin_tag_map = {} plugin_tag_map = {}
plugin_attrib_map = {} plugin_attrib_map = {}
field_types = {'boolean', 'fixed', 'hidden', 'jid-multi', field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi',
'jid-single', 'list-multi', 'list-single', 'jid-single', 'list-multi', 'list-single',
'text-multi', 'text-private', 'text-single'} 'text-multi', 'text-private', 'text-single'))
true_values = {True, '1', 'true'} true_values = set((True, '1', 'true'))
option_types = {'list-multi', 'list-single'} option_types = set(('list-multi', 'list-single'))
multi_line_types = {'hidden', 'text-multi'} multi_line_types = set(('hidden', 'text-multi'))
multi_value_types = {'hidden', 'jid-multi', multi_value_types = set(('hidden', 'jid-multi',
'list-multi', 'text-multi'} 'list-multi', 'text-multi'))
def setup(self, xml=None): def setup(self, xml=None):
if ElementBase.setup(self, xml): if ElementBase.setup(self, xml):
@@ -164,8 +164,8 @@ class FieldOption(ElementBase):
namespace = 'jabber:x:data' namespace = 'jabber:x:data'
name = 'option' name = 'option'
plugin_attrib = 'option' plugin_attrib = 'option'
interfaces = {'label', 'value'} interfaces = set(('label', 'value'))
sub_interfaces = {'value'} sub_interfaces = set(('value',))
plugin_multi_attrib = 'options' plugin_multi_attrib = 'options'

View File

@@ -23,9 +23,9 @@ class Form(ElementBase):
namespace = 'jabber:x:data' namespace = 'jabber:x:data'
name = 'x' name = 'x'
plugin_attrib = 'form' plugin_attrib = 'form'
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', 'values')) interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
sub_interfaces = {'title'} sub_interfaces = set(('title',))
form_types = {'cancel', 'form', 'result', 'submit'} form_types = set(('cancel', 'form', 'result', 'submit'))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
title = None title = None
@@ -81,6 +81,18 @@ class Form(ElementBase):
self.append(field) self.append(field)
return field return field
def getXML(self, type='submit'):
self['type'] = type
log.warning("Form.getXML() is deprecated API compatibility " + \
"with plugins/old_0004.py")
return self.xml
def fromXML(self, xml):
log.warning("Form.fromXML() is deprecated API compatibility " + \
"with plugins/old_0004.py")
n = Form(xml=xml)
return n
def add_item(self, values): def add_item(self, values):
itemXML = ET.Element('{%s}item' % self.namespace) itemXML = ET.Element('{%s}item' % self.namespace)
self.xml.append(itemXML) self.xml.append(itemXML)
@@ -184,13 +196,13 @@ class Form(ElementBase):
for var, field in fields: for var, field in fields:
field['var'] = var field['var'] = var
self.add_field( self.add_field(
var=field.get('var'), var = field.get('var'),
label=field.get('label'), label = field.get('label'),
desc=field.get('desc'), desc = field.get('desc'),
required=field.get('required'), required = field.get('required'),
value=field.get('value'), value = field.get('value'),
options=field.get('options'), options = field.get('options'),
type=field.get('type')) type = field.get('type'))
def set_instructions(self, instructions): def set_instructions(self, instructions):
del self['instructions'] del self['instructions']
@@ -209,7 +221,7 @@ class Form(ElementBase):
def set_reported(self, reported): def set_reported(self, reported):
""" """
This either needs a dictionary of dictionaries or a dictionary of form fields. This either needs a dictionary or dictionaries or a dictionary of form fields.
:param reported: :param reported:
:return: :return:
""" """

Some files were not shown because too many files have changed in this diff Show More