Compare commits

..

1 Commits

Author SHA1 Message Date
Emmanuel Gil Peyrot
6a43559f4f Add a Markup plugin. 2017-11-23 12:10:39 +00:00
148 changed files with 775 additions and 1800 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 +1,8 @@
stages:
- test
- trigger
test: test:
stage: test
tags: tags:
- docker - docker
image: ubuntu:latest image: ubuntu:latest
script: script:
- apt update - apt update
- apt install -y python3 cython3 gpg - apt install -y python3 cython3
- ./run_tests.py - ./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

@@ -5,7 +5,7 @@ To contribute, the preferred way is to commit your changes on some
publicly-available git repository (on a fork `on github publicly-available git repository (on a fork `on github
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to <https://github.com/poezio/slixmpp>`_ or on your own repository) and to
notify the developers with either: notify the developers with either:
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_ - a ticket `on the bug tracker <https://dev.poez.io/new>`_
- a pull request on github - a pull request on github
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_ - a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_

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,7 +1,7 @@
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
@@ -113,7 +113,6 @@ Slixmpp Credits
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_) - Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_) - Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_) - 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.1'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
# release = '1.4.0' release = '1.1'
# 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

@@ -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

@@ -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
@@ -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://git.poez.io/slixmpp/tree/examples>`_.
.. compound:: .. compound::

View File

@@ -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

View File

@@ -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

@@ -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.

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
@@ -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

@@ -15,6 +15,7 @@ from argparse import ArgumentParser
import slixmpp import slixmpp
from slixmpp.exceptions import XMPPError from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@@ -51,17 +51,18 @@ class AskConfirm(slixmpp.ClientXMPP):
else: else:
self.confirmed.set_result(True) self.confirmed.set_result(True)
async def start(self, event): @asyncio.coroutine
def start(self, event):
log.info('Sending confirm request %s to %s who wants to access %s using ' log.info('Sending confirm request %s to %s who wants to access %s using '
'method %s...' % (self.id, self.recipient, self.url, self.method)) 'method %s...' % (self.id, self.recipient, self.url, self.method))
try: try:
confirmed = await self['xep_0070'].ask_confirm(self.recipient, confirmed = yield from self['xep_0070'].ask_confirm(self.recipient,
id=self.id, id=self.id,
url=self.url, url=self.url,
method=self.method, method=self.method,
message='Plz say yes or no for {method} {url} ({id}).') message='Plz say yes or no for {method} {url} ({id}).')
if isinstance(confirmed, slixmpp.Message): if isinstance(confirmed, slixmpp.Message):
confirmed = await self.confirmed confirmed = yield from self.confirmed
else: else:
confirmed = True confirmed = True
except IqError: except IqError:

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

@@ -58,7 +58,7 @@ class GTalkBot(slixmpp.ClientXMPP):
logging.error(err.message) logging.error(err.message)
self.disconnect() self.disconnect()
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

@@ -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

@@ -15,6 +15,7 @@ from argparse import ArgumentParser
import slixmpp import slixmpp
from slixmpp.exceptions import XMPPError from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View File

@@ -39,7 +39,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.
@@ -53,7 +53,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

@@ -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,7 +65,7 @@ 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'].join_muc(self.room,
self.nick, self.nick,

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
@@ -20,7 +21,7 @@ class PubsubClient(slixmpp.ClientXMPP):
self.register_plugin('xep_0059') self.register_plugin('xep_0059')
self.register_plugin('xep_0060') self.register_plugin('xep_0060')
self.actions = ['nodes', 'create', 'delete', '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

@@ -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

@@ -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

@@ -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

@@ -20,7 +20,8 @@ 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,8 +29,9 @@ 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', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Internet :: XMPP', 'Topic :: Internet :: XMPP',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
] ]
@@ -77,12 +79,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,12 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
import asyncio
if hasattr(asyncio, 'sslproto'): # no ssl proto: very old asyncio = no need for this
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

@@ -104,15 +104,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

View File

@@ -265,7 +265,8 @@ class ClientXMPP(BaseXMPP):
self.bindfail = False self.bindfail = False
self.features = set() self.features = set()
async def _handle_stream_features(self, features): @asyncio.coroutine
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.
@@ -274,7 +275,7 @@ class ClientXMPP(BaseXMPP):
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 asyncio.iscoroutinefunction(handler):
result = await handler(features) result = yield from handler(features)
else: else:
result = handler(features) result = handler(features)
if result and restart: if result and restart:

View File

@@ -35,7 +35,8 @@ 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): @asyncio.coroutine
def _handle_bind_resource(self, features):
""" """
Handle requesting a specific resource. Handle requesting a specific resource.
@@ -50,7 +51,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) yield from 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

@@ -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

@@ -35,22 +35,18 @@ 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): @asyncio.coroutine
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) yield from 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

@@ -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:
@@ -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:
@@ -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.
@@ -242,17 +237,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,
@@ -311,7 +301,7 @@ class JID:
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full') __slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
def __init__(self, jid: Optional[str] = None): def __init__(self, jid=None):
if not jid: if not jid:
self._node = '' self._node = ''
self._domain = '' self._domain = ''
@@ -356,10 +346,30 @@ class JID:
def node(self): def node(self):
return self._node return self._node
@property
def user(self):
return self._node
@property
def local(self):
return self._node
@property
def username(self):
return self._node
@property @property
def domain(self): def domain(self):
return self._domain return self._domain
@property
def server(self):
return self._domain
@property
def host(self):
return self._domain
@property @property
def resource(self): def resource(self):
return self._resource return self._resource
@@ -372,18 +382,47 @@ class JID:
def full(self): def full(self):
return self._full return self._full
@property
def jid(self):
return self._full
@node.setter @node.setter
def node(self, value: str): def node(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@user.setter
def user(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@local.setter
def local(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@username.setter
def username(self, value):
self._node = _validate_node(value) self._node = _validate_node(value)
self._update_bare_full() self._update_bare_full()
@domain.setter @domain.setter
def domain(self, value: str): def domain(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@server.setter
def server(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@host.setter
def host(self, value):
self._domain = _validate_domain(value) self._domain = _validate_domain(value)
self._update_bare_full() 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
@@ -391,23 +430,19 @@ class JID:
self._update_bare_full() 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() 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() 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)
self._update_bare_full()
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."""
@@ -423,10 +458,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

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

@@ -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

@@ -23,7 +23,7 @@ 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 = {'title'}
form_types = {'cancel', 'form', 'result', 'submit'} form_types = {'cancel', 'form', 'result', 'submit'}

View File

@@ -61,7 +61,7 @@ def _intercept(method, name, public):
except InvocationException: except InvocationException:
raise raise
except Exception as e: except Exception as e:
raise InvocationException("A problem occurred calling %s.%s!" % (instance.FQN(), method.__name__), e) raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
_resolver._rpc = public _resolver._rpc = public
_resolver._rpc_name = method.__name__ if name is None else name _resolver._rpc_name = method.__name__ if name is None else name
return _resolver return _resolver
@@ -405,10 +405,8 @@ class Proxy(Endpoint):
self._callback = callback self._callback = callback
def __getattribute__(self, name, *args): def __getattribute__(self, name, *args):
if name in ('__dict__', '_endpoint', '_callback'): if name in ('__dict__', '_endpoint', 'async', '_callback'):
return object.__getattribute__(self, name) return object.__getattribute__(self, name)
elif name == 'async':
return lambda callback: Proxy(self._endpoint, callback)
else: else:
attribute = self._endpoint.__getattribute__(name) attribute = self._endpoint.__getattribute__(name)
if hasattr(attribute, '__call__'): if hasattr(attribute, '__call__'):
@@ -422,6 +420,9 @@ class Proxy(Endpoint):
pass # If the attribute doesn't exist, don't care! pass # If the attribute doesn't exist, don't care!
return attribute return attribute
def async(self, callback):
return Proxy(self._endpoint, callback)
def get_endpoint(self): def get_endpoint(self):
''' '''
Returns the proxified endpoint. Returns the proxified endpoint.
@@ -695,7 +696,7 @@ class RemoteSession(object):
e = { e = {
'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])), 'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])), 'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
'undefined-condition': RemoteException("An unexpected problem occurred trying to invoke %s at %s!" % (pmethod, iq['from'])), 'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),
}[condition] }[condition]
if e is None: if e is None:
RemoteException("An unexpected exception occurred at %s!" % iq['from']) RemoteException("An unexpected exception occurred at %s!" % iq['from'])

View File

@@ -7,7 +7,7 @@
""" """
from slixmpp.xmlstream.stanzabase import ElementBase from slixmpp.xmlstream.stanzabase import ElementBase
from xml.etree import ElementTree as ET from xml.etree import cElementTree as ET
class RPCQuery(ElementBase): class RPCQuery(ElementBase):

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 import Iq from slixmpp import Iq
@@ -124,8 +123,6 @@ class XEP_0030(BasePlugin):
for op in self._disco_ops: for op in self._disco_ops:
self.api.register(getattr(self.static, op), op, default=True) self.api.register(getattr(self.static, op), op, default=True)
self.domain_infos = {}
def session_bind(self, jid): def session_bind(self, jid):
self.add_feature('http://jabber.org/protocol/disco#info') self.add_feature('http://jabber.org/protocol/disco#info')
@@ -298,37 +295,6 @@ class XEP_0030(BasePlugin):
'cached': cached} 'cached': cached}
return self.api['has_identity'](jid, node, ifrom, data) return self.api['has_identity'](jid, node, ifrom, data)
async def get_info_from_domain(self, domain=None, timeout=None,
cached=True, callback=None):
"""Fetch disco#info of specified domain and one disco#items level below"""
if domain is None:
domain = self.xmpp.boundjid.domain
if not cached or domain not in self.domain_infos:
infos = [self.get_info(
domain, timeout=timeout)]
iq_items = await self.get_items(
domain, timeout=timeout)
items = iq_items['disco_items']['items']
infos += [
self.get_info(item[0], timeout=timeout)
for item in items]
info_futures, _ = await asyncio.wait(
infos,
timeout=timeout,
loop=self.xmpp.loop
)
self.domain_infos[domain] = [
future.result() for future in info_futures if not future.exception()]
results = self.domain_infos[domain]
if callback is not None:
callback(results)
return results
@future_wrapper @future_wrapper
def get_info(self, jid=None, node=None, local=None, def get_info(self, jid=None, node=None, local=None,
cached=None, **kwargs): cached=None, **kwargs):
@@ -350,7 +316,7 @@ class XEP_0030(BasePlugin):
combination handled by this Slixmpp instance and combination handled by this Slixmpp instance and
no stanzas need to be sent. no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the Otherwise, a disco stanza must be sent to the
remote JID to retrieve the info. remove JID to retrieve the info.
cached -- If true, then look for the disco info data from cached -- If true, then look for the disco info data from
the local cache system. If no results are found, the local cache system. If no results are found,
send the query as usual. The self.use_cache send the query as usual. The self.use_cache
@@ -680,11 +646,9 @@ class XEP_0030(BasePlugin):
info['id'] = iq['id'] info['id'] = iq['id']
info.send() info.send()
else: else:
node = iq['disco_info']['node']
iq = iq.reply() iq = iq.reply()
if info: if info:
info = self._fix_default_info(info) info = self._fix_default_info(info)
info['node'] = node
iq.set_payload(info.xml) iq.set_payload(info.xml)
iq.send() iq.send()
elif iq['type'] == 'result': elif iq['type'] == 'result':

View File

@@ -257,7 +257,7 @@ class StaticDisco(object):
def add_identity(self, jid, node, ifrom, data): def add_identity(self, jid, node, ifrom, data):
""" """
Add a new identity to the JID/node combination. Add a new identity to te JID/node combination.
The data parameter may provide: The data parameter may provide:
category -- The general category to which the agent belongs. category -- The general category to which the agent belongs.

View File

@@ -9,7 +9,7 @@ from __future__ import with_statement
import logging import logging
from slixmpp import Presence, Message from slixmpp import Presence
from slixmpp.plugins import BasePlugin, register_plugin from slixmpp.plugins import BasePlugin, register_plugin
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
from slixmpp.xmlstream.handler.callback import Callback from slixmpp.xmlstream.handler.callback import Callback
@@ -162,7 +162,7 @@ class XEP_0045(BasePlugin):
return return
self.xmpp.roster[pr['from']].ignore_updates = True self.xmpp.roster[pr['from']].ignore_updates = True
entry = pr['muc'].get_stanza_values() entry = pr['muc'].get_stanza_values()
entry['show'] = pr['show'] if pr['show'] in pr.showtypes else None entry['show'] = pr['show']
entry['status'] = pr['status'] entry['status'] = pr['status']
entry['alt_nick'] = pr['nick'] entry['alt_nick'] = pr['nick']
if pr['type'] == 'unavailable': if pr['type'] == 'unavailable':
@@ -181,7 +181,7 @@ class XEP_0045(BasePlugin):
if got_online: if got_online:
self.xmpp.event("muc::%s::got_online" % entry['room'], pr) self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
def handle_groupchat_message(self, msg: Message) -> None: def handle_groupchat_message(self, msg):
""" Handle a message event in a muc. """ Handle a message event in a muc.
""" """
self.xmpp.event('groupchat_message', msg) self.xmpp.event('groupchat_message', msg)
@@ -195,14 +195,10 @@ class XEP_0045(BasePlugin):
def handle_groupchat_subject(self, msg: Message) -> None: def handle_groupchat_subject(self, msg):
""" Handle a message coming from a muc indicating """ Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room) a change of subject (or announcing it when joining the room)
""" """
# See poezio#3452. A message containing subject _and_ (body or thread)
# is not a subject change.
if msg['body'] or msg['thread']:
return None
self.xmpp.event('groupchat_subject', msg) self.xmpp.event('groupchat_subject', msg)
def jid_in_room(self, room, jid): def jid_in_room(self, room, jid):

View File

@@ -31,7 +31,8 @@ class IBBytestream(object):
self.recv_queue = asyncio.Queue() self.recv_queue = asyncio.Queue()
async def send(self, data, timeout=None): @asyncio.coroutine
def send(self, data, timeout=None):
if not self.stream_started or self.stream_out_closed: if not self.stream_started or self.stream_out_closed:
raise socket.error raise socket.error
if len(data) > self.block_size: if len(data) > self.block_size:
@@ -55,20 +56,22 @@ class IBBytestream(object):
iq['ibb_data']['sid'] = self.sid iq['ibb_data']['sid'] = self.sid
iq['ibb_data']['seq'] = seq iq['ibb_data']['seq'] = seq
iq['ibb_data']['data'] = data iq['ibb_data']['data'] = data
await iq.send(timeout=timeout) yield from iq.send(timeout=timeout)
return len(data) return len(data)
async def sendall(self, data, timeout=None): @asyncio.coroutine
def sendall(self, data, timeout=None):
sent_len = 0 sent_len = 0
while sent_len < len(data): while sent_len < len(data):
sent_len += await self.send(data[sent_len:self.block_size], timeout=timeout) sent_len += yield from self.send(data[sent_len:self.block_size], timeout=timeout)
async def sendfile(self, file, timeout=None): @asyncio.coroutine
def sendfile(self, file, timeout=None):
while True: while True:
data = file.read(self.block_size) data = file.read(self.block_size)
if not data: if not data:
break break
await self.send(data, timeout=timeout) yield from self.send(data, timeout=timeout)
def _recv_data(self, stanza): def _recv_data(self, stanza):
new_seq = stanza['ibb_data']['seq'] new_seq = stanza['ibb_data']['seq']

View File

@@ -6,7 +6,6 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
from slixmpp import JID
from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin
@@ -53,12 +52,6 @@ class Conference(ElementBase):
if value in ('1', 'true', True): if value in ('1', 'true', True):
self._set_attr('autojoin', 'true') self._set_attr('autojoin', 'true')
def set_jid(self, value):
del self['jid']
if isinstance(value, JID):
value = value.full
self._set_attr('jid', value)
class URL(ElementBase): class URL(ElementBase):
name = 'url' name = 'url'

View File

@@ -89,17 +89,31 @@ class XEP_0050(BasePlugin):
self.commands = {} self.commands = {}
self.xmpp.register_handler( self.xmpp.register_handler(
Callback("Ad-Hoc Execute", Callback("Ad-Hoc Execute",
StanzaPath('iq@type=set/command'), StanzaPath('iq@type=set/command'),
self._handle_command)) self._handle_command))
register_stanza_plugin(Iq, Command) register_stanza_plugin(Iq, Command)
register_stanza_plugin(Command, Form, iterable=True) register_stanza_plugin(Command, Form, iterable=True)
self.xmpp.add_event_handler('command', self._handle_command_all) self.xmpp.add_event_handler('command_execute',
self._handle_command_start)
self.xmpp.add_event_handler('command_next',
self._handle_command_next)
self.xmpp.add_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.add_event_handler('command_complete',
self._handle_command_complete)
def plugin_end(self): def plugin_end(self):
self.xmpp.del_event_handler('command', self._handle_command_all) self.xmpp.del_event_handler('command_execute',
self._handle_command_start)
self.xmpp.del_event_handler('command_next',
self._handle_command_next)
self.xmpp.del_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.del_event_handler('command_complete',
self._handle_command_complete)
self.xmpp.remove_handler('Ad-Hoc Execute') self.xmpp.remove_handler('Ad-Hoc Execute')
self.xmpp['xep_0030'].del_feature(feature=Command.namespace) self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple()) self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
@@ -187,27 +201,8 @@ class XEP_0050(BasePlugin):
def _handle_command(self, iq): def _handle_command(self, iq):
"""Raise command events based on the command action.""" """Raise command events based on the command action."""
self.xmpp.event('command', iq)
self.xmpp.event('command_%s' % iq['command']['action'], iq) self.xmpp.event('command_%s' % iq['command']['action'], iq)
def _handle_command_all(self, iq: Iq) -> None:
action = iq['command']['action']
sessionid = iq['command']['sessionid']
session = self.sessions.get(sessionid)
if session is None:
return self._handle_command_start(iq)
if action in ('next', 'execute'):
return self._handle_command_next(iq)
if action == 'prev':
return self._handle_command_prev(iq)
if action == 'complete':
return self._handle_command_complete(iq)
if action == 'cancel':
return self._handle_command_cancel(iq)
return None
def _handle_command_start(self, iq): def _handle_command_start(self, iq):
""" """
Process an initial request to execute a command. Process an initial request to execute a command.
@@ -473,7 +468,7 @@ class XEP_0050(BasePlugin):
**kwargs) **kwargs)
def send_command(self, jid, node, ifrom=None, action='execute', def send_command(self, jid, node, ifrom=None, action='execute',
payload=None, sessionid=None, flow=False, **kwargs): payload=None, sessionid=None, flow=False, **kwargs):
""" """
Create and send a command stanza, without using the provided Create and send a command stanza, without using the provided
workflow management APIs. workflow management APIs.
@@ -616,7 +611,7 @@ class XEP_0050(BasePlugin):
def terminate_command(self, session): def terminate_command(self, session):
""" """
Delete a command's session after a command has completed Delete a command's session after a command has completed
or an error has occurred. or an error has occured.
Arguments: Arguments:
session -- All stored data relevant to the current session -- All stored data relevant to the current

View File

@@ -123,7 +123,7 @@ class XEP_0054(BasePlugin):
if iq['type'] == 'result': if iq['type'] == 'result':
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp']) self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
return return
elif iq['type'] == 'get' and self.xmpp.is_component: elif iq['type'] == 'get':
vcard = self.api['get_vcard'](iq['from'].bare) vcard = self.api['get_vcard'](iq['from'].bare)
if isinstance(vcard, Iq): if isinstance(vcard, Iq):
vcard.send() vcard.send()

View File

@@ -22,7 +22,7 @@ log = logging.getLogger(__name__)
class ResultIterator: class ResultIterator:
""" """
An iterator for Result Set Management An iterator for Result Set Managment
""" """
def __init__(self, query, interface, results='substanzas', amount=10, def __init__(self, query, interface, results='substanzas', amount=10,
@@ -79,8 +79,7 @@ class ResultIterator:
""" """
if self._stop: if self._stop:
raise StopAsyncIteration raise StopAsyncIteration
if self.query[self.interface]['rsm']['before'] is None: self.query[self.interface]['rsm']['before'] = self.reverse
self.query[self.interface]['rsm']['before'] = self.reverse
self.query['id'] = self.query.stream.new_id() self.query['id'] = self.query.stream.new_id()
self.query[self.interface]['rsm']['max'] = str(self.amount) self.query[self.interface]['rsm']['max'] = str(self.amount)
@@ -142,7 +141,7 @@ class XEP_0059(BasePlugin):
def session_bind(self, jid): def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Set.namespace) self.xmpp['xep_0030'].add_feature(Set.namespace)
def iterate(self, stanza, interface, results='substanzas', amount=10, reverse=False, def iterate(self, stanza, interface, results='substanzas',
recv_interface=None, pre_cb=None, post_cb=None): recv_interface=None, pre_cb=None, post_cb=None):
""" """
Create a new result set iterator for a given stanza query. Create a new result set iterator for a given stanza query.
@@ -170,6 +169,6 @@ class XEP_0059(BasePlugin):
results -- The name of the interface containing the results -- The name of the interface containing the
query results (typically just 'substanzas'). query results (typically just 'substanzas').
""" """
return ResultIterator(stanza, interface, results, amount, reverse=reverse, return ResultIterator(stanza, interface, results,
recv_interface=recv_interface, pre_cb=pre_cb, recv_interface=recv_interface, pre_cb=pre_cb,
post_cb=post_cb) post_cb=post_cb)

View File

@@ -13,7 +13,7 @@ from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
class Set(ElementBase): class Set(ElementBase):
""" """
XEP-0059 (Result Set Management) can be used to manage the XEP-0059 (Result Set Managment) can be used to manage the
results of queries. For example, limiting the number of items results of queries. For example, limiting the number of items
per response or starting at certain positions. per response or starting at certain positions.

View File

@@ -185,14 +185,14 @@ class XEP_0060(BasePlugin):
if config is not None: if config is not None:
form_type = 'http://jabber.org/protocol/pubsub#node_config' form_type = 'http://jabber.org/protocol/pubsub#node_config'
if 'FORM_TYPE' in config.get_fields(): if 'FORM_TYPE' in config['fields']:
config.field['FORM_TYPE']['value'] = form_type config.field['FORM_TYPE']['value'] = form_type
else: else:
config.add_field(var='FORM_TYPE', config.add_field(var='FORM_TYPE',
ftype='hidden', ftype='hidden',
value=form_type) value=form_type)
if ntype: if ntype:
if 'pubsub#node_type' in config.get_fields(): if 'pubsub#node_type' in config['fields']:
config.field['pubsub#node_type']['value'] = ntype config.field['pubsub#node_type']['value'] = ntype
else: else:
config.add_field(var='pubsub#node_type', value=ntype) config.add_field(var='pubsub#node_type', value=ntype)

View File

@@ -82,9 +82,9 @@ class Item(ElementBase):
self.xml.append(value) self.xml.append(value)
def get_payload(self): def get_payload(self):
children = list(self.xml) childs = list(self.xml)
if len(children) > 0: if len(childs) > 0:
return children[0] return childs[0]
def del_payload(self): def del_payload(self):
for child in self.xml: for child in self.xml:

View File

@@ -31,9 +31,9 @@ class EventItem(ElementBase):
self.xml.append(value) self.xml.append(value)
def get_payload(self): def get_payload(self):
children = list(self.xml) childs = list(self.xml)
if len(children) > 0: if len(childs) > 0:
return children[0] return childs[0]
def del_payload(self): def del_payload(self):
for child in self.xml: for child in self.xml:

View File

@@ -55,17 +55,18 @@ class XEP_0065(BasePlugin):
"""Returns the socket associated to the SID.""" """Returns the socket associated to the SID."""
return self._sessions.get(sid, None) return self._sessions.get(sid, None)
async def handshake(self, to, ifrom=None, sid=None, timeout=None): @asyncio.coroutine
def handshake(self, to, ifrom=None, sid=None, timeout=None):
""" Starts the handshake to establish the socks5 bytestreams """ Starts the handshake to establish the socks5 bytestreams
connection. connection.
""" """
if not self._proxies: if not self._proxies:
self._proxies = await self.discover_proxies() self._proxies = yield from self.discover_proxies()
if sid is None: if sid is None:
sid = uuid4().hex sid = uuid4().hex
used = await self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout) used = yield from self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
proxy = used['socks']['streamhost_used']['jid'] proxy = used['socks']['streamhost_used']['jid']
if proxy not in self._proxies: if proxy not in self._proxies:
@@ -73,16 +74,16 @@ class XEP_0065(BasePlugin):
return return
try: try:
self._sessions[sid] = (await self._connect_proxy( self._sessions[sid] = (yield from self._connect_proxy(
self._get_dest_sha1(sid, self.xmpp.boundjid, to), self._get_dest_sha1(sid, self.xmpp.boundjid, to),
self._proxies[proxy][0], self._proxies[proxy][0],
self._proxies[proxy][1]))[1] self._proxies[proxy][1]))[1]
except socket.error: except socket.error:
return None return None
addr, port = await self._sessions[sid].connected addr, port = yield from self._sessions[sid].connected
# Request that the proxy activate the session with the target. # Request that the proxy activate the session with the target.
await self.activate(proxy, sid, to, timeout=timeout) yield from self.activate(proxy, sid, to, timeout=timeout)
sock = self.get_socket(sid) sock = self.get_socket(sid)
self.xmpp.event('stream:%s:%s' % (sid, to), sock) self.xmpp.event('stream:%s:%s' % (sid, to), sock)
return sock return sock
@@ -104,7 +105,8 @@ class XEP_0065(BasePlugin):
iq['socks'].add_streamhost(proxy, host, port) iq['socks'].add_streamhost(proxy, host, port)
return iq.send(timeout=timeout, callback=callback) return iq.send(timeout=timeout, callback=callback)
async def discover_proxies(self, jid=None, ifrom=None, timeout=None): @asyncio.coroutine
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server.""" """Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
if jid is None: if jid is None:
if self.xmpp.is_component: if self.xmpp.is_component:
@@ -114,7 +116,7 @@ class XEP_0065(BasePlugin):
discovered = set() discovered = set()
disco_items = await self.xmpp['xep_0030'].get_items(jid, timeout=timeout) disco_items = yield from self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
disco_items = {item[0] for item in disco_items['disco_items']['items']} disco_items = {item[0] for item in disco_items['disco_items']['items']}
disco_info_futures = {} disco_info_futures = {}
@@ -123,7 +125,7 @@ class XEP_0065(BasePlugin):
for item in disco_items: for item in disco_items:
try: try:
disco_info = await disco_info_futures[item] disco_info = yield from disco_info_futures[item]
except XMPPError: except XMPPError:
continue continue
else: else:
@@ -135,7 +137,7 @@ class XEP_0065(BasePlugin):
for jid in discovered: for jid in discovered:
try: try:
addr = await self.get_network_address(jid, ifrom=ifrom, timeout=timeout) addr = yield from self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
self._proxies[jid] = (addr['socks']['streamhost']['host'], self._proxies[jid] = (addr['socks']['streamhost']['host'],
addr['socks']['streamhost']['port']) addr['socks']['streamhost']['port'])
except XMPPError: except XMPPError:
@@ -180,8 +182,9 @@ class XEP_0065(BasePlugin):
streamhost['host'], streamhost['host'],
streamhost['port'])) streamhost['port']))
async def gather(futures, iq, streamhosts): @asyncio.coroutine
proxies = await asyncio.gather(*futures, return_exceptions=True) def gather(futures, iq, streamhosts):
proxies = yield from asyncio.gather(*futures, return_exceptions=True)
for streamhost, proxy in zip(streamhosts, proxies): for streamhost, proxy in zip(streamhosts, proxies):
if isinstance(proxy, ValueError): if isinstance(proxy, ValueError):
continue continue
@@ -191,7 +194,7 @@ class XEP_0065(BasePlugin):
proxy = proxy[1] proxy = proxy[1]
# TODO: what if the future never happens? # TODO: what if the future never happens?
try: try:
addr, port = await proxy.connected addr, port = yield from proxy.connected
except socket.error: except socket.error:
log.exception('Socket error while connecting to the proxy.') log.exception('Socket error while connecting to the proxy.')
continue continue
@@ -212,7 +215,7 @@ class XEP_0065(BasePlugin):
self.xmpp.event('socks5_stream', conn) self.xmpp.event('socks5_stream', conn)
self.xmpp.event('stream:%s:%s' % (sid, requester), conn) self.xmpp.event('stream:%s:%s' % (sid, requester), conn)
asyncio.ensure_future(gather(proxy_futures, iq, streamhosts)) asyncio.async(gather(proxy_futures, iq, streamhosts))
def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None): def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None):
"""Activate the socks5 session that has been negotiated.""" """Activate the socks5 session that has been negotiated."""
@@ -230,7 +233,7 @@ class XEP_0065(BasePlugin):
sock.close() sock.close()
except socket.error: except socket.error:
pass pass
# Though this should not be necessary remove the closed session anyway # Though this should not be neccessary remove the closed session anyway
if sid in self._sessions: if sid in self._sessions:
log.warn(('SOCKS5 session with sid = "%s" was not ' + log.warn(('SOCKS5 session with sid = "%s" was not ' +
'removed from _sessions by sock.close()') % sid) 'removed from _sessions by sock.close()') % sid)

View File

@@ -137,8 +137,8 @@ class Socks5Protocol(asyncio.Protocol):
def resume_writing(self): def resume_writing(self):
self.paused.set_result(None) self.paused.set_result(None)
async def write(self, data): def write(self, data):
await self.paused yield from self.paused
self.transport.write(data) self.transport.write(data)
def _send_methods(self): def _send_methods(self):

View File

@@ -59,7 +59,7 @@ class XEP_0077(BasePlugin):
def _force_stream_feature(self, stanza): def _force_stream_feature(self, stanza):
if isinstance(stanza, StreamFeatures): if isinstance(stanza, StreamFeatures):
if not self.xmpp.disable_starttls: if self.xmpp.use_tls or self.xmpp.use_ssl:
if 'starttls' not in self.xmpp.features: if 'starttls' not in self.xmpp.features:
return stanza return stanza
elif not isinstance(self.xmpp.socket, ssl.SSLSocket): elif not isinstance(self.xmpp.socket, ssl.SSLSocket):

View File

@@ -130,7 +130,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
sec = now.second sec = now.second
if micro is None: if micro is None:
micro = now.microsecond micro = now.microsecond
if offset in (None, 0): if offset is None:
offset = tzutc() offset = tzutc()
elif not isinstance(offset, dt.tzinfo): elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset) offset = tzoffset(None, offset)
@@ -177,7 +177,7 @@ def datetime(year=None, month=None, day=None, hour=None,
sec = now.second sec = now.second
if micro is None: if micro is None:
micro = now.microsecond micro = now.microsecond
if offset in (None, 0): if offset is None:
offset = tzutc() offset = tzutc()
elif not isinstance(offset, dt.tzinfo): elif not isinstance(offset, dt.tzinfo):
offset = tzoffset(None, offset) offset = tzoffset(None, offset)

View File

@@ -65,14 +65,9 @@ class XEP_0092(BasePlugin):
iq -- The Iq stanza containing the software version query. iq -- The Iq stanza containing the software version query.
""" """
iq = iq.reply() iq = iq.reply()
if self.software_name: iq['software_version']['name'] = self.software_name
iq['software_version']['name'] = self.software_name iq['software_version']['version'] = self.version
iq['software_version']['version'] = self.version iq['software_version']['os'] = self.os
iq['software_version']['os'] = self.os
else:
iq.error()
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'service-unavailable'
iq.send() iq.send()
def get_version(self, jid, ifrom=None, timeout=None, callback=None, def get_version(self, jid, ifrom=None, timeout=None, callback=None,

View File

@@ -97,7 +97,7 @@ class XEP_0095(BasePlugin):
extension='bad-profile', extension='bad-profile',
extension_ns=SI.namespace) extension_ns=SI.namespace)
neg = iq['si']['feature_neg']['form'].get_fields() neg = iq['si']['feature_neg']['form']['fields']
options = neg['stream-method']['options'] or [] options = neg['stream-method']['options'] or []
methods = [] methods = []
for opt in options: for opt in options:

View File

@@ -15,7 +15,6 @@ from slixmpp.stanza import StreamFeatures, Presence, Iq
from slixmpp.xmlstream import register_stanza_plugin, JID from slixmpp.xmlstream import register_stanza_plugin, JID
from slixmpp.xmlstream.handler import Callback from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.util import MemoryCache
from slixmpp import asyncio from slixmpp import asyncio
from slixmpp.exceptions import XMPPError, IqError, IqTimeout from slixmpp.exceptions import XMPPError, IqError, IqTimeout
from slixmpp.plugins import BasePlugin from slixmpp.plugins import BasePlugin
@@ -28,7 +27,7 @@ log = logging.getLogger(__name__)
class XEP_0115(BasePlugin): class XEP_0115(BasePlugin):
""" """
XEP-0115: Entity Capabilities XEP-0115: Entity Capabalities
""" """
name = 'xep_0115' name = 'xep_0115'
@@ -38,8 +37,7 @@ class XEP_0115(BasePlugin):
default_config = { default_config = {
'hash': 'sha-1', 'hash': 'sha-1',
'caps_node': None, 'caps_node': None,
'broadcast': True, 'broadcast': True
'cache': None,
} }
def plugin_init(self): def plugin_init(self):
@@ -50,9 +48,6 @@ class XEP_0115(BasePlugin):
if self.caps_node is None: if self.caps_node is None:
self.caps_node = 'http://slixmpp.com/ver/%s' % __version__ self.caps_node = 'http://slixmpp.com/ver/%s' % __version__
if self.cache is None:
self.cache = MemoryCache()
register_stanza_plugin(Presence, stanza.Capabilities) register_stanza_plugin(Presence, stanza.Capabilities)
register_stanza_plugin(StreamFeatures, stanza.Capabilities) register_stanza_plugin(StreamFeatures, stanza.Capabilities)
@@ -137,7 +132,8 @@ class XEP_0115(BasePlugin):
self.xmpp.event('entity_caps', p) self.xmpp.event('entity_caps', p)
async def _process_caps(self, pres): @asyncio.coroutine
def _process_caps(self, pres):
if not pres['caps']['hash']: if not pres['caps']['hash']:
log.debug("Received unsupported legacy caps: %s, %s, %s", log.debug("Received unsupported legacy caps: %s, %s, %s",
pres['caps']['node'], pres['caps']['node'],
@@ -168,7 +164,7 @@ class XEP_0115(BasePlugin):
log.debug("New caps verification string: %s", ver) log.debug("New caps verification string: %s", ver)
try: try:
node = '%s#%s' % (pres['caps']['node'], ver) node = '%s#%s' % (pres['caps']['node'], ver)
caps = await self.xmpp['xep_0030'].get_info(pres['from'], node, caps = yield from self.xmpp['xep_0030'].get_info(pres['from'], node,
coroutine=True) coroutine=True)
if isinstance(caps, Iq): if isinstance(caps, Iq):
@@ -203,8 +199,8 @@ class XEP_0115(BasePlugin):
log.debug("Non form extension found, ignoring for caps") log.debug("Non form extension found, ignoring for caps")
caps.xml.remove(stanza.xml) caps.xml.remove(stanza.xml)
continue continue
if 'FORM_TYPE' in stanza.get_fields(): if 'FORM_TYPE' in stanza['fields']:
f_type = tuple(stanza.get_fields()['FORM_TYPE']['value']) f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
form_types.append(f_type) form_types.append(f_type)
deduped_form_types.add(f_type) deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types): if len(form_types) != len(deduped_form_types):
@@ -218,7 +214,7 @@ class XEP_0115(BasePlugin):
log.debug("Extra FORM_TYPE data, invalid for caps") log.debug("Extra FORM_TYPE data, invalid for caps")
return False return False
if stanza.get_fields()['FORM_TYPE']['type'] != 'hidden': if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', " + \ log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps") "ignoring form for caps")
caps.xml.remove(stanza.xml) caps.xml.remove(stanza.xml)
@@ -257,7 +253,7 @@ class XEP_0115(BasePlugin):
for stanza in info['substanzas']: for stanza in info['substanzas']:
if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form): if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
if 'FORM_TYPE' in stanza.get_fields(): if 'FORM_TYPE' in stanza['fields']:
f_type = stanza['values']['FORM_TYPE'] f_type = stanza['values']['FORM_TYPE']
if len(f_type): if len(f_type):
f_type = f_type[0] f_type = f_type[0]
@@ -269,11 +265,11 @@ class XEP_0115(BasePlugin):
for f_type in sorted_forms: for f_type in sorted_forms:
for form in form_types[f_type]: for form in form_types[f_type]:
S += '%s<' % f_type S += '%s<' % f_type
fields = sorted(form.get_fields().keys()) fields = sorted(form['fields'].keys())
fields.remove('FORM_TYPE') fields.remove('FORM_TYPE')
for field in fields: for field in fields:
S += '%s<' % field S += '%s<' % field
vals = form.get_fields()[field].get_value(convert=False) vals = form['fields'][field].get_value(convert=False)
if vals is None: if vals is None:
S += '<' S += '<'
else: else:
@@ -284,9 +280,10 @@ class XEP_0115(BasePlugin):
binary = hash(S.encode('utf8')).digest() binary = hash(S.encode('utf8')).digest()
return base64.b64encode(binary).decode('utf-8') return base64.b64encode(binary).decode('utf-8')
async def update_caps(self, jid=None, node=None, preserve=False): @asyncio.coroutine
def update_caps(self, jid=None, node=None, preserve=False):
try: try:
info = await self.xmpp['xep_0030'].get_info(jid, node, local=True) info = yield from self.xmpp['xep_0030'].get_info(jid, node, local=True)
if isinstance(info, Iq): if isinstance(info, Iq):
info = info['disco_info'] info = info['disco_info']
ver = self.generate_verstring(info, self.hash) ver = self.generate_verstring(info, self.hash)

View File

@@ -33,6 +33,7 @@ class StaticCaps(object):
self.disco = self.xmpp['xep_0030'] self.disco = self.xmpp['xep_0030']
self.caps = self.xmpp['xep_0115'] self.caps = self.xmpp['xep_0115']
self.static = static self.static = static
self.ver_cache = {}
self.jid_vers = {} self.jid_vers = {}
def supports(self, jid, node, ifrom, data): def supports(self, jid, node, ifrom, data):
@@ -127,7 +128,7 @@ class StaticCaps(object):
info = data.get('info', None) info = data.get('info', None)
if not verstring or not info: if not verstring or not info:
return return
self.caps.cache.store(verstring, info) self.ver_cache[verstring] = info
def assign_verstring(self, jid, node, ifrom, data): def assign_verstring(self, jid, node, ifrom, data):
if isinstance(jid, JID): if isinstance(jid, JID):
@@ -138,7 +139,4 @@ class StaticCaps(object):
return self.jid_vers.get(jid, None) return self.jid_vers.get(jid, None)
def get_caps(self, jid, node, ifrom, data): def get_caps(self, jid, node, ifrom, data):
verstring = data.get('verstring', None) return self.ver_cache.get(data.get('verstring', None), None)
if verstring is None:
return None
return self.caps.cache.retrieve(verstring)

View File

@@ -98,9 +98,10 @@ class XEP_0153(BasePlugin):
first_future.add_done_callback(propagate_timeout_exception) first_future.add_done_callback(propagate_timeout_exception)
return future return future
async def _start(self, event): @asyncio.coroutine
def _start(self, event):
try: try:
vcard = await self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare) vcard = yield from self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
data = vcard['vcard_temp']['PHOTO']['BINVAL'] data = vcard['vcard_temp']['PHOTO']['BINVAL']
if not data: if not data:
new_hash = '' new_hash = ''
@@ -137,11 +138,7 @@ class XEP_0153(BasePlugin):
if iq['type'] == 'error': if iq['type'] == 'error':
log.debug('Could not retrieve vCard for %s', jid) log.debug('Could not retrieve vCard for %s', jid)
return return
try: data = iq['vcard_temp']['PHOTO']['BINVAL']
data = iq['vcard_temp']['PHOTO']['BINVAL']
except ValueError:
log.debug('Invalid BINVAL in vCards PHOTO for %s:', jid, exc_info=True)
data = None
if not data: if not data:
new_hash = '' new_hash = ''
else: else:
@@ -167,7 +164,10 @@ class XEP_0153(BasePlugin):
data = pres['vcard_temp_update']['photo'] data = pres['vcard_temp_update']['photo']
if data is None: if data is None:
return return
self.xmpp.event('vcard_avatar_update', pres) elif data == '' or data != self.api['get_hash'](pres['from']):
ifrom = pres['to'] if self.xmpp.is_component else None
self.api['reset_hash'](pres['from'], ifrom=ifrom)
self.xmpp.event('vcard_avatar_update', pres)
# ================================================================= # =================================================================

View File

@@ -62,10 +62,7 @@ class XEP_0163(BasePlugin):
for ns in namespace: for ns in namespace:
self.xmpp['xep_0030'].add_feature('%s+notify' % ns, self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
jid=jid) jid=jid)
asyncio.ensure_future( asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
self.xmpp['xep_0115'].update_caps(jid),
loop=self.xmpp.loop,
)
def remove_interest(self, namespace, jid=None): def remove_interest(self, namespace, jid=None):
""" """
@@ -84,10 +81,7 @@ class XEP_0163(BasePlugin):
for ns in namespace: for ns in namespace:
self.xmpp['xep_0030'].del_feature(jid=jid, self.xmpp['xep_0030'].del_feature(jid=jid,
feature='%s+notify' % namespace) feature='%s+notify' % namespace)
asyncio.ensure_future( asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
self.xmpp['xep_0115'].update_caps(jid),
loop=self.xmpp.loop,
)
def publish(self, stanza, node=None, id=None, options=None, ifrom=None, def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
timeout_callback=None, callback=None, timeout=None): timeout_callback=None, callback=None, timeout=None):

View File

@@ -11,9 +11,10 @@ from slixmpp.xmlstream import ElementBase, ET
class UserGaming(ElementBase): class UserGaming(ElementBase):
name = 'game' name = 'gaming'
namespace = 'urn:xmpp:gaming:0' namespace = 'urn:xmpp:gaming:0'
plugin_attrib = 'gaming' plugin_attrib = 'gaming'
interfaces = {'character_name', 'character_profile', 'name', interfaces = {'character_name', 'character_profile', 'name',
'level', 'server_address', 'server_name', 'uri'} 'level', 'server_address', 'server_name', 'uri'}
sub_interfaces = interfaces sub_interfaces = interfaces

View File

@@ -71,8 +71,7 @@ class XEP_0198(BasePlugin):
self.window_counter = self.window self.window_counter = self.window
self.enabled_in = False self.enabled = False
self.enabled_out = False
self.unacked_queue = collections.deque() self.unacked_queue = collections.deque()
register_stanza_plugin(StreamFeatures, stanza.StreamManagement) register_stanza_plugin(StreamFeatures, stanza.StreamManagement)
@@ -83,6 +82,10 @@ class XEP_0198(BasePlugin):
self.xmpp.register_stanza(stanza.Ack) self.xmpp.register_stanza(stanza.Ack)
self.xmpp.register_stanza(stanza.RequestAck) self.xmpp.register_stanza(stanza.RequestAck)
# Only end the session when a </stream> element is sent,
# not just because the connection has died.
self.xmpp.end_session_on_disconnect = False
# Register the feature twice because it may be ordered two # Register the feature twice because it may be ordered two
# different ways: enabling after binding and resumption # different ways: enabling after binding and resumption
# before binding. # before binding.
@@ -128,7 +131,6 @@ class XEP_0198(BasePlugin):
self.xmpp.add_filter('in', self._handle_incoming) self.xmpp.add_filter('in', self._handle_incoming)
self.xmpp.add_filter('out_sync', self._handle_outgoing) self.xmpp.add_filter('out_sync', self._handle_outgoing)
self.xmpp.add_event_handler('disconnected', self.disconnected)
self.xmpp.add_event_handler('session_end', self.session_end) self.xmpp.add_event_handler('session_end', self.session_end)
def plugin_end(self): def plugin_end(self):
@@ -137,7 +139,6 @@ class XEP_0198(BasePlugin):
self.xmpp.unregister_feature('sm', self.order) self.xmpp.unregister_feature('sm', self.order)
self.xmpp.unregister_feature('sm', self.resume_order) self.xmpp.unregister_feature('sm', self.resume_order)
self.xmpp.del_event_handler('disconnected', self.disconnected)
self.xmpp.del_event_handler('session_end', self.session_end) self.xmpp.del_event_handler('session_end', self.session_end)
self.xmpp.del_filter('in', self._handle_incoming) self.xmpp.del_filter('in', self._handle_incoming)
self.xmpp.del_filter('out_sync', self._handle_outgoing) self.xmpp.del_filter('out_sync', self._handle_outgoing)
@@ -153,19 +154,9 @@ class XEP_0198(BasePlugin):
self.xmpp.remove_stanza(stanza.Ack) self.xmpp.remove_stanza(stanza.Ack)
self.xmpp.remove_stanza(stanza.RequestAck) self.xmpp.remove_stanza(stanza.RequestAck)
def disconnected(self, event):
"""Reset enabled state until we can resume/reenable."""
log.debug("disconnected, disabling SM")
self.xmpp.event('sm_disabled', event)
self.enabled_in = False
self.enabled_out = False
def session_end(self, event): def session_end(self, event):
"""Reset stream management state.""" """Reset stream management state."""
log.debug("session_end, disabling SM") self.enabled = False
self.xmpp.event('sm_disabled', event)
self.enabled_in = False
self.enabled_out = False
self.unacked_queue.clear() self.unacked_queue.clear()
self.sm_id = None self.sm_id = None
self.handled = 0 self.handled = 0
@@ -180,11 +171,11 @@ class XEP_0198(BasePlugin):
def request_ack(self, e=None): def request_ack(self, e=None):
"""Request an ack from the server.""" """Request an ack from the server."""
log.debug("requesting ack")
req = stanza.RequestAck(self.xmpp) req = stanza.RequestAck(self.xmpp)
self.xmpp.send_raw(str(req)) self.xmpp.send_raw(str(req))
async def _handle_sm_feature(self, features): @asyncio.coroutine
def _handle_sm_feature(self, features):
""" """
Enable or resume stream management. Enable or resume stream management.
@@ -203,20 +194,22 @@ class XEP_0198(BasePlugin):
enable = stanza.Enable(self.xmpp) enable = stanza.Enable(self.xmpp)
enable['resume'] = self.allow_resume enable['resume'] = self.allow_resume
enable.send() enable.send()
log.debug("enabling SM") self.enabled = True
self.handled = 0
self.unacked_queue.clear()
waiter = Waiter('enabled_or_failed', waiter = Waiter('enabled_or_failed',
MatchMany([ MatchMany([
MatchXPath(stanza.Enabled.tag_name()), MatchXPath(stanza.Enabled.tag_name()),
MatchXPath(stanza.Failed.tag_name())])) MatchXPath(stanza.Failed.tag_name())]))
self.xmpp.register_handler(waiter) self.xmpp.register_handler(waiter)
result = await waiter.wait() result = yield from waiter.wait()
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features: elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
self.enabled = True
resume = stanza.Resume(self.xmpp) resume = stanza.Resume(self.xmpp)
resume['h'] = self.handled resume['h'] = self.handled
resume['previd'] = self.sm_id resume['previd'] = self.sm_id
resume.send() resume.send()
log.debug("resuming SM")
# Wait for a response before allowing stream feature processing # Wait for a response before allowing stream feature processing
# to continue. The actual result processing will be done in the # to continue. The actual result processing will be done in the
@@ -226,7 +219,7 @@ class XEP_0198(BasePlugin):
MatchXPath(stanza.Resumed.tag_name()), MatchXPath(stanza.Resumed.tag_name()),
MatchXPath(stanza.Failed.tag_name())])) MatchXPath(stanza.Failed.tag_name())]))
self.xmpp.register_handler(waiter) self.xmpp.register_handler(waiter)
result = await waiter.wait() result = yield from waiter.wait()
if result is not None and result.name == 'resumed': if result is not None and result.name == 'resumed':
return True return True
return False return False
@@ -239,10 +232,7 @@ class XEP_0198(BasePlugin):
self.xmpp.features.add('stream_management') self.xmpp.features.add('stream_management')
if stanza['id']: if stanza['id']:
self.sm_id = stanza['id'] self.sm_id = stanza['id']
self.enabled_in = True
self.handled = 0
self.xmpp.event('sm_enabled', stanza) self.xmpp.event('sm_enabled', stanza)
self.xmpp.end_session_on_disconnect = False
def _handle_resumed(self, stanza): def _handle_resumed(self, stanza):
"""Finish resuming a stream by resending unacked stanzas. """Finish resuming a stream by resending unacked stanzas.
@@ -250,12 +240,10 @@ class XEP_0198(BasePlugin):
Raises a :term:`session_resumed` event. Raises a :term:`session_resumed` event.
""" """
self.xmpp.features.add('stream_management') self.xmpp.features.add('stream_management')
self.enabled_in = True
self._handle_ack(stanza) self._handle_ack(stanza)
for id, stanza in self.unacked_queue: for id, stanza in self.unacked_queue:
self.xmpp.send(stanza, use_filters=False) self.xmpp.send(stanza, use_filters=False)
self.xmpp.event('session_resumed', stanza) self.xmpp.event('session_resumed', stanza)
self.xmpp.end_session_on_disconnect = False
def _handle_failed(self, stanza): def _handle_failed(self, stanza):
""" """
@@ -265,8 +253,7 @@ class XEP_0198(BasePlugin):
Raises an :term:`sm_failed` event. Raises an :term:`sm_failed` event.
""" """
self.enabled_in = False self.enabled = False
self.enabled_out = False
self.unacked_queue.clear() self.unacked_queue.clear()
self.xmpp.event('sm_failed', stanza) self.xmpp.event('sm_failed', stanza)
@@ -303,7 +290,7 @@ class XEP_0198(BasePlugin):
def _handle_incoming(self, stanza): def _handle_incoming(self, stanza):
"""Increment the handled counter for each inbound stanza.""" """Increment the handled counter for each inbound stanza."""
if not self.enabled_in: if not self.enabled:
return stanza return stanza
if isinstance(stanza, (Message, Presence, Iq)): if isinstance(stanza, (Message, Presence, Iq)):
@@ -313,13 +300,7 @@ class XEP_0198(BasePlugin):
def _handle_outgoing(self, stanza): def _handle_outgoing(self, stanza):
"""Store outgoing stanzas in a queue to be acked.""" """Store outgoing stanzas in a queue to be acked."""
from slixmpp.plugins.xep_0198 import stanza as st if not self.enabled:
if isinstance(stanza, (st.Enable, st.Resume)):
self.enabled_out = True
self.unacked_queue.clear()
log.debug("enabling outgoing SM: %s" % stanza)
if not self.enabled_out:
return stanza return stanza
if isinstance(stanza, (Message, Presence, Iq)): if isinstance(stanza, (Message, Presence, Iq)):

View File

@@ -95,10 +95,7 @@ class XEP_0199(BasePlugin):
self.timeout = timeout self.timeout = timeout
self.keepalive = True self.keepalive = True
handler = lambda event=None: asyncio.ensure_future( handler = lambda event=None: asyncio.ensure_future(self._keepalive(event))
self._keepalive(event),
loop=self.xmpp.loop,
)
self.xmpp.schedule('Ping keepalive', self.xmpp.schedule('Ping keepalive',
self.interval, self.interval,
handler, handler,
@@ -107,14 +104,15 @@ class XEP_0199(BasePlugin):
def disable_keepalive(self, event=None): def disable_keepalive(self, event=None):
self.xmpp.cancel_schedule('Ping keepalive') self.xmpp.cancel_schedule('Ping keepalive')
async def _keepalive(self, event=None): @asyncio.coroutine
def _keepalive(self, event=None):
log.debug("Keepalive ping...") log.debug("Keepalive ping...")
try: try:
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout) rtt = yield from self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
except IqTimeout: except IqTimeout:
log.debug("Did not receive ping back in time. " + \ log.debug("Did not recieve ping back in time." + \
"Requesting Reconnect.") "Requesting Reconnect.")
self.xmpp.reconnect(0.0, "Ping timeout after %ds" % self.timeout) self.xmpp.reconnect()
else: else:
log.debug('Keepalive RTT: %s' % rtt) log.debug('Keepalive RTT: %s' % rtt)
@@ -147,7 +145,8 @@ class XEP_0199(BasePlugin):
return iq.send(timeout=timeout, callback=callback, return iq.send(timeout=timeout, callback=callback,
timeout_callback=timeout_callback) timeout_callback=timeout_callback)
async def ping(self, jid=None, ifrom=None, timeout=None): @asyncio.coroutine
def ping(self, jid=None, ifrom=None, timeout=None):
"""Send a ping request and calculate RTT. """Send a ping request and calculate RTT.
This is a coroutine. This is a coroutine.
@@ -175,7 +174,7 @@ class XEP_0199(BasePlugin):
log.debug('Pinging %s' % jid) log.debug('Pinging %s' % jid)
try: try:
await self.send_ping(jid, ifrom=ifrom, timeout=timeout) yield from self.send_ping(jid, ifrom=ifrom, timeout=timeout)
except IqError as e: except IqError as e:
if own_host: if own_host:
rtt = time.time() - start rtt = time.time() - start

View File

@@ -123,5 +123,5 @@ class EntityTime(ElementBase):
if not isinstance(value, dt.datetime): if not isinstance(value, dt.datetime):
date = xep_0082.parse(value) date = xep_0082.parse(value)
date = date.astimezone(tzutc()) date = date.astimezone(tzutc())
value = xep_0082.format_datetime(date) value = xep_0082.format_datetime(date)[:-1]
self._set_sub_text('utc', value) self._set_sub_text('utc', value)

View File

@@ -40,7 +40,7 @@ class XEP_0202(BasePlugin):
} }
def plugin_init(self): def plugin_init(self):
"""Start the XEP-0202 plugin.""" """Start the XEP-0203 plugin."""
if not self.local_time: if not self.local_time:
def default_local_time(jid): def default_local_time(jid):
@@ -50,7 +50,7 @@ class XEP_0202(BasePlugin):
self.xmpp.register_handler( self.xmpp.register_handler(
Callback('Entity Time', Callback('Entity Time',
StanzaPath('iq@type=get/entity_time'), StanzaPath('iq/entity_time'),
self._handle_time_request)) self._handle_time_request))
register_stanza_plugin(Iq, stanza.EntityTime) register_stanza_plugin(Iq, stanza.EntityTime)

View File

@@ -73,11 +73,11 @@ class XEP_0222(BasePlugin):
ftype='hidden', ftype='hidden',
value='http://jabber.org/protocol/pubsub#publish-options') value='http://jabber.org/protocol/pubsub#publish-options')
fields = options.get_fields() fields = options['fields']
for field, value in self.profile.items(): for field, value in self.profile.items():
if field not in fields: if field not in fields:
options.add_field(var=field) options.add_field(var=field)
options.get_fields()[field]['value'] = value options['fields'][field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node, return self.xmpp['xep_0163'].publish(stanza, node,
options=options, options=options,

View File

@@ -26,7 +26,7 @@ class XEP_0223(BasePlugin):
dependencies = {'xep_0163', 'xep_0060', 'xep_0004'} dependencies = {'xep_0163', 'xep_0060', 'xep_0004'}
profile = {'pubsub#persist_items': True, profile = {'pubsub#persist_items': True,
'pubsub#access_model': 'whitelist'} 'pubsub#send_last_published_item': 'never'}
def configure(self, node, ifrom=None, callback=None, timeout=None): def configure(self, node, ifrom=None, callback=None, timeout=None):
""" """
@@ -78,7 +78,7 @@ class XEP_0223(BasePlugin):
for field, value in self.profile.items(): for field, value in self.profile.items():
if field not in fields: if field not in fields:
options.add_field(var=field) options.add_field(var=field)
options.get_fields()[field]['value'] = value options['fields'][field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node, options=options, return self.xmpp['xep_0163'].publish(stanza, node, options=options,
ifrom=ifrom, callback=callback, ifrom=ifrom, callback=callback,

View File

@@ -31,11 +31,11 @@ class XEP_0279(BasePlugin):
def plugin_end(self): def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0') self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0')
def check_ip(self, ifrom=None, timeout=None, callback=None, def check_ip(self, ifrom=None, block=True, timeout=None, callback=None,
timeout_callback=None): timeout_callback=None):
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'get' iq['type'] = 'get'
iq['from'] = ifrom iq['from'] = ifrom
iq.enable('ip_check') iq.enable('ip_check')
return iq.send(timeout=timeout, callback=callback, return iq.send(block=block, timeout=timeout, callback=callback,
timeout_callback=timeout_callback) timeout_callback=timeout_callback)

View File

@@ -25,7 +25,7 @@ class XEP_0300(BasePlugin):
stanza = stanza stanza = stanza
default_config = { default_config = {
'block_size': 1024 * 1024, # One MiB 'block_size': 1024 * 1024, # One MiB
'preferded': 'sha-256', 'prefered': 'sha-256',
'enable_sha-1': False, 'enable_sha-1': False,
'enable_sha-256': True, 'enable_sha-256': True,
'enable_sha-512': True, 'enable_sha-512': True,
@@ -73,7 +73,7 @@ class XEP_0300(BasePlugin):
def compute_hash(self, filename, function=None): def compute_hash(self, filename, function=None):
if function is None: if function is None:
function = self.preferred function = self.prefered
h = self._hashlib_function[function]() h = self._hashlib_function[function]()
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
while True: while True:

View File

@@ -3,18 +3,16 @@
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of Slixmpp. This file is part of Slixmpp.
See the file LICENSE for copying permission See the file LICENSE for copying permissio
""" """
import logging import logging
from datetime import datetime import slixmpp
from typing import Any, Dict, Callable, Optional, Awaitable
from slixmpp import JID
from slixmpp.stanza import Message, Iq from slixmpp.stanza import Message, Iq
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream.handler import Collector from slixmpp.xmlstream.handler import Collector
from slixmpp.xmlstream.matcher import MatchXMLMask from slixmpp.xmlstream.matcher import StanzaPath
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.plugins.xep_0313 import stanza from slixmpp.plugins.xep_0313 import stanza
@@ -43,32 +41,8 @@ class XEP_0313(BasePlugin):
register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set) register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set) register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set)
def retrieve( def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
self, timeout=None, callback=None, iterator=False, rsm=None):
jid: Optional[JID] = None,
start: Optional[datetime] = None,
end: Optional[datetime] = None,
with_jid: Optional[JID] = None,
ifrom: Optional[JID] = None,
reverse: bool = False,
timeout: int = None,
callback: Callable[[Iq], None] = None,
iterator: bool = False,
rsm: Optional[Dict[str, Any]] = None
) -> Awaitable:
"""
Send a MAM query and retrieve the results.
:param JID jid: Entity holding the MAM records
:param datetime start,end: MAM query temporal boundaries
:param JID with_jid: Filter results on this JID
:param JID ifrom: To change the from address of the query
:param bool reverse: Get the results in reverse order
:param int timeout: IQ timeout
:param func callback: Custom callback for handling results
:param bool iterator: Use RSM and iterate over a paginated query
:param dict rsm: RSM custom options
"""
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
query_id = iq['id'] query_id = iq['id']
@@ -79,48 +53,35 @@ class XEP_0313(BasePlugin):
iq['mam']['start'] = start iq['mam']['start'] = start
iq['mam']['end'] = end iq['mam']['end'] = end
iq['mam']['with'] = with_jid iq['mam']['with'] = with_jid
amount = 10
if rsm: if rsm:
for key, value in rsm.items(): for key, value in rsm.items():
iq['mam']['rsm'][key] = str(value) iq['mam']['rsm'][key] = str(value)
if key == 'max':
amount = value
cb_data = {} cb_data = {}
def pre_cb(query):
stanza_mask = self.xmpp.Message()
stanza_mask.xml.remove(stanza_mask.xml.find('{urn:xmpp:sid:0}origin-id'))
del stanza_mask['id']
del stanza_mask['lang']
stanza_mask['from'] = jid
stanza_mask['mam_result']['queryid'] = query_id
xml_mask = str(stanza_mask)
def pre_cb(query: Iq) -> None:
stanza_mask['mam_result']['queryid'] = query['id']
xml_mask = str(stanza_mask)
query['mam']['queryid'] = query['id'] query['mam']['queryid'] = query['id']
collector = Collector( collector = Collector(
'MAM_Results_%s' % query_id, 'MAM_Results_%s' % query_id,
MatchXMLMask(xml_mask)) StanzaPath('message/mam_result@queryid=%s' % query['id']))
self.xmpp.register_handler(collector) self.xmpp.register_handler(collector)
cb_data['collector'] = collector cb_data['collector'] = collector
def post_cb(result: Iq) -> None: def post_cb(result):
results = cb_data['collector'].stop() results = cb_data['collector'].stop()
if result['type'] == 'result': if result['type'] == 'result':
result['mam']['results'] = results result['mam']['results'] = results
if iterator: if iterator:
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', amount=amount, return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results',
reverse=reverse, recv_interface='mam_fin', recv_interface='mam_fin',
pre_cb=pre_cb, post_cb=post_cb) pre_cb=pre_cb, post_cb=post_cb)
collector = Collector( collector = Collector(
'MAM_Results_%s' % query_id, 'MAM_Results_%s' % query_id,
MatchXMLMask(xml_mask)) StanzaPath('message/mam_result@queryid=%s' % query_id))
self.xmpp.register_handler(collector) self.xmpp.register_handler(collector)
def wrapped_cb(iq: Iq) -> None: def wrapped_cb(iq):
results = collector.stop() results = collector.stop()
if iq['type'] == 'result': if iq['type'] == 'result':
iq['mam']['results'] = results iq['mam']['results'] = results
@@ -129,15 +90,8 @@ class XEP_0313(BasePlugin):
return iq.send(timeout=timeout, callback=wrapped_cb) return iq.send(timeout=timeout, callback=wrapped_cb)
def get_preferences(self, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
query_id = iq['id']
iq['mam_prefs']['query_id'] = query_id
return iq.send(timeout=timeout, callback=callback)
def set_preferences(self, jid=None, default=None, always=None, never=None, def set_preferences(self, jid=None, default=None, always=None, never=None,
ifrom=None, timeout=None, callback=None): ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'set' iq['type'] = 'set'
iq['to'] = jid iq['to'] = jid
@@ -145,7 +99,7 @@ class XEP_0313(BasePlugin):
iq['mam_prefs']['default'] = default iq['mam_prefs']['default'] = default
iq['mam_prefs']['always'] = always iq['mam_prefs']['always'] = always
iq['mam_prefs']['never'] = never iq['mam_prefs']['never'] = never
return iq.send(timeout=timeout, callback=callback) return iq.send(block=block, timeout=timeout, callback=callback)
def get_configuration_commands(self, jid, **kwargs): def get_configuration_commands(self, jid, **kwargs):
return self.xmpp['xep_0030'].get_items( return self.xmpp['xep_0030'].get_items(

View File

@@ -291,7 +291,7 @@ class XEP_0323(BasePlugin):
request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600 request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600
if request_delay_sec <= 0: if request_delay_sec <= 0:
req_ok = False req_ok = False
error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past (%s). Current time: %s" % (dt.isoformat(), dtnow.isoformat()) error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat()
if req_ok: if req_ok:
session = self._new_session() session = self._new_session()

View File

@@ -516,7 +516,7 @@ class Field(ElementBase):
:param value: string :param value: string
""" """
pattern = re.compile(r"^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$") pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$")
if pattern.match(value) is not None: if pattern.match(value) is not None:
self.xml.stringIds = value self.xml.stringIds = value
else: else:

View File

@@ -399,7 +399,7 @@ class XEP_0325(BasePlugin):
""" """
if not session in self.sessions: if not session in self.sessions:
# This can happen if a session was deleted, like in a timeout. Just drop the data. # This can happend if a session was deleted, like in a timeout. Just drop the data.
return return
if result == "error": if result == "error":
@@ -457,7 +457,7 @@ class XEP_0325(BasePlugin):
Arguments: Arguments:
from_jid -- The jid of the requester from_jid -- The jid of the requester
to_jid -- The jid of the device(s) to_jid -- The jid of the device(s)
callback -- The callback function to call when data is available. callback -- The callback function to call when data is availble.
The callback function must support the following arguments: The callback function must support the following arguments:

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