Compare commits
	
		
			91 Commits
		
	
	
		
			slix-1.8.3
			...
			slix-1.8.5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 5226858e0c | ||
|   | 7128ea249b | ||
|   | 992d80dd09 | ||
|   | c25305e80f | ||
|   | 6765f84133 | ||
|   | 31fe7f7e06 | ||
|   | 84a7ac020f | ||
|   | 331c1c1e21 | ||
|   | 28a60c22e2 | ||
|   | af934b5bdf | ||
|   | 897f876504 | ||
|   | 2888be17ab | ||
|   | 975e31229c | ||
|   | 6e9e66139d | ||
|   | 380ac04d52 | ||
|   | 9e5b530607 | ||
|   | 71de274fab | ||
|   | 5a0b02378d | ||
|   | 9fc82e9e6f | ||
|   | ca90d3908e | ||
|   | 7de5cbcf33 | ||
|   | 76a11d4899 | ||
|   | dcfa0f20f9 | ||
|   | 7732af8991 | ||
|   | 25c28ff5d1 | ||
|   | e3e0d8f43e | ||
|   | 13729e47a6 | ||
|   | f12860bfad | ||
|   | bcbc7281e7 | ||
|   | 8787aa1064 | ||
|   | f3522eb84b | ||
|   | da9646cdaa | ||
|   | db1fc5fbc5 | ||
|   | 209554e63f | ||
|   | 2d02ef9bcb | ||
|   | 18c3db4d6e | ||
|   | 6d6fdc6419 | ||
|   | 4936fb06bf | ||
|   | 5e47286445 | ||
|   | 8bead23799 | ||
|   | 56c906f207 | ||
|   | 876c82037f | ||
|   | fae4a38e84 | ||
|   | 2b59d299a1 | ||
|   | 51a4efb0f4 | ||
|   | 8f77bd4ee5 | ||
|   | 71128349a4 | ||
|   | bc2cebae6c | ||
|   | 2080d08d63 | ||
|   | e16f72d32d | ||
|   | 4fa068da54 | ||
|   | 21e5cd4435 | ||
|   | 1a40699bcc | ||
|   | ebb8bd1e71 | ||
|   | 78b42bdbbe | ||
|   | abd3f40e96 | ||
|   | b6f148e4e6 | ||
|   | 968fb0bac3 | ||
|   | 8dcbcbf8a0 | ||
|   | de7b2d33a3 | ||
|   | fd1af054c5 | ||
|   | e34fbfb28f | ||
|   | af16832ad0 | ||
|   | 40a857de65 | ||
|   | 79ffa1668f | ||
|   | b4b1efe058 | ||
|   | de358464d0 | ||
|   | 92b4f2a7eb | ||
|   | 1f934d375c | ||
|   | 700ce6b32e | ||
|   | 5efa9804ba | ||
|   | 9b0be1ca2b | ||
|   | 5c19f16287 | ||
|   | af07864cbb | ||
|   | dc4b1c7367 | ||
|   | 4a6064772c | ||
|   | 80a89061f1 | ||
|   | 8f4d8f76d1 | ||
|   | 656248ede7 | ||
|   | 980afe791f | ||
|   | 3725177d0b | ||
|   | 26fb0d1f91 | ||
|   | 5eb17e7633 | ||
|   | fdca7d82c4 | ||
|   | 9b89401b36 | ||
|   | 7300f1285e | ||
|   | 9b51be1e17 | ||
|   | 89b1e1e682 | ||
|   | a7501abe56 | ||
|   | 6940e4276b | ||
|   | 65636b8cce | 
| @@ -12,14 +12,14 @@ mypy: | ||||
|     - pip3 install mypy | ||||
|     - mypy slixmpp | ||||
|  | ||||
| test: | ||||
| test-3.7: | ||||
|   stage: test | ||||
|   tags: | ||||
|     - docker | ||||
|   image: ubuntu:latest | ||||
|   image: python:3.7 | ||||
|   script: | ||||
|     - apt update | ||||
|     - apt install -y python3 python3-pip cython3 gpg | ||||
|     - apt-get update | ||||
|     - apt-get install -y python3 python3-pip cython3 gpg | ||||
|     - pip3 install emoji aiohttp cryptography | ||||
|     - ./run_tests.py | ||||
|  | ||||
| @@ -30,7 +30,7 @@ test-3.10: | ||||
|   image: python:3.10 | ||||
|   script: | ||||
|     - apt update | ||||
|     - apt install -y python3 python3-pip cython3 gpg | ||||
|     - apt-get install -y python3 python3-pip cython3 gpg | ||||
|     - pip3 install emoji aiohttp cryptography | ||||
|     - ./run_tests.py | ||||
|  | ||||
| @@ -38,11 +38,22 @@ test-3.11: | ||||
|   stage: test | ||||
|   tags: | ||||
|     - docker | ||||
|   image: python:3.11-rc | ||||
|   image: python:3.11 | ||||
|   script: | ||||
|     - apt-get update | ||||
|     - apt-get install -y python3 python3-pip cython3 gpg | ||||
|     - pip3 install emoji aiohttp cryptography | ||||
|     - ./run_tests.py | ||||
|  | ||||
| test-3.12: | ||||
|   stage: test | ||||
|   tags: | ||||
|     - docker | ||||
|   image: python:3.12-rc | ||||
|   allow_failure: true | ||||
|   script: | ||||
|     - apt update | ||||
|     - apt install -y python3 python3-pip cython3 gpg | ||||
|     - apt-get update | ||||
|     - apt-get install -y python3 python3-pip cython3 gpg | ||||
|     - pip3 install emoji aiohttp cryptography | ||||
|     - ./run_tests.py | ||||
|  | ||||
| @@ -50,14 +61,14 @@ test_integration: | ||||
|   stage: test | ||||
|   tags: | ||||
|     - docker | ||||
|   image: ubuntu:latest | ||||
|   image: python:3 | ||||
|   only: | ||||
|     variables: | ||||
|         - $CI_ACCOUNT1 | ||||
|         - $CI_ACCOUNT2 | ||||
|   script: | ||||
|     - apt update | ||||
|     - apt install -y python3 python3-pip cython3 gpg | ||||
|     - apt-get update | ||||
|     - apt-get install -y python3 python3-pip cython3 gpg | ||||
|     - pip3 install emoji aiohttp aiodns | ||||
|     - ./run_integration_tests.py | ||||
|  | ||||
|   | ||||
							
								
								
									
										22
									
								
								.readthedocs.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.readthedocs.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| # .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 | ||||
							
								
								
									
										6
									
								
								.woodpecker/lint.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.woodpecker/lint.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| steps: | ||||
|   mypy: | ||||
|     image: python:3 | ||||
|     script: | ||||
|       - pip3 install mypy types-setuptools | ||||
|       - mypy slixmpp | ||||
							
								
								
									
										9
									
								
								.woodpecker/test-integration.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.woodpecker/test-integration.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| steps: | ||||
|   test_integration: | ||||
|     image: "python:3.11" | ||||
|     secrets: [ci_account1, ci_account1_password, ci_account2, ci_account2_password] | ||||
|     commands: | ||||
|       - apt-get update | ||||
|       - apt-get install -y python3-pip cython3 gpg | ||||
|       - pip3 install emoji aiohttp aiodns | ||||
|       - ./run_integration_tests.py | ||||
							
								
								
									
										17
									
								
								.woodpecker/test.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.woodpecker/test.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| steps: | ||||
|   unit_tests: | ||||
|     image: "python:${TAG}" | ||||
|     commands: | ||||
|     - apt-get update | ||||
|     - apt-get install -y python3 python3-pip cython3 gpg | ||||
|     - pip3 install emoji aiohttp cryptography | ||||
|     - ./run_tests.py | ||||
|  | ||||
| matrix: | ||||
|   TAG: | ||||
|     - "3.7" | ||||
|     - "3.9" | ||||
|     - "3.8" | ||||
|     - "3.10" | ||||
|     - "3.11" | ||||
|     - "3.12" | ||||
| @@ -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://lab.louiz.org/poezio/slixmpp/issues/new>`_ | ||||
|  - a ticket `on the bug tracker <https://codeberg.org/poezio/slixmpp/issues/new>`_ | ||||
|  - a pull request on github | ||||
|  - a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_ | ||||
|  | ||||
|   | ||||
							
								
								
									
										45
									
								
								doap.xml
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								doap.xml
									
									
									
									
									
								
							| @@ -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://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"/> | ||||
|     <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"/> | ||||
|     <developer-forum rdf:resource="xmpp:slixmpp@muc.poez.io?join"/> | ||||
|     <support-forum rdf:resource="xmpp:slixmpp@muc.poez.io?join"/> | ||||
|  | ||||
|     <license rdf:resource="https://lab.louiz.org/poezio/slixmpp/blob/master/LICENSE"/> | ||||
|     <license rdf:resource="https://codeberg.org/poezio/slixmpp/raw/brach/master/LICENSE"/> | ||||
|  | ||||
|     <language>en</language> | ||||
|  | ||||
| @@ -59,8 +59,8 @@ | ||||
|  | ||||
|     <repository> | ||||
|         <GitRepository> | ||||
|             <browse rdf:resource="https://lab.louiz.org/poezio/slixmpp"/> | ||||
|             <location rdf:resource="https://lab.louiz.org/poezio/slixmpp.git"/> | ||||
|             <browse rdf:resource="https://codeberg.org/poezio/slixmpp"/> | ||||
|             <location rdf:resource="https://codeberg.org/poezio/slixmpp.git"/> | ||||
|         </GitRepository> | ||||
|     </repository> | ||||
|  | ||||
| @@ -784,7 +784,7 @@ | ||||
|         <xmpp:SupportedXep> | ||||
|             <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0377.html"/> | ||||
|             <xmpp:status>complete</xmpp:status> | ||||
|             <xmpp:version>0.2</xmpp:version> | ||||
|             <xmpp:version>0.3</xmpp:version> | ||||
|             <xmpp:since>1.6.0</xmpp:since> | ||||
|         </xmpp:SupportedXep> | ||||
|     </implements> | ||||
| @@ -903,9 +903,10 @@ | ||||
|     <implements> | ||||
|         <xmpp:SupportedXep> | ||||
|             <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html"/> | ||||
|             <xmpp:status>no thumbnail support</xmpp:status> | ||||
|             <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> | ||||
|  | ||||
| @@ -1011,49 +1012,63 @@ | ||||
|         <Version> | ||||
|             <revision>1.6.0</revision> | ||||
|             <created>2020-12-12</created> | ||||
|             <file-release rdf:resource="https://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.6.0/slixmpp-slix-1.6.0.tar.gz"/> | ||||
|             <file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/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://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.7.0/slixmpp-slix-1.7.0.tar.gz"/> | ||||
|             <file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/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://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.7.1/slixmpp-slix-1.7.1.tar.gz"/> | ||||
|             <file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/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://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.8.0/slixmpp-slix-1.8.0.tar.gz"/> | ||||
|             <file-release rdf:resource="https://codeberg.org/poezio/slixmpp/archive/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://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.8.1/slixmpp-slix-1.8.1.tar.gz"/> | ||||
|             <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://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.8.2/slixmpp-slix-1.8.2.tar.gz"/> | ||||
|             <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://lab.louiz.org/poezio/slixmpp/-/archive/slix-1.8.3/slixmpp-slix-1.8.3.tar.gz"/> | ||||
|             <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> | ||||
| </Project> | ||||
|   | ||||
							
								
								
									
										17
									
								
								docs/api/plugins/xep_0292.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								docs/api/plugins/xep_0292.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
|  | ||||
| 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: | ||||
| @@ -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://lab.louiz.org/poezio/slixmpp>`_. | ||||
| with `Git <https://codeberg.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 | ||||
|   | ||||
| @@ -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://lab.louiz.org/poezio/slixmpp>`_. | ||||
| with `Git <https://codeberg.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://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_. | ||||
| can also be found in the Slixmpp `examples directory <https://codeberg.org/poezio/slixmpp/src/branch/master/examples>`_. | ||||
|  | ||||
| .. compound:: | ||||
|  | ||||
|   | ||||
| @@ -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://lab.louiz.org/poezio/slixmpp>`_. | ||||
| from `Git <https://codeberg.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 | ||||
|   | ||||
| @@ -4,9 +4,9 @@ Slixmpp | ||||
| .. sidebar:: Get the Code | ||||
|  | ||||
|     The latest source code for Slixmpp may be found on the `Git repo | ||||
|     <https://lab.louiz.org/poezio/slixmpp>`_. :: | ||||
|     <https://codeberg.org/poezio/slixmpp>`_. :: | ||||
|  | ||||
|         git clone https://lab.louiz.org/poezio/slixmpp | ||||
|         git clone https://codeberg.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://lab.louiz.org/poezio/slixmpp/issues. | ||||
|         You can report bugs at http://codeberg.org/poezio/slixmpp/issues. | ||||
|  | ||||
| Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+, | ||||
|  | ||||
|   | ||||
							
								
								
									
										95
									
								
								docs/projects.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								docs/projects.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| Projects Using 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://github.com/moparisthebest/sendxmpp-py>`_ | ||||
|  | ||||
| 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>`_ | ||||
|  | ||||
| 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. | ||||
|  | ||||
| - `Groupchat <xmpp:slixmpp@muc.poez.io?join>`_ | ||||
| - `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. | ||||
|  | ||||
| - `Groupchat <xmpp:slixmpp@muc.poez.io?join>`_ | ||||
| - `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. | ||||
|  | ||||
| - `Groupchat <xmpp:slixmpp@muc.poez.io?join>`_ | ||||
| - `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. | ||||
|  | ||||
| - `Groupchat <xmpp:slixmpp@muc.poez.io?join>`_ | ||||
| - `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>`_ | ||||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| @@ -80,7 +80,7 @@ setup( | ||||
|     long_description=LONG_DESCRIPTION, | ||||
|     author='Florent Le Coz', | ||||
|     author_email='louiz@louiz.org', | ||||
|     url='https://lab.louiz.org/poezio/slixmpp', | ||||
|     url='https://codeberg.org/poezio/slixmpp', | ||||
|     license='MIT', | ||||
|     platforms=['any'], | ||||
|     package_data={'slixmpp': ['py.typed']}, | ||||
|   | ||||
| @@ -279,13 +279,13 @@ class BaseXMPP(XMLStream): | ||||
|         if self.plugin_whitelist: | ||||
|             plugin_list = self.plugin_whitelist | ||||
|         else: | ||||
|             plugin_list = plugins.__all__ | ||||
|             plugin_list = plugins.PLUGINS | ||||
|  | ||||
|         for plugin in plugin_list: | ||||
|             if plugin in plugins.__all__: | ||||
|             if plugin in plugins.PLUGINS: | ||||
|                 self.register_plugin(plugin) | ||||
|             else: | ||||
|                 raise NameError("Plugin %s not in plugins.__all__." % plugin) | ||||
|                 raise NameError("Plugin %s not in plugins.PLUGINS." % plugin) | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         """Return a plugin given its name, if it has been registered.""" | ||||
|   | ||||
| @@ -138,8 +138,8 @@ class ClientXMPP(BaseXMPP): | ||||
|         self.credentials['password'] = value | ||||
|  | ||||
|     def connect(self, address: Optional[Tuple[str, int]] = None,  # type: ignore | ||||
|                 use_ssl: bool = False, force_starttls: bool = True, | ||||
|                 disable_starttls: bool = False) -> None: | ||||
|                 use_ssl: Optional[bool] = None, force_starttls: Optional[bool] = None, | ||||
|                 disable_starttls: Optional[bool] = None) -> None: | ||||
|         """Connect to the XMPP server. | ||||
|  | ||||
|         When no address is given, a SRV lookup for the server will | ||||
| @@ -166,8 +166,8 @@ class ClientXMPP(BaseXMPP): | ||||
|             host, port = (self.boundjid.host, 5222) | ||||
|             self.dns_service = 'xmpp-client' | ||||
|  | ||||
|         return XMLStream.connect(self, host, port, use_ssl=use_ssl, | ||||
|                                  force_starttls=force_starttls, disable_starttls=disable_starttls) | ||||
|         XMLStream.connect(self, host, port, use_ssl=use_ssl, | ||||
|                           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. | ||||
|   | ||||
| @@ -9,13 +9,16 @@ | ||||
| import logging | ||||
| import hashlib | ||||
|  | ||||
| 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__) | ||||
|  | ||||
| @@ -39,9 +42,17 @@ 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): | ||||
|     def __init__(self, jid, secret, | ||||
|                  host=None, port=None, plugin_config=None, | ||||
|                  plugin_whitelist=None, use_jc_ns=False, | ||||
|                  fix_error_ns=False): | ||||
|  | ||||
|         if not plugin_whitelist: | ||||
|             plugin_whitelist = [] | ||||
| @@ -53,6 +64,8 @@ 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">' % ( | ||||
| @@ -77,7 +90,12 @@ class ComponentXMPP(BaseXMPP): | ||||
|         self.add_event_handler('presence_probe', | ||||
|                                self._handle_probe) | ||||
|  | ||||
|     def connect(self, host=None, port=None, use_ssl=False): | ||||
|     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: str = None, port: int = 0, use_ssl: Optional[bool] = None) -> None: | ||||
|         """Connect to the server. | ||||
|  | ||||
|  | ||||
| @@ -88,16 +106,15 @@ class ComponentXMPP(BaseXMPP): | ||||
|         :param use_ssl: Flag indicating if SSL should be used by connecting | ||||
|                         directly to a port using SSL. | ||||
|         """ | ||||
|         if host is None: | ||||
|             host = self.server_host | ||||
|         if port is None: | ||||
|             port = self.server_port | ||||
|         if host is not None: | ||||
|             self.server_host = host | ||||
|         if port: | ||||
|             self.server_port = port | ||||
|  | ||||
|         self.server_name = self.boundjid.host | ||||
|  | ||||
|         log.debug("Connecting to %s:%s", host, port) | ||||
|         return XMLStream.connect(self, host=host, port=port, | ||||
|                                        use_ssl=use_ssl) | ||||
|         XMLStream.connect(self, host=self.server_host, port=self.server_port, use_ssl=use_ssl) | ||||
|  | ||||
|     def incoming_filter(self, xml): | ||||
|         """ | ||||
|   | ||||
| @@ -5,6 +5,11 @@ | ||||
| # :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): | ||||
|  | ||||
|     """ | ||||
| @@ -37,12 +42,17 @@ class XMPPError(Exception): | ||||
|                   Defaults to ``True``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, condition='undefined-condition', text='', | ||||
|                 etype='cancel', extension=None, extension_ns=None, | ||||
|                 extension_args=None, clear=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): | ||||
|         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 | ||||
| @@ -110,3 +120,29 @@ 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", | ||||
|     "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", | ||||
| } | ||||
|   | ||||
| @@ -37,7 +37,8 @@ class FeatureMechanisms(BasePlugin): | ||||
|         'unencrypted_digest': False, | ||||
|         'unencrypted_cram': False, | ||||
|         'unencrypted_scram': True, | ||||
|         'order': 100 | ||||
|         'order': 100, | ||||
|         'tls_version': None, | ||||
|     } | ||||
|  | ||||
|     def plugin_init(self): | ||||
| @@ -96,7 +97,20 @@ class FeatureMechanisms(BasePlugin): | ||||
|                 result[value] = creds.get('email', jid) | ||||
|             elif value == 'channel_binding': | ||||
|                 if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)): | ||||
|                     result[value] = self.xmpp.socket.get_channel_binding() | ||||
|                     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 | ||||
|                 else: | ||||
|                     result[value] = None | ||||
|             elif value == 'host': | ||||
| @@ -121,6 +135,11 @@ 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 | ||||
|   | ||||
| @@ -303,13 +303,15 @@ 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): | ||||
|     def __init__(self, jid: Optional[Union[str, 'JID']] = None, bare: bool = False): | ||||
|         if not jid: | ||||
|             self._node = '' | ||||
|             self._domain = '' | ||||
| @@ -318,11 +320,14 @@ class JID: | ||||
|             self._full = '' | ||||
|             return | ||||
|         elif not isinstance(jid, JID): | ||||
|             self._node, self._domain, self._resource = _parse_jid(jid) | ||||
|             node, domain, resource = _parse_jid(jid) | ||||
|             self._node = node | ||||
|             self._domain = domain | ||||
|             self._resource = resource if not bare else '' | ||||
|         else: | ||||
|             self._node = jid._node | ||||
|             self._domain = jid._domain | ||||
|             self._resource = jid._resource | ||||
|             self._resource = jid._resource if not bare else '' | ||||
|         self._update_bare_full() | ||||
|  | ||||
|     def unescape(self): | ||||
|   | ||||
| @@ -6,7 +6,7 @@ from slixmpp.plugins.base import PluginManager, PluginNotFound, BasePlugin | ||||
| from slixmpp.plugins.base import register_plugin, load_plugin | ||||
|  | ||||
|  | ||||
| __all__ = [ | ||||
| PLUGINS = [ | ||||
|     # XEPS | ||||
|     'xep_0004',  # Data Forms | ||||
|     'xep_0009',  # Jabber-RPC | ||||
| @@ -79,6 +79,7 @@ __all__ = [ | ||||
| #   'xep_0270',  # XMPP Compliance Suites 2010. Don’t 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. Don’t automatically load | ||||
| @@ -100,7 +101,9 @@ __all__ = [ | ||||
|     '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 | ||||
| @@ -113,4 +116,15 @@ __all__ = [ | ||||
|     'xep_0439',  # Quick Response | ||||
|     'xep_0441',  # Message Archive Management Preferences | ||||
|     'xep_0444',  # Message Reactions | ||||
|     'xep_0447',  # Stateless file sharing | ||||
|     'xep_0461',  # Message Replies | ||||
|     # Meant to be imported by plugins | ||||
| ] | ||||
|  | ||||
| __all__ = PLUGINS + [ | ||||
|     'PluginManager', | ||||
|     'PluginNotFound', | ||||
|     'BasePlugin', | ||||
|     'register_plugin', | ||||
|     'load_plugin', | ||||
| ] | ||||
|   | ||||
| @@ -19,6 +19,8 @@ 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 | ||||
|   | ||||
| @@ -307,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): | ||||
|                                    cached=True, callback=None, **iqkwargs): | ||||
|         """Fetch disco#info of specified domain and one disco#items level below | ||||
|         """ | ||||
|  | ||||
| @@ -315,13 +315,13 @@ class XEP_0030(BasePlugin): | ||||
|             domain = self.xmpp.boundjid.domain | ||||
|  | ||||
|         if not cached or domain not in self.domain_infos: | ||||
|             infos = [self.get_info( | ||||
|                 domain, timeout=timeout)] | ||||
|             infos = [asyncio.create_task(self.get_info( | ||||
|                 domain, timeout=timeout, **iqkwargs))] | ||||
|             iq_items = await self.get_items( | ||||
|                 domain, timeout=timeout) | ||||
|                 domain, timeout=timeout, **iqkwargs) | ||||
|             items = iq_items['disco_items']['items'] | ||||
|             infos += [ | ||||
|                 self.get_info(item[0], timeout=timeout) | ||||
|                 asyncio.create_task(self.get_info(item[0], timeout=timeout, **iqkwargs)) | ||||
|                 for item in items] | ||||
|             info_futures, _ = await asyncio.wait( | ||||
|                 infos, | ||||
| @@ -457,9 +457,12 @@ 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(kwargs.get('ifrom', None), jid, items) | ||||
|             return self._wrap(ifrom, jid, items) | ||||
|  | ||||
|         iq = self.xmpp.Iq() | ||||
|         # Check dfrom parameter for backwards compatibility | ||||
|   | ||||
| @@ -323,7 +323,6 @@ 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) | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
| # This file is part of Slixmpp. | ||||
| # See the file LICENSE for copying permission. | ||||
| import asyncio | ||||
| import functools | ||||
| import logging | ||||
| import time | ||||
|  | ||||
| @@ -619,10 +620,16 @@ 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 asyncio.iscoroutinefunction(handler): | ||||
|     if _iscoroutine_or_partial_coroutine(handler): | ||||
|         log.debug(f"%s is async", handler) | ||||
|         return await handler(*args) | ||||
|     else: | ||||
|   | ||||
| @@ -134,8 +134,10 @@ 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 isinstance(vcard, Iq): | ||||
|                 vcard.send() | ||||
|             if vcard is None: | ||||
|                 raise XMPPError("item-not-found") | ||||
|             elif isinstance(vcard, Iq): | ||||
|                 await vcard.send() | ||||
|             else: | ||||
|                 iq = iq.reply() | ||||
|                 iq.append(vcard) | ||||
|   | ||||
| @@ -7,7 +7,8 @@ import logging | ||||
| import hashlib | ||||
| import base64 | ||||
|  | ||||
| from asyncio import Future | ||||
| from asyncio import Future, Lock | ||||
| from collections import defaultdict | ||||
| from typing import Optional | ||||
|  | ||||
| from slixmpp import __version__ | ||||
| @@ -94,6 +95,9 @@ 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) | ||||
| @@ -137,7 +141,7 @@ class XEP_0115(BasePlugin): | ||||
|  | ||||
|         self.xmpp.event('entity_caps', p) | ||||
|  | ||||
|     async def _process_caps(self, pres): | ||||
|     async def _process_caps(self, pres: Presence): | ||||
|         if not pres['caps']['hash']: | ||||
|             log.debug("Received unsupported legacy caps: %s, %s, %s", | ||||
|                     pres['caps']['node'], | ||||
| @@ -147,7 +151,11 @@ 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 | ||||
| @@ -162,7 +170,7 @@ class XEP_0115(BasePlugin): | ||||
|         if pres['caps']['hash'] not in self.hashes: | ||||
|             try: | ||||
|                 log.debug("Unknown caps hash: %s", pres['caps']['hash']) | ||||
|                 self.xmpp['xep_0030'].get_info(jid=pres['from'], ifrom=ifrom) | ||||
|                 await self.xmpp['xep_0030'].get_info(jid=pres['from'], ifrom=ifrom) | ||||
|                 return | ||||
|             except XMPPError: | ||||
|                 return | ||||
|   | ||||
| @@ -15,6 +15,32 @@ 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' | ||||
|   | ||||
							
								
								
									
										6
									
								
								slixmpp/plugins/xep_0234/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								slixmpp/plugins/xep_0234/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
|  | ||||
| from . import stanza | ||||
| from .jingle_file_transfer import XEP_0234 | ||||
|  | ||||
| register_plugin(XEP_0234) | ||||
							
								
								
									
										21
									
								
								slixmpp/plugins/xep_0234/jingle_file_transfer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								slixmpp/plugins/xep_0234/jingle_file_transfer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| 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 | ||||
							
								
								
									
										38
									
								
								slixmpp/plugins/xep_0234/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								slixmpp/plugins/xep_0234/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| 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 | ||||
							
								
								
									
										5
									
								
								slixmpp/plugins/xep_0292/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								slixmpp/plugins/xep_0292/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
|  | ||||
| from . import stanza, vcard4 | ||||
|  | ||||
| register_plugin(vcard4.XEP_0292) | ||||
							
								
								
									
										167
									
								
								slixmpp/plugins/xep_0292/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								slixmpp/plugins/xep_0292/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | ||||
| 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) | ||||
							
								
								
									
										111
									
								
								slixmpp/plugins/xep_0292/vcard4.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								slixmpp/plugins/xep_0292/vcard4.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| 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__) | ||||
| @@ -187,7 +187,7 @@ class Fin(ElementBase): | ||||
|     name = 'fin' | ||||
|     namespace = 'urn:xmpp:mam:2' | ||||
|     plugin_attrib = 'mam_fin' | ||||
|     interfaces = {'results'} | ||||
|     interfaces = {'results', 'stable', 'complete'} | ||||
|  | ||||
|     def setup(self, xml=None): | ||||
|         ElementBase.setup(self, xml) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
|  | ||||
| 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 | ||||
| from . import stanza | ||||
| from .privilege import XEP_0356 | ||||
| from .stanza import Perm, Privilege | ||||
|  | ||||
| register_plugin(XEP_0356) | ||||
|   | ||||
							
								
								
									
										36
									
								
								slixmpp/plugins/xep_0356/permissions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								slixmpp/plugins/xep_0356/permissions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| 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 | ||||
| @@ -1,14 +1,16 @@ | ||||
| import logging | ||||
| import typing | ||||
| import uuid | ||||
| from collections import defaultdict | ||||
|  | ||||
| from slixmpp import Message, JID, Iq | ||||
| from slixmpp import JID, Iq, Message | ||||
| from slixmpp.plugins.base import BasePlugin | ||||
| from slixmpp.xmlstream.matcher import StanzaPath | ||||
| from slixmpp.xmlstream import StanzaBase | ||||
| from slixmpp.xmlstream.handler import Callback | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
|  | ||||
| from slixmpp.plugins.xep_0356 import stanza, Privilege, Perm | ||||
| from slixmpp.xmlstream.matcher import StanzaPath | ||||
|  | ||||
| from . import stanza | ||||
| from .permissions import IqPermission, MessagePermission, Permissions, RosterAccess | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -29,7 +31,7 @@ class XEP_0356(BasePlugin): | ||||
|     dependencies = {"xep_0297"} | ||||
|     stanza = stanza | ||||
|  | ||||
|     granted_privileges = {"roster": "none", "message": "none", "presence": "none"} | ||||
|     granted_privileges = defaultdict(Permissions) | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         if not self.xmpp.is_component: | ||||
| @@ -49,32 +51,42 @@ class XEP_0356(BasePlugin): | ||||
|     def plugin_end(self): | ||||
|         self.xmpp.remove_handler("Privileges") | ||||
|  | ||||
|     def _handle_privilege(self, msg: Message): | ||||
|     def _handle_privilege(self, msg: StanzaBase): | ||||
|         """ | ||||
|         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"]: | ||||
|             self.granted_privileges[perm["access"]] = perm["type"] | ||||
|             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) | ||||
|         log.debug(f"Privileges: {self.granted_privileges}") | ||||
|         self.xmpp.event("privileges_advertised") | ||||
|  | ||||
|     def send_privileged_message(self, msg: Message): | ||||
|         if self.granted_privileges["message"] == "outgoing": | ||||
|             self._make_privileged_message(msg).send() | ||||
|         else: | ||||
|             log.error( | ||||
|         if ( | ||||
|             self.granted_privileges[msg.get_from().domain].message | ||||
|             != MessagePermission.OUTGOING | ||||
|         ): | ||||
|             raise PermissionError( | ||||
|                 "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): | ||||
|         stanza = self.xmpp.make_message( | ||||
|             mto=self.xmpp.server_host, mfrom=self.xmpp.boundjid.bare | ||||
|         ) | ||||
|         stanza["privilege"]["forwarded"].append(msg) | ||||
|         return stanza | ||||
|         server = msg.get_from().domain | ||||
|         wrapped = self.xmpp.make_message(mto=server, mfrom=self.xmpp.boundjid.bare) | ||||
|         wrapped["privilege"]["forwarded"].append(msg) | ||||
|         return wrapped | ||||
|  | ||||
|     def _make_get_roster(self, jid: typing.Union[JID, str], **iq_kwargs): | ||||
|         return self.xmpp.make_iq_get( | ||||
| @@ -106,9 +118,15 @@ class XEP_0356(BasePlugin): | ||||
|  | ||||
|         :param jid: user we want to fetch the roster from | ||||
|         """ | ||||
|         if self.granted_privileges["roster"] not in ("get", "both"): | ||||
|             log.error("The server did not grant us privileges to get rosters") | ||||
|             raise ValueError | ||||
|         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" | ||||
|             ) | ||||
|         else: | ||||
|             return await self._make_get_roster(jid).send(**send_kwargs) | ||||
|  | ||||
| @@ -137,8 +155,56 @@ class XEP_0356(BasePlugin): | ||||
|             }, | ||||
|         } | ||||
|         """ | ||||
|         if self.granted_privileges["roster"] not in ("set", "both"): | ||||
|             log.error("The server did not grant us privileges to set rosters") | ||||
|             raise ValueError | ||||
|         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" | ||||
|             ) | ||||
|         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"} | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| 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 = "urn:xmpp:privilege:2" | ||||
|     namespace = NS | ||||
|     name = "privilege" | ||||
|     plugin_attrib = "privilege" | ||||
|  | ||||
| @@ -25,26 +24,40 @@ class Privilege(ElementBase): | ||||
|     def presence(self): | ||||
|         return self.permission("presence") | ||||
|  | ||||
|     def iq(self): | ||||
|         return self.permission("iq") | ||||
|  | ||||
|     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 = "urn:xmpp:privilege:2" | ||||
|     namespace = NS | ||||
|     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) | ||||
|   | ||||
							
								
								
									
										6
									
								
								slixmpp/plugins/xep_0372/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								slixmpp/plugins/xep_0372/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
|  | ||||
| from . import stanza | ||||
| from .references import XEP_0372 | ||||
|  | ||||
| register_plugin(XEP_0372) | ||||
							
								
								
									
										23
									
								
								slixmpp/plugins/xep_0372/references.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								slixmpp/plugins/xep_0372/references.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| 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) | ||||
							
								
								
									
										9
									
								
								slixmpp/plugins/xep_0372/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								slixmpp/plugins/xep_0372/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| 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"} | ||||
| @@ -26,6 +26,9 @@ 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) | ||||
|   | ||||
| @@ -13,58 +13,23 @@ class Report(ElementBase): | ||||
|     Example sub stanza: | ||||
|     :: | ||||
|  | ||||
|         <report xmlns="urn:xmpp:reporting:0"> | ||||
|         <report xmlns="urn:xmpp:reporting:1" reason="urn:xmpp:reporting:abuse"> | ||||
|           <text xml:lang="en"> | ||||
|             Never came trouble to my house like this. | ||||
|           </text> | ||||
|           <spam/> | ||||
|         </report> | ||||
|  | ||||
|     Stanza Interface: | ||||
|     :: | ||||
|     The reason attribute is mandatory. | ||||
|  | ||||
|         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:0" | ||||
|     namespace = "urn:xmpp:reporting:1" | ||||
|     plugin_attrib = "report" | ||||
|     interfaces = ("spam", "abuse", "text") | ||||
|     interfaces = ("text", "reason") | ||||
|     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:0" | ||||
|     namespace = "urn:xmpp:reporting:1" | ||||
|   | ||||
							
								
								
									
										11
									
								
								slixmpp/plugins/xep_0385/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								slixmpp/plugins/xep_0385/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
|  | ||||
| # 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) | ||||
							
								
								
									
										66
									
								
								slixmpp/plugins/xep_0385/sims.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								slixmpp/plugins/xep_0385/sims.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| 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 | ||||
							
								
								
									
										14
									
								
								slixmpp/plugins/xep_0385/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								slixmpp/plugins/xep_0385/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| from slixmpp.xmlstream import ElementBase | ||||
|  | ||||
| NAMESPACE = "urn:xmpp:sims:1" | ||||
|  | ||||
|  | ||||
| class Sims(ElementBase): | ||||
|     name = "media-sharing" | ||||
|     plugin_attrib = "sims" | ||||
|     namespace = NAMESPACE | ||||
|  | ||||
|  | ||||
| class Sources(ElementBase): | ||||
|     name = plugin_attrib = "sources" | ||||
|     namespace = NAMESPACE | ||||
							
								
								
									
										6
									
								
								slixmpp/plugins/xep_0402/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								slixmpp/plugins/xep_0402/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
|  | ||||
| from . import stanza | ||||
| from .bookmarks import XEP_0402 | ||||
|  | ||||
| register_plugin(XEP_0402) | ||||
							
								
								
									
										18
									
								
								slixmpp/plugins/xep_0402/bookmarks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								slixmpp/plugins/xep_0402/bookmarks.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| from slixmpp.plugins import BasePlugin | ||||
|  | ||||
| from . import stanza | ||||
|  | ||||
|  | ||||
| class XEP_0402(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0402: PEP Native bookmarks | ||||
|     """ | ||||
|  | ||||
|     name = "xep_0402" | ||||
|     description = "XEP-0402: PEP Native bookmarks" | ||||
|     dependencies = {"xep_0402"} | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         stanza.register_plugin() | ||||
							
								
								
									
										33
									
								
								slixmpp/plugins/xep_0402/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								slixmpp/plugins/xep_0402/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| from slixmpp import register_stanza_plugin | ||||
| from slixmpp.plugins.xep_0060.stanza import Item | ||||
| from slixmpp.xmlstream import ElementBase | ||||
|  | ||||
| NS = "urn:xmpp:bookmarks:1" | ||||
|  | ||||
|  | ||||
| class Conference(ElementBase): | ||||
|     namespace = NS | ||||
|     name = "conference" | ||||
|     plugin_attrib = "conference" | ||||
|     interfaces = {"name", "autojoin", "nick", "password"} | ||||
|     sub_interfaces = {"nick", "password"} | ||||
|  | ||||
|     def set_autojoin(self, v: bool): | ||||
|         self._set_attr("autojoin", "true" if v else "false") | ||||
|  | ||||
|     def get_autojoin(self): | ||||
|         v = self._get_attr("autojoin", "") | ||||
|         if not v: | ||||
|             return False | ||||
|         return v == "1" or v.lower() == "true" | ||||
|  | ||||
|  | ||||
| class Extensions(ElementBase): | ||||
|     namespace = NS | ||||
|     name = "extensions" | ||||
|     plugin_attrib = "extensions" | ||||
|  | ||||
|  | ||||
| def register_plugin(): | ||||
|     register_stanza_plugin(Conference, Extensions) | ||||
|     register_stanza_plugin(Item, Conference) | ||||
| @@ -1,8 +1,13 @@ | ||||
|  | ||||
| # Slixmpp: The Slick XMPP Library | ||||
| # Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net> | ||||
| # This file is part of Slixmpp. | ||||
| # See the file LICENSE for copying permissio | ||||
| from abc import ABC | ||||
| try: | ||||
|     from typing import Literal | ||||
| except ImportError: | ||||
|     from typing_extensions import Literal | ||||
|  | ||||
| from slixmpp.stanza import Message | ||||
| from slixmpp.xmlstream import ( | ||||
|     ElementBase, | ||||
| @@ -10,14 +15,83 @@ from slixmpp.xmlstream import ( | ||||
| ) | ||||
|  | ||||
|  | ||||
| NS = 'urn:xmpp:fallback:0' | ||||
| NS = "urn:xmpp:fallback:0" | ||||
|  | ||||
|  | ||||
| class Fallback(ElementBase): | ||||
|     namespace = NS | ||||
|     name = 'fallback' | ||||
|     plugin_attrib = 'fallback' | ||||
|     name = "fallback" | ||||
|     plugin_attrib = "fallback" | ||||
|     plugin_multi_attrib = "fallbacks" | ||||
|     interfaces = {"for"} | ||||
|  | ||||
|     def _find_fallback(self, fallback_for: str) -> "Fallback": | ||||
|         if self["for"] == fallback_for: | ||||
|             return self | ||||
|         for fallback in self.parent()["fallbacks"]: | ||||
|             if fallback["for"] == fallback_for: | ||||
|                 return fallback | ||||
|         raise AttributeError("No fallback for this namespace", fallback_for) | ||||
|  | ||||
|     def get_stripped_body( | ||||
|         self, fallback_for: str, element: Literal["body", "subject"] = "body" | ||||
|     ) -> str: | ||||
|         """ | ||||
|         Get the body of a message, with the fallback part stripped | ||||
|  | ||||
|         :param fallback_for: namespace of the fallback to strip | ||||
|         :param element: set this to "subject" get the stripped subject instead | ||||
|             of body | ||||
|  | ||||
|         :return: body (or subject) content minus the fallback part | ||||
|         """ | ||||
|         fallback = self._find_fallback(fallback_for) | ||||
|         start = fallback[element]["start"] | ||||
|         end = fallback[element]["end"] | ||||
|         body = self.parent()[element] | ||||
|         if start == end == 0: | ||||
|             return "" | ||||
|         if start <= end < len(body): | ||||
|             return body[:start] + body[end:] | ||||
|         else: | ||||
|             return body | ||||
|  | ||||
|  | ||||
| class FallbackMixin(ABC): | ||||
|     namespace = NS | ||||
|     name = NotImplemented | ||||
|     plugin_attrib = NotImplemented | ||||
|     interfaces = {"start", "end"} | ||||
|  | ||||
|     def set_start(self, v: int): | ||||
|         self._set_attr("start", str(v)) | ||||
|  | ||||
|     def get_start(self): | ||||
|         return _int_or_zero(self._get_attr("start")) | ||||
|  | ||||
|     def set_end(self, v: int): | ||||
|         self._set_attr("end", str(v)) | ||||
|  | ||||
|     def get_end(self): | ||||
|         return _int_or_zero(self._get_attr("end")) | ||||
|  | ||||
|  | ||||
| class FallbackBody(FallbackMixin, ElementBase): | ||||
|     name = plugin_attrib = "body" | ||||
|  | ||||
|  | ||||
| class FallbackSubject(FallbackMixin, ElementBase): | ||||
|     name = plugin_attrib = "subject" | ||||
|  | ||||
|  | ||||
| def _int_or_zero(v: str): | ||||
|     try: | ||||
|         return int(v) | ||||
|     except ValueError: | ||||
|         return 0 | ||||
|  | ||||
|  | ||||
| def register_plugins(): | ||||
|     register_stanza_plugin(Message, Fallback) | ||||
|     register_stanza_plugin(Message, Fallback, iterable=True) | ||||
|     register_stanza_plugin(Fallback, FallbackBody) | ||||
|     register_stanza_plugin(Fallback, FallbackSubject) | ||||
|   | ||||
							
								
								
									
										6
									
								
								slixmpp/plugins/xep_0446/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								slixmpp/plugins/xep_0446/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| from slixmpp.plugins.base import register_plugin | ||||
|  | ||||
| from . import stanza | ||||
| from .file_metadata import XEP_0446 | ||||
|  | ||||
| register_plugin(XEP_0446) | ||||
							
								
								
									
										20
									
								
								slixmpp/plugins/xep_0446/file_metadata.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								slixmpp/plugins/xep_0446/file_metadata.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| import logging | ||||
|  | ||||
| from slixmpp.plugins import BasePlugin | ||||
|  | ||||
| from . import stanza | ||||
|  | ||||
| log = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class XEP_0446(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0446: File metadata element | ||||
|  | ||||
|     Minimum needed for xep 0447 (Stateless file sharing) | ||||
|     """ | ||||
|  | ||||
|     name = "xep_0446" | ||||
|     description = "XEP-0446: File metadata element" | ||||
|     stanza = stanza | ||||
							
								
								
									
										38
									
								
								slixmpp/plugins/xep_0446/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								slixmpp/plugins/xep_0446/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| from datetime import datetime | ||||
|  | ||||
| from slixmpp.plugins.xep_0082 import format_datetime, parse | ||||
| from slixmpp.xmlstream import ElementBase | ||||
|  | ||||
| NS = "urn:xmpp:file:metadata:0" | ||||
|  | ||||
|  | ||||
| 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 | ||||
							
								
								
									
										11
									
								
								slixmpp/plugins/xep_0447/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								slixmpp/plugins/xep_0447/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
|  | ||||
| # 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 .sfs import XEP_0447 | ||||
|  | ||||
| register_plugin(XEP_0447) | ||||
							
								
								
									
										64
									
								
								slixmpp/plugins/xep_0447/sfs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								slixmpp/plugins/xep_0447/sfs.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| 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_0447(BasePlugin): | ||||
|  | ||||
|     """ | ||||
|     XEP-0447: Stateless File Sharing | ||||
|  | ||||
|     Only support outgoing SFS, incoming is not handled at all. | ||||
|     """ | ||||
|  | ||||
|     name = "xep_0447" | ||||
|     description = "XEP-0447: Stateless File Sharing" | ||||
|     dependencies = {"xep_0300", "xep_0446"} | ||||
|     stanza = stanza | ||||
|  | ||||
|     def plugin_init(self): | ||||
|         register_stanza_plugin(Message, stanza.StatelessFileSharing) | ||||
|  | ||||
|         register_stanza_plugin(stanza.StatelessFileSharing, stanza.Sources) | ||||
|         register_stanza_plugin( | ||||
|             stanza.StatelessFileSharing, self.xmpp["xep_0446"].stanza.File | ||||
|         ) | ||||
|         register_stanza_plugin(stanza.Sources, stanza.UrlData, iterable=True) | ||||
|  | ||||
|     def get_sfs( | ||||
|         self, | ||||
|         path: Path, | ||||
|         uris: Iterable[str], | ||||
|         media_type: Optional[str], | ||||
|         desc: Optional[str], | ||||
|     ): | ||||
|         sfs = stanza.StatelessFileSharing() | ||||
|         sfs["disposition"] = "inline" | ||||
|         for uri in uris: | ||||
|             ref = stanza.UrlData() | ||||
|             ref["target"] = uri | ||||
|             sfs["sources"].append(ref) | ||||
|         if media_type: | ||||
|             sfs["file"]["media-type"] = media_type | ||||
|         if desc: | ||||
|             sfs["file"]["desc"] = desc | ||||
|         sfs["file"]["name"] = path.name | ||||
|  | ||||
|         stat = path.stat() | ||||
|         sfs["file"]["size"] = stat.st_size | ||||
|         sfs["file"]["date"] = datetime.fromtimestamp(stat.st_mtime) | ||||
|  | ||||
|         h = self.xmpp.plugin["xep_0300"].compute_hash(path) | ||||
|         h["value"] = h["value"].decode() | ||||
|         sfs["file"].append(h) | ||||
|  | ||||
|         return sfs | ||||
							
								
								
									
										21
									
								
								slixmpp/plugins/xep_0447/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								slixmpp/plugins/xep_0447/stanza.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| from slixmpp.xmlstream import ElementBase | ||||
|  | ||||
| NAMESPACE = "urn:xmpp:sfs:0" | ||||
|  | ||||
|  | ||||
| class StatelessFileSharing(ElementBase): | ||||
|     name = "file-sharing" | ||||
|     plugin_attrib = "sfs" | ||||
|     namespace = NAMESPACE | ||||
|     interfaces = {"disposition"} | ||||
|  | ||||
|  | ||||
| class Sources(ElementBase): | ||||
|     name = plugin_attrib = "sources" | ||||
|     namespace = NAMESPACE | ||||
|  | ||||
|  | ||||
| class UrlData(ElementBase): | ||||
|     name = plugin_attrib = "url-data" | ||||
|     namespace = "http://jabber.org/protocol/url-data" | ||||
|     interfaces = {"target"} | ||||
| @@ -13,7 +13,7 @@ class XEP_0461(BasePlugin): | ||||
|     name = "xep_0461" | ||||
|     description = "XEP-0461: Message Replies" | ||||
|  | ||||
|     dependencies = {"xep_0030"} | ||||
|     dependencies = {"xep_0030", "xep_0428"} | ||||
|     stanza = stanza | ||||
|     namespace = stanza.NS | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| from slixmpp.stanza import Message | ||||
| from slixmpp.xmlstream import ElementBase, register_stanza_plugin | ||||
| from slixmpp.plugins.xep_0428.stanza import Fallback | ||||
|  | ||||
| NS = "urn:xmpp:reply:0" | ||||
|  | ||||
| @@ -10,38 +13,44 @@ class Reply(ElementBase): | ||||
|     plugin_attrib = "reply" | ||||
|     interfaces = {"id", "to"} | ||||
|  | ||||
|     def add_quoted_fallback(self, fallback: str, nickname: Optional[str] = None): | ||||
|         """ | ||||
|         Add plain text fallback for clients not implementing XEP-0461. | ||||
|  | ||||
| class FeatureFallBack(ElementBase): | ||||
|     # should also be a multi attrib | ||||
|     namespace = "urn:xmpp:feature-fallback:0" | ||||
|     name = "fallback" | ||||
|     plugin_attrib = "feature_fallback" | ||||
|     interfaces = {"for"} | ||||
|         ``msg["reply"].add_quoted_fallback("Some text", "Bob")`` will | ||||
|         prepend "> Bob:\n> Some text\n" to the body of the message, and set the | ||||
|         fallback_body attributes accordingly, so that clients implementing | ||||
|         XEP-0461 can hide the fallback text. | ||||
|  | ||||
|     def get_stripped_body(self): | ||||
|         # only works for a single fallback_body attrib | ||||
|         start = self["fallback_body"]["start"] | ||||
|         end = self["fallback_body"]["end"] | ||||
|         body = self.parent()["body"] | ||||
|         try: | ||||
|             start = int(start) | ||||
|             end = int(end) | ||||
|         except ValueError: | ||||
|             return body | ||||
|         :param fallback: Body of the quoted message. | ||||
|         :param nickname: Optional, nickname of the quoted participant. | ||||
|         """ | ||||
|         msg = self.parent() | ||||
|         quoted = "\n".join("> " + x.strip() for x in fallback.split("\n")) + "\n" | ||||
|         if nickname: | ||||
|             quoted = "> " + nickname + ":\n" + quoted | ||||
|         msg["body"] = quoted + msg["body"] | ||||
|         fallback = Fallback() | ||||
|         fallback["for"] = NS | ||||
|         fallback["body"]["start"] = 0 | ||||
|         fallback["body"]["end"] = len(quoted) | ||||
|         msg.append(fallback) | ||||
|  | ||||
|     def get_fallback_body(self) -> str: | ||||
|         msg = self.parent() | ||||
|         for fallback in msg["fallbacks"]: | ||||
|             if fallback["for"] == NS: | ||||
|                 break | ||||
|         else: | ||||
|             return body[:start] + body[end:] | ||||
|  | ||||
|  | ||||
| class FallBackBody(ElementBase): | ||||
|     # According to https://xmpp.org/extensions/inbox/compatibility-fallback.html | ||||
|     # this should be a multi_attrib *but* since it's a protoXEP, we'll see... | ||||
|     namespace = FeatureFallBack.namespace | ||||
|     name = "body" | ||||
|     plugin_attrib = "fallback_body" | ||||
|     interfaces = {"start", "end"} | ||||
|             return "" | ||||
|         start = fallback["body"]["start"] | ||||
|         end = fallback["body"]["end"] | ||||
|         body = msg["body"] | ||||
|         if start <= end: | ||||
|             return body[start:end] | ||||
|         else: | ||||
|             return "" | ||||
|  | ||||
|  | ||||
| def register_plugins(): | ||||
|     register_stanza_plugin(Message, Reply) | ||||
|     register_stanza_plugin(Message, FeatureFallBack) | ||||
|     register_stanza_plugin(FeatureFallBack, FallBackBody) | ||||
|   | ||||
| @@ -176,7 +176,7 @@ class Message(RootStanza): | ||||
|         """ | ||||
|         new_message = StanzaBase.reply(self, clear) | ||||
|  | ||||
|         if self['type'] == 'groupchat': | ||||
|         if not getattr(self.stream, "is_component", False) and self['type'] == 'groupchat': | ||||
|             new_message['to'] = new_message['to'].bare | ||||
|  | ||||
|         new_message['thread'] = self['thread'] | ||||
|   | ||||
| @@ -63,6 +63,8 @@ class RootStanza(StanzaBase): | ||||
|             reply['error']['condition'] = e.condition | ||||
|             reply['error']['text'] = e.text | ||||
|             reply['error']['type'] = e.etype | ||||
|             if e.by: | ||||
|                 reply["error"]["by"] = e.by | ||||
|             if e.extension is not None: | ||||
|                 # Extended error tag | ||||
|                 extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| # Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout | ||||
| # This file is part of Slixmpp. | ||||
| # See the file LICENSE for copying permission. | ||||
| import atexit | ||||
| import unittest | ||||
| from queue import Queue | ||||
| from xml.parsers.expat import ExpatError | ||||
| @@ -10,11 +11,13 @@ from xml.parsers.expat import ExpatError | ||||
| from slixmpp.test import TestTransport | ||||
| from slixmpp import ClientXMPP, ComponentXMPP | ||||
| from slixmpp.stanza import Message, Iq, Presence | ||||
| from slixmpp.stanza.error import Error | ||||
| from slixmpp.xmlstream import ET | ||||
| from slixmpp.xmlstream import ElementBase | ||||
| from slixmpp.xmlstream.tostring import tostring, highlight | ||||
| from slixmpp.xmlstream.matcher import StanzaPath, MatcherId, MatchIDSender | ||||
| from slixmpp.xmlstream.matcher import MatchXMLMask, MatchXPath | ||||
| from slixmpp.xmlstream.stanzabase import register_stanza_plugin | ||||
|  | ||||
| import asyncio | ||||
|  | ||||
| @@ -322,6 +325,7 @@ class SlixTest(unittest.TestCase): | ||||
|         if not plugin_config: | ||||
|             plugin_config = {} | ||||
|  | ||||
|         self.mode = mode | ||||
|         if mode == 'client': | ||||
|             self.xmpp = ClientXMPP(jid, password, | ||||
|                                    sasl_mech=sasl_mech, | ||||
| @@ -740,3 +744,16 @@ class SlixTest(unittest.TestCase): | ||||
|  | ||||
|         # Everything matches | ||||
|         return True | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|         if getattr(self, "mode", None) == "component": | ||||
|             Error.namespace = 'jabber:client' | ||||
|             for st in Message, Iq, Presence: | ||||
|                 register_stanza_plugin(st, Error) | ||||
|  | ||||
|  | ||||
| @atexit.register | ||||
| def cleanup(): | ||||
|     loop = asyncio.get_event_loop() | ||||
|     loop.close() | ||||
|   | ||||
| @@ -83,8 +83,35 @@ MAMDefault = Literal['always', 'never', 'roster'] | ||||
|  | ||||
| FilterString = Literal['in', 'out', 'out_sync'] | ||||
|  | ||||
| __all__ = [ | ||||
|     'Protocol', 'TypedDict', 'Literal', 'OptJid', 'JidStr', 'MAMDefault', | ||||
|     'PresenceTypes', 'PresenceShows', 'MessageTypes', 'IqTypes', 'MucRole', | ||||
|     'MucAffiliation', 'FilterString', | ||||
| ErrorTypes = Literal["modify", "cancel", "auth", "wait", "cancel"] | ||||
|  | ||||
| ErrorConditions = Literal[ | ||||
|     "bad-request", | ||||
|     "conflict", | ||||
|     "feature-not-implemented", | ||||
|     "forbidden", | ||||
|     "gone", | ||||
|     "internal-server-error", | ||||
|     "item-not-found", | ||||
|     "jid-malformed", | ||||
|     "not-acceptable", | ||||
|     "not-allowed", | ||||
|     "not-authorized", | ||||
|     "payment-required", | ||||
|     "recipient-unavailable", | ||||
|     "redirect", | ||||
|     "registration-required", | ||||
|     "remote-server-not-found", | ||||
|     "remote-server-timeout", | ||||
|     "resource-constraint", | ||||
|     "service-unavailable", | ||||
|     "subscription-required", | ||||
|     "undefined-condition", | ||||
|     "unexpected-request", | ||||
| ] | ||||
|  | ||||
| __all__ = [ | ||||
|     'Protocol', 'TypedDict', 'Literal', 'OptJid', 'OptJidStr', 'JidStr', 'MAMDefault', | ||||
|     'PresenceTypes', 'PresenceShows', 'MessageTypes', 'IqTypes', 'MucRole', | ||||
|     'MucAffiliation', 'FilterString', 'ErrorConditions', 'ErrorTypes' | ||||
| ] | ||||
|   | ||||
| @@ -181,7 +181,7 @@ class SCRAM(Mech): | ||||
|     channel_binding = True | ||||
|     required_credentials = {'username', 'password'} | ||||
|     optional_credentials = {'authzid', 'channel_binding'} | ||||
|     security = {'encrypted', 'unencrypted_scram'} | ||||
|     security = {'tls_version', 'encrypted', 'unencrypted_scram', 'binding_proposed'} | ||||
|  | ||||
|     def setup(self, name): | ||||
|         self.use_channel_binding = False | ||||
| @@ -244,11 +244,15 @@ class SCRAM(Mech): | ||||
|         self.cnonce = bytes(('%s' % random.random())[2:]) | ||||
|  | ||||
|         gs2_cbind_flag = b'n' | ||||
|         if self.credentials['channel_binding']: | ||||
|             if self.use_channel_binding: | ||||
|                 gs2_cbind_flag = b'p=tls-unique' | ||||
|             else: | ||||
|                 gs2_cbind_flag = b'y' | ||||
|         if self.security_settings['binding_proposed']: | ||||
|             if self.credentials['channel_binding'] and \ | ||||
|                     self.use_channel_binding: | ||||
|                 if self.security_settings['tls_version'] != 'TLSv1.3': | ||||
|                     gs2_cbind_flag = b'p=tls-unique' | ||||
|                 else: | ||||
|                     gs2_cbind_flag = b'p=tls-exporter' | ||||
|         else: | ||||
|             gs2_cbind_flag = b'y' | ||||
|  | ||||
|         authzid = b'' | ||||
|         if self.credentials['authzid']: | ||||
| @@ -280,7 +284,7 @@ class SCRAM(Mech): | ||||
|             raise SASLCancelled('Invalid nonce') | ||||
|  | ||||
|         cbind_data = b'' | ||||
|         if self.use_channel_binding: | ||||
|         if self.use_channel_binding and self.credentials['channel_binding']: | ||||
|             cbind_data = self.credentials['channel_binding'] | ||||
|         cbind_input = self.gs2_header + cbind_data | ||||
|         channel_binding = b'c=' + b64encode(cbind_input).replace(b'\n', b'') | ||||
|   | ||||
| @@ -5,5 +5,5 @@ | ||||
| # We don't want to have to import the entire library | ||||
| # just to get the version info for setup.py | ||||
|  | ||||
| __version__ = '1.8.3' | ||||
| __version_info__ = (1, 8, 3) | ||||
| __version__ = '1.8.5' | ||||
| __version_info__ = (1, 8, 5) | ||||
|   | ||||
| @@ -10,5 +10,5 @@ from slixmpp.xmlstream.tostring import tostring, highlight | ||||
| from slixmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT | ||||
|  | ||||
| __all__ = ['JID', 'StanzaBase', 'ElementBase', | ||||
|            'ET', 'StateMachine', 'tostring', 'highlight', 'XMLStream', | ||||
|            'RESPONSE_TIMEOUT'] | ||||
|            'ET', 'tostring', 'highlight', 'XMLStream', | ||||
|            'RESPONSE_TIMEOUT', 'register_stanza_plugin'] | ||||
|   | ||||
| @@ -1243,7 +1243,7 @@ class ElementBase(object): | ||||
|                 self.init_plugin(item.__class__.plugin_multi_attrib) | ||||
|         else: | ||||
|             self.iterables.append(item) | ||||
|  | ||||
|         item.parent = weakref.ref(self) | ||||
|         return self | ||||
|  | ||||
|     def appendxml(self, xml: ET.Element) -> ElementBase: | ||||
|   | ||||
| @@ -290,8 +290,8 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|         self.xml_depth = 0 | ||||
|         self.xml_root = None | ||||
|  | ||||
|         self.force_starttls = None | ||||
|         self.disable_starttls = None | ||||
|         self.force_starttls = True | ||||
|         self.disable_starttls = False | ||||
|  | ||||
|         self.waiting_queue = asyncio.Queue() | ||||
|  | ||||
| @@ -405,8 +405,9 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|             self.disconnected.set_result(True) | ||||
|         self.disconnected = asyncio.Future() | ||||
|  | ||||
|     def connect(self, host: str = '', port: int = 0, use_ssl: Optional[bool] = False, | ||||
|                 force_starttls: Optional[bool] = True, disable_starttls: Optional[bool] = False) -> None: | ||||
|     def connect(self, host: str = '', port: int = 0, use_ssl: Optional[bool] = None, | ||||
|                 force_starttls: Optional[bool] = None, | ||||
|                 disable_starttls: Optional[bool] = None) -> None: | ||||
|         """Create a new socket and connect to the server. | ||||
|  | ||||
|         :param host: The name of the desired server for the connection. | ||||
| @@ -523,7 +524,7 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|             else: | ||||
|                 self.loop.run_until_complete(self.disconnected) | ||||
|         else: | ||||
|             tasks: List[Awaitable] = [asyncio.sleep(timeout)] | ||||
|             tasks: List[Awaitable] = [asyncio.Task(asyncio.sleep(timeout))] | ||||
|             if not forever: | ||||
|                 tasks.append(self.disconnected) | ||||
|             self.loop.run_until_complete(asyncio.wait(tasks)) | ||||
| @@ -1339,6 +1340,8 @@ class XMLStream(asyncio.BaseProtocol): | ||||
|                     passthrough = True | ||||
|                 elif data.get_plugin('session', check=True): | ||||
|                     passthrough = True | ||||
|                 elif data.get_plugin('register', check=True): | ||||
|                     passthrough = True | ||||
|             elif isinstance(data, Handshake): | ||||
|                 passthrough = True | ||||
|  | ||||
|   | ||||
| @@ -8,9 +8,6 @@ class TestLiveStream(SlixTest): | ||||
|     Test that we can test a live stanza stream. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testClientConnection(self): | ||||
|         """Test that we can interact with a live ClientXMPP instance.""" | ||||
|         self.stream_start(mode='client', | ||||
|   | ||||
| @@ -8,9 +8,6 @@ class TestEvents(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testEventHappening(self): | ||||
|         """Test handler working""" | ||||
|         happened = [] | ||||
|   | ||||
| @@ -5,10 +5,6 @@ from slixmpp.xmlstream.stanzabase import ET | ||||
|  | ||||
| class TestIqStanzas(SlixTest): | ||||
|  | ||||
|     def tearDown(self): | ||||
|         """Shutdown the XML stream after testing.""" | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testSetup(self): | ||||
|         """Test initializing default Iq values.""" | ||||
|         iq = self.Iq() | ||||
|   | ||||
							
								
								
									
										121
									
								
								tests/test_stanza_xep_0292.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								tests/test_stanza_xep_0292.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| import datetime | ||||
| import unittest | ||||
|  | ||||
| from slixmpp import Iq | ||||
| from slixmpp.test import SlixTest | ||||
|  | ||||
| from slixmpp.plugins.xep_0292 import stanza | ||||
|  | ||||
|  | ||||
| REF = """ | ||||
| <iq> | ||||
|     <vcard xmlns='urn:ietf:params:xml:ns:vcard-4.0'> | ||||
|         <fn> | ||||
|             <text>Full Name</text> | ||||
|         </fn> | ||||
|         <n><given>Full</given><surname>Name</surname></n> | ||||
|         <nickname> | ||||
|             <text>some nick</text> | ||||
|         </nickname> | ||||
|         <bday> | ||||
|             <date>1984-05-21</date> | ||||
|         </bday> | ||||
|         <url> | ||||
|             <uri>https://nicoco.fr</uri> | ||||
|         </url> | ||||
|         <note> | ||||
|             <text>About me</text> | ||||
|         </note> | ||||
|         <impp> | ||||
|             <uri>xmpp:test@localhost</uri> | ||||
|         </impp> | ||||
|         <email> | ||||
|             <text>test@gmail.com</text> | ||||
|         </email> | ||||
|         <tel> | ||||
|             <parameters> | ||||
|                 <type><text>work</text></type> | ||||
|             </parameters> | ||||
|             <uri>tel:+555</uri> | ||||
|         </tel> | ||||
|         <adr> | ||||
|             <locality>Nice</locality> | ||||
|             <country>France</country> | ||||
|         </adr> | ||||
|     </vcard> | ||||
| </iq> | ||||
| """ | ||||
|  | ||||
|  | ||||
| class TestVcard(SlixTest): | ||||
|     def test_basic_interfaces(self): | ||||
|         iq = Iq() | ||||
|         x = iq["vcard"] | ||||
|  | ||||
|         x["fn"]["text"] = "Full Name" | ||||
|         x["nickname"]["text"] = "some nick" | ||||
|         x["n"]["given"] = "Full" | ||||
|         x["n"]["surname"] = "Name" | ||||
|         x["bday"]["date"] = datetime.date(1984, 5, 21) | ||||
|         x["note"]["text"] = "About me" | ||||
|         x["url"]["uri"] = "https://nicoco.fr" | ||||
|         x["impp"]["uri"] = "xmpp:test@localhost" | ||||
|         x["email"]["text"] = "test@gmail.com" | ||||
|  | ||||
|         x["tel"]["uri"] = "tel:+555" | ||||
|         x["tel"]["parameters"]["type_"]["text"] = "work" | ||||
|         x["adr"]["locality"] = "Nice" | ||||
|         x["adr"]["country"] = "France" | ||||
|  | ||||
|         self.check(iq, REF, use_values=False) | ||||
|  | ||||
|     def test_easy_interface(self): | ||||
|         iq = Iq() | ||||
|         x: stanza.VCard4 = iq["vcard"] | ||||
|  | ||||
|         x["full_name"] = "Full Name" | ||||
|         x["given"] = "Full" | ||||
|         x["surname"] = "Name" | ||||
|         x["birthday"] = datetime.date(1984, 5, 21) | ||||
|         x.add_nickname("some nick") | ||||
|         x.add_note("About me") | ||||
|         x.add_url("https://nicoco.fr") | ||||
|         x.add_impp("xmpp:test@localhost") | ||||
|         x.add_email("test@gmail.com") | ||||
|         x.add_tel("+555", "work") | ||||
|         x.add_address("France", "Nice") | ||||
|  | ||||
|         self.check(iq, REF, use_values=False) | ||||
|  | ||||
|     def test_2_phones(self): | ||||
|         vcard = stanza.VCard4() | ||||
|         tel1 = stanza.Tel() | ||||
|         tel1["parameters"]["type_"]["text"] = "work" | ||||
|         tel1["uri"] = "tel:+555" | ||||
|         tel2 = stanza.Tel() | ||||
|         tel2["parameters"]["type_"]["text"] = "devil" | ||||
|         tel2["uri"] = "tel:+666" | ||||
|         vcard.append(tel1) | ||||
|         vcard.append(tel2) | ||||
|         self.check( | ||||
|             vcard, | ||||
|             """ | ||||
|             <vcard xmlns='urn:ietf:params:xml:ns:vcard-4.0'> | ||||
|                 <tel> | ||||
|                     <parameters> | ||||
|                         <type><text>work</text></type> | ||||
|                     </parameters> | ||||
|                     <uri>tel:+555</uri> | ||||
|                 </tel> | ||||
|                 <tel> | ||||
|                     <parameters> | ||||
|                         <type><text>devil</text></type> | ||||
|                     </parameters> | ||||
|                     <uri>tel:+666</uri> | ||||
|                 </tel> | ||||
|             </vcard> | ||||
|             """, | ||||
|             use_values=False | ||||
|         ) | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestVcard) | ||||
| @@ -1,9 +1,7 @@ | ||||
| import unittest | ||||
| from slixmpp import Message | ||||
| from slixmpp.test import SlixTest | ||||
| from slixmpp.xmlstream import register_stanza_plugin | ||||
|  | ||||
| from slixmpp.plugins.xep_0356 import stanza | ||||
| from slixmpp.plugins.xep_0356 import stanza, permissions | ||||
|  | ||||
|  | ||||
| class TestPermissions(SlixTest): | ||||
| @@ -12,30 +10,57 @@ class TestPermissions(SlixTest): | ||||
|  | ||||
|     def testAdvertisePermission(self): | ||||
|         xmlstring = """ | ||||
|             <message from='capulet.net' to='pubub.capulet.lit'> | ||||
|             <message from='capulet.lit' to='pubsub.capulet.lit'> | ||||
|                 <privilege xmlns='urn:xmpp:privilege:2'> | ||||
|                     <perm access='roster' type='both'/> | ||||
|                     <perm access='message' type='outgoing'/> | ||||
|                     <perm access='presence' type='managed_entity'/> | ||||
|                     <perm access='iq' type='both'/> | ||||
|                 </privilege> | ||||
|             </message> | ||||
|         """ | ||||
|         msg = self.Message() | ||||
|         msg["from"] = "capulet.net" | ||||
|         msg["to"] = "pubub.capulet.lit" | ||||
|         # This raises AttributeError: 'NoneType' object has no attribute 'use_origin_id' | ||||
|         # msg["id"] = "id" | ||||
|         msg["from"] = "capulet.lit" | ||||
|         msg["to"] = "pubsub.capulet.lit" | ||||
|  | ||||
|         for access, type_ in [ | ||||
|             ("roster", "both"), | ||||
|             ("message", "outgoing"), | ||||
|             ("presence", "managed_entity"), | ||||
|             ("roster", permissions.RosterAccess.BOTH), | ||||
|             ("message", permissions.MessagePermission.OUTGOING), | ||||
|             ("presence", permissions.PresencePermission.MANAGED_ENTITY), | ||||
|             ("iq", permissions.IqPermission.BOTH), | ||||
|         ]: | ||||
|             msg["privilege"].add_perm(access, type_) | ||||
|  | ||||
|         self.check(msg, xmlstring) | ||||
|         # Should this one work? →        # AttributeError: 'Message' object has no attribute 'permission' | ||||
|         # self.assertEqual(msg.permission["roster"], "both") | ||||
|  | ||||
|     def testIqPermission(self): | ||||
|         x = stanza.Privilege() | ||||
|         x["access"] = "iq" | ||||
|         ns = stanza.NameSpace() | ||||
|         ns["ns"] = "some_ns" | ||||
|         ns["type"] = "get" | ||||
|         x["perm"]["access"] = "iq" | ||||
|         x["perm"].append(ns) | ||||
|         ns = stanza.NameSpace() | ||||
|         ns["ns"] = "some_other_ns" | ||||
|         ns["type"] = "both" | ||||
|         x["perm"].append(ns) | ||||
|         self.check( | ||||
|             x, | ||||
|             """ | ||||
|               <privilege xmlns='urn:xmpp:privilege:2'> | ||||
|                 <perm access='iq'> | ||||
|                   <namespace ns='some_ns' type='get' /> | ||||
|                   <namespace ns='some_other_ns' type='both' /> | ||||
|                 </perm> | ||||
|               </privilege> | ||||
|             """ | ||||
|         ) | ||||
|         nss = set() | ||||
|         for perm in x["perms"]: | ||||
|             for ns in perm["namespaces"]: | ||||
|                 nss.add((ns["ns"], ns["type"])) | ||||
|         assert nss == {("some_ns", "get"), ("some_other_ns", "both")} | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestPermissions) | ||||
|   | ||||
| @@ -23,34 +23,30 @@ class TestSpamReporting(SlixTest): | ||||
|         report = """ | ||||
|           <iq type="set"> | ||||
|             <block xmlns="urn:xmpp:blocking"> | ||||
|                 <report xmlns="urn:xmpp:reporting:0"> | ||||
|                     <spam/> | ||||
|                 </report> | ||||
|                 <report xmlns="urn:xmpp:reporting:1" reason="urn:xmpp:reporting:spam"/> | ||||
|             </block> | ||||
|           </iq> | ||||
|         """ | ||||
|  | ||||
|         iq = self.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['block']['report']['spam'] = True | ||||
|         iq['block']['report']['reason'] = xep_0377.XEP_0377.SPAM | ||||
|  | ||||
|         self.check(iq, report) | ||||
|         self.check(iq, report, use_values=False) | ||||
|  | ||||
|     def testEnforceOnlyOneSubElement(self): | ||||
|         report = """ | ||||
|           <iq type="set"> | ||||
|             <block xmlns="urn:xmpp:blocking"> | ||||
|                 <report xmlns="urn:xmpp:reporting:0"> | ||||
|                     <abuse/> | ||||
|                 </report> | ||||
|                 <report xmlns="urn:xmpp:reporting:1" reason="urn:xmpp:reporting:abuse"/> | ||||
|             </block> | ||||
|           </iq> | ||||
|         """ | ||||
|  | ||||
|         iq = self.Iq() | ||||
|         iq['type'] = 'set' | ||||
|         iq['block']['report']['spam'] = True | ||||
|         iq['block']['report']['abuse'] = True | ||||
|         self.check(iq, report) | ||||
|         iq['block']['report']['reason'] = xep_0377.XEP_0377.SPAM | ||||
|         iq['block']['report']['reason'] = xep_0377.XEP_0377.ABUSE | ||||
|         self.check(iq, report, use_values=False) | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestSpamReporting) | ||||
|   | ||||
							
								
								
									
										50
									
								
								tests/test_stanza_xep_0402.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								tests/test_stanza_xep_0402.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import unittest | ||||
|  | ||||
| from slixmpp.test import SlixTest | ||||
| from slixmpp.xmlstream import ElementBase | ||||
| from slixmpp.plugins.xep_0402 import stanza | ||||
|  | ||||
|  | ||||
| class Ext1(ElementBase): | ||||
|     name = "ext1" | ||||
|     namespace = "http://ext1" | ||||
|  | ||||
|  | ||||
| class Ext2(ElementBase): | ||||
|     name = "ext2" | ||||
|     namespace = "http://ext2" | ||||
|  | ||||
|  | ||||
| class TestPepBookmarks(SlixTest): | ||||
|     def setUp(self): | ||||
|         stanza.register_plugin() | ||||
|  | ||||
|     def test_bookmarks_extensions(self): | ||||
|         extension1 = Ext1() | ||||
|         extension2 = Ext2() | ||||
|  | ||||
|         bookmark = stanza.Conference() | ||||
|         bookmark["password"] = "pass" | ||||
|         bookmark["nick"] = "nick" | ||||
|         bookmark["autojoin"] = False | ||||
|         bookmark["extensions"].append(extension1) | ||||
|         bookmark["extensions"].append(extension2) | ||||
|         self.check( | ||||
|             bookmark, | ||||
|             """ | ||||
|             <conference xmlns='urn:xmpp:bookmarks:1' | ||||
|                         autojoin='false'> | ||||
|               <nick>nick</nick> | ||||
|               <password>pass</password> | ||||
|               <extensions> | ||||
|                 <ext1 xmlns="http://ext1" /> | ||||
|                 <ext2 xmlns="http://ext2" /> | ||||
|               </extensions> | ||||
|             </conference> | ||||
|             """, | ||||
|             use_values=False | ||||
|         ) | ||||
|  | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestPepBookmarks) | ||||
							
								
								
									
										149
									
								
								tests/test_stanza_xep_0428.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								tests/test_stanza_xep_0428.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| import unittest | ||||
|  | ||||
| from slixmpp import Message | ||||
| from slixmpp.test import SlixTest | ||||
| from slixmpp.plugins.xep_0428 import stanza | ||||
|  | ||||
| from slixmpp.plugins import xep_0461 | ||||
| from slixmpp.plugins import xep_0444 | ||||
|  | ||||
|  | ||||
| class TestFallback(SlixTest): | ||||
|     def setUp(self): | ||||
|         stanza.register_plugins() | ||||
|  | ||||
|     def testSingleFallbackBody(self): | ||||
|         message = Message() | ||||
|         message["fallback"]["for"] = "ns" | ||||
|         message["fallback"]["body"]["start"] = 0 | ||||
|         message["fallback"]["body"]["end"] = 8 | ||||
|  | ||||
|         self.check( | ||||
|             message,  # language=XML | ||||
|             """ | ||||
|             <message> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='ns'> | ||||
|                 <body start="0" end="8" /> | ||||
|               </fallback> | ||||
|             </message> | ||||
|             """, | ||||
|         ) | ||||
|  | ||||
|     def testSingleFallbackSubject(self): | ||||
|         message = Message() | ||||
|         message["fallback"]["for"] = "ns" | ||||
|         message["fallback"]["subject"]["start"] = 0 | ||||
|         message["fallback"]["subject"]["end"] = 8 | ||||
|  | ||||
|         self.check( | ||||
|             message,  # language=XML | ||||
|             """ | ||||
|             <message> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='ns'> | ||||
|                 <subject start="0" end="8" /> | ||||
|               </fallback> | ||||
|             </message> | ||||
|             """, | ||||
|         ) | ||||
|  | ||||
|     def testSingleFallbackWholeBody(self): | ||||
|         message = Message() | ||||
|         message["fallback"]["for"] = "ns" | ||||
|         message["fallback"].enable("body") | ||||
|         self.check( | ||||
|             message,  # language=XML | ||||
|             """ | ||||
|             <message> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='ns'> | ||||
|                 <body /> | ||||
|               </fallback> | ||||
|             </message> | ||||
|             """, | ||||
|         ) | ||||
|  | ||||
|     def testMultiFallback(self): | ||||
|         message = Message() | ||||
|  | ||||
|         f1 = stanza.Fallback() | ||||
|         f1["for"] = "ns1" | ||||
|  | ||||
|         f2 = stanza.Fallback() | ||||
|         f2["for"] = "ns2" | ||||
|  | ||||
|         message.append(f1) | ||||
|         message.append(f2) | ||||
|  | ||||
|         self.check( | ||||
|             message,  # language=XML | ||||
|             """ | ||||
|             <message> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='ns1' /> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='ns2' /> | ||||
|             </message> | ||||
|             """, | ||||
|         ) | ||||
|  | ||||
|         for i, fallback in enumerate(message["fallbacks"], start=1): | ||||
|             self.assertEqual(fallback["for"], f"ns{i}") | ||||
|  | ||||
|     def testStripFallbackPartOfBody(self): | ||||
|         message = Message() | ||||
|         message["body"] = "> quoted\nsome-body" | ||||
|         message["fallback"]["for"] = xep_0461.stanza.NS | ||||
|         message["fallback"]["body"]["start"] = 0 | ||||
|         message["fallback"]["body"]["end"] = 9 | ||||
|  | ||||
|         self.check( | ||||
|             message,  # language=XML | ||||
|             """ | ||||
|             <message> | ||||
|               <body>> quoted\nsome-body</body> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='urn:xmpp:reply:0'> | ||||
|                 <body start="0" end="9" /> | ||||
|               </fallback> | ||||
|             </message> | ||||
|             """, | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             message["fallback"].get_stripped_body(xep_0461.stanza.NS), "some-body" | ||||
|         ) | ||||
|  | ||||
|     def testStripWholeBody(self): | ||||
|         message = Message() | ||||
|         message["body"] = "> quoted\nsome-body" | ||||
|         message["fallback"]["for"] = "ns" | ||||
|         message["fallback"].enable("body") | ||||
|  | ||||
|         self.check( | ||||
|             message,  # language=XML | ||||
|             """ | ||||
|             <message> | ||||
|               <body>> quoted\nsome-body</body> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='ns'> | ||||
|                 <body /> | ||||
|               </fallback> | ||||
|             </message> | ||||
|             """, | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual(message["fallback"].get_stripped_body("ns"), "") | ||||
|  | ||||
|     def testStripMultiFallback(self): | ||||
|         message = Message() | ||||
|         message["body"] = "> huuuuu\n👍" | ||||
|  | ||||
|         message["fallback"]["for"] = xep_0461.stanza.NS | ||||
|         message["fallback"]["body"]["start"] = 0 | ||||
|         message["fallback"]["body"]["end"] = 9 | ||||
|  | ||||
|         reaction_fallback = stanza.Fallback() | ||||
|         reaction_fallback["for"] = xep_0444.stanza.NS | ||||
|         reaction_fallback.enable("body") | ||||
|         message.append(reaction_fallback) | ||||
|  | ||||
|         self.assertEqual(message["fallback"].get_stripped_body(xep_0461.stanza.NS), "👍") | ||||
|         self.assertEqual(message["fallback"].get_stripped_body(xep_0444.stanza.NS), "") | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestFallback) | ||||
| @@ -1,11 +1,13 @@ | ||||
| import unittest | ||||
| from slixmpp import Message | ||||
| from slixmpp.test import SlixTest | ||||
| from slixmpp.plugins.xep_0428 import stanza as fallback_stanza | ||||
| from slixmpp.plugins.xep_0461 import stanza | ||||
|  | ||||
|  | ||||
| class TestReply(SlixTest): | ||||
|     def setUp(self): | ||||
|         fallback_stanza.register_plugins() | ||||
|         stanza.register_plugins() | ||||
|  | ||||
|     def testReply(self): | ||||
| @@ -26,23 +28,50 @@ class TestReply(SlixTest): | ||||
|     def testFallback(self): | ||||
|         message = Message() | ||||
|         message["body"] = "12345\nrealbody" | ||||
|         message["feature_fallback"]["for"] = "NS" | ||||
|         message["feature_fallback"]["fallback_body"]["start"] = "0" | ||||
|         message["feature_fallback"]["fallback_body"]["end"] = "6" | ||||
|         message["fallback"]["for"] = "NS" | ||||
|         message["fallback"]["body"]["start"] = 0 | ||||
|         message["fallback"]["body"]["end"] = 6 | ||||
|  | ||||
|         self.check( | ||||
|             message, | ||||
|             """ | ||||
|             <message xmlns="jabber:client"> | ||||
|               <body>12345\nrealbody</body> | ||||
|               <fallback xmlns='urn:xmpp:feature-fallback:0' for='NS'> | ||||
|               <fallback xmlns='urn:xmpp:fallback:0' for='NS'> | ||||
|                 <body start="0" end="6" /> | ||||
|               </fallback> | ||||
|             </message> | ||||
|             """, | ||||
|         ) | ||||
|  | ||||
|         assert message["feature_fallback"].get_stripped_body() == "realbody" | ||||
|         assert message["fallback"].get_stripped_body("NS") == "realbody" | ||||
|  | ||||
|     def testAddFallBackHelper(self): | ||||
|         msg = Message() | ||||
|         msg["body"] = "Great" | ||||
|         msg["reply"].add_quoted_fallback("Anna wrote:\nHi, how are you?") | ||||
|         self.check( | ||||
|             msg,  # language=XML | ||||
|             """ | ||||
|         <message xmlns="jabber:client" type="normal"> | ||||
|             <body>> Anna wrote:\n> Hi, how are you?\nGreat</body> | ||||
|             <reply xmlns="urn:xmpp:reply:0" /> | ||||
|             <fallback xmlns="urn:xmpp:fallback:0" for="urn:xmpp:reply:0"> | ||||
|                 <body start='0' end='33' /> | ||||
|             </fallback> | ||||
|         </message> | ||||
|             """ | ||||
|         ) | ||||
|  | ||||
|     def testGetFallBackBody(self): | ||||
|         body = "Anna wrote:\nHi, how are you?" | ||||
|         quoted = "> Anna wrote:\n> Hi, how are you?\n" | ||||
|  | ||||
|         msg = Message() | ||||
|         msg["body"] = "Great" | ||||
|         msg["reply"].add_quoted_fallback(body) | ||||
|         body2 = msg["reply"].get_fallback_body() | ||||
|         self.assertTrue(body2 == quoted, body2) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestReply) | ||||
|   | ||||
| @@ -8,9 +8,6 @@ class TestStreamTester(SlixTest): | ||||
|     Test that we can simulate and test a stanza stream. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testClientEcho(self): | ||||
|         """Test that we can interact with a ClientXMPP instance.""" | ||||
|         self.stream_start(mode='client') | ||||
|   | ||||
| @@ -10,9 +10,6 @@ class TestStreamExceptions(SlixTest): | ||||
|     Test handling roster updates. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testExceptionContinueWorking(self): | ||||
|         """Test that Slixmpp continues to respond after an XMPPError is raised.""" | ||||
|  | ||||
|   | ||||
| @@ -14,9 +14,6 @@ class TestFilters(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testIncoming(self): | ||||
|  | ||||
|         data = [] | ||||
|   | ||||
| @@ -15,9 +15,6 @@ class TestHandlers(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testCallback(self): | ||||
|         """Test using stream callback handlers.""" | ||||
|  | ||||
|   | ||||
| @@ -11,9 +11,6 @@ class TestStreamPresence(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start(jid='tester@localhost', plugins=[]) | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testInitialUnavailablePresences(self): | ||||
|         """ | ||||
|         Test receiving unavailable presences from JIDs that | ||||
|   | ||||
| @@ -13,9 +13,6 @@ class TestStreamRoster(SlixTest): | ||||
|     Test handling roster updates. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testGetRoster(self): | ||||
|         """Test handling roster requests.""" | ||||
|         self.stream_start(mode='client', jid='tester@localhost') | ||||
|   | ||||
| @@ -11,9 +11,6 @@ class TestStreamDisco(SlixTest): | ||||
|     Test using the XEP-0030 plugin. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testInfoEmptyDefaultNode(self): | ||||
|         """ | ||||
|         Info query result from an entity MUST have at least one identity | ||||
|   | ||||
| @@ -11,9 +11,6 @@ class TestInBandByteStreams(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start(plugins=['xep_0047', 'xep_0030']) | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testOpenStream(self): | ||||
|         """Test requesting a stream, successfully""" | ||||
|  | ||||
|   | ||||
| @@ -16,9 +16,6 @@ class TestAdHocCommands(SlixTest): | ||||
|         # a dummy value. | ||||
|         self.xmpp['xep_0050'].new_session = lambda: '_sessionid_' | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testInitialPayloadCommand(self): | ||||
|         """Test a command with an initial payload.""" | ||||
|  | ||||
|   | ||||
| @@ -19,9 +19,6 @@ class TestJabberSearch(SlixTest): | ||||
|         self.xmpp["xep_0055"].api.register(get_results, "search_query") | ||||
|         self.xmpp["xep_0055"].api.register(get_results, "search_query") | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testRequestingSearchFields(self): | ||||
|         self.recv( | ||||
|             """ | ||||
|   | ||||
| @@ -15,9 +15,6 @@ class TestStreamPubsub(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testCreateInstantNode(self): | ||||
|         """Test creating an instant node""" | ||||
|         self.xmpp['xep_0060'].create_node('pubsub.example.com', None) | ||||
|   | ||||
| @@ -6,9 +6,6 @@ from slixmpp.test import SlixTest | ||||
|  | ||||
| class TestOOB(SlixTest): | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testSendOOB(self): | ||||
|         """Test sending an OOB transfer request.""" | ||||
|         self.stream_start(plugins=['xep_0066', 'xep_0030']) | ||||
|   | ||||
| @@ -6,9 +6,6 @@ from slixmpp.test import SlixTest | ||||
|  | ||||
| class TestStreamChatStates(SlixTest): | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testChatStates(self): | ||||
|         self.stream_start(mode='client', plugins=['xep_0030', 'xep_0085']) | ||||
|  | ||||
|   | ||||
| @@ -6,9 +6,6 @@ from slixmpp.test import SlixTest | ||||
|  | ||||
| class TestStreamSet(SlixTest): | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testHandleSoftwareVersionRequest(self): | ||||
|         self.stream_start(mode='client', plugins=['xep_0030', 'xep_0092']) | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,7 @@ class TestStreamGateway(SlixTest): | ||||
|                 "xep_0100": {"component_name": "AIM Gateway", "type": "aim"} | ||||
|             }, | ||||
|         ) | ||||
|         self.xmpp._fix_error_ns() | ||||
|  | ||||
|     def next_sent(self): | ||||
|         self.wait_for_send_queue() | ||||
| @@ -160,7 +161,6 @@ class TestStreamGateway(SlixTest): | ||||
|             </iq> | ||||
|             """ | ||||
|         ) | ||||
|         # xmlns="jabber:client" in error substanza, bug in XEP-0077 plugin or OK? | ||||
|         self.send( | ||||
|             """ | ||||
|             <iq type='error' | ||||
| @@ -171,7 +171,7 @@ class TestStreamGateway(SlixTest): | ||||
|                 <username>RomeoMyRomeo</username> | ||||
|                 <password>ILoveJuliet</password> | ||||
|             </query> | ||||
|             <error code='406' type='modify' xmlns="jabber:client"> | ||||
|             <error code='406' type='modify'> | ||||
|                 <not-acceptable | ||||
|                     xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> | ||||
|                 <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Not good</text> | ||||
|   | ||||
							
								
								
									
										76
									
								
								tests/test_stream_xep_0115.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								tests/test_stream_xep_0115.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| import logging | ||||
| import unittest | ||||
| from slixmpp.test import SlixTest | ||||
|  | ||||
|  | ||||
| class TestCaps(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start(plugins=["xep_0115"]) | ||||
|  | ||||
|     def testConcurrentSameHash(self): | ||||
|         """ | ||||
|         Check that we only resolve a given ver string to a disco info once, | ||||
|         even if we receive several presences with that same ver string | ||||
|         consecutively. | ||||
|         """ | ||||
|         self.recv(  # language=XML | ||||
|             """ | ||||
|             <presence from='romeo@montague.lit/orchard'> | ||||
|               <c xmlns='http://jabber.org/protocol/caps' | ||||
|                  hash='sha-1' | ||||
|                  node='a-node' | ||||
|                  ver='h0TdMvqNR8FHUfFG1HauOLYZDqE='/> | ||||
|             </presence> | ||||
|             """ | ||||
|         ) | ||||
|         self.recv(  # language=XML | ||||
|             """ | ||||
|             <presence from='i-dont-know-much-shakespeare@montague.lit/orchard'> | ||||
|               <c xmlns='http://jabber.org/protocol/caps' | ||||
|                  hash='sha-1' | ||||
|                  node='a-node' | ||||
|                  ver='h0TdMvqNR8FHUfFG1HauOLYZDqE='/> | ||||
|             </presence> | ||||
|             """ | ||||
|         ) | ||||
|         self.send(  # language=XML | ||||
|             """ | ||||
|             <iq xmlns="jabber:client" | ||||
|                 id="1" | ||||
|                 to="romeo@montague.lit/orchard" | ||||
|                 type="get"> | ||||
|               <query xmlns="http://jabber.org/protocol/disco#info" | ||||
|                      node="a-node#h0TdMvqNR8FHUfFG1HauOLYZDqE="/> | ||||
|             </iq> | ||||
|             """ | ||||
|         ) | ||||
|         self.send(None) | ||||
|         self.recv(  # language=XML | ||||
|             """ | ||||
|             <iq from='romeo@montague.lit/orchard' | ||||
|                 id='1' | ||||
|                 type='result'> | ||||
|               <query xmlns='http://jabber.org/protocol/disco#info' | ||||
|                      node='a-nodes#h0TdMvqNR8FHUfFG1HauOLYZDqE='> | ||||
|                 <identity category='client' name='a client' type='pc'/> | ||||
|                 <feature var='http://jabber.org/protocol/caps'/> | ||||
|               </query> | ||||
|             </iq> | ||||
|             """ | ||||
|         ) | ||||
|         self.send(None) | ||||
|         self.assertTrue( | ||||
|             self.xmpp["xep_0030"].supports( | ||||
|                 "romeo@montague.lit/orchard", "http://jabber.org/protocol/caps" | ||||
|             ) | ||||
|         ) | ||||
|         self.assertTrue( | ||||
|             self.xmpp["xep_0030"].supports( | ||||
|                 "i-dont-know-much-shakespeare@montague.lit/orchard", | ||||
|                 "http://jabber.org/protocol/caps", | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|  | ||||
| logging.basicConfig(level=logging.DEBUG) | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestCaps) | ||||
| @@ -8,9 +8,6 @@ class TestStreamExtendedDisco(SlixTest): | ||||
|     Test using the XEP-0128 plugin. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testUsingExtendedInfo(self): | ||||
|         self.stream_start(mode='client', | ||||
|                           jid='tester@localhost', | ||||
|   | ||||
| @@ -10,9 +10,6 @@ class TestStreamDirectInvite(SlixTest): | ||||
|     Test using the XEP-0249 plugin. | ||||
|     """ | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testReceiveInvite(self): | ||||
|         self.stream_start(mode='client', | ||||
|                           plugins=['xep_0030', | ||||
|   | ||||
| @@ -9,9 +9,6 @@ class TestMAM(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start(plugins=['xep_0313']) | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testRetrieveSimple(self): | ||||
|         """Test requesting MAM messages without RSM""" | ||||
|  | ||||
|   | ||||
| @@ -24,9 +24,6 @@ class TestStreamSensorData(SlixTest): | ||||
|     def _time_now(self): | ||||
|         return datetime.datetime.now().replace(microsecond=0).isoformat() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testRequestAccept(self): | ||||
|         self.stream_start(mode='component', | ||||
|                           plugins=['xep_0030', | ||||
|   | ||||
| @@ -30,9 +30,6 @@ class TestStreamControl(SlixTest): | ||||
|     def _time_now(self): | ||||
|         return datetime.datetime.now().replace(microsecond=0).isoformat() | ||||
|  | ||||
|     def tearDown(self): | ||||
|         self.stream_close() | ||||
|  | ||||
|     def testRequestSetOk(self): | ||||
|         self.stream_start(mode='component', | ||||
|                           plugins=['xep_0030', | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import unittest | ||||
|  | ||||
| from slixmpp import ComponentXMPP, Iq, Message | ||||
| from slixmpp.roster import RosterItem | ||||
| from slixmpp import Message, JID, Iq | ||||
| from slixmpp.plugins.xep_0356 import permissions | ||||
| from slixmpp.test import SlixTest | ||||
|  | ||||
|  | ||||
| @@ -9,9 +9,9 @@ class TestPermissions(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start( | ||||
|             mode="component", | ||||
|             plugins=["xep_0356"], | ||||
|             plugins=["xep_0356", "xep_0045"], | ||||
|             jid="pubsub.capulet.lit", | ||||
|             server="capulet.net", | ||||
|             server="capulet.lit", | ||||
|         ) | ||||
|  | ||||
|     def testPluginEnd(self): | ||||
| @@ -23,26 +23,44 @@ class TestPermissions(SlixTest): | ||||
|         self.assertFalse(exc) | ||||
|  | ||||
|     def testGrantedPrivileges(self): | ||||
|         # https://xmpp.org/extensions/xep-0356.html#example-4 | ||||
|         results = {"event": False} | ||||
|         x = self.xmpp["xep_0356"] | ||||
|         self.xmpp.add_event_handler( | ||||
|             "privileges_advertised", lambda msg: results.__setitem__("event", True) | ||||
|         ) | ||||
|         self.recv( | ||||
|             """ | ||||
|             <message from='capulet.net' to='pubub.capulet.lit' id='54321'> | ||||
|             <message from='capulet.lit' to='pubsub.capulet.lit' id='54321'> | ||||
|                 <privilege xmlns='urn:xmpp:privilege:2'> | ||||
|                     <perm access='roster' type='both'/> | ||||
|                     <perm access='message' type='outgoing'/> | ||||
|                     <perm access='iq'> | ||||
|                       <namespace ns='some_ns' type='get' /> | ||||
|                       <namespace ns='some_other_ns' type='both' /> | ||||
|                     </perm> | ||||
|                 </privilege> | ||||
|             </message> | ||||
|             """ | ||||
|         ) | ||||
|         self.assertEqual(self.xmpp["xep_0356"].granted_privileges["roster"], "both") | ||||
|         server = JID("capulet.lit") | ||||
|         self.assertEqual( | ||||
|             self.xmpp["xep_0356"].granted_privileges["message"], "outgoing" | ||||
|             x.granted_privileges[server].roster, permissions.RosterAccess.BOTH | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             x.granted_privileges[server].message, permissions.MessagePermission.OUTGOING | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             x.granted_privileges[server].presence, permissions.PresencePermission.NONE | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             x.granted_privileges[server].iq["nope"], permissions.IqPermission.NONE | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             x.granted_privileges[server].iq["some_ns"], permissions.IqPermission.GET | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             x.granted_privileges[server].iq["some_other_ns"], permissions.IqPermission.BOTH | ||||
|         ) | ||||
|         self.assertEqual(self.xmpp["xep_0356"].granted_privileges["presence"], "none") | ||||
|         self.assertTrue(results["event"]) | ||||
|  | ||||
|     def testGetRosterIq(self): | ||||
| @@ -94,7 +112,7 @@ class TestPermissions(SlixTest): | ||||
|  | ||||
|     def testMakeOutgoingMessage(self): | ||||
|         xmlstring = """ | ||||
|         <message xmlns="jabber:component:accept" from='pubsub.capulet.lit' to='capulet.net'> | ||||
|         <message xmlns="jabber:component:accept" from='pubsub.capulet.lit' to='capulet.lit'> | ||||
|             <privilege xmlns='urn:xmpp:privilege:2'> | ||||
|                 <forwarded xmlns='urn:xmpp:forward:0'> | ||||
|                     <message from="juliet@capulet.lit" to="romeo@montague.lit" xmlns="jabber:client"> | ||||
| @@ -108,9 +126,49 @@ class TestPermissions(SlixTest): | ||||
|         msg["from"] = "juliet@capulet.lit" | ||||
|         msg["to"] = "romeo@montague.lit" | ||||
|         msg["body"] = "I do not hate you" | ||||
|          | ||||
|  | ||||
|         priv_msg = self.xmpp["xep_0356"]._make_privileged_message(msg) | ||||
|         self.check(priv_msg, xmlstring, use_values=False) | ||||
|  | ||||
|     def testDetectServer(self): | ||||
|         msg = Message() | ||||
|         msg["from"] = "juliet@something" | ||||
|         msg["to"] = "romeo@montague.lit" | ||||
|         msg["body"] = "I do not hate you" | ||||
|  | ||||
|         priv_msg = self.xmpp["xep_0356"]._make_privileged_message(msg) | ||||
|         assert priv_msg.get_to() == "something" | ||||
|         assert priv_msg.get_from() == "pubsub.capulet.lit" | ||||
|  | ||||
|     def testIqOnBehalf(self): | ||||
|         iq = Iq() | ||||
|         iq["mucadmin_query"]["item"]["affiliation"] = "member" | ||||
|         iq.set_from("juliet@xxx") | ||||
|         iq.set_to("somemuc@conf") | ||||
|         iq.set_type("get") | ||||
|         self.xmpp["xep_0356"].granted_privileges["conf"].iq["http://jabber.org/protocol/muc#admin"] = permissions.IqPermission.BOTH | ||||
|         r = self.xmpp.loop.create_task(self.xmpp["xep_0356"].send_privileged_iq(iq, iq_id="0")) | ||||
|         self.send( | ||||
|             """ | ||||
|             <iq from="pubsub.capulet.lit" | ||||
|                 to="juliet@xxx" | ||||
|                 xmlns="jabber:component:accept" | ||||
|                 type="get" id="0"> | ||||
|                 <privileged_iq xmlns='urn:xmpp:privilege:2'> | ||||
|                     <iq xmlns='jabber:client' | ||||
|                         type='get' | ||||
|                         to='somemuc@conf' | ||||
|                         from='juliet@xxx' | ||||
|                          id="0"> | ||||
|                           <query xmlns='http://jabber.org/protocol/muc#admin'> | ||||
|                             <item affiliation='member'/> | ||||
|                           </query> | ||||
|                     </iq> | ||||
|                 </privileged_iq> | ||||
|             </iq> | ||||
|             """, | ||||
|             use_values=False | ||||
|         ) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestPermissions) | ||||
|   | ||||
							
								
								
									
										57
									
								
								tests/test_stream_xep_0385.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/test_stream_xep_0385.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import unittest | ||||
| from base64 import b64encode | ||||
| from datetime import datetime | ||||
| from pathlib import Path | ||||
| from tempfile import NamedTemporaryFile | ||||
| from hashlib import sha256 | ||||
|  | ||||
| from slixmpp.plugins.xep_0082 import format_datetime | ||||
| from slixmpp.test import SlixTest | ||||
|  | ||||
| class TestSIMS(SlixTest): | ||||
|     def setUp(self): | ||||
|         self.stream_start( | ||||
|             mode="component", jid="whatevs.shakespeare.lit", plugins={"xep_0385"} | ||||
|         ) | ||||
|  | ||||
|     def test_set_file(self): | ||||
|         with NamedTemporaryFile("wb+") as f: | ||||
|             n = 10 | ||||
|             size = 0 | ||||
|             for i in range(n): | ||||
|                 size += len(bytes(i)) | ||||
|                 f.write(bytes(i)) | ||||
|  | ||||
|             f.seek(0) | ||||
|             h = b64encode(sha256(f.read()).digest()).decode() | ||||
|             sims = self.xmpp["xep_0385"].get_sims( | ||||
|                 Path(f.name), | ||||
|                 ["https://xxx.com"], | ||||
|                 media_type="MEDIA", | ||||
|                 desc="DESCRIPTION", | ||||
|             ) | ||||
|  | ||||
|             self.check( | ||||
|                 sims, | ||||
|                 f""" | ||||
|                 <reference xmlns='urn:xmpp:reference:0' type='data'> | ||||
|                 <media-sharing xmlns='urn:xmpp:sims:1'> | ||||
|                   <file xmlns='urn:xmpp:jingle:apps:file-transfer:5'> | ||||
|                     <media-type>MEDIA</media-type> | ||||
|                     <name>{Path(f.name).name}</name> | ||||
|                     <size>{size}</size> | ||||
|                     <hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>{h}</hash> | ||||
|                     <desc>DESCRIPTION</desc> | ||||
|                     <date>{format_datetime(datetime.fromtimestamp(Path(f.name).stat().st_mtime))}</date> | ||||
|                   </file> | ||||
|                   <sources> | ||||
|                     <reference xmlns='urn:xmpp:reference:0' type='data' uri='https://xxx.com' /> | ||||
|                   </sources> | ||||
|                 </media-sharing> | ||||
|                 </reference> | ||||
|                 """, | ||||
|                 use_values=False, | ||||
|             ) | ||||
|  | ||||
|  | ||||
| suite = unittest.TestLoader().loadTestsFromTestCase(TestSIMS) | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user