Compare commits

..

No commits in common. "master" and "slix-1.8.1" have entirely different histories.

198 changed files with 890 additions and 5532 deletions

13
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,13 @@
################ 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.

70
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,70 @@
stages:
- lint
- test
- trigger
mypy:
stage: lint
tags:
- docker
image: python:3
script:
- pip3 install mypy
- mypy slixmpp
test:
stage: test
tags:
- docker
image: ubuntu:latest
script:
- apt update
- apt install -y python3 python3-pip cython3 gpg
- pip3 install emoji aiohttp
- ./run_tests.py
test-3.10:
stage: test
tags:
- docker
image: python:3.10
script:
- apt update
- apt install -y python3 python3-pip cython3 gpg
- pip3 install emoji aiohttp
- ./run_tests.py
test-3.11:
stage: test
tags:
- docker
image: python:3.11-rc
allow_failure: true
script:
- apt update
- apt install -y python3 python3-pip cython3 gpg
- pip3 install emoji aiohttp
- ./run_tests.py
test_integration:
stage: test
tags:
- docker
image: ubuntu:latest
only:
variables:
- $CI_ACCOUNT1
- $CI_ACCOUNT2
script:
- apt update
- apt install -y python3 python3-pip cython3 gpg
- pip3 install emoji aiohttp aiodns
- ./run_integration_tests.py
trigger_poezio:
stage: trigger
tags:
- docker
image: curlimages/curl:7.79.1
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,22 +0,0 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt

7
.travis.yml Normal file
View File

@ -0,0 +1,7 @@
language: python
python:
- "3.7"
- "3.8-dev"
install:
- "pip install ."
script: testall.py

View File

@ -1,9 +0,0 @@
when:
event: [ push, pull_request ]
steps:
mypy:
image: python:3
commands:
- pip3 install mypy types-setuptools
- mypy slixmpp

View File

@ -1,23 +0,0 @@
when:
event: [ push, pull_request ]
steps:
test_integration:
image: "python:3.11"
environment:
CI_ACCOUNT1:
from_secret: ci_account1
CI_ACCOUNT1_PASSWORD:
from_secret: ci_account1_password
CI_ACCOUNT2:
from_secret: ci_account2
CI_ACCOUNT2_PASSWORD:
from_secret: ci_account2_password
CI_MUC_SERVER:
from_secret: ci_muc_server
commands:
- apt-get update
- apt-get install -y python3-pip cython3 gpg idn libidn-dev
- pip3 install emoji aiohttp aiodns
- python3 setup.py build_ext --inplace
- ./run_integration_tests.py

View File

@ -1,19 +0,0 @@
when:
event: [ push, pull_request ]
steps:
unit_tests:
image: "python:${TAG}"
commands:
- apt-get update
- apt-get install -y python3 python3-pip cython3 gpg
- pip3 install emoji aiohttp cryptography setuptools
- ./run_tests.py
matrix:
TAG:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"

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
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
notify the developers with either:
- a ticket `on the bug tracker <https://codeberg.org/poezio/slixmpp/issues/new>`_
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
- a pull request on github
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_

136
doap.xml
View File

@ -8,13 +8,13 @@
<shortdesc xml:lang="en">Elegant Python library for XMPP</shortdesc>
<shortdesc xml:lang="fr">Bibliothèque pour XMPP élégante, en Python</shortdesc>
<homepage rdf:resource="https://codeberg.org/poezio/slixmpp/"/>
<download-page rdf:resource="https://codeberg.org/poezio/slixmpp/tags"/>
<bug-database rdf:resource="https://codeberg.org/poezio/slixmpp/issues"/>
<homepage rdf:resource="https://lab.louiz.org/poezio/slixmpp/"/>
<download-page rdf:resource="https://lab.louiz.org/poezio/slixmpp/tags"/>
<bug-database rdf:resource="https://lab.louiz.org/poezio/slixmpp/issues"/>
<developer-forum rdf:resource="xmpp:slixmpp@muc.poez.io?join"/>
<support-forum rdf:resource="xmpp:slixmpp@muc.poez.io?join"/>
<license rdf:resource="https://codeberg.org/poezio/slixmpp/raw/brach/master/LICENSE"/>
<license rdf:resource="https://lab.louiz.org/poezio/slixmpp/blob/master/LICENSE"/>
<language>en</language>
@ -59,8 +59,8 @@
<repository>
<GitRepository>
<browse rdf:resource="https://codeberg.org/poezio/slixmpp"/>
<location rdf:resource="https://codeberg.org/poezio/slixmpp.git"/>
<browse rdf:resource="https://lab.louiz.org/poezio/slixmpp"/>
<location rdf:resource="https://lab.louiz.org/poezio/slixmpp.git"/>
</GitRepository>
</repository>
@ -455,14 +455,6 @@
<xmpp:since>1.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0175.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2</xmpp:version>
<xmpp:since>1.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/>
@ -616,14 +608,6 @@
<xmpp:since>1.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0264.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.4.2</xmpp:version>
<xmpp:since>1.8.6</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0270.html"/>
@ -690,14 +674,6 @@
<xmpp:since>1.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0317.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.8.6</xmpp:version>
<xmpp:since>0.2</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0319.html"/>
@ -800,7 +776,7 @@
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0377.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3</xmpp:version>
<xmpp:version>0.2</xmpp:version>
<xmpp:since>1.6.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
@ -872,7 +848,7 @@
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0424.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.4.0</xmpp:version>
<xmpp:version>0.3.0</xmpp:version>
<xmpp:since>1.6.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
@ -880,7 +856,7 @@
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0425.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3.0</xmpp:version>
<xmpp:version>0.2.1</xmpp:version>
<xmpp:since>1.6.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
@ -916,55 +892,6 @@
<xmpp:since>1.6.0</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0446.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.2.0</xmpp:version>
<xmpp:since>1.8.7</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html"/>
<xmpp:status>partial</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>1.8.1</xmpp:since>
<xmpp:note>no thumbnail support</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0469.html"/>
<xmpp:status>partial</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>1.8.6</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0482.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>1.8.7</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0490.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>1.8.6</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0492.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>1.8.7</xmpp:since>
</xmpp:SupportedXep>
</implements>
<release>
<Version>
@ -1068,70 +995,35 @@
<Version>
<revision>1.6.0</revision>
<created>2020-12-12</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.6.0.tar.gz"/>
<file-release rdf:resource="https://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.6.0/slixmpp-slix-1.6.0.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.7.0</revision>
<created>2021-01-29</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.7.0.tar.gz"/>
<file-release rdf:resource="https://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.7.0/slixmpp-slix-1.7.0.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.7.1</revision>
<created>2021-04-30</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.7.1.tar.gz"/>
<file-release rdf:resource="https://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.7.1/slixmpp-slix-1.7.1.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.8.0</revision>
<created>2022-02-27</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.8.0.tar.gz"/>
<file-release rdf:resource="https://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.8.0/slixmpp-slix-1.8.0.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.8.1</revision>
<created>2022-03-20</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.8.1.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.8.2</revision>
<created>2022-04-06</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.8.2.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.8.3</revision>
<created>2022-11-12</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.8.3.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.8.4</revision>
<created>2023-05-28</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.8.4.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.8.5</revision>
<created>2024-02-02</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.8.5.tar.gz"/>
</Version>
</release>
<release>
<Version>
<revision>1.8.6</revision>
<created>2024-12-26</created>
<file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/slix-1.8.6.tar.gz"/>
<file-release rdf:resource="https://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.8.1/slixmpp-slix-1.8.1.tar.gz"/>
</Version>
</release>
</Project>

View File

@ -17,7 +17,6 @@ Plugin index
xep_0049
xep_0050
xep_0054
xep_0055
xep_0059
xep_0060
xep_0065
@ -32,7 +31,6 @@ Plugin index
xep_0085
xep_0086
xep_0092
xep_0100
xep_0106
xep_0107
xep_0108
@ -64,15 +62,12 @@ Plugin index
xep_0256
xep_0257
xep_0258
xep_0264
xep_0279
xep_0280
xep_0292
xep_0297
xep_0300
xep_0308
xep_0313
xep_0317
xep_0319
xep_0332
xep_0333
@ -84,13 +79,9 @@ Plugin index
xep_0359
xep_0363
xep_0369
xep_0372
xep_0377
xep_0380
xep_0382
xep_0385
xep_0394
xep_0402
xep_0403
xep_0404
xep_0405
@ -103,9 +94,3 @@ Plugin index
xep_0439
xep_0441
xep_0444
xep_0446
xep_0447
xep_0461
xep_0469
xep_0490
xep_0492

View File

@ -1,18 +0,0 @@
XEP-0055: Jabber search
=======================
.. module:: slixmpp.plugins.xep_0055
.. autoclass:: XEP_0055
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0055.stanza
:members:
:undoc-members:

View File

@ -1,5 +1,5 @@
XEP-0100: Gateway interaction
XEP-0106: Gateway interaction
=============================
.. module:: slixmpp.plugins.xep_0100

View File

@ -1,18 +0,0 @@
XEP-0264: Jingle Content Thumbnails
===================================
.. module:: slixmpp.plugins.xep_0264
.. autoclass:: XEP_0264
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0264.stanza
:members:
:undoc-members:

View File

@ -1,17 +0,0 @@
XEP-0292: vCard4 Over XMPP
==========================
.. module:: slixmpp.plugins.xep_0292
.. autoclass:: XEP_0292
:members:
:exclude-members: plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0292.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0317: Hats
==============
.. module:: slixmpp.plugins.xep_0317
.. autoclass:: XEP_0317
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0317.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0372: References
====================
.. module:: slixmpp.plugins.xep_0372
.. autoclass:: XEP_0372
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0372.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0382: Spoiler Messages
==========================
.. module:: slixmpp.plugins.xep_0382
.. autoclass:: XEP_0382
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0382.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0385: Stateless Inline Media Sharing (SIMS)
===============================================
.. module:: slixmpp.plugins.xep_0385
.. autoclass:: XEP_0385
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0385.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0402: PEP Native Bookmarks
==============================
.. module:: slixmpp.plugins.xep_0402
.. autoclass:: XEP_0402
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0402.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0446: File metadata element
===============================
.. module:: slixmpp.plugins.xep_0446
.. autoclass:: XEP_0446
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0446.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0447: Stateless File Sharing
================================
.. module:: slixmpp.plugins.xep_0447
.. autoclass:: XEP_0447
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0447.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0461: Message Replies
=========================
.. module:: slixmpp.plugins.xep_0461
.. autoclass:: XEP_0461
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0461.stanza
:members:
:undoc-members:

View File

@ -1,17 +0,0 @@
XEP-0469: Bookmark Pinning
==========================
.. module:: slixmpp.plugins.xep_0469
.. autoclass:: XEP_0469
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0469.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0490: Message Displayed Synchronization
===========================================
.. module:: slixmpp.plugins.xep_0490
.. autoclass:: XEP_0490
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0490.stanza
:members:
:undoc-members:

View File

@ -1,18 +0,0 @@
XEP-0492: Chat Notification Settings
====================================
.. module:: slixmpp.plugins.xep_0492
.. autoclass:: XEP_0492
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0492.stanza
:members:
:undoc-members:

View File

@ -167,9 +167,8 @@ processing the same stanza twice.
- **Data:** :py:class:`~.Message`
- **Source:** :py:class:`BaseXMPP <.BaseXMPP>`
Makes the contents of message stanzas that include <body> tags available
whenever one is received.
Be sure to check the message type to handle error messages appropriately.
Makes the contents of message stanzas available whenever one is received. Be
sure to check the message type in order to handle error messages.
message_error
- **Data:** :py:class:`~.Message`

View File

@ -11,7 +11,7 @@ Create and Run a Server Component
<xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version
with `Git <https://codeberg.org/poezio/slixmpp>`_.
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
Many XMPP applications eventually graduate to requiring to run as a server
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>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version
with `Git <https://codeberg.org/poezio/slixmpp>`_.
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
As a basic starting project, we will create an echo bot which will reply to any
messages sent to it. We will also go through adding some basic command line configuration
@ -325,7 +325,7 @@ The Final Product
-----------------
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://codeberg.org/poezio/slixmpp/src/branch/master/examples>`_.
can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_.
.. compound::

View File

@ -11,7 +11,7 @@ Multi-User Chat (MUC) Bot
<xmpp:slixmpp@muc.poez.io?join>`_.
If you have not yet installed Slixmpp, do so now by either checking out a version
from `Git <https://codeberg.org/poezio/slixmpp>`_.
from `Git <https://lab.louiz.org/poezio/slixmpp>`_.
Now that you've got the basic gist of using Slixmpp by following the
echobot example (:ref:`echobot`), we can use one of the bundled plugins

View File

@ -4,9 +4,9 @@ Slixmpp
.. sidebar:: Get the Code
The latest source code for Slixmpp may be found on the `Git repo
<https://codeberg.org/poezio/slixmpp>`_. ::
<https://lab.louiz.org/poezio/slixmpp>`_. ::
git clone https://codeberg.org/poezio/slixmpp
git clone https://lab.louiz.org/poezio/slixmpp
An XMPP chat room is available for discussing and getting help with slixmpp.
@ -14,7 +14,7 @@ Slixmpp
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
**Reporting bugs**
You can report bugs at http://codeberg.org/poezio/slixmpp/issues.
You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues.
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+,

View File

@ -1,100 +0,0 @@
Projects Using Slixmpp
======================
This page enumerates software in the form of applications, bots and gateways utilizing the XMPP protocols with slixmpp.
Applications
------------
sendxmpp-py
~~~~~~~~~~~
sendxmpp is a command line program and is the XMPP equivalent of sendmail. It is a Python version of the original sendxmpp which is written in Perl.
- `Source <https://code.moparisthebest.com/moparisthebest/sendxmpp-py>`__
- `Groupchat <xmpp:xmpp-ircd@chatrooms.hackerposse.com?join>`__
Bots
----
BotLogMauve
~~~~~~~~~~~
XMPP bot which logs groupchat messages. Logs are in text format, with one file per day and per groupchat.
- `Source <https://git.khaganat.net/khaganat/BotLogMauve>`__
BukuBot
~~~~~~~
BukuBot makes it possible to manage and search your bookmarks from your chat.
- `Source <https://codeberg.org/sch/BukuBot>`__
LinkBot
~~~~~~~
This bot reveals the title of any shared link in a groupchat for quick content insight.
- `Source <https://git.xmpp-it.net/mario/XMPPBot>`__
llama-bot
~~~~~~~~~
Llama-bot enables engaging communication with the LLM (large language model) of llama.cpp, providing seamless and dynamic conversation with it.
- `Source <https://github.com/decent-im/llama-bot>`__
- `Demo <xmpp:llama@decent.im?message>`__
Morbot
~~~~~~
Morbot is a simple Slixmpp bot that will take new articles from listed RSS feeds and send them to assigned XMPP MUCs.
- `Source <https://codeberg.org/TheCoffeMaker/Morbot>`__
Slixfeed
~~~~~~~~
Slixfeed aims to be an easy to use and fully-featured news aggregator bot for XMPP. It provides a convenient access to Blogs, Fediverse and News websites along with filtering functionality.
- `Groupchat <xmpp:slixfeed@chat.woodpeckersnest.space?join>`__
- `Source <https://gitgud.io/sjehuda/slixfeed>`__
sms4you
~~~~~~~
sms4you forwards messages from and to SMS and connects either with sms4you-xmpp or sms4you-email to choose the other mean of communication. Nice for receiving or sending SMS, independently from carrying a SIM card.
- `Homepage <https://sms4you-team.pages.debian.net/sms4you/>`__
- `Source <https://salsa.debian.org/sms4you-team/sms4you>`__
Stable Diffusion
~~~~~~~~~~~~~~~~
XMPP bot that generates digital images from textual descriptions.
- `Groupchat <xmpp:slidge@conference.nicoco.fr?join>`__
- `Source <https://www.nicoco.fr/blog/2022/08/31/xmpp-bot-stable-diffusion/>`__
WhisperBot
~~~~~~~~~~
XMPP bot that transliterates audio messages using OpenAI's Whisper libraries.
- `Source <https://codeberg.org/TheCoffeMaker/WhisperBot>`__
XMPP MUC Message Gateway
~~~~~~~~~~~~~~~~~~~~~~~~
A multipurpose JSON forwarder microservice from HTTP POST to XMPP MUC room over TLSv1.2 with SliXMPP.
- `Source <https://github.com/immanuelfodor/xmpp-muc-message-gateway>`__
Services
--------
AtomToPubsub
~~~~~~~~~~~~
AtomToPubsub is a simple Python script that parses Atom + RSS feeds and pushes the entries to a designated XMPP Pubsub Node.
- `Groupchat <xmpp:movim@conference.movim.eu?join>`__
- `Source <https://github.com/imattau/atomtopubsub>`__
Slidge
~~~~~~
Slidge is a general purpose XMPP gateway framework in Python.
- `Groupchat <xmpp:slidge@conference.nicoco.fr?join>`__
- `Homepage <https://slidge.im/core/>`__
- `Source <https://sr.ht/~nicoco/slidge>`__

View File

@ -50,39 +50,10 @@ Running the event loop
only run for this amount of time, and if ``forever`` is False it will
run until disconnection).
This wrapper should be removed in slixmpp 1.9.0.
Therefore you can handle the event loop in any way you like
instead of using ``process()``.
Using connect()
~~~~~~~~~~~~~~~
:meth:`.XMLStream.connect` schedules a lot of things in the background, but that
only holds true if the event loop is running!
That is why in all examples we usually call connect() right before calling
a `loop.run_…` function, or the deprecated `process()` function.
Using a different event loop
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Immediately upon XMPP object creation (`ClientXMPP` / `ComponentXMPP`) you
should sets its `loop` attribute to whatever you want, and ideally this
should work. This path is less tested, so it may break, if that is the case
please report a bug.
Any access to the `loop` attribute if not user-initialized will set it
to the default asyncio event loop by default.
.. warning::
If the loop attribute is modified at runtime, the application will probably
end up in an hybrid state and asyncio may complain loudly that things bound
to an event loop are being ran in another. Try to avoid that situation.
Examples
~~~~~~~~
@ -102,11 +73,10 @@ callbacks while everything is not ready.
callback = lambda _: client.connected_event.set()
client.add_event_handler('session_start', callback)
client.connect()
loop = asyncio.get_event_loop()
loop.run_until_complete(event.wait())
# do some other stuff before running the event loop, e.g.
# loop.run_until_complete(httpserver.init())
loop.run_forever()
client.process()
Use with other asyncio-based libraries
@ -136,7 +106,7 @@ a simple <message>.
client.add_event_handler('session_start', get_pythonorg)
client.add_event_handler('session_start', get_asyncioorg)
client.connect()
client.loop.run_until_complete(client.disconnected)
client.process()
Blocking Iq
@ -166,6 +136,6 @@ JID indicating its findings.
client = ExampleClient('jid@example', 'password')
client.connect()
client.loop.run_until_complete(client.disconnected)
client.process()

View File

@ -5,16 +5,11 @@
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
from typing import Optional
import sys
import logging
from pathlib import Path
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp import JID
from slixmpp.exceptions import IqTimeout
log = logging.getLogger(__name__)
@ -26,40 +21,20 @@ class HttpUpload(slixmpp.ClientXMPP):
A basic client asking an entity if they confirm the access to an HTTP URL.
"""
def __init__(
self,
jid: JID,
password: str,
recipient: JID,
filename: Path,
domain: Optional[JID] = None,
encrypted: bool = False,
):
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.encrypted = encrypted
self.add_event_handler("session_start", self.start)
async def start(self, event):
log.info('Uploading file %s...', self.filename)
try:
upload_file = self['xep_0363'].upload_file
if self.encrypted and not self['xep_0454']:
print(
'The xep_0454 module isn\'t available. '
'Ensure you have \'cryptography\' '
'from extras_require installed.',
file=sys.stderr,
)
return
elif self.encrypted:
upload_file = self['xep_0454'].upload_file
url = await upload_file(
self.filename, domain=self.domain, timeout=10,
url = await self['xep_0363'].upload_file(
self.filename, domain=self.domain, timeout=10
)
except IqTimeout:
raise TimeoutError('Could not send message in time')
@ -104,10 +79,6 @@ if __name__ == '__main__':
parser.add_argument("--domain",
help="Domain to use for HTTP File Upload (leave out for your own servers)")
parser.add_argument("-e", "--encrypt", dest="encrypted",
help="Whether to encrypt", action="store_true",
default=False)
args = parser.parse_args()
# Setup logging.
@ -115,41 +86,15 @@ if __name__ == '__main__':
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = JID(input("Username: "))
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
domain = args.domain
if domain is not None:
domain = JID(domain)
if args.encrypted:
print(
'You are using the --encrypt flag. '
'Be aware that the transport being used is NOT end-to-end '
'encrypted. The server will be able to decrypt the file.',
file=sys.stderr,
)
xmpp = HttpUpload(
jid=args.jid,
password=args.password,
recipient=JID(args.recipient),
filename=Path(args.file),
domain=domain,
encrypted=args.encrypted,
)
xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain)
xmpp.register_plugin('xep_0066')
xmpp.register_plugin('xep_0071')
xmpp.register_plugin('xep_0128')
xmpp.register_plugin('xep_0363')
try:
xmpp.register_plugin('xep_0454')
except slixmpp.plugins.base.PluginNotFound:
log.error(
'Could not load xep_0454. '
'Ensure you have \'cryptography\' from extras_require installed.'
)
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()

View File

@ -1,184 +0,0 @@
"""
Recognize image file formats based on their first few bytes.
Taken from cpython 3.11 source code before the removal in 3.13.
Licensed under Zero-Clause BSD
"""
from os import PathLike
import warnings
__all__ = ["what"]
warnings._deprecated(__name__, remove=(3, 13))
#-------------------------#
# Recognize image headers #
#-------------------------#
def what(file, h=None):
f = None
try:
if h is None:
if isinstance(file, (str, PathLike)):
f = open(file, 'rb')
h = f.read(32)
else:
location = file.tell()
h = file.read(32)
file.seek(location)
for tf in tests:
res = tf(h, f)
if res:
return res
finally:
if f: f.close()
return None
#---------------------------------#
# Subroutines per image file type #
#---------------------------------#
tests = []
def test_jpeg(h, f):
"""JPEG data with JFIF or Exif markers; and raw JPEG"""
if h[6:10] in (b'JFIF', b'Exif'):
return 'jpeg'
elif h[:4] == b'\xff\xd8\xff\xdb':
return 'jpeg'
tests.append(test_jpeg)
def test_png(h, f):
if h.startswith(b'\211PNG\r\n\032\n'):
return 'png'
tests.append(test_png)
def test_gif(h, f):
"""GIF ('87 and '89 variants)"""
if h[:6] in (b'GIF87a', b'GIF89a'):
return 'gif'
tests.append(test_gif)
def test_tiff(h, f):
"""TIFF (can be in Motorola or Intel byte order)"""
if h[:2] in (b'MM', b'II'):
return 'tiff'
tests.append(test_tiff)
def test_rgb(h, f):
"""SGI image library"""
if h.startswith(b'\001\332'):
return 'rgb'
tests.append(test_rgb)
def test_pbm(h, f):
"""PBM (portable bitmap)"""
if len(h) >= 3 and \
h[0] == ord(b'P') and h[1] in b'14' and h[2] in b' \t\n\r':
return 'pbm'
tests.append(test_pbm)
def test_pgm(h, f):
"""PGM (portable graymap)"""
if len(h) >= 3 and \
h[0] == ord(b'P') and h[1] in b'25' and h[2] in b' \t\n\r':
return 'pgm'
tests.append(test_pgm)
def test_ppm(h, f):
"""PPM (portable pixmap)"""
if len(h) >= 3 and \
h[0] == ord(b'P') and h[1] in b'36' and h[2] in b' \t\n\r':
return 'ppm'
tests.append(test_ppm)
def test_rast(h, f):
"""Sun raster file"""
if h.startswith(b'\x59\xA6\x6A\x95'):
return 'rast'
tests.append(test_rast)
def test_xbm(h, f):
"""X bitmap (X10 or X11)"""
if h.startswith(b'#define '):
return 'xbm'
tests.append(test_xbm)
def test_bmp(h, f):
if h.startswith(b'BM'):
return 'bmp'
tests.append(test_bmp)
def test_webp(h, f):
if h.startswith(b'RIFF') and h[8:12] == b'WEBP':
return 'webp'
tests.append(test_webp)
def test_exr(h, f):
if h.startswith(b'\x76\x2f\x31\x01'):
return 'exr'
tests.append(test_exr)
#--------------------#
# Small test program #
#--------------------#
def test():
import sys
recursive = 0
if sys.argv[1:] and sys.argv[1] == '-r':
del sys.argv[1:2]
recursive = 1
try:
if sys.argv[1:]:
testall(sys.argv[1:], recursive, 1)
else:
testall(['.'], recursive, 1)
except KeyboardInterrupt:
sys.stderr.write('\n[Interrupted]\n')
sys.exit(1)
def testall(list, recursive, toplevel):
import sys
import os
for filename in list:
if os.path.isdir(filename):
print(filename + '/:', end=' ')
if recursive or toplevel:
print('recursing down:')
import glob
names = glob.glob(os.path.join(glob.escape(filename), '*'))
testall(names, recursive, 0)
else:
print('*** directory (use -r) ***')
else:
print(filename + ':', end=' ')
sys.stdout.flush()
try:
print(what(filename))
except OSError:
print('*** not found ***')
if __name__ == '__main__':
test()

View File

@ -1,184 +0,0 @@
"""
Recognize image file formats based on their first few bytes.
Taken from cpython 3.11 source code before the removal in 3.13.
Licensed under Zero-Clause BSD
"""
from os import PathLike
import warnings
__all__ = ["what"]
warnings._deprecated(__name__, remove=(3, 13))
#-------------------------#
# Recognize image headers #
#-------------------------#
def what(file, h=None):
f = None
try:
if h is None:
if isinstance(file, (str, PathLike)):
f = open(file, 'rb')
h = f.read(32)
else:
location = file.tell()
h = file.read(32)
file.seek(location)
for tf in tests:
res = tf(h, f)
if res:
return res
finally:
if f: f.close()
return None
#---------------------------------#
# Subroutines per image file type #
#---------------------------------#
tests = []
def test_jpeg(h, f):
"""JPEG data with JFIF or Exif markers; and raw JPEG"""
if h[6:10] in (b'JFIF', b'Exif'):
return 'jpeg'
elif h[:4] == b'\xff\xd8\xff\xdb':
return 'jpeg'
tests.append(test_jpeg)
def test_png(h, f):
if h.startswith(b'\211PNG\r\n\032\n'):
return 'png'
tests.append(test_png)
def test_gif(h, f):
"""GIF ('87 and '89 variants)"""
if h[:6] in (b'GIF87a', b'GIF89a'):
return 'gif'
tests.append(test_gif)
def test_tiff(h, f):
"""TIFF (can be in Motorola or Intel byte order)"""
if h[:2] in (b'MM', b'II'):
return 'tiff'
tests.append(test_tiff)
def test_rgb(h, f):
"""SGI image library"""
if h.startswith(b'\001\332'):
return 'rgb'
tests.append(test_rgb)
def test_pbm(h, f):
"""PBM (portable bitmap)"""
if len(h) >= 3 and \
h[0] == ord(b'P') and h[1] in b'14' and h[2] in b' \t\n\r':
return 'pbm'
tests.append(test_pbm)
def test_pgm(h, f):
"""PGM (portable graymap)"""
if len(h) >= 3 and \
h[0] == ord(b'P') and h[1] in b'25' and h[2] in b' \t\n\r':
return 'pgm'
tests.append(test_pgm)
def test_ppm(h, f):
"""PPM (portable pixmap)"""
if len(h) >= 3 and \
h[0] == ord(b'P') and h[1] in b'36' and h[2] in b' \t\n\r':
return 'ppm'
tests.append(test_ppm)
def test_rast(h, f):
"""Sun raster file"""
if h.startswith(b'\x59\xA6\x6A\x95'):
return 'rast'
tests.append(test_rast)
def test_xbm(h, f):
"""X bitmap (X10 or X11)"""
if h.startswith(b'#define '):
return 'xbm'
tests.append(test_xbm)
def test_bmp(h, f):
if h.startswith(b'BM'):
return 'bmp'
tests.append(test_bmp)
def test_webp(h, f):
if h.startswith(b'RIFF') and h[8:12] == b'WEBP':
return 'webp'
tests.append(test_webp)
def test_exr(h, f):
if h.startswith(b'\x76\x2f\x31\x01'):
return 'exr'
tests.append(test_exr)
#--------------------#
# Small test program #
#--------------------#
def test():
import sys
recursive = 0
if sys.argv[1:] and sys.argv[1] == '-r':
del sys.argv[1:2]
recursive = 1
try:
if sys.argv[1:]:
testall(sys.argv[1:], recursive, 1)
else:
testall(['.'], recursive, 1)
except KeyboardInterrupt:
sys.stderr.write('\n[Interrupted]\n')
sys.exit(1)
def testall(list, recursive, toplevel):
import sys
import os
for filename in list:
if os.path.isdir(filename):
print(filename + '/:', end=' ')
if recursive or toplevel:
print('recursing down:')
import glob
names = glob.glob(os.path.join(glob.escape(filename), '*'))
testall(names, recursive, 0)
else:
print('*** directory (use -r) ***')
else:
print(filename + ':', end=' ')
sys.stdout.flush()
try:
print(what(filename))
except OSError:
print('*** not found ***')
if __name__ == '__main__':
test()

View File

@ -10,7 +10,7 @@ UNIQUE = uuid4().hex
class TestMUC(SlixIntegration):
async def asyncSetUp(self):
self.mucserver = self.envjid('CI_MUC_SERVER', default='chat.jabberfr.org')
self.mucserver = self.envjid('CI_MUC_SERVER')
self.muc = JID('%s@%s' % (UNIQUE, self.mucserver))
self.add_client(
self.envjid('CI_ACCOUNT1'),

View File

@ -23,6 +23,7 @@ class TestRetract(SlixIntegration):
fallback_text='Twas a mistake',
)
msg = await self.clients[1].wait_until('message_retract')
self.assertEqual(msg['retract']['id'], 'toto')
self.assertEqual(msg['apply_to']['id'], 'toto')
self.assertTrue(msg['apply_to']['retract'])
suite = unittest.TestLoader().loadTestsFromTestCase(TestRetract)

View File

@ -5,7 +5,7 @@ import logging
import unittest
from argparse import ArgumentParser
from setuptools import Command
from distutils.core import Command
from importlib import import_module
from pathlib import Path

View File

@ -5,7 +5,7 @@ import logging
import unittest
from argparse import ArgumentParser
from setuptools import Command
from distutils.core import Command
from importlib import import_module
from pathlib import Path

View File

@ -33,17 +33,12 @@ CLASSIFIERS = [
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Topic :: Internet :: XMPP',
'Topic :: Software Development :: Libraries :: Python Modules',
]
packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')]
def check_include(library_name, header):
command = [os.environ.get('PKG_CONFIG', 'pkg-config'), '--cflags', library_name]
try:
@ -64,7 +59,6 @@ def check_include(library_name, header):
print('%s headers not found.' % library_name)
return False
HAS_PYTHON_HEADERS = check_include('python3', 'Python.h')
HAS_STRINGPREP_HEADERS = check_include('libidn', 'stringprep.h')
@ -86,22 +80,16 @@ setup(
long_description=LONG_DESCRIPTION,
author='Florent Le Coz',
author_email='louiz@louiz.org',
url='https://codeberg.org/poezio/slixmpp',
url='https://lab.louiz.org/poezio/slixmpp',
license='MIT',
platforms=['any'],
package_data={'slixmpp': ['py.typed']},
packages=packages,
ext_modules=ext_modules,
install_requires=[
'aiodns >= 1.0; sys_platform=="linux" or sys_platform=="darwin"',
'pyasn1',
'pyasn1_modules',
'typing_extensions; python_version < "3.8.0"',
],
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'typing_extensions; python_version < "3.8.0"'],
extras_require={
'XEP-0363': ['aiohttp'],
'XEP-0444 compliance': ['emoji'],
'XEP-0454': ['cryptography'],
'Safer XML parsing': ['defusedxml'],
},
classifiers=CLASSIFIERS,

View File

@ -5,6 +5,7 @@
# See the file LICENSE for copying permission.
import logging
from os import getenv
logging.getLogger(__name__).addHandler(logging.NullHandler())
# Use defusedxml if wanted
# Since enabling it can have adverse consequences for the programs using
@ -27,9 +28,3 @@ from slixmpp.clientxmpp import ClientXMPP
from slixmpp.componentxmpp import ComponentXMPP
from slixmpp.version import __version__, __version_info__
__all__ = [
'Message', 'Presence', 'Iq', 'JID', 'InvalidJID', 'ET', 'ElementBase',
'register_stanza_plugin', 'XMLStream', 'BaseXMPP', 'ClientXMPP', 'ComponentXMPP',
'__version__', '__version_info__'
]

View File

@ -140,7 +140,7 @@ class BaseXMPP(XMLStream):
self.use_presence_ids = True
#: XEP-0359 <origin-id/> tag that gets added to <message/> stanzas.
self.use_origin_id = False
self.use_origin_id = True
#: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is
@ -279,13 +279,13 @@ class BaseXMPP(XMLStream):
if self.plugin_whitelist:
plugin_list = self.plugin_whitelist
else:
plugin_list = plugins.PLUGINS
plugin_list = plugins.__all__
for plugin in plugin_list:
if plugin in plugins.PLUGINS:
if plugin in plugins.__all__:
self.register_plugin(plugin)
else:
raise NameError("Plugin %s not in plugins.PLUGINS." % plugin)
raise NameError("Plugin %s not in plugins.__all__." % plugin)
def __getitem__(self, key):
"""Return a plugin given its name, if it has been registered."""
@ -315,12 +315,13 @@ class BaseXMPP(XMLStream):
pres['lang'] = self.default_lang
return pres
def make_iq(self, id: Optional[str] = None, ifrom: OptJidStr = None,
def make_iq(self, id: str = "0", ifrom: OptJidStr = None,
ito: OptJidStr = None, itype: Optional[IqTypes] = None,
iquery: Optional[str] = None) -> stanza.Iq:
"""Create a new :class:`~.Iq` stanza with a given Id and from JID.
:param id: An ideally unique ID value for this stanza thread.
Defaults to 0.
:param ifrom: The from :class:`~.JID`
to use for this stanza.
:param ito: The destination :class:`~.JID`
@ -331,7 +332,6 @@ class BaseXMPP(XMLStream):
:param iquery: Optional namespace for adding a query element.
"""
iq = self.Iq()
if id is not None:
iq['id'] = str(id)
iq['to'] = ito
iq['from'] = ifrom

View File

@ -138,8 +138,8 @@ class ClientXMPP(BaseXMPP):
self.credentials['password'] = value
def connect(self, address: Optional[Tuple[str, int]] = None, # type: ignore
use_ssl: Optional[bool] = None, force_starttls: Optional[bool] = None,
disable_starttls: Optional[bool] = None) -> asyncio.Future:
use_ssl: bool = False, force_starttls: bool = True,
disable_starttls: bool = False) -> None:
"""Connect to the XMPP server.
When no address is given, a SRV lookup for the server will
@ -167,8 +167,7 @@ class ClientXMPP(BaseXMPP):
self.dns_service = 'xmpp-client'
return XMLStream.connect(self, host, port, use_ssl=use_ssl,
force_starttls=force_starttls,
disable_starttls=disable_starttls)
force_starttls=force_starttls, disable_starttls=disable_starttls)
def register_feature(self, name: str, handler: Callable, restart: bool = False, order: int = 5000) -> None:
"""Register a stream feature handler.

View File

@ -9,17 +9,13 @@
import logging
import hashlib
from asyncio import Future
from typing import Optional
from slixmpp import Message, Iq, Presence
from slixmpp.basexmpp import BaseXMPP
from slixmpp.stanza import Handshake
from slixmpp.stanza.error import Error
from slixmpp.xmlstream import XMLStream
from slixmpp.xmlstream import ET
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.stanzabase import register_stanza_plugin
log = logging.getLogger(__name__)
@ -43,17 +39,9 @@ class ComponentXMPP(BaseXMPP):
should be used instead of the standard
``'jabber:component:accept'`` namespace.
Defaults to ``False``.
:param fix_error_ns: Fix the namespace of error stanzas.
If you use ``use_jc_ns`` namespace, you probably want that, but
it can be a problem if you use both a ClientXMPP and a ComponentXMPP
in the same interpreter. This is ``False`` by default for backwards
compatibility.
"""
def __init__(self, jid, secret,
host=None, port=None, plugin_config=None,
plugin_whitelist=None, use_jc_ns=False,
fix_error_ns=False):
def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
if not plugin_whitelist:
plugin_whitelist = []
@ -65,8 +53,6 @@ class ComponentXMPP(BaseXMPP):
else:
default_ns = 'jabber:component:accept'
BaseXMPP.__init__(self, jid, default_ns)
if fix_error_ns:
self._fix_error_ns()
self.auto_authorize = None
self.stream_header = '<stream:stream %s %s to="%s">' % (
@ -91,14 +77,7 @@ class ComponentXMPP(BaseXMPP):
self.add_event_handler('presence_probe',
self._handle_probe)
def _fix_error_ns(self):
Error.namespace = self.default_ns
for st in Message, Iq, Presence:
register_stanza_plugin(st, Error)
def connect(self, host: Optional[str] = None, port: int = 0, use_ssl: Optional[bool] = None,
force_starttls: Optional[bool] = None,
disable_starttls: Optional[bool] = None) -> Future:
def connect(self, host=None, port=None, use_ssl=False):
"""Connect to the server.
@ -108,18 +87,17 @@ class ComponentXMPP(BaseXMPP):
Defauts to :attr:`server_port`.
:param use_ssl: Flag indicating if SSL should be used by connecting
directly to a port using SSL.
:param force_starttls: UNUSED
:param disable_starttls: UNUSED
"""
if host is not None:
self.server_host = host
if port:
self.server_port = port
if host is None:
host = self.server_host
if port is None:
port = self.server_port
self.server_name = self.boundjid.host
log.debug("Connecting to %s:%s", host, port)
return XMLStream.connect(self, host=self.server_host, port=self.server_port, use_ssl=use_ssl)
return XMLStream.connect(self, host=host, port=port,
use_ssl=use_ssl)
def incoming_filter(self, xml):
"""

View File

@ -5,11 +5,6 @@
# :copyright: (c) 2011 Nathanael C. Fritz
# :license: MIT, see LICENSE for more details
from typing import Dict, Optional
from .types import ErrorConditions, ErrorTypes, JidStr
class XMPPError(Exception):
"""
@ -42,17 +37,12 @@ class XMPPError(Exception):
Defaults to ``True``.
"""
def __init__(self, condition: ErrorConditions='undefined-condition', text='',
etype: Optional[ErrorTypes]=None, extension=None, extension_ns=None,
extension_args=None, clear=True, by: Optional[JidStr] = None):
def __init__(self, condition='undefined-condition', text='',
etype='cancel', extension=None, extension_ns=None,
extension_args=None, clear=True):
if extension_args is None:
extension_args = {}
if condition not in _DEFAULT_ERROR_TYPES:
raise ValueError("This is not a valid condition type", condition)
if etype is None:
etype = _DEFAULT_ERROR_TYPES[condition]
self.by = by
self.condition = condition
self.text = text
self.etype = etype
@ -120,30 +110,3 @@ class PresenceError(XMPPError):
etype=pres['error']['type'],
)
self.presence = pres
_DEFAULT_ERROR_TYPES: Dict[ErrorConditions, ErrorTypes] = {
"bad-request": "modify",
"conflict": "cancel",
"feature-not-implemented": "cancel",
"forbidden": "auth",
"gone": "modify",
"internal-server-error": "wait",
"item-not-found": "cancel",
"jid-malformed": "modify",
"not-acceptable": "modify",
"not-allowed": "cancel",
"not-authorized": "auth",
"payment-required": "auth",
"policy-violation": "modify",
"recipient-unavailable": "wait",
"redirect": "modify",
"registration-required": "auth",
"remote-server-not-found": "cancel",
"remote-server-timeout": "wait",
"resource-constraint": "wait",
"service-unavailable": "cancel",
"subscription-required": "auth",
"undefined-condition": "cancel",
"unexpected-request": "modify",
}

View File

@ -37,8 +37,7 @@ class FeatureMechanisms(BasePlugin):
'unencrypted_digest': False,
'unencrypted_cram': False,
'unencrypted_scram': True,
'order': 100,
'tls_version': None,
'order': 100
}
def plugin_init(self):
@ -97,20 +96,7 @@ class FeatureMechanisms(BasePlugin):
result[value] = creds.get('email', jid)
elif value == 'channel_binding':
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
version = self.xmpp.socket.version()
# As of now, python does not implement anything else
# than tls-unique, which is forbidden on TLSv1.3
# see https://github.com/python/cpython/issues/95341
if version != 'TLSv1.3':
result[value] = self.xmpp.socket.get_channel_binding(
cb_type="tls-unique"
)
elif 'tls-exporter' in ssl.CHANNEL_BINDING_TYPES:
result[value] = self.xmpp.socket.get_channel_binding(
cb_type="tls-exporter"
)
else:
result[value] = None
result[value] = self.xmpp.socket.get_channel_binding()
else:
result[value] = None
elif value == 'host':
@ -135,11 +121,6 @@ class FeatureMechanisms(BasePlugin):
result[value] = True
else:
result[value] = False
elif value == 'tls_version':
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
result[value] = self.xmpp.socket.version()
elif value == 'binding_proposed':
result[value] = any(x for x in self.mech_list if x.endswith('-PLUS'))
else:
result[value] = self.config.get(value, False)
return result

View File

@ -3,12 +3,8 @@
# Copyright (C) 2011 Nathanael C. Fritz
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
from typing import Set, ClassVar
from slixmpp.xmlstream import StanzaBase, ElementBase
from slixmpp.xmlstream.xmlstream import InvalidCABundle
import logging
log = logging.getLogger(__name__)
from typing import Set, ClassVar
class STARTTLS(StanzaBase):
@ -40,12 +36,6 @@ class Proceed(StanzaBase):
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
interfaces: ClassVar[Set[str]] = set()
def exception(self, e: Exception) -> None:
log.exception('Error handling {%s}%s stanza',
self.namespace, self.name)
if isinstance(e, InvalidCABundle):
raise e
class Failure(StanzaBase):
"""

View File

@ -303,15 +303,13 @@ class JID:
:param string jid:
A string of the form ``'[user@]domain[/resource]'``.
:param bool bare:
If present, discard the provided resource.
:raises InvalidJID:
"""
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
def __init__(self, jid: Optional[Union[str, 'JID']] = None, bare: bool = False):
def __init__(self, jid: Optional[Union[str, 'JID']] = None):
if not jid:
self._node = ''
self._domain = ''
@ -320,14 +318,11 @@ class JID:
self._full = ''
return
elif not isinstance(jid, JID):
node, domain, resource = _parse_jid(jid)
self._node = node
self._domain = domain
self._resource = resource if not bare else ''
self._node, self._domain, self._resource = _parse_jid(jid)
else:
self._node = jid._node
self._domain = jid._domain
self._resource = jid._resource if not bare else ''
self._resource = jid._resource
self._update_bare_full()
def unescape(self):
@ -373,7 +368,7 @@ class JID:
return self._node
@node.setter
def node(self, value: Optional[str]):
def node(self, value: str):
self._node = _validate_node(value)
self._update_bare_full()
@ -391,7 +386,7 @@ class JID:
return self._resource
@resource.setter
def resource(self, value: Optional[str]):
def resource(self, value: str):
self._resource = _validate_resource(value)
self._update_bare_full()

View File

@ -1,3 +1,4 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2010 Nathanael C. Fritz
# This file is part of Slixmpp.
@ -6,7 +7,7 @@ from slixmpp.plugins.base import PluginManager, PluginNotFound, BasePlugin
from slixmpp.plugins.base import register_plugin, load_plugin
PLUGINS = [
__all__ = [
# XEPS
'xep_0004', # Data Forms
'xep_0009', # Jabber-RPC
@ -23,7 +24,6 @@ PLUGINS = [
'xep_0049', # Private XML Storage
'xep_0050', # Ad-hoc Commands
'xep_0054', # vcard-temp
'xep_0055', # Jabber Search
'xep_0059', # Result Set Management
'xep_0060', # Pubsub (Client)
'xep_0065', # SOCKS5 Bytestreams
@ -76,17 +76,14 @@ PLUGINS = [
'xep_0256', # Last Activity in Presence
'xep_0257', # Client Certificate Management for SASL EXTERNAL
'xep_0258', # Security Labels in XMPP
'xep_0264', # Jingle Content Thumbnails
# 'xep_0270', # XMPP Compliance Suites 2010. Dont automatically load
'xep_0279', # Server IP Check
'xep_0280', # Message Carbons
'xep_0292', # vCard4 Over XMPP
'xep_0297', # Stanza Forwarding
'xep_0300', # Use of Cryptographic Hash Functions in XMPP
# 'xep_0302', # XMPP Compliance Suites 2012. Dont automatically load
'xep_0308', # Last Message Correction
'xep_0313', # Message Archive Management
'xep_0317', # Hats
'xep_0319', # Last User Interaction in Presence
# 'xep_0323', # IoT Systems Sensor Data. Dont automatically load
# 'xep_0325', # IoT Systems Control. Dont automatically load
@ -96,42 +93,23 @@ PLUGINS = [
'xep_0335', # JSON Containers
'xep_0352', # Client State Indication
'xep_0353', # Jingle Message Initiation
'xep_0356', # Privileged entity
'xep_0359', # Unique and Stable Stanza IDs
'xep_0363', # HTTP File Upload
'xep_0369', # MIX-CORE
'xep_0377', # Spam reporting
'xep_0380', # Explicit Message Encryption
'xep_0382', # Spoiler Messages
'xep_0385', # Stateless Inline Media Sharing (SIMS)
'xep_0394', # Message Markup
'xep_0402', # PEP Native Bookmarks
'xep_0403', # MIX-Presence
'xep_0404', # MIX-Anon
'xep_0405', # MIX-PAM
'xep_0421', # Anonymous unique occupant identifiers for MUCs
'xep_0422', # Message Fastening
'xep_0424', # Message Retraction
'xep_0425', # Moderated Message Retraction
'xep_0425', # Message Moderation
'xep_0428', # Message Fallback
'xep_0437', # Room Activity Indicators
'xep_0439', # Quick Response
'xep_0441', # Message Archive Management Preferences
'xep_0444', # Message Reactions
'xep_0446', # File metadata element
'xep_0447', # Stateless file sharing
'xep_0461', # Message Replies
'xep_0469', # Bookmarks Pinning
'xep_0482', # Call Invites
'xep_0490', # Message Displayed Synchronization
'xep_0492', # Chat Notification Settings
# Meant to be imported by plugins
]
__all__ = PLUGINS + [
'PluginManager',
'PluginNotFound',
'BasePlugin',
'register_plugin',
'load_plugin',
]

View File

@ -6,18 +6,14 @@
# Part of Slixmpp: The Slick XMPP Library
# :copyright: (c) 2012 Nathanael C. Fritz
# :license: MIT, see LICENSE for more details
from __future__ import annotations
import sys
import copy
import logging
import threading
from typing import Any, Dict, Set, ClassVar, Union, TYPE_CHECKING
from typing import Any, Dict, Set, ClassVar
if TYPE_CHECKING:
from slixmpp.clientxmpp import ClientXMPP
from slixmpp.componentxmpp import ComponentXMPP
log = logging.getLogger(__name__)
@ -276,7 +272,7 @@ class BasePlugin(object):
#: `plugin.config['foo']`.
default_config: ClassVar[Dict[str, Any]] = {}
def __init__(self, xmpp: Union[ClientXMPP,ComponentXMPP], config=None):
def __init__(self, xmpp, config=None):
self.xmpp = xmpp
if self.xmpp:
self.api = self.xmpp.api.wrap(self.name)

View File

@ -1,9 +1,8 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
import logging
from slixmpp.xmlstream import ElementBase, ET
@ -79,14 +78,7 @@ class FormField(ElementBase):
reqXML = self.xml.find('{%s}required' % self.namespace)
return reqXML is not None
def get_value(self, convert=True, convert_list=False):
"""
Gets the value for this field
:param convert: Convert truthy values to boolean
:param convert_list: Convert text-multi fields to a string with
\n as separator for values
"""
def get_value(self, convert=True):
valsXML = self.xml.findall('{%s}value' % self.namespace)
if len(valsXML) == 0:
return None
@ -100,7 +92,7 @@ class FormField(ElementBase):
if valXML.text is None:
valXML.text = ''
values.append(valXML.text)
if self._type == 'text-multi' and convert_list:
if self._type == 'text-multi' and convert:
values = "\n".join(values)
return values
else:
@ -135,17 +127,6 @@ class FormField(ElementBase):
del self['value']
valXMLName = '{%s}value' % self.namespace
if not self._type:
if isinstance(value, bool):
log.debug("Passed a 'boolean' as value of an untyped field, assuming it is a 'boolean'")
self._type = "boolean"
elif isinstance(value, str):
log.debug("Passed a 'str' as value of an untyped field, assuming it is a 'text-single'")
self._type = "text-single"
elif isinstance(value, (list, tuple)):
log.debug("Passed a %s as value of an untyped field, assuming it is a 'text-multi'")
self._type = "text-multi"
if self._type == 'boolean':
if value in self.true_values:
valXML = ET.Element(valXMLName)
@ -199,6 +180,3 @@ FormField.setOptions = FormField.set_options
FormField.setRequired = FormField.set_required
FormField.setTrue = FormField.set_true
FormField.setValue = FormField.set_value
log = logging.getLogger(__name__)

View File

@ -162,7 +162,7 @@ class XEP_0009(BasePlugin):
if not forwarded and self.xmpp.event_handled('jabber_rpc_method_call') > 1:
return
# Reply with error by default
error = self.xmpp.plugin['xep_0009']._item_not_found(iq)
error = self.client.plugin['xep_0009']._item_not_found(iq)
error.send()
def _on_jabber_rpc_method_response(self, iq, forwarded=False):
@ -175,7 +175,7 @@ class XEP_0009(BasePlugin):
"""
if not forwarded and self.xmpp.event_handled('jabber_rpc_method_response') > 1:
return
error = self.xmpp.plugin['xep_0009']._recipient_unvailable(iq)
error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
error.send()
def _on_jabber_rpc_method_fault(self, iq, forwarded=False):
@ -188,7 +188,7 @@ class XEP_0009(BasePlugin):
"""
if not forwarded and self.xmpp.event_handled('jabber_rpc_method_fault') > 1:
return
error = self.xmpp.plugin['xep_0009']._recipient_unvailable(iq)
error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
error.send()
def _on_jabber_rpc_error(self, iq, forwarded=False):
@ -201,7 +201,7 @@ class XEP_0009(BasePlugin):
"""
if not forwarded and self.xmpp.event_handled('jabber_rpc_error') > 1:
return
error = self.xmpp.plugin['xep_0009']._recipient_unvailable(iq)
error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload())
error.send()
def _send_fault(self, iq, fault_xml): #

View File

@ -19,8 +19,6 @@ def _extract_data(data, kind):
stripped = []
begin_headers = False
begin_data = False
if isinstance(data, bytes):
data = data.decode()
for line in data.split('\n'):
if not begin_headers and 'BEGIN PGP %s' % kind in line:
begin_headers = True

View File

@ -57,9 +57,6 @@ class XEP_0030(BasePlugin):
Given Given A single node
====== ======= ============================
Adding information for a given node without specifying the JID will
use the bound JID and therefore must be done after the bind happens.
Stream Handlers:
::
@ -310,7 +307,7 @@ class XEP_0030(BasePlugin):
return self.api['has_identity'](jid, node, ifrom, data)
async def get_info_from_domain(self, domain=None, timeout=None,
cached=True, callback=None, **iqkwargs):
cached=True, callback=None):
"""Fetch disco#info of specified domain and one disco#items level below
"""
@ -318,13 +315,13 @@ class XEP_0030(BasePlugin):
domain = self.xmpp.boundjid.domain
if not cached or domain not in self.domain_infos:
infos = [asyncio.create_task(self.get_info(
domain, timeout=timeout, **iqkwargs))]
infos = [self.get_info(
domain, timeout=timeout)]
iq_items = await self.get_items(
domain, timeout=timeout, **iqkwargs)
domain, timeout=timeout)
items = iq_items['disco_items']['items']
infos += [
asyncio.create_task(self.get_info(item[0], timeout=timeout, **iqkwargs))
self.get_info(item[0], timeout=timeout)
for item in items]
info_futures, _ = await asyncio.wait(
infos,
@ -388,8 +385,6 @@ class XEP_0030(BasePlugin):
local = True
ifrom = kwargs.pop('ifrom', None)
if self.xmpp.is_component and ifrom is None:
ifrom = self.xmpp.boundjid
if local:
log.debug("Looking up local disco#info data "
"for %s, node %s.", jid, node)
@ -460,12 +455,9 @@ class XEP_0030(BasePlugin):
the XEP-0059 plugin, if the plugin is loaded.
Otherwise the parameter is ignored.
"""
if ifrom is None and self.xmpp.is_component:
ifrom = self.xmpp.boundjid.bare
if local or local is None and jid is None:
items = await self.api['get_items'](jid, node, ifrom, kwargs)
return self._wrap(ifrom, jid, items)
return self._wrap(kwargs.get('ifrom', None), jid, items)
iq = self.xmpp.Iq()
# Check dfrom parameter for backwards compatibility

View File

@ -9,7 +9,6 @@ from typing import (
Set,
Tuple,
Union,
Dict,
)
from slixmpp.xmlstream import ElementBase, ET
@ -145,25 +144,6 @@ class DiscoInfo(ElementBase):
return True
return False
def dict_identities(self, lang: Optional[str] = None) -> Set[Dict[str, str]]:
"""
Return the set of all identities, each one as a dict with
category, type, xml_lang, and name keys.
:param lang: If there is a need to filter identities by lang.
"""
ids = self.get_identities(lang=lang, dedupe=True)
dict_ids = set()
for identity in ids:
dict_ids.add({
'category': identity[0],
'type': identity[1],
'xml_lang': identity[2],
'name': identity[3],
})
return dict_ids
def get_identities(self, lang: Optional[str] = None, dedupe: bool = True
) -> Iterable[IdentityType]:
"""
@ -185,11 +165,11 @@ class DiscoInfo(ElementBase):
identities = []
for id_xml in self.xml.findall('{%s}identity' % self.namespace):
xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None)
category = id_xml.attrib.get('category', None)
type_ = id_xml.attrib.get('type', None)
name = id_xml.attrib.get('name', None)
if lang is None or xml_lang == lang:
id = (category, type_, xml_lang, name)
id = (id_xml.attrib['category'],
id_xml.attrib['type'],
id_xml.attrib.get('{%s}lang' % self.xml_ns, None),
id_xml.attrib.get('name', None))
if isinstance(identities, set):
identities.add(id)
else:
@ -273,12 +253,10 @@ class DiscoInfo(ElementBase):
else:
features = []
for feature_xml in self.xml.findall('{%s}feature' % self.namespace):
feature = feature_xml.attrib.get('var', None)
if feature:
if isinstance(features, set):
features.add(feature)
features.add(feature_xml.attrib['var'])
else:
features.append(feature)
features.append(feature_xml.attrib['var'])
return features
def set_features(self, features: Iterable[str]):

View File

@ -49,13 +49,11 @@ from slixmpp.plugins.xep_0045.stanza import (
MUCUserItem,
)
from slixmpp.types import (
JidStr,
MucRole,
MucAffiliation,
MucRoomItem,
MucRoomItemKeys,
PresenceArgs,
PresenceShows,
)
JoinResult = Tuple[Presence, Message, List[Presence], List[Message]]
@ -189,7 +187,7 @@ class XEP_0045(BasePlugin):
def _handle_config_change(self, msg: Message):
"""Handle a MUC configuration change (with status code)."""
self.xmpp.event('groupchat_config_status', msg)
self.xmpp.event('muc::%s::config_status' % msg['from'].bare, msg)
self.xmpp.event('muc::%s::config_status' % msg['from'].bare , msg)
def _client_handle_presence(self, pr: Presence):
"""As a client, handle a presence stanza"""
@ -266,7 +264,7 @@ class XEP_0045(BasePlugin):
seconds: Optional[int] = None,
since: Optional[datetime] = None,
presence_options: Optional[PresenceArgs] = None,
timeout: int = 300) -> JoinResult:
timeout: Optional[int] = None) -> JoinResult:
"""
Try to join a MUC and block until we are joined or get an error.
@ -312,7 +310,7 @@ class XEP_0045(BasePlugin):
stanza.send()
return await self._await_join(room, timeout)
async def _await_join(self, room: JID, timeout: int = 300) -> JoinResult:
async def _await_join(self, room: JID, timeout: Optional[int] = None) -> JoinResult:
"""Do the heavy lifting for awaiting a MUC join
A muc join, once the join stanza is sent, is:
@ -325,6 +323,7 @@ class XEP_0045(BasePlugin):
def add_message(msg: Message):
delay = msg.get_plugin('delay', check=True)
print(delay)
if delay is not None and delay['from'] == room:
history_buffer.append(msg)
@ -360,7 +359,7 @@ class XEP_0045(BasePlugin):
return (pres, subject, occupant_buffer, history_buffer)
def join_muc(self, room: JID, nick: str, maxhistory="0", password='',
pstatus='', pshow: PresenceShows='chat', pfrom: JidStr='') -> asyncio.Future:
pstatus='', pshow='', pfrom='') -> asyncio.Future:
""" Join the specified room, requesting 'maxhistory' lines of history.
.. deprecated:: 1.8.0
@ -414,7 +413,7 @@ class XEP_0045(BasePlugin):
)
del self.rooms[room]
def set_subject(self, room: JidStr, subject: str, *, mfrom: Optional[JID] = None):
def set_subject(self, room: JID, subject: str, *, mfrom: Optional[JID] = None):
"""Set a rooms subject.
:param room: JID of the room.
@ -425,7 +424,7 @@ class XEP_0045(BasePlugin):
msg['subject'] = subject
msg.send()
async def get_room_config(self, room: JidStr, ifrom: Optional[JID] = None,
async def get_room_config(self, room: JID, ifrom: Optional[JID] = None,
**iqkwargs) -> Form:
"""Get the room config form in 0004 plugin format.
@ -440,7 +439,7 @@ class XEP_0045(BasePlugin):
raise ValueError("Configuration form not found")
return form
async def set_room_config(self, room: JidStr, config: Form, *,
async def set_room_config(self, room: JID, config: Form, *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Send a room config form.
@ -453,8 +452,8 @@ class XEP_0045(BasePlugin):
iq = self.xmpp.make_iq_set(query, ito=room, ifrom=ifrom)
await iq.send(**iqkwargs)
async def cancel_config(self, room: JidStr, *,
ifrom: Optional[JidStr] = None, **iqkwargs):
async def cancel_config(self, room: JID, *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Cancel a requested config form.
:param room: Room to cancel the form for.
@ -464,8 +463,8 @@ class XEP_0045(BasePlugin):
iq = self.xmpp.make_iq_set(query, ito=room, ifrom=ifrom)
await iq.send(**iqkwargs)
async def destroy(self, room: JidStr, reason: str = '', altroom: Optional[JidStr] = None, *,
ifrom: Optional[JidStr] = None, **iqkwargs):
async def destroy(self, room: JID, reason: str = '', altroom: Optional[JID] = None, *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Destroy a room.
:param room: Room JID to destroy.
@ -481,10 +480,10 @@ class XEP_0045(BasePlugin):
iq['mucowner_query']['destroy']['reason'] = reason
await iq.send(**iqkwargs)
async def set_affiliation(self, room: JidStr, affiliation: MucAffiliation, *,
jid: Optional[JidStr] = None,
async def set_affiliation(self, room: JID, affiliation: MucAffiliation, *,
jid: Optional[JID] = None,
nick: Optional[str] = None, reason: str = '',
ifrom: Optional[JidStr] = None, **iqkwargs):
ifrom: Optional[JID] = None, **iqkwargs):
""" Change room affiliation for a JID or nickname.
:param room: Room to modify.
@ -494,8 +493,6 @@ class XEP_0045(BasePlugin):
"""
if affiliation not in AFFILIATIONS:
raise ValueError('%s is not a valid affiliation' % affiliation)
if affiliation == 'outcast' and not jid:
raise ValueError('Outcast affiliation requires using a jid')
if not any((jid, nick)):
raise ValueError('One of jid or nick must be set')
iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom)
@ -508,8 +505,8 @@ class XEP_0045(BasePlugin):
iq['mucadmin_query']['item']['reason'] = reason
await iq.send(**iqkwargs)
async def get_affiliation_list(self, room: JidStr, affiliation: MucAffiliation, *,
ifrom: Optional[JidStr] = None, **iqkwargs) -> List[JID]:
async def get_affiliation_list(self, room: JID, affiliation: MucAffiliation, *,
ifrom: Optional[JID] = None, **iqkwargs) -> List[JID]:
"""Get a list of JIDs with the specified affiliation
:param room: Room to get affiliations from.
@ -520,9 +517,9 @@ class XEP_0045(BasePlugin):
result = await iq.send(**iqkwargs)
return [item['jid'] for item in result['mucadmin_query']]
async def send_affiliation_list(self, room: JidStr,
affiliations: List[Tuple[JidStr, MucAffiliation]], *,
ifrom: Optional[JidStr] = None, **iqkwargs):
async def send_affiliation_list(self, room: JID,
affiliations: List[Tuple[JID, MucAffiliation]], *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Send an affiliation delta list.
:param room: Room to send the affiliations to.
@ -536,8 +533,8 @@ class XEP_0045(BasePlugin):
iq['mucadmin_query'].append(item)
await iq.send(**iqkwargs)
async def set_role(self, room: JidStr, nick: str, role: MucRole, *,
reason: str = '', ifrom: Optional[JidStr] = None, **iqkwargs):
async def set_role(self, room: JID, nick: str, role: MucRole, *,
reason: str = '', ifrom: Optional[JID] = None, **iqkwargs):
""" Change role property of a nick in a room.
Typically, roles are temporary (they last only as long as you are in the
room), whereas affiliations are permanent (they last across groupchat
@ -557,8 +554,8 @@ class XEP_0045(BasePlugin):
iq['mucadmin_query']['item']['reason'] = reason
await iq.send(**iqkwargs)
async def get_roles_list(self, room: JidStr, role: MucRole, *,
ifrom: Optional[JidStr] = None, **iqkwargs) -> List[str]:
async def get_roles_list(self, room: JID, role: MucRole, *,
ifrom: Optional[JID] = None, **iqkwargs) -> List[str]:
""""Get a list of JIDs with the specified role
:param room: Room to get roles from.
@ -569,8 +566,8 @@ class XEP_0045(BasePlugin):
result = await iq.send(**iqkwargs)
return [item['nick'] for item in result['mucadmin_query']]
async def send_role_list(self, room: JidStr, roles: List[Tuple[str, MucRole]], *,
ifrom: Optional[JidStr] = None, **iqkwargs):
async def send_role_list(self, room: JID, roles: List[Tuple[str, MucRole]], *,
ifrom: Optional[JID] = None, **iqkwargs):
"""Send a role delta list.
:param room: Room to send the roles to.
@ -584,8 +581,8 @@ class XEP_0045(BasePlugin):
iq['mucadmin_query'].append(item)
await iq.send(**iqkwargs)
def invite(self, room: JidStr, jid: JidStr, reason: str = '', *,
mfrom: Optional[JidStr] = None):
def invite(self, room: JID, jid: JID, reason: str = '', *,
mfrom: Optional[JID] = None):
""" Invite a jid to a room (mediated invitation).
:param room: Room to invite the user in.
@ -598,8 +595,8 @@ class XEP_0045(BasePlugin):
msg['muc']['invite']['reason'] = reason
self.xmpp.send(msg)
def invite_server(self, room: JidStr, jid: JidStr,
invite_from: JidStr, reason: str = ''):
def invite_server(self, room: JID, jid: JID,
invite_from: JID, reason: str = ''):
"""Send a mediated invite to a user, as a MUC service.
.. versionadded:: 1.8.0
@ -617,8 +614,8 @@ class XEP_0045(BasePlugin):
msg['muc']['invite']['reason'] = reason
msg.send()
def decline(self, room: JidStr, jid: JidStr, reason: str = '', *,
mfrom: Optional[JidStr] = None):
def decline(self, room: JID, jid: JID, reason: str = '', *,
mfrom: Optional[JID] = None):
"""Decline a mediated invitation.
:param room: Room the invitation came from.
@ -631,7 +628,7 @@ class XEP_0045(BasePlugin):
msg['muc']['decline']['reason'] = reason
self.xmpp.send(msg)
def request_voice(self, room: JidStr, role: str, *, mfrom: Optional[JidStr] = None):
def request_voice(self, room: JID, role: str, *, mfrom: Optional[JID] = None):
"""Request voice in a moderated room.
:param room: Room to request voice from.
@ -648,49 +645,29 @@ class XEP_0045(BasePlugin):
"""Check if a JID is present in a room.
:param room: Room to check.
:param jid: FULL JID to check.
:param jid: JID to check.
"""
bare_match = False
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if not entry.get('jid'):
continue
if entry['jid'] == jid.full:
if entry is not None and entry['jid'].full == jid:
return True
elif JID(entry['jid']).bare == jid.bare:
bare_match = True
if bare_match:
logging.info(
"Could not retrieve full JID, falling back to bare JID for %s in %s",
jid, room
)
return bare_match
return False
def get_nick(self, room: JID, jid: JID) -> Optional[str]:
"""Get the nickname of a specific JID in a room.
:param room: Room to inspect.
:param jid: FULL JID whose nick to return.
:param jid: JID whose nick to return.
"""
bare_match = None
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if not entry.get('jid'):
continue
if entry['jid'] == jid.full:
if entry is not None and entry['jid'].full == jid:
return nick
elif JID(entry['jid']).bare == jid.bare:
bare_match = nick
if bare_match:
logging.info(
"Could not retrieve full JID, falling back to bare JID for %s in %s",
jid, room
)
return bare_match
return None
def get_joined_rooms(self) -> List[JID]:
"""Get the list of rooms we sent a join presence to
@ -726,7 +703,7 @@ class XEP_0045(BasePlugin):
raise ValueError("Room %s is not joined" % room)
return list(self.rooms[room].keys())
def get_users_by_affiliation(self, room: JidStr, affiliation='member', *, ifrom: Optional[JidStr] = None):
def get_users_by_affiliation(self, room: JID, affiliation='member', *, ifrom: Optional[JID] = None):
# Preserve old API
if affiliation not in AFFILIATIONS:
raise ValueError("Affiliation %s does not exist" % affiliation)

View File

@ -28,7 +28,7 @@ class MUCBase(ElementBase):
plugin_attrib = 'muc'
interfaces = {'affiliation', 'role', 'jid', 'nick', 'room', 'status_codes'}
def get_status_codes(self) -> Set[int]:
def get_status_codes(self) -> Set[str]:
status = self.xml.findall(f'{{{NS_USER}}}status')
return {int(status.attrib['code']) for status in status}
@ -275,8 +275,7 @@ class MUCUserItem(ElementBase):
jid = self.xml.attrib.get('jid', None)
if jid:
return JID(jid)
else:
return None
return jid
class MUCActor(ElementBase):
@ -289,8 +288,7 @@ class MUCActor(ElementBase):
jid = self.xml.attrib.get('jid', None)
if jid:
return JID(jid)
else:
return None
return jid
class MUCDestroy(ElementBase):

View File

@ -4,7 +4,6 @@
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
import asyncio
import functools
import logging
import time
@ -326,10 +325,7 @@ class XEP_0050(BasePlugin):
iq['command']['actions'] = actions
iq['command']['status'] = 'executing'
else:
actions = ['complete']
if session['allow_prev']:
actions.append('prev')
iq['command']['actions'] = actions
iq['command']['actions'] = ['complete']
iq['command']['status'] = 'executing'
iq['command']['notes'] = session['notes']
@ -623,16 +619,8 @@ class XEP_0050(BasePlugin):
self.terminate_command(session)
def _iscoroutine_or_partial_coroutine(handler):
return asyncio.iscoroutinefunction(handler) \
or isinstance(handler, functools.partial) \
and asyncio.iscoroutinefunction(handler.func)
async def _await_if_needed(handler, *args):
if handler is None:
raise XMPPError("bad-request", text="The command is completed")
if _iscoroutine_or_partial_coroutine(handler):
if asyncio.iscoroutinefunction(handler):
log.debug(f"%s is async", handler)
return await handler(*args)
else:

View File

@ -134,10 +134,8 @@ class XEP_0054(BasePlugin):
return
elif iq['type'] == 'get' and self.xmpp.is_component:
vcard = await self.api['get_vcard'](iq['to'].bare, ifrom=iq['from'])
if vcard is None:
raise XMPPError("item-not-found")
elif isinstance(vcard, Iq):
await vcard.send()
if isinstance(vcard, Iq):
vcard.send()
else:
iq = iq.reply()
iq.append(vcard)

View File

@ -1,6 +0,0 @@
from slixmpp.plugins.base import register_plugin
from .search import XEP_0055
register_plugin(XEP_0055)

View File

@ -1,89 +0,0 @@
import logging
from slixmpp import CoroutineCallback, StanzaPath, Iq, register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import StanzaBase
from . import stanza
class XEP_0055(BasePlugin):
"""
XEP-0055: Jabber Search
The config options are only useful for a "server-side" search feature,
and if the ``provide_search`` option is set to True.
API
===
``search_get_form``: customize the search form content (ie fields)
``search_query``: return search results
"""
name = "xep_0055"
description = "XEP-0055: Jabber search"
dependencies = {"xep_0004", "xep_0030"}
stanza = stanza
default_config = {
"form_fields": {"first", "last"},
"form_instructions": "",
"form_title": "",
"provide_search": True
}
def plugin_init(self):
register_stanza_plugin(Iq, stanza.Search)
register_stanza_plugin(stanza.Search, self.xmpp["xep_0004"].stanza.Form)
if self.provide_search:
self.xmpp["xep_0030"].add_feature(stanza.Search.namespace)
self.xmpp.register_handler(
CoroutineCallback(
"search",
StanzaPath("/iq/search"),
self._handle_search,
)
)
self.api.register(self._get_form, "search_get_form")
self.api.register(self._get_results, "search_query")
async def _handle_search(self, iq: StanzaBase):
if iq["search"]["form"].get_values():
reply = await self.api["search_query"](None, None, iq.get_from(), iq)
reply["search"]["form"]["type"] = "result"
else:
reply = await self.api["search_get_form"](None, None, iq.get_from(), iq)
reply["search"]["form"].add_field(
"FORM_TYPE", value=stanza.Search.namespace, ftype="hidden"
)
reply.send()
async def _get_form(self, jid, node, ifrom, iq):
reply = iq.reply()
form = reply["search"]["form"]
form["title"] = self.form_title
form["instructions"] = self.form_instructions
for field in self.form_fields:
form.add_field(field)
return reply
async def _get_results(self, jid, node, ifrom, iq):
reply = iq.reply()
form = reply["search"]["form"]
form["type"] = "result"
for field in self.form_fields:
form.add_reported(field)
return reply
def make_search_iq(self, **kwargs):
iq = self.xmpp.make_iq(itype="set", **kwargs)
iq["search"]["form"].set_type("submit")
iq["search"]["form"].add_field(
"FORM_TYPE", value=stanza.Search.namespace, ftype="hidden"
)
return iq
log = logging.getLogger(__name__)

View File

@ -1,10 +0,0 @@
from typing import Set, ClassVar
from slixmpp.xmlstream import ElementBase
class Search(ElementBase):
namespace = "jabber:iq:search"
name = "query"
plugin_attrib = "search"
interfaces: ClassVar[Set[str]] = set()

View File

@ -464,7 +464,7 @@ class XEP_0060(BasePlugin):
"""
Retrieve the ItemIDs hosted by a given node, using disco.
"""
return self.xmpp['xep_0030'].get_items(jid, node, ifrom=ifrom,
self.xmpp['xep_0030'].get_items(jid, node, ifrom=ifrom,
callback=callback, timeout=timeout,
iterator=iterator,
timeout_callback=timeout_callback)

View File

@ -6,6 +6,7 @@
import datetime as dt
from slixmpp.plugins import BasePlugin, register_plugin
from slixmpp.thirdparty import tzutc, tzoffset, parse_iso
# =====================================================================
@ -20,10 +21,7 @@ def parse(time_str):
Arguments:
time_str -- A formatted timestamp string.
"""
try:
return dt.datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S.%f%z')
except ValueError:
return dt.datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S%z')
return parse_iso(time_str)
def format_date(time_obj):
@ -54,7 +52,7 @@ def format_time(time_obj):
if isinstance(time_obj, dt.datetime):
time_obj = time_obj.timetz()
timestamp = time_obj.isoformat()
if time_obj.tzinfo == dt.timezone.utc:
if time_obj.tzinfo == tzutc():
timestamp = timestamp[:-6]
return '%sZ' % timestamp
return timestamp
@ -71,7 +69,7 @@ def format_datetime(time_obj):
time_obj -- A datetime object.
"""
timestamp = time_obj.isoformat('T')
if time_obj.tzinfo == dt.timezone.utc:
if time_obj.tzinfo == tzutc():
timestamp = timestamp[:-6]
return '%sZ' % timestamp
return timestamp
@ -130,9 +128,9 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
if micro is None:
micro = now.microsecond
if offset in (None, 0):
offset = dt.timezone.utc
offset = tzutc()
elif not isinstance(offset, dt.tzinfo):
offset = dt.timezone(dt.timedelta(seconds=offset))
offset = tzoffset(None, offset)
value = dt.time(hour, min, sec, micro, offset)
if obj:
return value
@ -177,9 +175,9 @@ def datetime(year=None, month=None, day=None, hour=None,
if micro is None:
micro = now.microsecond
if offset in (None, 0):
offset = dt.timezone.utc
offset = tzutc()
elif not isinstance(offset, dt.tzinfo):
offset = dt.timezone(dt.timedelta(seconds=offset))
offset = tzoffset(None, offset)
value = dt.datetime(year, month, day, hour,
min, sec, micro, offset)

View File

@ -80,16 +80,16 @@ class Info(ElementBase):
self._set_int('bytes', value)
def get_height(self) -> int:
return self._get_int('height')
self._get_int('height')
def set_height(self, value: int):
self._set_int('height', value)
def get_width(self) -> int:
return self._get_int('width')
self._get_int(self, 'width')
def set_width(self, value: int):
self._set_int('width', value)
self._set_int('with', value)
class Pointer(ElementBase):

View File

@ -7,8 +7,7 @@ import logging
import hashlib
import base64
from asyncio import Future, Lock
from collections import defaultdict
from asyncio import Future
from typing import Optional
from slixmpp import __version__
@ -95,9 +94,6 @@ class XEP_0115(BasePlugin):
disco.assign_verstring = self.assign_verstring
disco.get_verstring = self.get_verstring
# prevent concurrent fetches for the same hash
self._locks = defaultdict(Lock)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
self.xmpp.del_filter('out', self._filter_add_caps)
@ -141,7 +137,7 @@ class XEP_0115(BasePlugin):
self.xmpp.event('entity_caps', p)
async def _process_caps(self, pres: Presence):
async def _process_caps(self, pres):
if not pres['caps']['hash']:
log.debug("Received unsupported legacy caps: %s, %s, %s",
pres['caps']['node'],
@ -151,11 +147,7 @@ class XEP_0115(BasePlugin):
return
ver = pres['caps']['ver']
async with self._locks[ver]:
await self._process_caps_wrapped(pres, ver)
self._locks.pop(ver, None)
async def _process_caps_wrapped(self, pres: Presence, ver: str):
existing_verstring = await self.get_verstring(pres['from'].full)
if str(existing_verstring) == str(ver):
return
@ -170,7 +162,7 @@ class XEP_0115(BasePlugin):
if pres['caps']['hash'] not in self.hashes:
try:
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
await self.xmpp['xep_0030'].get_info(jid=pres['from'], ifrom=ifrom)
self.xmpp['xep_0030'].get_info(jid=pres['from'], ifrom=ifrom)
return
except XMPPError:
return

View File

@ -60,7 +60,7 @@ class StaticCaps(object):
return False
if node in (None, ''):
info = await self.caps.get_caps(jid)
info = self.caps.get_caps(jid)
if info and feature in info['features']:
return True
@ -134,7 +134,7 @@ class StaticCaps(object):
def get_verstring(self, jid, node, ifrom, data):
return self.jid_vers.get(jid, None)
async def get_caps(self, jid, node, ifrom, data):
def get_caps(self, jid, node, ifrom, data):
verstring = data.get('verstring', None)
if verstring is None:
return None

View File

@ -137,14 +137,7 @@ class XEP_0199(BasePlugin):
async def _keepalive(self, event=None):
log.debug("Keepalive ping...")
try:
ifrom = None
if self.xmpp.is_component:
ifrom = self.xmpp.boundjid
rtt = await self.ping(
self.xmpp.boundjid.host,
timeout=self.timeout,
ifrom=ifrom
)
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
except IqTimeout:
log.debug("Did not receive ping back in time. " + \
"Requesting Reconnect.")

View File

@ -8,6 +8,7 @@ import datetime as dt
from slixmpp.xmlstream import ElementBase
from slixmpp.plugins import xep_0082
from slixmpp.thirdparty import tzutc, tzoffset
class EntityTime(ElementBase):
@ -86,7 +87,7 @@ class EntityTime(ElementBase):
seconds (positive or negative) to offset.
"""
time = xep_0082.time(offset=value)
if xep_0082.parse(time).tzinfo == dt.timezone.utc:
if xep_0082.parse(time).tzinfo == tzutc():
self._set_sub_text('tzo', 'Z')
else:
self._set_sub_text('tzo', time[-6:])
@ -110,6 +111,6 @@ class EntityTime(ElementBase):
date = value
if not isinstance(value, dt.datetime):
date = xep_0082.parse(value)
date = date.astimezone(dt.timezone.utc)
date = date.astimezone(tzutc())
value = xep_0082.format_datetime(date)
self._set_sub_text('utc', value)

View File

@ -30,10 +30,6 @@ class Delay(ElementBase):
def set_stamp(self, value):
if isinstance(value, dt.datetime):
if value.tzinfo is None:
raise ValueError(f'Datetime provided without timezone information: {value}')
if value.tzinfo != dt.timezone.utc:
value = value.astimezone(dt.timezone.utc)
value = xep_0082.format_datetime(value)
self._set_attr('stamp', value)

View File

@ -15,32 +15,6 @@ log = logging.getLogger(__name__)
class XEP_0221(BasePlugin):
"""
XEP-0221: Data Forms Media Element
In certain implementations of Data Forms (XEP-0004), it can be
helpful to include media data such as small images. One example is
CAPTCHA Forms (XEP-0158). This plugin implements a method for
including media data in a data form.
Typical use pattern:
.. code-block:: python
self.register_plugin('xep_0221')
self['xep_0050'].add_command(node="showimage",
name="Show my image",
handler=self.form_handler)
def form_handler(self,iq,session):
image_url="https://xmpp.org/images/logos/xmpp-logo.svg"
form=self['xep_0004'].make_form('result','My Image')
form.addField(var='myimage', ftype='text-single', label='My Image', value=image_url)
form.field['myimage']['media'].add_uri(value=image_url, itype="image/svg")
session['payload']=form
return session
"""
name = 'xep_0221'
description = 'XEP-0221: Data Forms Media Element'

View File

@ -20,18 +20,6 @@ class XEP_0223(BasePlugin):
"""
XEP-0223: Persistent Storage of Private Data via PubSub
If a specific pubsub node requires additional publish options, edit the
:attr:`.node_profile` attribute of this plugin:
.. code-block:: python
self.xmpp.plugin["xep_0223"].node_profiles["urn:some:node"] = {
"pubsub#max_items" = "max"
}
This makes :meth:`.store` add these publish options whenever it is called
for the ``urn:some:node`` node.
"""
name = 'xep_0223'
@ -40,7 +28,6 @@ class XEP_0223(BasePlugin):
profile = {'pubsub#persist_items': True,
'pubsub#access_model': 'whitelist'}
node_profiles = dict[str, dict[str, str]]()
def configure(self, node: str, **iqkwargs) -> Future:
"""
@ -48,12 +35,8 @@ class XEP_0223(BasePlugin):
:param node: Node to set the configuration at.
"""
config = self.xmpp['xep_0004'].stanza.Form()
config = self.xmpp['xep_0004'].Form()
config['type'] = 'submit'
config.add_field(
var='FORM_TYPE',
ftype='hidden',
value='http://jabber.org/protocol/pubsub#node_config')
for field, value in self.profile.items():
config.add_field(var=field, value=value)
@ -87,8 +70,7 @@ class XEP_0223(BasePlugin):
value='http://jabber.org/protocol/pubsub#publish-options')
fields = options['fields']
profile = self.profile | self.node_profiles.get(node, {})
for field, value in profile.items():
for field, value in self.profile.items():
if field not in fields:
options.add_field(var=field)
options.get_fields()[field]['value'] = value

View File

@ -10,7 +10,6 @@ from asyncio import Future
from typing import Optional
from slixmpp import JID
from slixmpp.exceptions import XMPPError
from slixmpp.stanza import Iq, Message, Presence
from slixmpp.xmlstream.handler import CoroutineCallback
from slixmpp.xmlstream.matcher import StanzaPath
@ -140,13 +139,6 @@ class XEP_0231(BasePlugin):
self.xmpp.event('bob', iq)
elif iq['type'] == 'get':
data = await self.api['get_bob'](iq['to'], None, iq['from'], args=cid)
if data is None:
raise XMPPError(
"item-not-found",
f"Bits of binary '{cid}' is not available",
)
if isinstance(data, Iq):
data['id'] = iq['id']
data.send()

View File

@ -1,6 +0,0 @@
from slixmpp.plugins.base import register_plugin
from . import stanza
from .jingle_file_transfer import XEP_0234
register_plugin(XEP_0234)

View File

@ -1,21 +0,0 @@
import logging
from slixmpp.plugins import BasePlugin
from . import stanza
log = logging.getLogger(__name__)
class XEP_0234(BasePlugin):
"""
XEP-0234: Jingle File Transfer
Minimum needed for xep 0385 (Stateless inline media sharing)
"""
name = "xep_0234"
description = "XEP-0234: Jingle File Transfer"
dependencies = {"xep_0082", "xep_0300"}
stanza = stanza

View File

@ -1,38 +0,0 @@
from datetime import datetime
from slixmpp.plugins.xep_0082 import format_datetime, parse
from slixmpp.xmlstream import ElementBase
NS = "urn:xmpp:jingle:apps:file-transfer:5"
class File(ElementBase):
name = "file"
namespace = NS
plugin_attrib = "file"
interfaces = sub_interfaces = {"media-type", "name", "date", "size", "hash", "desc"}
def set_size(self, size: int):
self._set_sub_text("size", str(size))
def get_size(self):
return _int_or_none(self._get_sub_text("size"))
def get_date(self):
try:
return parse(self._get_sub_text("date"))
except ValueError:
return
def set_date(self, stamp: datetime):
try:
self._set_sub_text("date", format_datetime(stamp))
except ValueError:
pass
def _int_or_none(v):
try:
return int(v)
except ValueError:
return None

View File

@ -1,5 +0,0 @@
from slixmpp.plugins.base import register_plugin
from .thumbnail import XEP_0264
register_plugin(XEP_0264)

View File

@ -1,36 +0,0 @@
from typing import Optional
from slixmpp import register_stanza_plugin
from slixmpp.plugins.xep_0234.stanza import File
from slixmpp.xmlstream import ElementBase
NS = "urn:xmpp:thumbs:1"
class Thumbnail(ElementBase):
name = plugin_attrib = "thumbnail"
namespace = NS
interfaces = {"uri", "media-type", "width", "height"}
def get_width(self) -> int:
return _int_or_none(self._get_attr("width"))
def get_height(self) -> int:
return _int_or_none(self._get_attr("height"))
def set_width(self, v: int) -> None:
self._set_attr("width", str(v))
def set_height(self, v: int) -> None:
self._set_attr("height", str(v))
def _int_or_none(v) -> Optional[int]:
try:
return int(v)
except ValueError:
return None
def register_plugin():
register_stanza_plugin(File, Thumbnail)

View File

@ -1,24 +0,0 @@
import logging
from slixmpp.plugins import BasePlugin
from . import stanza
log = logging.getLogger(__name__)
class XEP_0264(BasePlugin):
"""
XEP-0264: Jingle Content Thumbnails
Can also be used with 0385 (Stateless inline media sharing)
"""
name = "xep_0264"
description = "XEP-0264: Jingle Content Thumbnails"
dependencies = {"xep_0234"}
stanza = stanza
def plugin_init(self):
stanza.register_plugin()

View File

@ -1,6 +0,0 @@
from slixmpp.plugins.base import register_plugin
from . import stanza, vcard4
from .vcard4 import XEP_0292
register_plugin(vcard4.XEP_0292)

View File

@ -1,167 +0,0 @@
import datetime
from typing import Optional
from slixmpp import ElementBase, Iq, register_stanza_plugin
NS = "urn:ietf:params:xml:ns:vcard-4.0"
class _VCardElementBase(ElementBase):
namespace = NS
class VCard4(_VCardElementBase):
name = plugin_attrib = "vcard"
interfaces = {"full_name", "given", "surname", "birthday"}
def set_full_name(self, full_name: str):
self["fn"]["text"] = full_name
def get_full_name(self):
return self["fn"]["text"]
def set_given(self, given: str):
self["n"]["given"] = given
def get_given(self):
return self["n"]["given"]
def set_surname(self, surname: str):
self["n"]["surname"] = surname
def get_surname(self):
return self["n"]["surname"]
def set_birthday(self, birthday: datetime.date):
self["bday"]["date"] = birthday
def get_birthday(self):
return self["bday"]["date"]
def add_tel(self, number: str, name: Optional[str] = None):
tel = Tel()
if name:
tel["parameters"]["type_"]["text"] = name
tel["uri"] = f"tel:{number}"
self.append(tel)
def add_address(
self, country: Optional[str] = None, locality: Optional[str] = None
):
adr = Adr()
if locality:
adr["locality"] = locality
if country:
adr["country"] = country
self.append(adr)
def add_nickname(self, nick: str):
el = Nickname()
el["text"] = nick
self.append(el)
def add_note(self, note: str):
el = Note()
el["text"] = note
self.append(el)
def add_impp(self, impp: str):
el = Impp()
el["uri"] = impp
self.append(el)
def add_url(self, url: str):
el = Url()
el["uri"] = url
self.append(el)
def add_email(self, email: str):
el = Email()
el["text"] = email
self.append(el)
class _VCardTextElementBase(_VCardElementBase):
interfaces = {"text"}
sub_interfaces = {"text"}
class Fn(_VCardTextElementBase):
name = plugin_attrib = "fn"
class Nickname(_VCardTextElementBase):
name = plugin_attrib = "nickname"
class Note(_VCardTextElementBase):
name = plugin_attrib = "note"
class _VCardUriElementBase(_VCardElementBase):
interfaces = {"uri"}
sub_interfaces = {"uri"}
class Url(_VCardUriElementBase):
name = plugin_attrib = "url"
class Impp(_VCardUriElementBase):
name = plugin_attrib = "impp"
class Email(_VCardTextElementBase):
name = plugin_attrib = "email"
class N(_VCardElementBase):
name = "n"
plugin_attrib = "n"
interfaces = sub_interfaces = {"given", "surname", "additional"}
class BDay(_VCardElementBase):
name = plugin_attrib = "bday"
interfaces = {"date"}
def set_date(self, date: datetime.date):
d = Date()
d.xml.text = date.strftime("%Y-%m-%d")
self.append(d)
def get_date(self):
for elem in self.xml:
try:
return datetime.date.fromisoformat(elem.text)
except ValueError:
return None
class Date(_VCardElementBase):
name = "date"
class Tel(_VCardUriElementBase):
name = plugin_attrib = "tel"
class Parameters(_VCardElementBase):
name = plugin_attrib = "parameters"
class Type(_VCardTextElementBase):
name = "type"
plugin_attrib = "type_"
class Adr(_VCardElementBase):
name = plugin_attrib = "adr"
interfaces = sub_interfaces = {"locality", "country"}
register_stanza_plugin(Parameters, Type)
register_stanza_plugin(Tel, Parameters)
for p in N, Fn, Nickname, Note, Url, Impp, Email, BDay, Tel, Adr:
register_stanza_plugin(VCard4, p, iterable=True)
register_stanza_plugin(Iq, VCard4)

View File

@ -1,111 +0,0 @@
import logging
from datetime import date
from typing import Optional
from slixmpp import (
JID,
ComponentXMPP,
register_stanza_plugin,
)
from slixmpp.plugins.base import BasePlugin
from . import stanza
class XEP_0292(BasePlugin):
"""
vCard4 over XMPP
Does not implement the IQ semantics that neither movim does gajim implement,
cf https://xmpp.org/extensions/xep-0292.html#self-iq-retrieval and
https://xmpp.org/extensions/xep-0292.html#self-iq-publication
Does not implement the "empty pubsub event item" as a notification mechanism,
that neither gajim nor movim implement
https://xmpp.org/extensions/xep-0292.html#sect-idm45744791178720
Relies on classic pubsub semantics instead.
"""
xmpp: ComponentXMPP
name = "xep_0292"
description = "vCard4 Over XMPP"
dependencies = {"xep_0163", "xep_0060", "xep_0030"}
stanza = stanza
def plugin_init(self):
pubsub_stanza = self.xmpp["xep_0060"].stanza
register_stanza_plugin(pubsub_stanza.Item, stanza.VCard4)
register_stanza_plugin(pubsub_stanza.EventItem, stanza.VCard4)
self.xmpp['xep_0060'].map_node_event(stanza.NS, 'vcard4')
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.NS)
self.xmpp['xep_0163'].remove_interest(stanza.NS)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('vcard4', stanza.VCard4)
def publish_vcard(
self,
full_name: Optional[str] = None,
given: Optional[str] = None,
surname: Optional[str] = None,
birthday: Optional[date] = None,
nickname: Optional[str] = None,
phone: Optional[str] = None,
note: Optional[str] = None,
url: Optional[str] = None,
email: Optional[str] = None,
country: Optional[str] = None,
locality: Optional[str] = None,
impp: Optional[str] = None,
**pubsubkwargs,
):
"""
Publish a vcard using PEP
"""
vcard = stanza.VCard4()
if impp:
vcard.add_impp(impp)
if nickname:
vcard.add_nickname(nickname)
if full_name:
vcard["full_name"] = full_name
if given:
vcard["given"] = given
if surname:
vcard["surname"] = surname
if birthday:
vcard["birthday"] = birthday
if note:
vcard.add_note(note)
if url:
vcard.add_url(url)
if email:
vcard.add_email(email)
if phone:
vcard.add_tel(phone)
if country and locality:
vcard.add_address(country, locality)
elif country:
vcard.add_address(country, locality)
return self.xmpp["xep_0163"].publish(vcard, id="current", **pubsubkwargs)
def retrieve_vcard(self, jid: JID, **pubsubkwargs):
"""
Retrieve a vcard using PEP
"""
return self.xmpp["xep_0060"].get_item(
jid, stanza.VCard4.namespace, "current", **pubsubkwargs
)
log = logging.getLogger(__name__)

View File

@ -1,12 +1,12 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
# This file is part of Slixmpp.
# See the file LICENSE for copying permissio
import logging
from typing import Optional
import slixmpp
from slixmpp.stanza import Message
from slixmpp.jid import JID
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.xmlstream import register_stanza_plugin
@ -45,59 +45,5 @@ class XEP_0308(BasePlugin):
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(Replace.namespace)
def is_correction(self, msg: Message):
return msg.xml.find('{%s}replace' % Replace.namespace) is not None
def _handle_correction(self, msg: Message):
def _handle_correction(self, msg):
self.xmpp.event('message_correction', msg)
def build_correction(self, id_to_replace: str, mto: JID,
mfrom: Optional[JID] = None, mtype: str = 'chat',
mbody: str = '') -> Message:
"""
Build a corrected message.
:param id_to_replace: The id of the original message.
:param mto: Recipient of the message, must be the same as the original
message.
:param mfrom: Sender of the message, must be the same as the original
message.
:param mtype: Type of the message, must be the send as the original
message.
:param mbody: The corrected message body.
"""
msg = self.xmpp.make_message(
mto=mto,
mfrom=mfrom,
mbody=mbody,
mtype=mtype
)
msg['replace']['id'] = id_to_replace
return msg
def correct_message(self, msg: Message, body: str) -> Message:
"""
Send a correction to an existing message.
:param msg: The message that must be replaced.
:param body: The body to set in the correcting message.
:returns: The message that was sent.
"""
to_replace = msg['id']
mto = msg['to']
mfrom = msg['from']
mtype = msg['type']
if not to_replace:
raise ValueError('No available ID for replacing the message')
if not mto:
raise ValueError('No available recipient JID')
new = self.build_correction(
id_to_replace=to_replace,
mto=mto,
mfrom=mfrom,
mtype=mtype,
mbody=body,
)
new.send()
return new

View File

@ -52,10 +52,9 @@ class MAM(ElementBase):
#: fetch, not relevant for the stanza itself.
interfaces = {
'queryid', 'start', 'end', 'with', 'results',
'before_id', 'after_id', 'ids', 'flip_page',
'before_id', 'after_id', 'ids',
}
sub_interfaces = {'start', 'end', 'with', 'before_id', 'after_id', 'ids',
'flip_page'}
sub_interfaces = {'start', 'end', 'with', 'before_id', 'after_id', 'ids'}
def setup(self, xml=None):
ElementBase.setup(self, xml)
@ -82,7 +81,7 @@ class MAM(ElementBase):
def get_start(self) -> Optional[datetime]:
fields = self.get_fields()
field = fields.get('start')
if field and field["value"]:
if field:
return xep_0082.parse(field['value'])
return None
@ -95,7 +94,7 @@ class MAM(ElementBase):
def get_end(self) -> Optional[datetime]:
fields = self.get_fields()
field = fields.get('end')
if field and field["value"]:
if field:
return xep_0082.parse(field['value'])
return None
@ -169,8 +168,6 @@ class MAM(ElementBase):
def del_results(self):
self._results = []
def get_flip_page(self):
return self.xml.find(f'{{{self.namespace}}}flip-page') is not None
class Fin(ElementBase):
"""A MAM fin element (end of query).
@ -190,7 +187,7 @@ class Fin(ElementBase):
name = 'fin'
namespace = 'urn:xmpp:mam:2'
plugin_attrib = 'mam_fin'
interfaces = {'results', 'stable', 'complete'}
interfaces = {'results'}
def setup(self, xml=None):
ElementBase.setup(self, xml)

View File

@ -1,11 +0,0 @@
# Slixmpp: The Slick XMPP Library
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
from slixmpp.plugins import register_plugin
from slixmpp.plugins.xep_0317 import stanza
from slixmpp.plugins.xep_0317.hats import XEP_0317
from slixmpp.plugins.xep_0317.stanza import Hat, Hats
register_plugin(XEP_0317)
__all__ = ['stanza', 'XEP_317']

View File

@ -1,16 +0,0 @@
from slixmpp.plugins import BasePlugin
from . import stanza
class XEP_0317(BasePlugin):
"""
XEP-0317: Hats
"""
name = 'xep_0317'
description = 'XEP-0317: Hats'
dependencies = {'xep_0030', 'xep_0045', 'xep_0050'}
stanza = stanza
namespace = stanza.NS
def plugin_init(self):
stanza.register_plugin()

View File

@ -1,58 +0,0 @@
from slixmpp import Presence
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
from typing import List, Tuple
NS = 'urn:xmpp:hats:0'
class Hats(ElementBase):
"""
Hats element, container for multiple hats:
.. code-block::xml
<hats xmlns='urn:xmpp:hats:0'>
<hat title='Host' uri='http://schemas.example.com/hats#host' xml:lang='en-us'>
<badge xmlns="urn:example:badges" fgcolor="#000000" bgcolor="#58C5BA"/>
</hat>
<hat title='Presenter' uri='http://schemas.example.com/hats#presenter' xml:lang='en-us'>
<badge xmlns="urn:example:badges" fgcolor="#000000" bgcolor="#EC0524"/>
</hat>
</hats>
"""
name = 'hats'
namespace = NS
plugin_attrib = 'hats'
def add_hats(self, data: List[Tuple[str, str]]) -> None:
for uri, title in data:
hat = Hat()
hat["uri"] = uri
hat["title"] = title
self.append(hat)
class Hat(ElementBase):
"""
Hat element, has a title and url, may contain arbitrary sub-elements.
.. code-block::xml
<hat title='Host' uri='http://schemas.example.com/hats#host' xml:lang='en-us'>
<badge xmlns="urn:example:badges" fgcolor="#000000" bgcolor="#58C5BA"/>
</hat>
"""
name = 'hat'
plugin_attrib = 'hat'
namespace = NS
interfaces = {'title', 'uri'}
plugin_multi_attrib = "hats"
def register_plugin() -> None:
register_stanza_plugin(Hats, Hat, iterable=True)
register_stanza_plugin(Presence, Hats)

View File

@ -1,3 +1,4 @@
# slixmpp: The Slick XMPP Library
# Copyright (C) 2016 Emmanuel Gil Peyrot
# This file is part of slixmpp.
@ -67,11 +68,11 @@ class XEP_0333(BasePlugin):
:param JID mto: recipient of the marker
:param str id: Identifier of the marked message
:param str marker: Marker to send (one of
displayed, received, or acknowledged)
displayed, retrieved, or acknowledged)
:param str thread: Message thread
:param str mfrom: Use a specific JID to send the message
"""
if marker not in ('displayed', 'received', 'acknowledged'):
if marker not in ('displayed', 'retrieved', 'acknowledged'):
raise ValueError('Invalid marker: %s' % marker)
msg = self.xmpp.make_message(mto=mto, mfrom=mfrom)
if thread:

View File

@ -1,7 +1,7 @@
from slixmpp.plugins.base import register_plugin
from . import stanza
from .privilege import XEP_0356
from .stanza import Perm, Privilege
from slixmpp.plugins.xep_0356 import stanza
from slixmpp.plugins.xep_0356.stanza import Perm, Privilege
from slixmpp.plugins.xep_0356.privilege import XEP_0356
register_plugin(XEP_0356)

View File

@ -1,36 +0,0 @@
import dataclasses
from collections import defaultdict
from enum import Enum
class RosterAccess(str, Enum):
NONE = "none"
GET = "get"
SET = "set"
BOTH = "both"
class MessagePermission(str, Enum):
NONE = "none"
OUTGOING = "outgoing"
class IqPermission(str, Enum):
NONE = "none"
GET = "get"
SET = "set"
BOTH = "both"
class PresencePermission(str, Enum):
NONE = "none"
MANAGED_ENTITY = "managed_entity"
ROSTER = "roster"
@dataclasses.dataclass
class Permissions:
roster = RosterAccess.NONE
message = MessagePermission.NONE
iq = defaultdict(lambda: IqPermission.NONE)
presence = PresencePermission.NONE

View File

@ -1,16 +1,14 @@
import logging
import typing
import uuid
from collections import defaultdict
from slixmpp import JID, Iq, Message
from slixmpp import Message, JID, Iq
from slixmpp.plugins.base import BasePlugin
from slixmpp.xmlstream import StanzaBase
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins.xep_0356 import stanza, Privilege, Perm
from . import stanza
from .permissions import IqPermission, MessagePermission, Permissions, RosterAccess
log = logging.getLogger(__name__)
@ -31,7 +29,7 @@ class XEP_0356(BasePlugin):
dependencies = {"xep_0297"}
stanza = stanza
granted_privileges = defaultdict(Permissions)
granted_privileges = {"roster": "none", "message": "none", "presence": "none"}
def plugin_init(self):
if not self.xmpp.is_component:
@ -51,42 +49,32 @@ class XEP_0356(BasePlugin):
def plugin_end(self):
self.xmpp.remove_handler("Privileges")
def _handle_privilege(self, msg: StanzaBase):
def _handle_privilege(self, msg: Message):
"""
Called when the XMPP server advertise the component's privileges.
Stores the privileges in this instance's granted_privileges attribute (a dict)
and raises the privileges_advertised event
"""
permissions = self.granted_privileges[msg.get_from()]
for perm in msg["privilege"]["perms"]:
access = perm["access"]
if access == "iq":
for ns in perm["namespaces"]:
permissions.iq[ns["ns"]] = ns["type"]
elif access in _VALID_ACCESSES:
setattr(permissions, access, perm["type"])
else:
log.warning("Received an invalid privileged access: %s", access)
self.granted_privileges[perm["access"]] = perm["type"]
log.debug(f"Privileges: {self.granted_privileges}")
self.xmpp.event("privileges_advertised")
def send_privileged_message(self, msg: Message):
if (
self.granted_privileges[msg.get_from().domain].message
!= MessagePermission.OUTGOING
):
raise PermissionError(
if self.granted_privileges["message"] == "outgoing":
self._make_privileged_message(msg).send()
else:
log.error(
"The server hasn't authorized us to send messages on behalf of other users"
)
else:
self._make_privileged_message(msg).send()
def _make_privileged_message(self, msg: Message):
server = msg.get_from().domain
wrapped = self.xmpp.make_message(mto=server, mfrom=self.xmpp.boundjid.bare)
wrapped["privilege"]["forwarded"].append(msg)
return wrapped
stanza = self.xmpp.make_message(
mto=self.xmpp.server_host, mfrom=self.xmpp.boundjid.bare
)
stanza["privilege"]["forwarded"].append(msg)
return stanza
def _make_get_roster(self, jid: typing.Union[JID, str], **iq_kwargs):
return self.xmpp.make_iq_get(
@ -118,15 +106,9 @@ class XEP_0356(BasePlugin):
:param jid: user we want to fetch the roster from
"""
if isinstance(jid, str):
jid = JID(jid)
if self.granted_privileges[jid.domain].roster not in (
RosterAccess.GET,
RosterAccess.BOTH,
):
raise PermissionError(
"The server did not grant us privileges to get rosters"
)
if self.granted_privileges["roster"] not in ("get", "both"):
log.error("The server did not grant us privileges to get rosters")
raise ValueError
else:
return await self._make_get_roster(jid).send(**send_kwargs)
@ -138,10 +120,10 @@ class XEP_0356(BasePlugin):
Raises ValueError if the server did not advertise the corresponding privileges
Here is an example of a roster_items value:
.. code-block:: json
:param jid: user we want to add or modify roster items
:param roster_items: a dict containing the roster items' JIDs as keys and
nested dicts containing names, subscriptions and groups.
Example:
{
"friend1@example.com": {
"name": "Friend 1",
@ -154,62 +136,9 @@ class XEP_0356(BasePlugin):
"groups": ["group3"],
},
}
:param jid: user we want to add or modify roster items
:param roster_items: a dict containing the roster items' JIDs as keys and
nested dicts containing names, subscriptions and groups.
"""
if isinstance(jid, str):
jid = JID(jid)
if self.granted_privileges[jid.domain].roster not in (
RosterAccess.GET,
RosterAccess.BOTH,
):
raise PermissionError(
"The server did not grant us privileges to set rosters"
)
if self.granted_privileges["roster"] not in ("set", "both"):
log.error("The server did not grant us privileges to set rosters")
raise ValueError
else:
return await self._make_set_roster(jid, roster_items).send(**send_kwargs)
async def send_privileged_iq(
self, encapsulated_iq: Iq, iq_id: typing.Optional[str] = None
):
"""
Send an IQ on behalf of a user
Caution: the IQ *must* have the jabber:client namespace
"""
iq_id = iq_id or str(uuid.uuid4())
encapsulated_iq["id"] = iq_id
server = encapsulated_iq.get_to().domain
perms = self.granted_privileges.get(server)
if not perms:
raise PermissionError(f"{server} has not granted us any privilege")
itype = encapsulated_iq["type"]
for ns in encapsulated_iq.plugins.values():
type_ = perms.iq[ns.namespace]
if type_ == IqPermission.NONE:
raise PermissionError(
f"{server} has not granted any IQ privilege for namespace {ns.namespace}"
)
elif type_ == IqPermission.BOTH:
pass
elif type_ != itype:
raise PermissionError(
f"{server} has not granted IQ {itype} privilege for namespace {ns.namespace}"
)
iq = self.xmpp.make_iq(
itype=itype,
ifrom=self.xmpp.boundjid.bare,
ito=encapsulated_iq.get_from(),
id=iq_id,
)
iq["privileged_iq"].append(encapsulated_iq)
resp = await iq.send()
return resp["privilege"]["forwarded"]["iq"]
# does not include iq access that is handled differently
_VALID_ACCESSES = {"message", "roster", "presence"}

View File

@ -1,12 +1,13 @@
from slixmpp.stanza import Message
from slixmpp.xmlstream import (
ElementBase,
register_stanza_plugin,
)
from slixmpp.plugins.xep_0297 import Forwarded
from slixmpp.stanza import Iq, Message
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
NS = "urn:xmpp:privilege:2"
class Privilege(ElementBase):
namespace = NS
namespace = "urn:xmpp:privilege:1"
name = "privilege"
plugin_attrib = "privilege"
@ -24,40 +25,23 @@ class Privilege(ElementBase):
def presence(self):
return self.permission("presence")
def add_perm(self, access, type_):
def add_perm(self, access, type):
# This should only be needed for servers, so maybe out of scope for slixmpp
perm = Perm()
perm["type"] = type_
perm["type"] = type
perm["access"] = access
self.append(perm)
class Perm(ElementBase):
namespace = NS
namespace = "urn:xmpp:privilege:1"
name = "perm"
plugin_attrib = "perm"
plugin_multi_attrib = "perms"
interfaces = {"type", "access"}
class NameSpace(ElementBase):
namespace = NS
name = "namespace"
plugin_attrib = "namespace"
plugin_multi_attrib = "namespaces"
interfaces = {"ns", "type"}
class PrivilegedIq(ElementBase):
namespace = NS
name = "privileged_iq"
plugin_attrib = "privileged_iq"
def register():
register_stanza_plugin(Message, Privilege)
register_stanza_plugin(Iq, Privilege)
register_stanza_plugin(Privilege, Forwarded)
register_stanza_plugin(Privilege, Perm, iterable=True)
register_stanza_plugin(Perm, NameSpace, iterable=True)
register_stanza_plugin(Iq, PrivilegedIq)

View File

@ -14,8 +14,6 @@ from typing import (
IO,
)
from pathlib import Path
from slixmpp import JID, __version__
from slixmpp.stanza import Iq
from slixmpp.plugins import BasePlugin
@ -101,17 +99,12 @@ class XEP_0363(BasePlugin):
:param domain: Domain to disco to find a service.
"""
if domain is None and self.xmpp.is_component:
domain = self.xmpp.server_host
results = await self.xmpp['xep_0030'].get_info_from_domain(
domain=domain, **iqkwargs
)
candidates = []
for info in results:
if not info['disco_info']:
continue
for identity in info['disco_info']['identities']:
if identity[0] == 'store' and identity[1] == 'file':
candidates.append(info)
@ -120,7 +113,7 @@ class XEP_0363(BasePlugin):
if feature == Request.namespace:
return info
def request_slot(self, jid: JID, filename: Path, size: int,
def request_slot(self, jid: JID, filename: str, size: int,
content_type: Optional[str] = None, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Future:
"""Request an HTTP upload slot from a service.
@ -132,12 +125,12 @@ class XEP_0363(BasePlugin):
"""
iq = self.xmpp.make_iq_get(ito=jid, ifrom=ifrom)
request = iq['http_upload_request']
request['filename'] = str(filename)
request['filename'] = filename
request['size'] = str(size)
request['content-type'] = content_type or self.default_content_type
return iq.send(**iqkwargs)
async def upload_file(self, filename: Path, size: Optional[int] = None,
async def upload_file(self, filename: str, size: Optional[int] = None,
content_type: Optional[str] = None, *,
input_file: Optional[IO[bytes]]=None,
domain: Optional[JID] = None,

View File

@ -1,6 +0,0 @@
from slixmpp.plugins.base import register_plugin
from . import stanza
from .references import XEP_0372
register_plugin(XEP_0372)

View File

@ -1,23 +0,0 @@
import logging
from slixmpp import Message, register_stanza_plugin
from slixmpp.plugins import BasePlugin
from . import stanza
log = logging.getLogger(__name__)
class XEP_0372(BasePlugin):
"""
XEP-0372: References
Minimum needed for xep 0385 (Stateless inline media sharing)
"""
name = "xep_0372"
description = "XEP-0372: References"
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.Reference)

View File

@ -1,9 +0,0 @@
from slixmpp.xmlstream import ElementBase
NAMESPACE = "urn:xmpp:reference:0"
class Reference(ElementBase):
name = plugin_attrib = "reference"
namespace = NAMESPACE
interfaces = {"type", "uri", "id", "begin", "end"}

View File

@ -26,9 +26,6 @@ class XEP_0377(BasePlugin):
dependencies = {'xep_0030', 'xep_0191'}
stanza = stanza
SPAM = 'urn:xmpp:reporting:spam'
ABUSE = 'urn:xmpp:reporting:abuse'
def plugin_init(self):
register_stanza_plugin(Block, stanza.Report)
register_stanza_plugin(stanza.Report, stanza.Text)

View File

@ -13,23 +13,58 @@ class Report(ElementBase):
Example sub stanza:
::
<report xmlns="urn:xmpp:reporting:1" reason="urn:xmpp:reporting:abuse">
<report xmlns="urn:xmpp:reporting:0">
<text xml:lang="en">
Never came trouble to my house like this.
</text>
<spam/>
</report>
The reason attribute is mandatory.
Stanza Interface:
::
abuse -- Flag the report as abuse
spam -- Flag the report as spam
text -- Add a reason to the report
Only one <spam/> or <abuse/> element can be present at once.
"""
name = "report"
namespace = "urn:xmpp:reporting:1"
namespace = "urn:xmpp:reporting:0"
plugin_attrib = "report"
interfaces = ("text", "reason")
interfaces = ("spam", "abuse", "text")
sub_interfaces = {'text'}
def _purge_spam(self):
spam = self.xml.findall('{%s}spam' % self.namespace)
for element in spam:
self.xml.remove(element)
def _purge_abuse(self):
abuse = self.xml.findall('{%s}abuse' % self.namespace)
for element in abuse:
self.xml.remove(element)
def get_spam(self):
return self.xml.find('{%s}spam' % self.namespace) is not None
def set_spam(self, value):
self._purge_spam()
if bool(value):
self._purge_abuse()
self.xml.append(ET.Element('{%s}spam' % self.namespace))
def get_abuse(self):
return self.xml.find('{%s}abuse' % self.namespace) is not None
def set_abuse(self, value):
self._purge_abuse()
if bool(value):
self._purge_spam()
self.xml.append(ET.Element('{%s}abuse' % self.namespace))
class Text(ElementBase):
name = "text"
plugin_attrib = "text"
namespace = "urn:xmpp:reporting:1"
namespace = "urn:xmpp:reporting:0"

View File

@ -1,11 +0,0 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
# This file is part of Slixmpp.
# See the file LICENSE for copying permission
from slixmpp.plugins.base import register_plugin
from . import stanza
from .sims import XEP_0385
register_plugin(XEP_0385)

View File

@ -1,66 +0,0 @@
import logging
from datetime import datetime
from pathlib import Path
from typing import Iterable, Optional
from slixmpp.plugins import BasePlugin
from slixmpp.stanza import Message
from slixmpp.xmlstream import register_stanza_plugin
from . import stanza
log = logging.getLogger(__name__)
class XEP_0385(BasePlugin):
"""
XEP-0385: Stateless Inline Media Sharing (SIMS)
Only support outgoing SIMS, incoming is not handled at all.
"""
name = "xep_0385"
description = "XEP-0385: Stateless Inline Media Sharing (SIMS)"
dependencies = {"xep_0234", "xep_0300", "xep_0372"}
stanza = stanza
def plugin_init(self):
register_stanza_plugin(self.xmpp["xep_0372"].stanza.Reference, stanza.Sims)
register_stanza_plugin(Message, stanza.Sims)
register_stanza_plugin(stanza.Sims, stanza.Sources)
register_stanza_plugin(stanza.Sims, self.xmpp["xep_0234"].stanza.File)
register_stanza_plugin(stanza.Sources, self.xmpp["xep_0372"].stanza.Reference)
def get_sims(
self,
path: Path,
uris: Iterable[str],
media_type: Optional[str],
desc: Optional[str],
):
sims = stanza.Sims()
for uri in uris:
ref = self.xmpp["xep_0372"].stanza.Reference()
ref["uri"] = uri
ref["type"] = "data"
sims["sources"].append(ref)
if media_type:
sims["file"]["media-type"] = media_type
if desc:
sims["file"]["desc"] = desc
sims["file"]["name"] = path.name
stat = path.stat()
sims["file"]["size"] = stat.st_size
sims["file"]["date"] = datetime.fromtimestamp(stat.st_mtime)
h = self.xmpp.plugin["xep_0300"].compute_hash(path)
h["value"] = h["value"].decode()
sims["file"].append(h)
ref = self.xmpp["xep_0372"].stanza.Reference()
ref.append(sims)
ref["type"] = "data"
return ref

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