Compare commits
	
		
			150 Commits
		
	
	
		
			WIP
			...
			adhoc-exec
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					412a9169bd | ||
| 
						 | 
					72b355de8c | ||
| 
						 | 
					af246dcfe1 | ||
| 
						 | 
					9612e518fb | ||
| 
						 | 
					fde8264191 | ||
| 
						 | 
					1cdc656208 | ||
| 
						 | 
					0042108a67 | ||
| 
						 | 
					704161a285 | ||
| 
						 | 
					6b1b58a339 | ||
| 
						 | 
					4f96e5fa75 | ||
| 
						 | 
					bcb90a653e | ||
| 
						 | 
					7e435b703d | ||
| 
						 | 
					2dda6b80d4 | ||
| 
						 | 
					5629e44710 | ||
| 
						 | 
					6a06881d8b | ||
| 
						 | 
					2b666eb1de | ||
| 
						 | 
					400e7a3903 | ||
| 
						 | 
					fbab3ad214 | ||
| 
						 | 
					628b357b06 | ||
| 
						 | 
					88260cc240 | ||
| 
						 | 
					e9f2f503b8 | ||
| 
						 | 
					696a72247b | ||
| 
						 | 
					05d76e4b1d | ||
| 
						 | 
					d52d4fbbbe | ||
| 
						 | 
					e53c0fcb30 | ||
| 
						 | 
					97d68c5196 | ||
| 
						 | 
					b42fafabb4 | ||
| 
						 | 
					3a44ec8f15 | ||
| 
						 | 
					93f385562f | ||
| 
						 | 
					9cab02438b | ||
| 
						 | 
					74ed50e626 | ||
| 
						 | 
					9d378c611c | ||
| 
						 | 
					d85d8f4479 | ||
| 
						 | 
					fb75f7cda9 | ||
| 
						 | 
					41419a2161 | ||
| 
						 | 
					7cd73b594e | ||
| 
						 | 
					15c6b775ff | ||
| 
						 | 
					4b482477e2 | ||
| 
						 | 
					f7e4caadfe | ||
| 
						 | 
					5f25b0b6a0 | ||
| 
						 | 
					d228bc42ea | ||
| 
						 | 
					ecdc44a601 | ||
| 
						 | 
					33370e42f1 | ||
| 
						 | 
					4699861925 | ||
| 
						 | 
					2d228bdb56 | ||
| 
						 | 
					570e653ac2 | ||
| 
						 | 
					282a481059 | ||
| 
						 | 
					f386db380b | ||
| 
						 | 
					7b87d98fff | ||
| 
						 | 
					8779d40602 | ||
| 
						 | 
					f0b21c42d5 | ||
| 
						 | 
					e241d4e3c7 | ||
| 
						 | 
					bd22a41a78 | ||
| 
						 | 
					a29a29227a | ||
| 
						 | 
					d4d542b741 | ||
| 
						 | 
					dc4936a6d3 | ||
| 
						 | 
					897610d819 | ||
| 
						 | 
					d33366badd | ||
| 
						 | 
					809c500002 | ||
| 
						 | 
					dda4e18b81 | ||
| 
						 | 
					8c09d932c8 | ||
| 
						 | 
					31f5e84671 | ||
| 
						 | 
					ad0dc33df9 | ||
| 
						 | 
					7c3b3827b4 | ||
| 
						 | 
					9f6fa65139 | ||
| 
						 | 
					35fa33e3c2 | ||
| 
						 | 
					86a2f280d2 | ||
| 
						 | 
					490f15b8fc | ||
| 
						 | 
					62661ee04f | ||
| 
						 | 
					37d1f2a6b0 | ||
| 
						 | 
					20107ad516 | ||
| 
						 | 
					7738a01311 | ||
| 
						 | 
					a9abed6151 | ||
| 
						 | 
					0f690d4005 | ||
| 
						 | 
					59d4420739 | ||
| 
						 | 
					a88f317bbf | ||
| 
						 | 
					2fc2a88970 | ||
| 
						 | 
					c55e9279ac | ||
| 
						 | 
					3502480384 | ||
| 
						 | 
					caae713dd6 | ||
| 
						 | 
					df0198abfe | ||
| 
						 | 
					c20f4bf5fa | ||
| 
						 | 
					9740e93aeb | ||
| 
						 | 
					e7872aaa29 | ||
| 
						 | 
					037706552c | ||
| 
						 | 
					b881c6729b | ||
| 
						 | 
					66909aafb3 | ||
| 
						 | 
					cdfb5d56fc | ||
| 
						 | 
					d146ce9fb6 | ||
| 
						 | 
					cb59d60034 | ||
| 
						 | 
					1d9fe3553e | ||
| 
						 | 
					fe66c022ad | ||
| 
						 | 
					92ea131721 | ||
| 
						 | 
					dd7f67d10d | ||
| 
						 | 
					c1562b76b2 | ||
| 
						 | 
					32839f5252 | ||
| 
						 | 
					80b7cf6ff8 | ||
| 
						 | 
					128cc2eeb4 | ||
| 
						 | 
					037912ee89 | ||
| 
						 | 
					769bc6d3bf | ||
| 
						 | 
					084d6cb5d9 | ||
| 
						 | 
					5184713356 | ||
| 
						 | 
					2f1225bad3 | ||
| 
						 | 
					841f5a5a5b | ||
| 
						 | 
					0c6de5e972 | ||
| 
						 | 
					81dc61c55c | ||
| 
						 | 
					bd63b1ce70 | ||
| 
						 | 
					29faf114a7 | ||
| 
						 | 
					94ea8151d4 | ||
| 
						 | 
					66500ef5fb | ||
| 
						 | 
					979396bb1e | ||
| 
						 | 
					e177726387 | ||
| 
						 | 
					20e88fda50 | ||
| 
						 | 
					f252be9b6d | ||
| 
						 | 
					ee98159586 | ||
| 
						 | 
					c6443af29a | ||
| 
						 | 
					d73f56a7af | ||
| 
						 | 
					7c7f4308c5 | ||
| 
						 | 
					eab8c265f4 | ||
| 
						 | 
					80b9cd43b1 | ||
| 
						 | 
					af1f9e08ad | ||
| 
						 | 
					e3fd0af9c8 | ||
| 
						 | 
					27e23672c1 | ||
| 
						 | 
					b38e229359 | ||
| 
						 | 
					9a563f1425 | ||
| 
						 | 
					8b6f5953a7 | ||
| 
						 | 
					2d2a80c73d | ||
| 
						 | 
					4dfdd5d8e3 | ||
| 
						 | 
					1994ed3025 | ||
| 
						 | 
					aaa45846d3 | ||
| 
						 | 
					d7ffcb54eb | ||
| 
						 | 
					c33749e57a | ||
| 
						 | 
					e4107d8b4d | ||
| 
						 | 
					da5cb72d3a | ||
| 
						 | 
					c372bd5168 | ||
| 
						 | 
					cabf623131 | ||
| 
						 | 
					ffc240d5b6 | ||
| 
						 | 
					cc4522d9cd | ||
| 
						 | 
					5bf69dca76 | ||
| 
						 | 
					59dad12820 | ||
| 
						 | 
					007c836296 | ||
| 
						 | 
					3721bf9f6b | ||
| 
						 | 
					802949eba8 | ||
| 
						 | 
					24f35e433f | ||
| 
						 | 
					22664ee7b8 | ||
| 
						 | 
					6476cfcde5 | ||
| 
						 | 
					5bb347e884 | ||
| 
						 | 
					eb1251b919 | ||
| 
						 | 
					820144c40c | ||
| 
						 | 
					8b06aa1146 | 
@@ -1,8 +1,21 @@
 | 
			
		||||
stages:
 | 
			
		||||
  - test
 | 
			
		||||
  - trigger
 | 
			
		||||
 | 
			
		||||
test:
 | 
			
		||||
  stage: test
 | 
			
		||||
  tags:
 | 
			
		||||
    - docker
 | 
			
		||||
  image: ubuntu:latest
 | 
			
		||||
  script:
 | 
			
		||||
    - apt update
 | 
			
		||||
    - apt install -y python3 cython3
 | 
			
		||||
    - apt install -y python3 cython3 gpg
 | 
			
		||||
    - ./run_tests.py
 | 
			
		||||
 | 
			
		||||
trigger_poezio:
 | 
			
		||||
  stage: trigger
 | 
			
		||||
  tags:
 | 
			
		||||
    - docker
 | 
			
		||||
  image: appropriate/curl:latest
 | 
			
		||||
  script:
 | 
			
		||||
    - curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
language: python
 | 
			
		||||
python:
 | 
			
		||||
  - "2.6"
 | 
			
		||||
  - "2.7"
 | 
			
		||||
  - "3.2"
 | 
			
		||||
  - "3.3"
 | 
			
		||||
  - "3.4"
 | 
			
		||||
  - "3.5"
 | 
			
		||||
  - "3.6"
 | 
			
		||||
  - "3.7-dev"
 | 
			
		||||
install:
 | 
			
		||||
  - "pip install ."
 | 
			
		||||
script: testall.py
 | 
			
		||||
 
 | 
			
		||||
@@ -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://dev.poez.io/new>`_
 | 
			
		||||
 - a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
 | 
			
		||||
 - a pull request on github
 | 
			
		||||
 - a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								INSTALL
									
									
									
									
									
								
							@@ -1,6 +1,7 @@
 | 
			
		||||
Pre-requisites:
 | 
			
		||||
- Python 3.4
 | 
			
		||||
- Python 3.5+
 | 
			
		||||
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
 | 
			
		||||
- GnuPG, for testing
 | 
			
		||||
 | 
			
		||||
Install:
 | 
			
		||||
> python3 setup.py install
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								LICENSE
									
									
									
									
									
								
							@@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 | 
			
		||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 | 
			
		||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 | 
			
		||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
socksipy: A Python SOCKS client module.
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
Copyright 2006 Dan-Haim. All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without modification,
 | 
			
		||||
are permitted provided that the following conditions are met:
 | 
			
		||||
1. Redistributions of source code must retain the above copyright notice, this
 | 
			
		||||
   list of conditions and the following disclaimer.
 | 
			
		||||
2. Redistributions in binary form must reproduce the above copyright notice,
 | 
			
		||||
   this list of conditions and the following disclaimer in the documentation
 | 
			
		||||
   and/or other materials provided with the distribution.
 | 
			
		||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
 | 
			
		||||
   to endorse or promote products derived from this software without specific
 | 
			
		||||
   prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
 | 
			
		||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 | 
			
		||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 | 
			
		||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
 | 
			
		||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 | 
			
		||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 | 
			
		||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
Slixmpp
 | 
			
		||||
#########
 | 
			
		||||
 | 
			
		||||
Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of
 | 
			
		||||
Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of
 | 
			
		||||
SleekXMPP.
 | 
			
		||||
 | 
			
		||||
Slixmpp's goals is to only rewrite the core of the library (the low level
 | 
			
		||||
@@ -36,7 +36,7 @@ The Slixmpp Boilerplate
 | 
			
		||||
-------------------------
 | 
			
		||||
Projects using Slixmpp tend to follow a basic pattern for setting up client/component
 | 
			
		||||
connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp
 | 
			
		||||
based project. See the documetation or examples directory for more detailed archetypes for
 | 
			
		||||
based project. See the documentation or examples directory for more detailed archetypes for
 | 
			
		||||
Slixmpp projects::
 | 
			
		||||
 | 
			
		||||
    import logging
 | 
			
		||||
@@ -113,6 +113,7 @@ Slixmpp Credits
 | 
			
		||||
    - Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
 | 
			
		||||
    - Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
 | 
			
		||||
    - Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
 | 
			
		||||
    - Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
 | 
			
		||||
 | 
			
		||||
Credits (SleekXMPP)
 | 
			
		||||
-------------------
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								docs/conf.py
									
									
									
									
									
								
							@@ -12,12 +12,17 @@
 | 
			
		||||
# serve to show the default.
 | 
			
		||||
 | 
			
		||||
import sys, os
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
# If extensions (or modules to document with autodoc) are in another directory,
 | 
			
		||||
# add these directories to sys.path here. If the directory is relative to the
 | 
			
		||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
 | 
			
		||||
sys.path.insert(0, os.path.abspath('..'))
 | 
			
		||||
 | 
			
		||||
# get version automagically from source tree
 | 
			
		||||
from slixmpp.version import __version__ as version
 | 
			
		||||
release = ".".join(version.split(".")[0:2])
 | 
			
		||||
 | 
			
		||||
# -- General configuration -----------------------------------------------------
 | 
			
		||||
 | 
			
		||||
# If your documentation needs a minimal Sphinx version, state it here.
 | 
			
		||||
@@ -41,16 +46,18 @@ master_doc = 'index'
 | 
			
		||||
 | 
			
		||||
# General information about the project.
 | 
			
		||||
project = u'Slixmpp'
 | 
			
		||||
copyright = u'2011, Nathan Fritz, Lance Stout'
 | 
			
		||||
year = datetime.datetime.now().year
 | 
			
		||||
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year)
 | 
			
		||||
 | 
			
		||||
# The version info for the project you're documenting, acts as replacement for
 | 
			
		||||
# |version| and |release|, also used in various other places throughout the
 | 
			
		||||
# built documents.
 | 
			
		||||
#
 | 
			
		||||
# auto imported from code!
 | 
			
		||||
# The short X.Y version.
 | 
			
		||||
version = '1.1'
 | 
			
		||||
# version = '1.4'
 | 
			
		||||
# The full version, including alpha/beta/rc tags.
 | 
			
		||||
release = '1.1'
 | 
			
		||||
# release = '1.4.0'
 | 
			
		||||
 | 
			
		||||
# The language for content autogenerated by Sphinx. Refer to documentation
 | 
			
		||||
# for a list of supported languages.
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,8 @@
 | 
			
		||||
Differences from SleekXMPP
 | 
			
		||||
==========================
 | 
			
		||||
 | 
			
		||||
**Python 3.4+ only**
 | 
			
		||||
    slixmpp will only work on python 3.4 and above.
 | 
			
		||||
**Python 3.5+ only**
 | 
			
		||||
    slixmpp will only work on python 3.5 and above.
 | 
			
		||||
 | 
			
		||||
**Stanza copies**
 | 
			
		||||
    The same stanza object is given through all the handlers; a handler that
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <http://git.poez.io/slixmpp>`_.
 | 
			
		||||
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
 | 
			
		||||
 | 
			
		||||
Many XMPP applications eventually graduate to requiring to run as a server
 | 
			
		||||
component in order to meet scalability requirements. To demonstrate how to
 | 
			
		||||
 
 | 
			
		||||
@@ -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 <http://git.poez.io/slixmpp>`_.
 | 
			
		||||
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
 | 
			
		||||
 | 
			
		||||
As a basic starting project, we will create an echo bot which will reply to any
 | 
			
		||||
messages sent to it. We will also go through adding some basic command line configuration
 | 
			
		||||
@@ -329,7 +329,7 @@ The Final Product
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
Here then is what the final result should look like after working through the guide above. The code
 | 
			
		||||
can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_.
 | 
			
		||||
can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_.
 | 
			
		||||
 | 
			
		||||
.. compound::
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
.. _mucbot:
 | 
			
		||||
 | 
			
		||||
=========================
 | 
			
		||||
Mulit-User Chat (MUC) Bot
 | 
			
		||||
Multi-User Chat (MUC) Bot
 | 
			
		||||
=========================
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
@@ -11,7 +11,7 @@ Mulit-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 <http://git.poez.io/slixmpp>`_.
 | 
			
		||||
from `Git <https://lab.louiz.org/poezio/slixmpp>`_.
 | 
			
		||||
 | 
			
		||||
Now that you've got the basic gist of using Slixmpp by following the
 | 
			
		||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,9 @@ Slixmpp
 | 
			
		||||
.. sidebar:: Get the Code
 | 
			
		||||
 | 
			
		||||
    The latest source code for Slixmpp may be found on the `Git repo
 | 
			
		||||
    <http://git.poez.io/slixmpp>`_. ::
 | 
			
		||||
    <https://lab.louiz.org/poezio/slixmpp>`_. ::
 | 
			
		||||
 | 
			
		||||
        git clone git://git.poez.io/slixmpp
 | 
			
		||||
        git clone https://lab.louiz.org/poezio/slixmpp
 | 
			
		||||
 | 
			
		||||
    An XMPP chat room is available for discussing and getting help with slixmpp.
 | 
			
		||||
 | 
			
		||||
@@ -14,14 +14,14 @@ Slixmpp
 | 
			
		||||
        `slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
 | 
			
		||||
 | 
			
		||||
    **Reporting bugs**
 | 
			
		||||
        You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
 | 
			
		||||
        You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
    slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
 | 
			
		||||
    which goal is to use asyncio instead of threads to handle networking. See
 | 
			
		||||
    :ref:`differences`.
 | 
			
		||||
 | 
			
		||||
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.4+,
 | 
			
		||||
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+,
 | 
			
		||||
 | 
			
		||||
Slixmpp's design goals and philosphy are:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
 | 
			
		||||
 | 
			
		||||
import slixmpp
 | 
			
		||||
from slixmpp.exceptions import XMPPError
 | 
			
		||||
from slixmpp import asyncio
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -51,18 +51,17 @@ class AskConfirm(slixmpp.ClientXMPP):
 | 
			
		||||
            else:
 | 
			
		||||
                self.confirmed.set_result(True)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        log.info('Sending confirm request %s to %s who wants to access %s using '
 | 
			
		||||
                 'method %s...' % (self.id, self.recipient, self.url, self.method))
 | 
			
		||||
        try:
 | 
			
		||||
            confirmed = yield from self['xep_0070'].ask_confirm(self.recipient,
 | 
			
		||||
            confirmed = await self['xep_0070'].ask_confirm(self.recipient,
 | 
			
		||||
                                                                id=self.id,
 | 
			
		||||
                                                                url=self.url,
 | 
			
		||||
                                                                method=self.method,
 | 
			
		||||
                                                                message='Plz say yes or no for {method} {url} ({id}).')
 | 
			
		||||
            if isinstance(confirmed, slixmpp.Message):
 | 
			
		||||
                confirmed = yield from self.confirmed
 | 
			
		||||
                confirmed = await self.confirmed
 | 
			
		||||
            else:
 | 
			
		||||
                confirmed = True
 | 
			
		||||
        except IqError:
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ class ActionBot(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, Action)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -73,7 +73,7 @@ class ActionBot(slixmpp.ClientXMPP):
 | 
			
		||||
        """
 | 
			
		||||
        self.event('custom_action', iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_action_event(self, iq):
 | 
			
		||||
    async def _handle_action_event(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Respond to the custom action event.
 | 
			
		||||
        """
 | 
			
		||||
@@ -82,17 +82,20 @@ class ActionBot(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        if method == 'is_prime' and param == '2':
 | 
			
		||||
            print("got message: %s" % iq)
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            iq['action']['status'] = 'done'
 | 
			
		||||
            iq.send()
 | 
			
		||||
            rep = iq.reply()
 | 
			
		||||
            rep['action']['status'] = 'done'
 | 
			
		||||
            await rep.send()
 | 
			
		||||
        elif method == 'bye':
 | 
			
		||||
            print("got message: %s" % iq)
 | 
			
		||||
            rep = iq.reply()
 | 
			
		||||
            rep['action']['status'] = 'done'
 | 
			
		||||
            await rep.send()
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
        else:
 | 
			
		||||
            print("got message: %s" % iq)
 | 
			
		||||
            iq.reply()
 | 
			
		||||
            iq['action']['status'] = 'error'
 | 
			
		||||
            iq.send()
 | 
			
		||||
            rep = iq.reply()
 | 
			
		||||
            rep['action']['status'] = 'error'
 | 
			
		||||
            await rep.send()
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ class ActionUserBot(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, Action)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -57,11 +57,11 @@ class ActionUserBot(slixmpp.ClientXMPP):
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        await self.get_roster()
 | 
			
		||||
 | 
			
		||||
        self.send_custom_iq()
 | 
			
		||||
        await self.send_custom_iq()
 | 
			
		||||
 | 
			
		||||
    def send_custom_iq(self):
 | 
			
		||||
    async def send_custom_iq(self):
 | 
			
		||||
        """Create and send two custom actions.
 | 
			
		||||
 | 
			
		||||
        If the first action was successful, then send
 | 
			
		||||
@@ -74,14 +74,14 @@ class ActionUserBot(slixmpp.ClientXMPP):
 | 
			
		||||
        iq['action']['param'] = '2'
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            resp = iq.send()
 | 
			
		||||
            resp = await iq.send()
 | 
			
		||||
            if resp['action']['status'] == 'done':
 | 
			
		||||
                #sending bye
 | 
			
		||||
                iq2 = self.Iq()
 | 
			
		||||
                iq2['to'] = self.action_provider
 | 
			
		||||
                iq2['type'] = 'set'
 | 
			
		||||
                iq2['action']['method'] = 'bye'
 | 
			
		||||
                iq2.send(block=False)
 | 
			
		||||
                await iq2.send()
 | 
			
		||||
 | 
			
		||||
                self.disconnect()
 | 
			
		||||
        except XMPPError:
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
 | 
			
		||||
 | 
			
		||||
import slixmpp
 | 
			
		||||
from slixmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
from slixmpp.xmlstream.asyncio import asyncio
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Disco(slixmpp.ClientXMPP):
 | 
			
		||||
@@ -54,8 +53,7 @@ class Disco(slixmpp.ClientXMPP):
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -77,13 +75,13 @@ class Disco(slixmpp.ClientXMPP):
 | 
			
		||||
        try:
 | 
			
		||||
            if self.get in self.info_types:
 | 
			
		||||
                # function using the callback parameter.
 | 
			
		||||
                info = yield from self['xep_0030'].get_info(jid=self.target_jid,
 | 
			
		||||
                info = await self['xep_0030'].get_info(jid=self.target_jid,
 | 
			
		||||
                                                            node=self.target_node)
 | 
			
		||||
            if self.get in self.items_types:
 | 
			
		||||
                # The same applies from above. Listen for the
 | 
			
		||||
                # disco_items event or pass a callback function
 | 
			
		||||
                # if you need to process a non-blocking request.
 | 
			
		||||
                items = yield from self['xep_0030'].get_items(jid=self.target_jid,
 | 
			
		||||
                items = await self['xep_0030'].get_items(jid=self.target_jid,
 | 
			
		||||
                                                              node=self.target_node)
 | 
			
		||||
            if self.get not in self.info_types and self.get not in self.items_types:
 | 
			
		||||
                logging.error("Invalid disco request type.")
 | 
			
		||||
 
 | 
			
		||||
@@ -47,8 +47,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
 | 
			
		||||
        self.roster_received.set()
 | 
			
		||||
        self.presences_received.clear()
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -65,16 +64,15 @@ class AvatarDownloader(slixmpp.ClientXMPP):
 | 
			
		||||
        self.get_roster(callback=self.roster_received_cb)
 | 
			
		||||
 | 
			
		||||
        print('Waiting for presence updates...\n')
 | 
			
		||||
        yield from self.roster_received.wait()
 | 
			
		||||
        await self.roster_received.wait()
 | 
			
		||||
        print('Roster received')
 | 
			
		||||
        yield from self.presences_received.wait()
 | 
			
		||||
        await self.presences_received.wait()
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def on_vcard_avatar(self, pres):
 | 
			
		||||
    async def on_vcard_avatar(self, pres):
 | 
			
		||||
        print("Received vCard avatar update from %s" % pres['from'].bare)
 | 
			
		||||
        try:
 | 
			
		||||
            result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
 | 
			
		||||
            result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
 | 
			
		||||
                                                           timeout=5)
 | 
			
		||||
        except XMPPError:
 | 
			
		||||
            print("Error retrieving avatar for %s" % pres['from'])
 | 
			
		||||
@@ -89,14 +87,13 @@ class AvatarDownloader(slixmpp.ClientXMPP):
 | 
			
		||||
        with open(filename, 'wb+') as img:
 | 
			
		||||
            img.write(avatar['BINVAL'])
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def on_avatar(self, msg):
 | 
			
		||||
    async def on_avatar(self, msg):
 | 
			
		||||
        print("Received avatar update from %s" % msg['from'])
 | 
			
		||||
        metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
 | 
			
		||||
        for info in metadata['items']:
 | 
			
		||||
            if not info['url']:
 | 
			
		||||
                try:
 | 
			
		||||
                    result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
 | 
			
		||||
                    result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
 | 
			
		||||
                                                                         timeout=5)
 | 
			
		||||
                except XMPPError:
 | 
			
		||||
                    print("Error retrieving avatar for %s" % msg['from'])
 | 
			
		||||
 
 | 
			
		||||
@@ -55,8 +55,8 @@ class GTalkBot(slixmpp.ClientXMPP):
 | 
			
		||||
            cert.verify('talk.google.com', der_cert)
 | 
			
		||||
            logging.debug("CERT: Found GTalk certificate")
 | 
			
		||||
        except cert.CertificateError as err:
 | 
			
		||||
            log.error(err.message)
 | 
			
		||||
            self.disconnect(send_close=False)
 | 
			
		||||
            logging.error(err.message)
 | 
			
		||||
            self.disconnect()
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ class HTTPOverXMPPClient(ClientXMPP):
 | 
			
		||||
        ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
        self.register_plugin('xep_0332')    # HTTP over XMPP Transport
 | 
			
		||||
        self.add_event_handler(
 | 
			
		||||
            'session_start', self.session_start, threaded=True
 | 
			
		||||
            'session_start', self.session_start
 | 
			
		||||
        )
 | 
			
		||||
        self.add_event_handler('http_request', self.http_request_received)
 | 
			
		||||
        self.add_event_handler('http_response', self.http_response_received)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										96
									
								
								examples/http_upload.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										96
									
								
								examples/http_upload.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2018 Emmanuel Gil Peyrot
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
 | 
			
		||||
import slixmpp
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpUpload(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A basic client asking an entity if they confirm the access to an HTTP URL.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, recipient, filename, domain=None):
 | 
			
		||||
        slixmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        self.recipient = recipient
 | 
			
		||||
        self.filename = filename
 | 
			
		||||
        self.domain = domain
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        log.info('Uploading file %s...', self.filename)
 | 
			
		||||
        def timeout_callback(arg):
 | 
			
		||||
            raise TimeoutError("could not send message in time")
 | 
			
		||||
        url = await self['xep_0363'].upload_file(
 | 
			
		||||
            self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback)
 | 
			
		||||
        log.info('Upload success!')
 | 
			
		||||
 | 
			
		||||
        log.info('Sending file to %s', self.recipient)
 | 
			
		||||
        html = '<body xmlns="http://www.w3.org/1999/xhtml"><a href="%s">%s</a></body>' % (url, url)
 | 
			
		||||
        self.send_message(self.recipient, url, mhtml=html)
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    parser = ArgumentParser()
 | 
			
		||||
    parser.add_argument("-q","--quiet", help="set logging to ERROR",
 | 
			
		||||
                        action="store_const",
 | 
			
		||||
                        dest="loglevel",
 | 
			
		||||
                        const=logging.ERROR,
 | 
			
		||||
                        default=logging.INFO)
 | 
			
		||||
    parser.add_argument("-d","--debug", help="set logging to DEBUG",
 | 
			
		||||
                        action="store_const",
 | 
			
		||||
                        dest="loglevel",
 | 
			
		||||
                        const=logging.DEBUG,
 | 
			
		||||
                        default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    parser.add_argument("-j", "--jid", dest="jid",
 | 
			
		||||
                        help="JID to use")
 | 
			
		||||
    parser.add_argument("-p", "--password", dest="password",
 | 
			
		||||
                        help="password to use")
 | 
			
		||||
 | 
			
		||||
    # Other options.
 | 
			
		||||
    parser.add_argument("-r", "--recipient", required=True,
 | 
			
		||||
                        help="Recipient JID")
 | 
			
		||||
    parser.add_argument("-f", "--file", required=True,
 | 
			
		||||
                        help="File to send")
 | 
			
		||||
    parser.add_argument("--domain",
 | 
			
		||||
                        help="Domain to use for HTTP File Upload (leave out for your own server’s)")
 | 
			
		||||
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=args.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if args.jid is None:
 | 
			
		||||
        args.jid = input("Username: ")
 | 
			
		||||
    if args.password is None:
 | 
			
		||||
        args.password = getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain)
 | 
			
		||||
    xmpp.register_plugin('xep_0071')
 | 
			
		||||
    xmpp.register_plugin('xep_0128')
 | 
			
		||||
    xmpp.register_plugin('xep_0363')
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    xmpp.connect()
 | 
			
		||||
    xmpp.process(forever=False)
 | 
			
		||||
@@ -9,7 +9,6 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
@@ -39,8 +38,7 @@ class IBBSender(slixmpp.ClientXMPP):
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -58,13 +56,13 @@ class IBBSender(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # Open the IBB stream in which to write to.
 | 
			
		||||
            stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
 | 
			
		||||
            stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
 | 
			
		||||
 | 
			
		||||
            # If you want to send in-memory bytes, use stream.sendall() instead.
 | 
			
		||||
            yield from stream.sendfile(self.file, timeout=10)
 | 
			
		||||
            await stream.sendfile(self.file, timeout=10)
 | 
			
		||||
 | 
			
		||||
            # And finally close the stream.
 | 
			
		||||
            yield from stream.close(timeout=10)
 | 
			
		||||
            await stream.close(timeout=10)
 | 
			
		||||
        except (IqError, IqTimeout):
 | 
			
		||||
            print('File transfer errored')
 | 
			
		||||
        else:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								examples/mam.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										97
									
								
								examples/mam.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2017 Mathieu Pasquet
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
 | 
			
		||||
import slixmpp
 | 
			
		||||
from slixmpp.exceptions import XMPPError
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MAM(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A basic client fetching mam archive messages
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password, remote_jid, start):
 | 
			
		||||
        slixmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
        self.remote_jid = remote_jid
 | 
			
		||||
        self.start_date = start
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    async def start(self, *args):
 | 
			
		||||
        """
 | 
			
		||||
        Fetch mam results for the specified JID.
 | 
			
		||||
        Use RSM to paginate the results.
 | 
			
		||||
        """
 | 
			
		||||
        results = self.plugin['xep_0313'].retrieve(jid=self.remote_jid, iterator=True, rsm={'max': 10}, start=self.start_date)
 | 
			
		||||
        page = 1
 | 
			
		||||
        async for rsm in results:
 | 
			
		||||
            print('Page %d' % page)
 | 
			
		||||
            for msg in rsm['mam']['results']:
 | 
			
		||||
                forwarded = msg['mam_result']['forwarded']
 | 
			
		||||
                timestamp = forwarded['delay']['stamp']
 | 
			
		||||
                message = forwarded['stanza']
 | 
			
		||||
                print('[%s] %s: %s' % (timestamp, message['from'], message['body']))
 | 
			
		||||
            page += 1
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    parser = ArgumentParser()
 | 
			
		||||
    parser.add_argument("-q","--quiet", help="set logging to ERROR",
 | 
			
		||||
                        action="store_const",
 | 
			
		||||
                        dest="loglevel",
 | 
			
		||||
                        const=logging.ERROR,
 | 
			
		||||
                        default=logging.INFO)
 | 
			
		||||
    parser.add_argument("-d","--debug", help="set logging to DEBUG",
 | 
			
		||||
                        action="store_const",
 | 
			
		||||
                        dest="loglevel",
 | 
			
		||||
                        const=logging.DEBUG,
 | 
			
		||||
                        default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    parser.add_argument("-j", "--jid", dest="jid",
 | 
			
		||||
                        help="JID to use")
 | 
			
		||||
    parser.add_argument("-p", "--password", dest="password",
 | 
			
		||||
                        help="password to use")
 | 
			
		||||
 | 
			
		||||
    # Other options
 | 
			
		||||
    parser.add_argument("-r", "--remote-jid", dest="remote_jid",
 | 
			
		||||
                        help="Remote JID")
 | 
			
		||||
    parser.add_argument("--start", help="Start date", default='2017-09-20T12:00:00Z')
 | 
			
		||||
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=args.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if args.jid is None:
 | 
			
		||||
        args.jid = input("Username: ")
 | 
			
		||||
    if args.password is None:
 | 
			
		||||
        args.password = getpass("Password: ")
 | 
			
		||||
    if args.remote_jid is None:
 | 
			
		||||
        args.remote_jid = input("Remote JID: ")
 | 
			
		||||
    if args.start is None:
 | 
			
		||||
        args.start = input("Start time: ")
 | 
			
		||||
 | 
			
		||||
    xmpp = MAM(args.jid, args.password, args.remote_jid, args.start)
 | 
			
		||||
    xmpp.register_plugin('xep_0313')
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    xmpp.connect()
 | 
			
		||||
    xmpp.process(forever=False)
 | 
			
		||||
							
								
								
									
										120
									
								
								examples/markup.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										120
									
								
								examples/markup.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2010  Nathanael C. Fritz
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
 | 
			
		||||
import slixmpp
 | 
			
		||||
from slixmpp.plugins.xep_0394 import stanza as markup_stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EchoBot(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A simple Slixmpp bot that will echo messages it
 | 
			
		||||
    receives, along with a short thank you message.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid, password):
 | 
			
		||||
        slixmpp.ClientXMPP.__init__(self, jid, password)
 | 
			
		||||
 | 
			
		||||
        # The session_start event will be triggered when
 | 
			
		||||
        # the bot establishes its connection with the server
 | 
			
		||||
        # and the XML streams are ready for use. We want to
 | 
			
		||||
        # listen for this event so that we we can initialize
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
        # The message event is triggered whenever a message
 | 
			
		||||
        # stanza is received. Be aware that that includes
 | 
			
		||||
        # MUC messages and error messages.
 | 
			
		||||
        self.add_event_handler("message", self.message)
 | 
			
		||||
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
        Typical actions for the session_start event are
 | 
			
		||||
        requesting the roster and broadcasting an initial
 | 
			
		||||
        presence stanza.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            event -- An empty dictionary. The session_start
 | 
			
		||||
                     event does not provide any additional
 | 
			
		||||
                     data.
 | 
			
		||||
        """
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
    def message(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Process incoming message stanzas. Be aware that this also
 | 
			
		||||
        includes MUC messages and error messages. It is usually
 | 
			
		||||
        a good idea to check the messages's type before processing
 | 
			
		||||
        or sending replies.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            msg -- The received message stanza. See the documentation
 | 
			
		||||
                   for stanza objects and the Message stanza to see
 | 
			
		||||
                   how it may be used.
 | 
			
		||||
        """
 | 
			
		||||
        body = msg['body']
 | 
			
		||||
        new_body = self['xep_0394'].to_plain_text(body, msg['markup'])
 | 
			
		||||
        xhtml = self['xep_0394'].to_xhtml_im(body, msg['markup'])
 | 
			
		||||
        print('Plain text:', new_body)
 | 
			
		||||
        print('XHTML-IM:', xhtml['body'])
 | 
			
		||||
        message = msg.reply()
 | 
			
		||||
        message['body'] = new_body
 | 
			
		||||
        message['html']['body'] = xhtml['body']
 | 
			
		||||
        self.send(message)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    # Setup the command line arguments.
 | 
			
		||||
    parser = ArgumentParser(description=EchoBot.__doc__)
 | 
			
		||||
 | 
			
		||||
    # Output verbosity options.
 | 
			
		||||
    parser.add_argument("-q", "--quiet", help="set logging to ERROR",
 | 
			
		||||
                        action="store_const", dest="loglevel",
 | 
			
		||||
                        const=logging.ERROR, default=logging.INFO)
 | 
			
		||||
    parser.add_argument("-d", "--debug", help="set logging to DEBUG",
 | 
			
		||||
                        action="store_const", dest="loglevel",
 | 
			
		||||
                        const=logging.DEBUG, default=logging.INFO)
 | 
			
		||||
 | 
			
		||||
    # JID and password options.
 | 
			
		||||
    parser.add_argument("-j", "--jid", dest="jid",
 | 
			
		||||
                        help="JID to use")
 | 
			
		||||
    parser.add_argument("-p", "--password", dest="password",
 | 
			
		||||
                        help="password to use")
 | 
			
		||||
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Setup logging.
 | 
			
		||||
    logging.basicConfig(level=args.loglevel,
 | 
			
		||||
                        format='%(levelname)-8s %(message)s')
 | 
			
		||||
 | 
			
		||||
    if args.jid is None:
 | 
			
		||||
        args.jid = input("Username: ")
 | 
			
		||||
    if args.password is None:
 | 
			
		||||
        args.password = getpass("Password: ")
 | 
			
		||||
 | 
			
		||||
    # Setup the EchoBot and register plugins. Note that while plugins may
 | 
			
		||||
    # have interdependencies, the order in which you register them does
 | 
			
		||||
    # not matter.
 | 
			
		||||
    xmpp = EchoBot(args.jid, args.password)
 | 
			
		||||
    xmpp.register_plugin('xep_0030') # Service Discovery
 | 
			
		||||
    xmpp.register_plugin('xep_0199') # XMPP Ping
 | 
			
		||||
    xmpp.register_plugin('xep_0394') # Message Markup
 | 
			
		||||
 | 
			
		||||
    # Connect to the XMPP server and start processing XMPP stanzas.
 | 
			
		||||
    xmpp.connect()
 | 
			
		||||
    xmpp.process()
 | 
			
		||||
@@ -13,7 +13,6 @@ import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
from slixmpp.exceptions import IqError, IqTimeout
 | 
			
		||||
from slixmpp import asyncio
 | 
			
		||||
 | 
			
		||||
import slixmpp
 | 
			
		||||
 | 
			
		||||
@@ -38,8 +37,7 @@ class PingTest(slixmpp.ClientXMPP):
 | 
			
		||||
        # our roster.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +54,7 @@ class PingTest(slixmpp.ClientXMPP):
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            rtt = yield from self['xep_0199'].ping(self.pingjid,
 | 
			
		||||
            rtt = await self['xep_0199'].ping(self.pingjid,
 | 
			
		||||
                                                   timeout=10)
 | 
			
		||||
            logging.info("Success! RTT: %s", rtt)
 | 
			
		||||
        except IqError as e:
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
import slixmpp
 | 
			
		||||
from slixmpp.exceptions import XMPPError
 | 
			
		||||
from slixmpp.xmlstream import ET, tostring
 | 
			
		||||
@@ -21,7 +20,7 @@ class PubsubClient(slixmpp.ClientXMPP):
 | 
			
		||||
        self.register_plugin('xep_0059')
 | 
			
		||||
        self.register_plugin('xep_0060')
 | 
			
		||||
 | 
			
		||||
        self.actions = ['nodes', 'create', 'delete',
 | 
			
		||||
        self.actions = ['nodes', 'create', 'delete', 'get_configure',
 | 
			
		||||
                        'publish', 'get', 'retract',
 | 
			
		||||
                        'purge', 'subscribe', 'unsubscribe']
 | 
			
		||||
 | 
			
		||||
@@ -32,80 +31,86 @@ class PubsubClient(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        self.add_event_handler('session_start', self.start)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        self.get_roster()
 | 
			
		||||
        self.send_presence()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            yield from getattr(self, self.action)()
 | 
			
		||||
            await getattr(self, self.action)()
 | 
			
		||||
        except:
 | 
			
		||||
            logging.error('Could not execute: %s', self.action)
 | 
			
		||||
            logging.exception('Could not execute %s:', self.action)
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
    def nodes(self):
 | 
			
		||||
    async def nodes(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
 | 
			
		||||
            result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node)
 | 
			
		||||
            for item in result['disco_items']['items']:
 | 
			
		||||
                logging.info('  - %s', str(item))
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not retrieve node list: %s', error.format())
 | 
			
		||||
 | 
			
		||||
    def create(self):
 | 
			
		||||
    async def create(self):
 | 
			
		||||
        try:
 | 
			
		||||
            yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
 | 
			
		||||
            await self['xep_0060'].create_node(self.pubsub_server, self.node)
 | 
			
		||||
            logging.info('Created node %s', self.node)
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not create node %s: %s', self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
    async def delete(self):
 | 
			
		||||
        try:
 | 
			
		||||
            yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
 | 
			
		||||
            await self['xep_0060'].delete_node(self.pubsub_server, self.node)
 | 
			
		||||
            logging.info('Deleted node %s', self.node)
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not delete node %s: %s', self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    def publish(self):
 | 
			
		||||
    async def get_configure(self):
 | 
			
		||||
        try:
 | 
			
		||||
            configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node)
 | 
			
		||||
            logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form'])
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    async def publish(self):
 | 
			
		||||
        payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
 | 
			
		||||
        try:
 | 
			
		||||
            result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
 | 
			
		||||
            result = await self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
 | 
			
		||||
            logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not publish to %s: %s', self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    def get(self):
 | 
			
		||||
    async def get(self):
 | 
			
		||||
        try:
 | 
			
		||||
            result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
 | 
			
		||||
            result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
 | 
			
		||||
            for item in result['pubsub']['items']['substanzas']:
 | 
			
		||||
                logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    def retract(self):
 | 
			
		||||
    async def retract(self):
 | 
			
		||||
        try:
 | 
			
		||||
            yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
 | 
			
		||||
            await self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
 | 
			
		||||
            logging.info('Retracted item %s from node %s', self.data, self.node)
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    def purge(self):
 | 
			
		||||
    async def purge(self):
 | 
			
		||||
        try:
 | 
			
		||||
            yield from self['xep_0060'].purge(self.pubsub_server, self.node)
 | 
			
		||||
            await self['xep_0060'].purge(self.pubsub_server, self.node)
 | 
			
		||||
            logging.info('Purged all items from node %s', self.node)
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not purge items from node %s: %s', self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    def subscribe(self):
 | 
			
		||||
    async def subscribe(self):
 | 
			
		||||
        try:
 | 
			
		||||
            iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
 | 
			
		||||
            iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node)
 | 
			
		||||
            subscription = iq['pubsub']['subscription']
 | 
			
		||||
            logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
 | 
			
		||||
 | 
			
		||||
    def unsubscribe(self):
 | 
			
		||||
    async def unsubscribe(self):
 | 
			
		||||
        try:
 | 
			
		||||
            yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
 | 
			
		||||
            await self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
 | 
			
		||||
            logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
 | 
			
		||||
        except XMPPError as error:
 | 
			
		||||
            logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
 | 
			
		||||
@@ -118,7 +123,7 @@ if __name__ == '__main__':
 | 
			
		||||
    parser = ArgumentParser()
 | 
			
		||||
    parser.version = '%%prog 0.1'
 | 
			
		||||
    parser.usage = "Usage: %%prog [options] <jid> " + \
 | 
			
		||||
                             'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \
 | 
			
		||||
                             'nodes|create|delete|get_configure|purge|subscribe|unsubscribe|publish|retract|get' + \
 | 
			
		||||
                             ' [<node> <data>]'
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("-q","--quiet", help="set logging to ERROR",
 | 
			
		||||
@@ -139,7 +144,7 @@ if __name__ == '__main__':
 | 
			
		||||
                        help="password to use")
 | 
			
		||||
 | 
			
		||||
    parser.add_argument("server")
 | 
			
		||||
    parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
 | 
			
		||||
    parser.add_argument("action", choices=["nodes", "create", "delete", "get_configure", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
 | 
			
		||||
    parser.add_argument("node", nargs='?')
 | 
			
		||||
    parser.add_argument("data", nargs='?')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ class RegisterBot(slixmpp.ClientXMPP):
 | 
			
		||||
        # We're only concerned about registering, so nothing more to do here.
 | 
			
		||||
        self.disconnect()
 | 
			
		||||
 | 
			
		||||
    def register(self, iq):
 | 
			
		||||
    async def register(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Fill out and submit a registration form.
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
 | 
			
		||||
        resp['register']['password'] = self.password
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            yield from resp.send()
 | 
			
		||||
            await resp.send()
 | 
			
		||||
            logging.info("Account created for %s!" % self.boundjid)
 | 
			
		||||
        except IqError as e:
 | 
			
		||||
            logging.error("Could not register account: %s" %
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
 | 
			
		||||
        self.received = set()
 | 
			
		||||
        self.presences_received = asyncio.Event()
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +56,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
 | 
			
		||||
            future.set_result(None)
 | 
			
		||||
        try:
 | 
			
		||||
            self.get_roster(callback=callback)
 | 
			
		||||
            yield from future
 | 
			
		||||
            await future
 | 
			
		||||
        except IqError as err:
 | 
			
		||||
            print('Error: %s' % err.iq['error']['condition'])
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
@@ -66,7 +65,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        print('Waiting for presence updates...\n')
 | 
			
		||||
        yield from asyncio.sleep(10)
 | 
			
		||||
        await asyncio.sleep(10)
 | 
			
		||||
 | 
			
		||||
        print('Roster for %s' % self.boundjid.bare)
 | 
			
		||||
        groups = self.client_roster.groups()
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
import logging
 | 
			
		||||
from getpass import getpass
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
@@ -36,8 +35,7 @@ class S5BSender(slixmpp.ClientXMPP):
 | 
			
		||||
        # and the XML streams are ready for use.
 | 
			
		||||
        self.add_event_handler("session_start", self.start)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -53,14 +51,14 @@ class S5BSender(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # Open the S5B stream in which to write to.
 | 
			
		||||
            proxy = yield from self['xep_0065'].handshake(self.receiver)
 | 
			
		||||
            proxy = await self['xep_0065'].handshake(self.receiver)
 | 
			
		||||
 | 
			
		||||
            # Send the entire file.
 | 
			
		||||
            while True:
 | 
			
		||||
                data = self.file.read(1048576)
 | 
			
		||||
                if not data:
 | 
			
		||||
                    break
 | 
			
		||||
                yield from proxy.write(data)
 | 
			
		||||
                await proxy.write(data)
 | 
			
		||||
 | 
			
		||||
            # And finally close the stream.
 | 
			
		||||
            proxy.transport.write_eof()
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ from argparse import ArgumentParser
 | 
			
		||||
 | 
			
		||||
import slixmpp
 | 
			
		||||
from slixmpp.exceptions import XMPPError
 | 
			
		||||
from slixmpp import asyncio
 | 
			
		||||
 | 
			
		||||
class AvatarSetter(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
@@ -33,8 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
 | 
			
		||||
 | 
			
		||||
        self.filepath = filepath
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def start(self, event):
 | 
			
		||||
    async def start(self, event):
 | 
			
		||||
        """
 | 
			
		||||
        Process the session_start event.
 | 
			
		||||
 | 
			
		||||
@@ -68,20 +66,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
 | 
			
		||||
        used_xep84 = False
 | 
			
		||||
 | 
			
		||||
        print('Publish XEP-0084 avatar data')
 | 
			
		||||
        result = yield from self['xep_0084'].publish_avatar(avatar)
 | 
			
		||||
        result = await self['xep_0084'].publish_avatar(avatar)
 | 
			
		||||
        if isinstance(result, XMPPError):
 | 
			
		||||
            print('Could not publish XEP-0084 avatar')
 | 
			
		||||
        else:
 | 
			
		||||
            used_xep84 = True
 | 
			
		||||
 | 
			
		||||
        print('Update vCard with avatar')
 | 
			
		||||
        result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
 | 
			
		||||
        result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
 | 
			
		||||
        if isinstance(result, XMPPError):
 | 
			
		||||
            print('Could not set vCard avatar')
 | 
			
		||||
 | 
			
		||||
        if used_xep84:
 | 
			
		||||
            print('Advertise XEP-0084 avatar metadata')
 | 
			
		||||
            result = yield from self['xep_0084'].publish_avatar_metadata([
 | 
			
		||||
            result = await self['xep_0084'].publish_avatar_metadata([
 | 
			
		||||
                {'id': avatar_id,
 | 
			
		||||
                 'type': avatar_type,
 | 
			
		||||
                 'bytes': avatar_bytes}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								setup.py
									
									
									
									
									
								
							@@ -20,8 +20,7 @@ from run_tests import TestCommand
 | 
			
		||||
from slixmpp.version import __version__
 | 
			
		||||
 | 
			
		||||
VERSION = __version__
 | 
			
		||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, '
 | 
			
		||||
               'Google Talk, etc).')
 | 
			
		||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).')
 | 
			
		||||
with open('README.rst', encoding='utf8') as readme:
 | 
			
		||||
    LONG_DESCRIPTION = readme.read()
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +28,10 @@ CLASSIFIERS = [
 | 
			
		||||
    'Intended Audience :: Developers',
 | 
			
		||||
    'License :: OSI Approved :: MIT License',
 | 
			
		||||
    'Programming Language :: Python',
 | 
			
		||||
    'Programming Language :: Python :: 3.4',
 | 
			
		||||
    'Programming Language :: Python :: 3.5',
 | 
			
		||||
    'Programming Language :: Python :: 3.6',
 | 
			
		||||
    'Programming Language :: Python :: 3.7',
 | 
			
		||||
    'Topic :: Internet :: XMPP',
 | 
			
		||||
    'Topic :: Software Development :: Libraries :: Python Modules',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -76,12 +78,12 @@ setup(
 | 
			
		||||
    long_description=LONG_DESCRIPTION,
 | 
			
		||||
    author='Florent Le Coz',
 | 
			
		||||
    author_email='louiz@louiz.org',
 | 
			
		||||
    url='https://dev.louiz.org/projects/slixmpp',
 | 
			
		||||
    url='https://lab.louiz.org/poezio/slixmpp',
 | 
			
		||||
    license='MIT',
 | 
			
		||||
    platforms=['any'],
 | 
			
		||||
    packages=packages,
 | 
			
		||||
    ext_modules=ext_modules,
 | 
			
		||||
    install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
 | 
			
		||||
    install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'],
 | 
			
		||||
    classifiers=CLASSIFIERS,
 | 
			
		||||
    cmdclass={'test': TestCommand}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,13 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
if hasattr(asyncio, 'sslproto'): # no ssl proto: very old asyncio = no need for this
 | 
			
		||||
    asyncio.sslproto._is_sslproto_available=lambda: False
 | 
			
		||||
import logging
 | 
			
		||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
# Required for python < 3.7 to use the old ssl implementation
 | 
			
		||||
# and manage to do starttls as an unintended side effect
 | 
			
		||||
asyncio.sslproto._is_sslproto_available = lambda: False
 | 
			
		||||
 | 
			
		||||
from slixmpp.stanza import Message, Presence, Iq
 | 
			
		||||
from slixmpp.jid import JID, InvalidJID
 | 
			
		||||
 
 | 
			
		||||
@@ -104,12 +104,12 @@ class BaseXMPP(XMLStream):
 | 
			
		||||
        #: :attr:`use_message_ids` to `True` will assign all outgoing
 | 
			
		||||
        #: messages an ID. Some plugin features require enabling
 | 
			
		||||
        #: this option.
 | 
			
		||||
        self.use_message_ids = False
 | 
			
		||||
        self.use_message_ids = True
 | 
			
		||||
 | 
			
		||||
        #: Presence updates may optionally be tagged with ID values.
 | 
			
		||||
        #: Setting :attr:`use_message_ids` to `True` will assign all
 | 
			
		||||
        #: outgoing messages an ID.
 | 
			
		||||
        self.use_presence_ids = False
 | 
			
		||||
        self.use_presence_ids = True
 | 
			
		||||
 | 
			
		||||
        #: The API registry is a way to process callbacks based on
 | 
			
		||||
        #: JID+node combinations. Each callback in the registry is
 | 
			
		||||
 
 | 
			
		||||
@@ -255,7 +255,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
                orig_cb(resp)
 | 
			
		||||
            callback = wrapped
 | 
			
		||||
 | 
			
		||||
        iq.send(callback, timeout, timeout_callback)
 | 
			
		||||
        return iq.send(callback, timeout, timeout_callback)
 | 
			
		||||
 | 
			
		||||
    def _reset_connection_state(self, event=None):
 | 
			
		||||
        #TODO: Use stream state here
 | 
			
		||||
@@ -265,8 +265,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
        self.bindfail = False
 | 
			
		||||
        self.features = set()
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _handle_stream_features(self, features):
 | 
			
		||||
    async def _handle_stream_features(self, features):
 | 
			
		||||
        """Process the received stream features.
 | 
			
		||||
 | 
			
		||||
        :param features: The features stanza.
 | 
			
		||||
@@ -275,7 +274,7 @@ class ClientXMPP(BaseXMPP):
 | 
			
		||||
            if name in features['features']:
 | 
			
		||||
                handler, restart = self._stream_feature_handlers[name]
 | 
			
		||||
                if asyncio.iscoroutinefunction(handler):
 | 
			
		||||
                    result = yield from handler(features)
 | 
			
		||||
                    result = await handler(features)
 | 
			
		||||
                else:
 | 
			
		||||
                    result = handler(features)
 | 
			
		||||
                if result and restart:
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,7 @@ class FeatureBind(BasePlugin):
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.Bind)
 | 
			
		||||
        register_stanza_plugin(StreamFeatures, stanza.Bind)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _handle_bind_resource(self, features):
 | 
			
		||||
    async def _handle_bind_resource(self, features):
 | 
			
		||||
        """
 | 
			
		||||
        Handle requesting a specific resource.
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +50,7 @@ class FeatureBind(BasePlugin):
 | 
			
		||||
        if self.xmpp.requested_jid.resource:
 | 
			
		||||
            iq['bind']['resource'] = self.xmpp.requested_jid.resource
 | 
			
		||||
 | 
			
		||||
        yield from iq.send(callback=self._on_bind_response)
 | 
			
		||||
        await iq.send(callback=self._on_bind_response)
 | 
			
		||||
 | 
			
		||||
    def _on_bind_response(self, response):
 | 
			
		||||
        self.xmpp.boundjid = JID(response['bind']['jid'])
 | 
			
		||||
 
 | 
			
		||||
@@ -97,12 +97,9 @@ class FeatureMechanisms(BasePlugin):
 | 
			
		||||
                jid = self.xmpp.requested_jid.bare
 | 
			
		||||
                result[value] = creds.get('email', jid)
 | 
			
		||||
            elif value == 'channel_binding':
 | 
			
		||||
                if hasattr(self.xmpp.socket, 'get_channel_binding'):
 | 
			
		||||
                if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
 | 
			
		||||
                    result[value] = self.xmpp.socket.get_channel_binding()
 | 
			
		||||
                else:
 | 
			
		||||
                    log.debug("Channel binding not supported.")
 | 
			
		||||
                    log.debug("Use Python 3.3+ for channel binding and " + \
 | 
			
		||||
                              "SCRAM-SHA-1-PLUS support")
 | 
			
		||||
                    result[value] = None
 | 
			
		||||
            elif value == 'host':
 | 
			
		||||
                result[value] = creds.get('host', self.xmpp.requested_jid.domain)
 | 
			
		||||
@@ -122,7 +119,7 @@ class FeatureMechanisms(BasePlugin):
 | 
			
		||||
            if value == 'encrypted':
 | 
			
		||||
                if 'starttls' in self.xmpp.features:
 | 
			
		||||
                    result[value] = True
 | 
			
		||||
                elif isinstance(self.xmpp.socket, ssl.SSLSocket):
 | 
			
		||||
                elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
 | 
			
		||||
                    result[value] = True
 | 
			
		||||
                else:
 | 
			
		||||
                    result[value] = False
 | 
			
		||||
 
 | 
			
		||||
@@ -35,18 +35,22 @@ class FeatureSession(BasePlugin):
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.Session)
 | 
			
		||||
        register_stanza_plugin(StreamFeatures, stanza.Session)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _handle_start_session(self, features):
 | 
			
		||||
    async def _handle_start_session(self, features):
 | 
			
		||||
        """
 | 
			
		||||
        Handle the start of the session.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            feature -- The stream features element.
 | 
			
		||||
        """
 | 
			
		||||
        if features['session']['optional']:
 | 
			
		||||
            self.xmpp.sessionstarted = True
 | 
			
		||||
            self.xmpp.event('session_start')
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq.enable('session')
 | 
			
		||||
        yield from iq.send(callback=self._on_start_session_response)
 | 
			
		||||
        await iq.send(callback=self._on_start_session_response)
 | 
			
		||||
 | 
			
		||||
    def _on_start_session_response(self, response):
 | 
			
		||||
        self.xmpp.features.add('session')
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.xmlstream import ElementBase
 | 
			
		||||
from slixmpp.xmlstream import ElementBase, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Session(ElementBase):
 | 
			
		||||
@@ -16,5 +16,19 @@ class Session(ElementBase):
 | 
			
		||||
 | 
			
		||||
    name = 'session'
 | 
			
		||||
    namespace = 'urn:ietf:params:xml:ns:xmpp-session'
 | 
			
		||||
    interfaces = set()
 | 
			
		||||
    interfaces = {'optional'}
 | 
			
		||||
    plugin_attrib = 'session'
 | 
			
		||||
 | 
			
		||||
    def get_optional(self):
 | 
			
		||||
        return self.xml.find('{%s}optional' % self.namespace) is not None
 | 
			
		||||
 | 
			
		||||
    def set_optional(self, value):
 | 
			
		||||
        if value:
 | 
			
		||||
            optional = ET.Element('{%s}optional' % self.namespace)
 | 
			
		||||
            self.xml.append(optional)
 | 
			
		||||
        else:
 | 
			
		||||
            self.del_optional()
 | 
			
		||||
 | 
			
		||||
    def del_optional(self):
 | 
			
		||||
        optional = self.xml.find('{%s}optional' % self.namespace)
 | 
			
		||||
        self.xml.remove(optional)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ from slixmpp.stanza import StreamFeatures
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
from slixmpp.xmlstream.matcher import MatchXPath
 | 
			
		||||
from slixmpp.xmlstream.handler import Callback
 | 
			
		||||
from slixmpp.xmlstream.handler import CoroutineCallback
 | 
			
		||||
from slixmpp.features.feature_starttls import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -28,7 +28,7 @@ class FeatureSTARTTLS(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('STARTTLS Proceed',
 | 
			
		||||
                CoroutineCallback('STARTTLS Proceed',
 | 
			
		||||
                        MatchXPath(stanza.Proceed.tag_name()),
 | 
			
		||||
                        self._handle_starttls_proceed,
 | 
			
		||||
                        instream=True))
 | 
			
		||||
@@ -58,8 +58,8 @@ class FeatureSTARTTLS(BasePlugin):
 | 
			
		||||
            self.xmpp.send(features['starttls'])
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
    def _handle_starttls_proceed(self, proceed):
 | 
			
		||||
    async def _handle_starttls_proceed(self, proceed):
 | 
			
		||||
        """Restart the XML stream when TLS is accepted."""
 | 
			
		||||
        log.debug("Starting TLS")
 | 
			
		||||
        if self.xmpp.start_tls():
 | 
			
		||||
        if await self.xmpp.start_tls():
 | 
			
		||||
            self.xmpp.features.add('starttls')
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import socket
 | 
			
		||||
 | 
			
		||||
from copy import deepcopy
 | 
			
		||||
from functools import lru_cache
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
 | 
			
		||||
 | 
			
		||||
@@ -71,7 +72,7 @@ def _parse_jid(data):
 | 
			
		||||
    return node, domain, resource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_node(node):
 | 
			
		||||
def _validate_node(node: Optional[str]):
 | 
			
		||||
    """Validate the local, or username, portion of a JID.
 | 
			
		||||
 | 
			
		||||
    :raises InvalidJID:
 | 
			
		||||
@@ -93,7 +94,7 @@ def _validate_node(node):
 | 
			
		||||
    return node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_domain(domain):
 | 
			
		||||
def _validate_domain(domain: str):
 | 
			
		||||
    """Validate the domain portion of a JID.
 | 
			
		||||
 | 
			
		||||
    IP literal addresses are left as-is, if valid. Domain names
 | 
			
		||||
@@ -152,7 +153,7 @@ def _validate_domain(domain):
 | 
			
		||||
    return domain
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_resource(resource):
 | 
			
		||||
def _validate_resource(resource: Optional[str]):
 | 
			
		||||
    """Validate the resource portion of a JID.
 | 
			
		||||
 | 
			
		||||
    :raises InvalidJID:
 | 
			
		||||
@@ -174,7 +175,7 @@ def _validate_resource(resource):
 | 
			
		||||
    return resource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _unescape_node(node):
 | 
			
		||||
def _unescape_node(node: str):
 | 
			
		||||
    """Unescape a local portion of a JID.
 | 
			
		||||
 | 
			
		||||
    .. note::
 | 
			
		||||
@@ -199,7 +200,11 @@ def _unescape_node(node):
 | 
			
		||||
    return ''.join(unescaped)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _format_jid(local=None, domain=None, resource=None):
 | 
			
		||||
def _format_jid(
 | 
			
		||||
        local: Optional[str] = None,
 | 
			
		||||
        domain: Optional[str] = None,
 | 
			
		||||
        resource: Optional[str] = None,
 | 
			
		||||
    ):
 | 
			
		||||
    """Format the given JID components into a full or bare JID.
 | 
			
		||||
 | 
			
		||||
    :param string local: Optional. The local portion of the JID.
 | 
			
		||||
@@ -237,12 +242,17 @@ class UnescapedJID:
 | 
			
		||||
 | 
			
		||||
    __slots__ = ('_node', '_domain', '_resource')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, node, domain, resource):
 | 
			
		||||
    def __init__(
 | 
			
		||||
            self,
 | 
			
		||||
            node: Optional[str],
 | 
			
		||||
            domain: Optional[str],
 | 
			
		||||
            resource: Optional[str],
 | 
			
		||||
        ):
 | 
			
		||||
        self._node = node
 | 
			
		||||
        self._domain = domain
 | 
			
		||||
        self._resource = resource
 | 
			
		||||
 | 
			
		||||
    def __getattribute__(self, name):
 | 
			
		||||
    def __getattribute__(self, name: str):
 | 
			
		||||
        """Retrieve the given JID component.
 | 
			
		||||
 | 
			
		||||
        :param name: one of: user, server, domain, resource,
 | 
			
		||||
@@ -301,7 +311,7 @@ class JID:
 | 
			
		||||
 | 
			
		||||
    __slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid=None):
 | 
			
		||||
    def __init__(self, jid: Optional[str] = None):
 | 
			
		||||
        if not jid:
 | 
			
		||||
            self._node = ''
 | 
			
		||||
            self._domain = ''
 | 
			
		||||
@@ -346,30 +356,10 @@ class JID:
 | 
			
		||||
    def node(self):
 | 
			
		||||
        return self._node
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def user(self):
 | 
			
		||||
        return self._node
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def local(self):
 | 
			
		||||
        return self._node
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def username(self):
 | 
			
		||||
        return self._node
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def domain(self):
 | 
			
		||||
        return self._domain
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def server(self):
 | 
			
		||||
        return self._domain
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def host(self):
 | 
			
		||||
        return self._domain
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def resource(self):
 | 
			
		||||
        return self._resource
 | 
			
		||||
@@ -382,47 +372,18 @@ class JID:
 | 
			
		||||
    def full(self):
 | 
			
		||||
        return self._full
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def jid(self):
 | 
			
		||||
        return self._full
 | 
			
		||||
 | 
			
		||||
    @node.setter
 | 
			
		||||
    def node(self, value):
 | 
			
		||||
        self._node = _validate_node(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @user.setter
 | 
			
		||||
    def user(self, value):
 | 
			
		||||
        self._node = _validate_node(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @local.setter
 | 
			
		||||
    def local(self, value):
 | 
			
		||||
        self._node = _validate_node(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @username.setter
 | 
			
		||||
    def username(self, value):
 | 
			
		||||
    def node(self, value: str):
 | 
			
		||||
        self._node = _validate_node(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @domain.setter
 | 
			
		||||
    def domain(self, value):
 | 
			
		||||
        self._domain = _validate_domain(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @server.setter
 | 
			
		||||
    def server(self, value):
 | 
			
		||||
        self._domain = _validate_domain(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @host.setter
 | 
			
		||||
    def host(self, value):
 | 
			
		||||
    def domain(self, value: str):
 | 
			
		||||
        self._domain = _validate_domain(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @bare.setter
 | 
			
		||||
    def bare(self, value):
 | 
			
		||||
    def bare(self, value: str):
 | 
			
		||||
        node, domain, resource = _parse_jid(value)
 | 
			
		||||
        assert not resource
 | 
			
		||||
        self._node = node
 | 
			
		||||
@@ -430,19 +391,23 @@ class JID:
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @resource.setter
 | 
			
		||||
    def resource(self, value):
 | 
			
		||||
    def resource(self, value: str):
 | 
			
		||||
        self._resource = _validate_resource(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @full.setter
 | 
			
		||||
    def full(self, value):
 | 
			
		||||
    def full(self, value: str):
 | 
			
		||||
        self._node, self._domain, self._resource = _parse_jid(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @jid.setter
 | 
			
		||||
    def jid(self, value):
 | 
			
		||||
        self._node, self._domain, self._resource = _parse_jid(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
    user = node
 | 
			
		||||
    local = node
 | 
			
		||||
    username = node
 | 
			
		||||
 | 
			
		||||
    server = domain
 | 
			
		||||
    host = domain
 | 
			
		||||
 | 
			
		||||
    jid = full
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        """Use the full JID as the string value."""
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										47
									
								
								slixmpp/plugins/google/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								slixmpp/plugins/google/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.base import register_plugin, BasePlugin
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.google.gmail import Gmail
 | 
			
		||||
from slixmpp.plugins.google.auth import GoogleAuth
 | 
			
		||||
from slixmpp.plugins.google.settings import GoogleSettings
 | 
			
		||||
from slixmpp.plugins.google.nosave import GoogleNoSave
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Google(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Google: Custom GTalk Features
 | 
			
		||||
 | 
			
		||||
    Also see: <https://developers.google.com/talk/jep_extensions/extensions>
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'google'
 | 
			
		||||
    description = 'Google: Custom GTalk Features'
 | 
			
		||||
    dependencies = set([
 | 
			
		||||
        'gmail',
 | 
			
		||||
        'google_settings',
 | 
			
		||||
        'google_nosave',
 | 
			
		||||
        'google_auth'
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, attr):
 | 
			
		||||
        if attr in ('settings', 'nosave', 'auth'):
 | 
			
		||||
            return self.xmpp['google_%s' % attr]
 | 
			
		||||
        elif attr == 'gmail':
 | 
			
		||||
            return self.xmpp['gmail']
 | 
			
		||||
        else:
 | 
			
		||||
            raise KeyError(attr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(Gmail)
 | 
			
		||||
register_plugin(GoogleAuth)
 | 
			
		||||
register_plugin(GoogleSettings)
 | 
			
		||||
register_plugin(GoogleNoSave)
 | 
			
		||||
register_plugin(Google)
 | 
			
		||||
							
								
								
									
										10
									
								
								slixmpp/plugins/google/auth/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								slixmpp/plugins/google/auth/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.google.auth import stanza
 | 
			
		||||
from slixmpp.plugins.google.auth.auth import GoogleAuth
 | 
			
		||||
							
								
								
									
										47
									
								
								slixmpp/plugins/google/auth/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								slixmpp/plugins/google/auth/auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
from slixmpp.plugins.google.auth import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GoogleAuth(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Google: Auth Extensions (JID Domain Discovery, OAuth2)
 | 
			
		||||
 | 
			
		||||
    Also see:
 | 
			
		||||
        <https://developers.google.com/talk/jep_extensions/jid_domain_change>
 | 
			
		||||
        <https://developers.google.com/talk/jep_extensions/oauth>
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'google_auth'
 | 
			
		||||
    description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)'
 | 
			
		||||
    dependencies = set(['feature_mechanisms'])
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga'
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth,
 | 
			
		||||
                               stanza.GoogleAuth)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.add_filter('out', self._auth)
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        self.xmpp.del_filter('out', self._auth)
 | 
			
		||||
 | 
			
		||||
    def _auth(self, stanza):
 | 
			
		||||
        if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth):
 | 
			
		||||
            stanza.stream = self.xmpp
 | 
			
		||||
            stanza['google']['client_uses_full_bind_result'] = True
 | 
			
		||||
            if stanza['mechanism'] == 'X-OAUTH2':
 | 
			
		||||
                stanza['google']['service'] = 'oauth2'
 | 
			
		||||
            print(stanza)
 | 
			
		||||
        return stanza
 | 
			
		||||
							
								
								
									
										10
									
								
								slixmpp/plugins/google/gmail/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								slixmpp/plugins/google/gmail/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.google.gmail import stanza
 | 
			
		||||
from slixmpp.plugins.google.gmail.notifications import Gmail
 | 
			
		||||
							
								
								
									
										101
									
								
								slixmpp/plugins/google/gmail/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								slixmpp/plugins/google/gmail/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GmailQuery(ElementBase):
 | 
			
		||||
    namespace = 'google:mail:notify'
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    plugin_attrib = 'gmail'
 | 
			
		||||
    interfaces = set(['newer_than_time', 'newer_than_tid', 'search'])
 | 
			
		||||
 | 
			
		||||
    def get_search(self):
 | 
			
		||||
        return self._get_attr('q', '')
 | 
			
		||||
 | 
			
		||||
    def set_search(self, search):
 | 
			
		||||
        self._set_attr('q', search)
 | 
			
		||||
 | 
			
		||||
    def del_search(self):
 | 
			
		||||
        self._del_attr('q')
 | 
			
		||||
 | 
			
		||||
    def get_newer_than_time(self):
 | 
			
		||||
        return self._get_attr('newer-than-time', '')
 | 
			
		||||
 | 
			
		||||
    def set_newer_than_time(self, value):
 | 
			
		||||
        self._set_attr('newer-than-time', value)
 | 
			
		||||
 | 
			
		||||
    def del_newer_than_time(self):
 | 
			
		||||
        self._del_attr('newer-than-time')
 | 
			
		||||
 | 
			
		||||
    def get_newer_than_tid(self):
 | 
			
		||||
        return self._get_attr('newer-than-tid', '')
 | 
			
		||||
 | 
			
		||||
    def set_newer_than_tid(self, value):
 | 
			
		||||
        self._set_attr('newer-than-tid', value)
 | 
			
		||||
 | 
			
		||||
    def del_newer_than_tid(self):
 | 
			
		||||
        self._del_attr('newer-than-tid')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MailBox(ElementBase):
 | 
			
		||||
    namespace = 'google:mail:notify'
 | 
			
		||||
    name = 'mailbox'
 | 
			
		||||
    plugin_attrib = 'gmail_messages'
 | 
			
		||||
    interfaces = set(['result_time', 'url', 'matched', 'estimate'])
 | 
			
		||||
 | 
			
		||||
    def get_matched(self):
 | 
			
		||||
        return self._get_attr('total-matched', '')
 | 
			
		||||
 | 
			
		||||
    def get_estimate(self):
 | 
			
		||||
        return self._get_attr('total-estimate', '') == '1'
 | 
			
		||||
 | 
			
		||||
    def get_result_time(self):
 | 
			
		||||
        return self._get_attr('result-time', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MailThread(ElementBase):
 | 
			
		||||
    namespace = 'google:mail:notify'
 | 
			
		||||
    name = 'mail-thread-info'
 | 
			
		||||
    plugin_attrib = 'thread'
 | 
			
		||||
    plugin_multi_attrib = 'threads'
 | 
			
		||||
    interfaces = set(['tid', 'participation', 'messages', 'date',
 | 
			
		||||
                      'senders', 'url', 'labels', 'subject', 'snippet'])
 | 
			
		||||
    sub_interfaces = set(['labels', 'subject', 'snippet'])
 | 
			
		||||
 | 
			
		||||
    def get_senders(self):
 | 
			
		||||
        result = []
 | 
			
		||||
        senders = self.xml.findall('{%s}senders/{%s}sender' % (
 | 
			
		||||
            self.namespace, self.namespace))
 | 
			
		||||
 | 
			
		||||
        for sender in senders:
 | 
			
		||||
            result.append(MailSender(xml=sender))
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MailSender(ElementBase):
 | 
			
		||||
    namespace = 'google:mail:notify'
 | 
			
		||||
    name = 'sender'
 | 
			
		||||
    plugin_attrib = name
 | 
			
		||||
    interfaces = set(['address', 'name', 'originator', 'unread'])
 | 
			
		||||
 | 
			
		||||
    def get_originator(self):
 | 
			
		||||
        return self.xml.attrib.get('originator', '0') == '1'
 | 
			
		||||
 | 
			
		||||
    def get_unread(self):
 | 
			
		||||
        return self.xml.attrib.get('unread', '0') == '1'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NewMail(ElementBase):
 | 
			
		||||
    namespace = 'google:mail:notify'
 | 
			
		||||
    name = 'new-mail'
 | 
			
		||||
    plugin_attrib = 'gmail_notification'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(MailBox, MailThread, iterable=True)
 | 
			
		||||
							
								
								
									
										10
									
								
								slixmpp/plugins/google/nosave/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								slixmpp/plugins/google/nosave/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.google.nosave import stanza
 | 
			
		||||
from slixmpp.plugins.google.nosave.nosave import GoogleNoSave
 | 
			
		||||
							
								
								
									
										78
									
								
								slixmpp/plugins/google/nosave/nosave.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								slixmpp/plugins/google/nosave/nosave.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.stanza import Iq, Message
 | 
			
		||||
from slixmpp.xmlstream.handler import Callback
 | 
			
		||||
from slixmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
from slixmpp.plugins.google.nosave import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GoogleNoSave(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    Google: Off the Record Chats
 | 
			
		||||
 | 
			
		||||
    NOTE: This is NOT an encryption method.
 | 
			
		||||
 | 
			
		||||
    Also see <https://developers.google.com/talk/jep_extensions/otr>.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'google_nosave'
 | 
			
		||||
    description = 'Google: Off the Record Chats'
 | 
			
		||||
    dependencies = set(['google_settings'])
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        register_stanza_plugin(Message, stanza.NoSave)
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.NoSaveQuery)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('Google Nosave',
 | 
			
		||||
                    StanzaPath('iq@type=set/google_nosave'),
 | 
			
		||||
                    self._handle_nosave_change))
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        self.xmpp.remove_handler('Google Nosave')
 | 
			
		||||
 | 
			
		||||
    def enable(self, jid=None, timeout=None, callback=None):
 | 
			
		||||
        if jid is None:
 | 
			
		||||
            self.xmpp['google_settings'].update({'archiving_enabled': False},
 | 
			
		||||
                    timeout=timeout, callback=callback)
 | 
			
		||||
        else:
 | 
			
		||||
            iq = self.xmpp.Iq()
 | 
			
		||||
            iq['type'] = 'set'
 | 
			
		||||
            iq['google_nosave']['item']['jid'] = jid
 | 
			
		||||
            iq['google_nosave']['item']['value'] = True
 | 
			
		||||
            return iq.send(timeout=timeout, callback=callback)
 | 
			
		||||
 | 
			
		||||
    def disable(self, jid=None, timeout=None, callback=None):
 | 
			
		||||
        if jid is None:
 | 
			
		||||
            self.xmpp['google_settings'].update({'archiving_enabled': True},
 | 
			
		||||
                    timeout=timeout, callback=callback)
 | 
			
		||||
        else:
 | 
			
		||||
            iq = self.xmpp.Iq()
 | 
			
		||||
            iq['type'] = 'set'
 | 
			
		||||
            iq['google_nosave']['item']['jid'] = jid
 | 
			
		||||
            iq['google_nosave']['item']['value'] = False
 | 
			
		||||
            return iq.send(timeout=timeout, callback=callback)
 | 
			
		||||
 | 
			
		||||
    def get(self, timeout=None, callback=None):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq.enable('google_nosave')
 | 
			
		||||
        return iq.send(timeout=timeout, callback=callback)
 | 
			
		||||
 | 
			
		||||
    def _handle_nosave_change(self, iq):
 | 
			
		||||
        reply = self.xmpp.Iq()
 | 
			
		||||
        reply['type'] = 'result'
 | 
			
		||||
        reply['id'] = iq['id']
 | 
			
		||||
        reply['to'] = iq['from']
 | 
			
		||||
        reply.send()
 | 
			
		||||
        self.xmpp.event('google_nosave_change', iq)
 | 
			
		||||
							
								
								
									
										10
									
								
								slixmpp/plugins/google/settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								slixmpp/plugins/google/settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.google.settings import stanza
 | 
			
		||||
from slixmpp.plugins.google.settings.settings import GoogleSettings
 | 
			
		||||
							
								
								
									
										110
									
								
								slixmpp/plugins/google/settings/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								slixmpp/plugins/google/settings/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.xmlstream import ET, ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserSettings(ElementBase):
 | 
			
		||||
    name = 'usersetting'
 | 
			
		||||
    namespace = 'google:setting'
 | 
			
		||||
    plugin_attrib = 'google_settings'
 | 
			
		||||
    interfaces = set(['auto_accept_suggestions',
 | 
			
		||||
                      'mail_notifications',
 | 
			
		||||
                      'archiving_enabled',
 | 
			
		||||
                      'gmail',
 | 
			
		||||
                      'email_verified',
 | 
			
		||||
                      'domain_privacy_notice',
 | 
			
		||||
                      'display_name'])
 | 
			
		||||
 | 
			
		||||
    def _get_setting(self, setting):
 | 
			
		||||
        xml = self.xml.find('{%s}%s' % (self.namespace, setting))
 | 
			
		||||
        if xml is not None:
 | 
			
		||||
            return xml.attrib.get('value', '') == 'true'
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def _set_setting(self, setting, value):
 | 
			
		||||
        self._del_setting(setting)
 | 
			
		||||
        if value in (True, False):
 | 
			
		||||
            xml = ET.Element('{%s}%s' % (self.namespace, setting))
 | 
			
		||||
            xml.attrib['value'] = 'true' if value else 'false'
 | 
			
		||||
            self.xml.append(xml)
 | 
			
		||||
 | 
			
		||||
    def _del_setting(self, setting):
 | 
			
		||||
        xml = self.xml.find('{%s}%s' % (self.namespace, setting))
 | 
			
		||||
        if xml is not None:
 | 
			
		||||
            self.xml.remove(xml)
 | 
			
		||||
 | 
			
		||||
    def get_display_name(self):
 | 
			
		||||
        xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname'))
 | 
			
		||||
        if xml is not None:
 | 
			
		||||
            return xml.attrib.get('value', '')
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    def set_display_name(self, value):
 | 
			
		||||
        self._del_setting(setting)
 | 
			
		||||
        if value:
 | 
			
		||||
            xml = ET.Element('{%s}%s' % (self.namespace, 'displayname'))
 | 
			
		||||
            xml.attrib['value'] = value
 | 
			
		||||
            self.xml.append(xml)
 | 
			
		||||
 | 
			
		||||
    def del_display_name(self):
 | 
			
		||||
        self._del_setting('displayname')
 | 
			
		||||
 | 
			
		||||
    def get_auto_accept_suggestions(self):
 | 
			
		||||
        return self._get_setting('autoacceptsuggestions')
 | 
			
		||||
 | 
			
		||||
    def get_mail_notifications(self):
 | 
			
		||||
        return self._get_setting('mailnotifications')
 | 
			
		||||
 | 
			
		||||
    def get_archiving_enabled(self):
 | 
			
		||||
        return self._get_setting('archivingenabled')
 | 
			
		||||
 | 
			
		||||
    def get_gmail(self):
 | 
			
		||||
        return self._get_setting('gmail')
 | 
			
		||||
 | 
			
		||||
    def get_email_verified(self):
 | 
			
		||||
        return self._get_setting('emailverified')
 | 
			
		||||
 | 
			
		||||
    def get_domain_privacy_notice(self):
 | 
			
		||||
        return self._get_setting('domainprivacynotice')
 | 
			
		||||
 | 
			
		||||
    def set_auto_accept_suggestions(self, value):
 | 
			
		||||
        self._set_setting('autoacceptsuggestions', value)
 | 
			
		||||
 | 
			
		||||
    def set_mail_notifications(self, value):
 | 
			
		||||
        self._set_setting('mailnotifications', value)
 | 
			
		||||
 | 
			
		||||
    def set_archiving_enabled(self, value):
 | 
			
		||||
        self._set_setting('archivingenabled', value)
 | 
			
		||||
 | 
			
		||||
    def set_gmail(self, value):
 | 
			
		||||
        self._set_setting('gmail', value)
 | 
			
		||||
 | 
			
		||||
    def set_email_verified(self, value):
 | 
			
		||||
        self._set_setting('emailverified', value)
 | 
			
		||||
 | 
			
		||||
    def set_domain_privacy_notice(self, value):
 | 
			
		||||
        self._set_setting('domainprivacynotice', value)
 | 
			
		||||
 | 
			
		||||
    def del_auto_accept_suggestions(self):
 | 
			
		||||
        self._del_setting('autoacceptsuggestions')
 | 
			
		||||
 | 
			
		||||
    def del_mail_notifications(self):
 | 
			
		||||
        self._del_setting('mailnotifications')
 | 
			
		||||
 | 
			
		||||
    def del_archiving_enabled(self):
 | 
			
		||||
        self._del_setting('archivingenabled')
 | 
			
		||||
 | 
			
		||||
    def del_gmail(self):
 | 
			
		||||
        self._del_setting('gmail')
 | 
			
		||||
 | 
			
		||||
    def del_email_verified(self):
 | 
			
		||||
        self._del_setting('emailverified')
 | 
			
		||||
 | 
			
		||||
    def del_domain_privacy_notice(self):
 | 
			
		||||
        self._del_setting('domainprivacynotice')
 | 
			
		||||
@@ -23,7 +23,7 @@ class Form(ElementBase):
 | 
			
		||||
    namespace = 'jabber:x:data'
 | 
			
		||||
    name = 'x'
 | 
			
		||||
    plugin_attrib = 'form'
 | 
			
		||||
    interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
 | 
			
		||||
    interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', 'values'))
 | 
			
		||||
    sub_interfaces = {'title'}
 | 
			
		||||
    form_types = {'cancel', 'form', 'result', 'submit'}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ def _intercept(method, name, public):
 | 
			
		||||
        except InvocationException:
 | 
			
		||||
            raise
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
 | 
			
		||||
            raise InvocationException("A problem occurred calling %s.%s!" % (instance.FQN(), method.__name__), e)
 | 
			
		||||
    _resolver._rpc = public
 | 
			
		||||
    _resolver._rpc_name = method.__name__ if name is None else name
 | 
			
		||||
    return _resolver
 | 
			
		||||
@@ -405,8 +405,10 @@ class Proxy(Endpoint):
 | 
			
		||||
        self._callback = callback
 | 
			
		||||
 | 
			
		||||
    def __getattribute__(self, name, *args):
 | 
			
		||||
        if name in ('__dict__', '_endpoint', 'async', '_callback'):
 | 
			
		||||
        if name in ('__dict__', '_endpoint', '_callback'):
 | 
			
		||||
            return object.__getattribute__(self, name)
 | 
			
		||||
        elif name == 'async':
 | 
			
		||||
            return lambda callback: Proxy(self._endpoint, callback)
 | 
			
		||||
        else:
 | 
			
		||||
            attribute = self._endpoint.__getattribute__(name)
 | 
			
		||||
            if hasattr(attribute, '__call__'):
 | 
			
		||||
@@ -420,9 +422,6 @@ class Proxy(Endpoint):
 | 
			
		||||
                    pass   # If the attribute doesn't exist, don't care!
 | 
			
		||||
            return attribute
 | 
			
		||||
 | 
			
		||||
    def async(self, callback):
 | 
			
		||||
        return Proxy(self._endpoint, callback)
 | 
			
		||||
 | 
			
		||||
    def get_endpoint(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Returns the proxified endpoint.
 | 
			
		||||
@@ -696,7 +695,7 @@ class RemoteSession(object):
 | 
			
		||||
        e = {
 | 
			
		||||
            'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
 | 
			
		||||
            'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
 | 
			
		||||
            'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),
 | 
			
		||||
            'undefined-condition': RemoteException("An unexpected problem occurred trying to invoke %s at %s!" % (pmethod, iq['from'])),
 | 
			
		||||
        }[condition]
 | 
			
		||||
        if e is None:
 | 
			
		||||
            RemoteException("An unexpected exception occurred at %s!" % iq['from'])
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from slixmpp import Iq
 | 
			
		||||
@@ -123,6 +124,8 @@ class XEP_0030(BasePlugin):
 | 
			
		||||
        for op in self._disco_ops:
 | 
			
		||||
            self.api.register(getattr(self.static, op), op, default=True)
 | 
			
		||||
 | 
			
		||||
        self.domain_infos = {}
 | 
			
		||||
 | 
			
		||||
    def session_bind(self, jid):
 | 
			
		||||
        self.add_feature('http://jabber.org/protocol/disco#info')
 | 
			
		||||
 | 
			
		||||
@@ -295,6 +298,35 @@ class XEP_0030(BasePlugin):
 | 
			
		||||
                'cached': cached}
 | 
			
		||||
        return self.api['has_identity'](jid, node, ifrom, data)
 | 
			
		||||
 | 
			
		||||
    async def get_info_from_domain(self, domain=None, timeout=None,
 | 
			
		||||
                                   cached=True, callback=None):
 | 
			
		||||
        if domain is None:
 | 
			
		||||
            domain = self.xmpp.boundjid.domain
 | 
			
		||||
 | 
			
		||||
        if not cached or domain not in self.domain_infos:
 | 
			
		||||
            infos = [self.get_info(
 | 
			
		||||
                domain, timeout=timeout)]
 | 
			
		||||
            iq_items = await self.get_items(
 | 
			
		||||
                domain, timeout=timeout)
 | 
			
		||||
            items = iq_items['disco_items']['items']
 | 
			
		||||
            infos += [
 | 
			
		||||
                self.get_info(item[0], timeout=timeout)
 | 
			
		||||
                for item in items]
 | 
			
		||||
            info_futures, _ = await asyncio.wait(
 | 
			
		||||
                infos,
 | 
			
		||||
                timeout=timeout,
 | 
			
		||||
                loop=self.xmpp.loop
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            self.domain_infos[domain] = [
 | 
			
		||||
                future.result() for future in info_futures if not future.exception()]
 | 
			
		||||
 | 
			
		||||
        results = self.domain_infos[domain]
 | 
			
		||||
 | 
			
		||||
        if callback is not None:
 | 
			
		||||
            callback(results)
 | 
			
		||||
        return results
 | 
			
		||||
 | 
			
		||||
    @future_wrapper
 | 
			
		||||
    def get_info(self, jid=None, node=None, local=None,
 | 
			
		||||
                       cached=None, **kwargs):
 | 
			
		||||
@@ -316,7 +348,7 @@ class XEP_0030(BasePlugin):
 | 
			
		||||
                        combination handled by this Slixmpp instance and
 | 
			
		||||
                        no stanzas need to be sent.
 | 
			
		||||
                        Otherwise, a disco stanza must be sent to the
 | 
			
		||||
                        remove JID to retrieve the info.
 | 
			
		||||
                        remote JID to retrieve the info.
 | 
			
		||||
            cached   -- If true, then look for the disco info data from
 | 
			
		||||
                        the local cache system. If no results are found,
 | 
			
		||||
                        send the query as usual. The self.use_cache
 | 
			
		||||
@@ -646,9 +678,11 @@ class XEP_0030(BasePlugin):
 | 
			
		||||
                info['id'] = iq['id']
 | 
			
		||||
                info.send()
 | 
			
		||||
            else:
 | 
			
		||||
                node = iq['disco_info']['node']
 | 
			
		||||
                iq = iq.reply()
 | 
			
		||||
                if info:
 | 
			
		||||
                    info = self._fix_default_info(info)
 | 
			
		||||
                    info['node'] = node
 | 
			
		||||
                    iq.set_payload(info.xml)
 | 
			
		||||
                iq.send()
 | 
			
		||||
        elif iq['type'] == 'result':
 | 
			
		||||
 
 | 
			
		||||
@@ -66,10 +66,11 @@ class StaticDisco(object):
 | 
			
		||||
        if isinstance(ifrom, JID):
 | 
			
		||||
            ifrom = ifrom.full
 | 
			
		||||
        if (jid, node, ifrom) not in self.nodes:
 | 
			
		||||
            self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(),
 | 
			
		||||
                                       'items': DiscoItems()}
 | 
			
		||||
            self.nodes[(jid, node, ifrom)]['info']['node'] = node
 | 
			
		||||
            self.nodes[(jid, node, ifrom)]['items']['node'] = node
 | 
			
		||||
            new_node = {'info': DiscoInfo(), 'items': DiscoItems()}
 | 
			
		||||
            new_node['info']['node'] = node
 | 
			
		||||
            new_node['items']['node'] = node
 | 
			
		||||
            self.nodes[(jid, node, ifrom)] = new_node
 | 
			
		||||
        return self.nodes[(jid, node, ifrom)]
 | 
			
		||||
 | 
			
		||||
    def get_node(self, jid=None, node=None, ifrom=None):
 | 
			
		||||
        if jid is None:
 | 
			
		||||
@@ -208,8 +209,8 @@ class StaticDisco(object):
 | 
			
		||||
 | 
			
		||||
        The data parameter is a disco#info substanza.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.get_node(jid, node)['info'] = data
 | 
			
		||||
        new_node = self.add_node(jid, node)
 | 
			
		||||
        new_node['info'] = data
 | 
			
		||||
 | 
			
		||||
    def del_info(self, jid, node, ifrom, data):
 | 
			
		||||
        """
 | 
			
		||||
@@ -242,8 +243,8 @@ class StaticDisco(object):
 | 
			
		||||
            items -- A set of items in tuple format.
 | 
			
		||||
        """
 | 
			
		||||
        items = data.get('items', set())
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.get_node(jid, node)['items']['items'] = items
 | 
			
		||||
        new_node = self.add_node(jid, node)
 | 
			
		||||
        new_node['items']['items'] = items
 | 
			
		||||
 | 
			
		||||
    def del_items(self, jid, node, ifrom, data):
 | 
			
		||||
        """
 | 
			
		||||
@@ -256,7 +257,7 @@ class StaticDisco(object):
 | 
			
		||||
 | 
			
		||||
    def add_identity(self, jid, node, ifrom, data):
 | 
			
		||||
        """
 | 
			
		||||
        Add a new identity to te JID/node combination.
 | 
			
		||||
        Add a new identity to the JID/node combination.
 | 
			
		||||
 | 
			
		||||
        The data parameter may provide:
 | 
			
		||||
            category -- The general category to which the agent belongs.
 | 
			
		||||
@@ -264,8 +265,8 @@ class StaticDisco(object):
 | 
			
		||||
            name     -- Optional human readable name for this identity.
 | 
			
		||||
            lang     -- Optional standard xml:lang value.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.get_node(jid, node)['info'].add_identity(
 | 
			
		||||
        new_node = self.add_node(jid, node)
 | 
			
		||||
        new_node['info'].add_identity(
 | 
			
		||||
                data.get('category', ''),
 | 
			
		||||
                data.get('itype', ''),
 | 
			
		||||
                data.get('name', None),
 | 
			
		||||
@@ -280,8 +281,8 @@ class StaticDisco(object):
 | 
			
		||||
                            (category, type, name, lang)
 | 
			
		||||
        """
 | 
			
		||||
        identities = data.get('identities', set())
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.get_node(jid, node)['info']['identities'] = identities
 | 
			
		||||
        new_node = self.add_node(jid, node)
 | 
			
		||||
        new_node['info']['identities'] = identities
 | 
			
		||||
 | 
			
		||||
    def del_identity(self, jid, node, ifrom, data):
 | 
			
		||||
        """
 | 
			
		||||
@@ -316,8 +317,8 @@ class StaticDisco(object):
 | 
			
		||||
        The data parameter should include:
 | 
			
		||||
            feature -- The namespace of the supported feature.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.get_node(jid, node)['info'].add_feature(
 | 
			
		||||
        new_node = self.add_node(jid, node)
 | 
			
		||||
        new_node['info'].add_feature(
 | 
			
		||||
                data.get('feature', ''))
 | 
			
		||||
 | 
			
		||||
    def set_features(self, jid, node, ifrom, data):
 | 
			
		||||
@@ -328,8 +329,8 @@ class StaticDisco(object):
 | 
			
		||||
            features -- The new set of supported features.
 | 
			
		||||
        """
 | 
			
		||||
        features = data.get('features', set())
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.get_node(jid, node)['info']['features'] = features
 | 
			
		||||
        new_node = self.add_node(jid, node)
 | 
			
		||||
        new_node['info']['features'] = features
 | 
			
		||||
 | 
			
		||||
    def del_feature(self, jid, node, ifrom, data):
 | 
			
		||||
        """
 | 
			
		||||
@@ -362,8 +363,8 @@ class StaticDisco(object):
 | 
			
		||||
                     non-addressable items.
 | 
			
		||||
            name  -- Optional human readable name for the item.
 | 
			
		||||
        """
 | 
			
		||||
        self.add_node(jid, node)
 | 
			
		||||
        self.get_node(jid, node)['items'].add_item(
 | 
			
		||||
        new_node = self.add_node(jid, node)
 | 
			
		||||
        new_node['items'].add_item(
 | 
			
		||||
                data.get('ijid', ''),
 | 
			
		||||
                node=data.get('inode', ''),
 | 
			
		||||
                name=data.get('name', ''))
 | 
			
		||||
@@ -392,8 +393,8 @@ class StaticDisco(object):
 | 
			
		||||
        if isinstance(data, Iq):
 | 
			
		||||
            data = data['disco_info']
 | 
			
		||||
 | 
			
		||||
        self.add_node(jid, node, ifrom)
 | 
			
		||||
        self.get_node(jid, node, ifrom)['info'] = data
 | 
			
		||||
        new_node = self.add_node(jid, node, ifrom)
 | 
			
		||||
        new_node['info'] = data
 | 
			
		||||
 | 
			
		||||
    def get_cached_info(self, jid, node, ifrom, data):
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ from __future__ import with_statement
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from slixmpp import Presence
 | 
			
		||||
from slixmpp import Presence, Message
 | 
			
		||||
from slixmpp.plugins import BasePlugin, register_plugin
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
 | 
			
		||||
from slixmpp.xmlstream.handler.callback import Callback
 | 
			
		||||
@@ -181,7 +181,7 @@ class XEP_0045(BasePlugin):
 | 
			
		||||
        if got_online:
 | 
			
		||||
            self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
 | 
			
		||||
 | 
			
		||||
    def handle_groupchat_message(self, msg):
 | 
			
		||||
    def handle_groupchat_message(self, msg: Message) -> None:
 | 
			
		||||
        """ Handle a message event in a muc.
 | 
			
		||||
        """
 | 
			
		||||
        self.xmpp.event('groupchat_message', msg)
 | 
			
		||||
@@ -195,10 +195,14 @@ class XEP_0045(BasePlugin):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def handle_groupchat_subject(self, msg):
 | 
			
		||||
    def handle_groupchat_subject(self, msg: Message) -> None:
 | 
			
		||||
        """ Handle a message coming from a muc indicating
 | 
			
		||||
        a change of subject (or announcing it when joining the room)
 | 
			
		||||
        """
 | 
			
		||||
        # See poezio#3452. A message containing subject _and_ (body or thread)
 | 
			
		||||
        # is not a subject change.
 | 
			
		||||
        if msg['body'] or msg['thread']:
 | 
			
		||||
            return None
 | 
			
		||||
        self.xmpp.event('groupchat_subject', msg)
 | 
			
		||||
 | 
			
		||||
    def jid_in_room(self, room, jid):
 | 
			
		||||
 
 | 
			
		||||
@@ -31,8 +31,7 @@ class IBBytestream(object):
 | 
			
		||||
 | 
			
		||||
        self.recv_queue = asyncio.Queue()
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def send(self, data, timeout=None):
 | 
			
		||||
    async def send(self, data, timeout=None):
 | 
			
		||||
        if not self.stream_started or self.stream_out_closed:
 | 
			
		||||
            raise socket.error
 | 
			
		||||
        if len(data) > self.block_size:
 | 
			
		||||
@@ -56,22 +55,20 @@ class IBBytestream(object):
 | 
			
		||||
            iq['ibb_data']['sid'] = self.sid
 | 
			
		||||
            iq['ibb_data']['seq'] = seq
 | 
			
		||||
            iq['ibb_data']['data'] = data
 | 
			
		||||
            yield from iq.send(timeout=timeout)
 | 
			
		||||
            await iq.send(timeout=timeout)
 | 
			
		||||
        return len(data)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def sendall(self, data, timeout=None):
 | 
			
		||||
    async def sendall(self, data, timeout=None):
 | 
			
		||||
        sent_len = 0
 | 
			
		||||
        while sent_len < len(data):
 | 
			
		||||
            sent_len += yield from self.send(data[sent_len:self.block_size], timeout=timeout)
 | 
			
		||||
            sent_len += await self.send(data[sent_len:self.block_size], timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def sendfile(self, file, timeout=None):
 | 
			
		||||
    async def sendfile(self, file, timeout=None):
 | 
			
		||||
        while True:
 | 
			
		||||
            data = file.read(self.block_size)
 | 
			
		||||
            if not data:
 | 
			
		||||
                break
 | 
			
		||||
            yield from self.send(data, timeout=timeout)
 | 
			
		||||
            await self.send(data, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
    def _recv_data(self, stanza):
 | 
			
		||||
        new_seq = stanza['ibb_data']['seq']
 | 
			
		||||
 
 | 
			
		||||
@@ -89,31 +89,17 @@ class XEP_0050(BasePlugin):
 | 
			
		||||
        self.commands = {}
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback("Ad-Hoc Execute",
 | 
			
		||||
                         StanzaPath('iq@type=set/command'),
 | 
			
		||||
                         self._handle_command))
 | 
			
		||||
            Callback("Ad-Hoc Execute",
 | 
			
		||||
                     StanzaPath('iq@type=set/command'),
 | 
			
		||||
                     self._handle_command))
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Iq, Command)
 | 
			
		||||
        register_stanza_plugin(Command, Form, iterable=True)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.add_event_handler('command_execute',
 | 
			
		||||
                                    self._handle_command_start)
 | 
			
		||||
        self.xmpp.add_event_handler('command_next',
 | 
			
		||||
                                    self._handle_command_next)
 | 
			
		||||
        self.xmpp.add_event_handler('command_cancel',
 | 
			
		||||
                                    self._handle_command_cancel)
 | 
			
		||||
        self.xmpp.add_event_handler('command_complete',
 | 
			
		||||
                                    self._handle_command_complete)
 | 
			
		||||
        self.xmpp.add_event_handler('command', self._handle_command_all)
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        self.xmpp.del_event_handler('command_execute',
 | 
			
		||||
                                    self._handle_command_start)
 | 
			
		||||
        self.xmpp.del_event_handler('command_next',
 | 
			
		||||
                                    self._handle_command_next)
 | 
			
		||||
        self.xmpp.del_event_handler('command_cancel',
 | 
			
		||||
                                    self._handle_command_cancel)
 | 
			
		||||
        self.xmpp.del_event_handler('command_complete',
 | 
			
		||||
                                    self._handle_command_complete)
 | 
			
		||||
        self.xmpp.del_event_handler('command', self._handle_command_all)
 | 
			
		||||
        self.xmpp.remove_handler('Ad-Hoc Execute')
 | 
			
		||||
        self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
 | 
			
		||||
        self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
 | 
			
		||||
@@ -201,8 +187,27 @@ class XEP_0050(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    def _handle_command(self, iq):
 | 
			
		||||
        """Raise command events based on the command action."""
 | 
			
		||||
        self.xmpp.event('command', iq)
 | 
			
		||||
        self.xmpp.event('command_%s' % iq['command']['action'], iq)
 | 
			
		||||
 | 
			
		||||
    def _handle_command_all(self, iq: Iq) -> None:
 | 
			
		||||
        action = iq['command']['action']
 | 
			
		||||
        sessionid = iq['command']['sessionid']
 | 
			
		||||
        session = self.sessions.get(sessionid)
 | 
			
		||||
 | 
			
		||||
        if session is None:
 | 
			
		||||
            return self._handle_command_start(iq)
 | 
			
		||||
 | 
			
		||||
        if action in ('next', 'execute'):
 | 
			
		||||
            return self._handle_command_next(iq)
 | 
			
		||||
        if action == 'prev':
 | 
			
		||||
            return self._handle_command_prev(iq)
 | 
			
		||||
        if action == 'complete':
 | 
			
		||||
            return self._handle_command_complete(iq)
 | 
			
		||||
        if action == 'cancel':
 | 
			
		||||
            return self._handle_command_cancel(iq)
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def _handle_command_start(self, iq):
 | 
			
		||||
        """
 | 
			
		||||
        Process an initial request to execute a command.
 | 
			
		||||
@@ -468,7 +473,7 @@ class XEP_0050(BasePlugin):
 | 
			
		||||
                                               **kwargs)
 | 
			
		||||
 | 
			
		||||
    def send_command(self, jid, node, ifrom=None, action='execute',
 | 
			
		||||
                    payload=None, sessionid=None, flow=False, **kwargs):
 | 
			
		||||
                     payload=None, sessionid=None, flow=False, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Create and send a command stanza, without using the provided
 | 
			
		||||
        workflow management APIs.
 | 
			
		||||
@@ -611,7 +616,7 @@ class XEP_0050(BasePlugin):
 | 
			
		||||
    def terminate_command(self, session):
 | 
			
		||||
        """
 | 
			
		||||
        Delete a command's session after a command has completed
 | 
			
		||||
        or an error has occured.
 | 
			
		||||
        or an error has occurred.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            session -- All stored data relevant to the current
 | 
			
		||||
 
 | 
			
		||||
@@ -261,7 +261,7 @@ class BinVal(ElementBase):
 | 
			
		||||
 | 
			
		||||
    def get_binval(self):
 | 
			
		||||
        parent = self.parent()
 | 
			
		||||
        xml = parent.find('{%s}BINVAL' % self.namespace)
 | 
			
		||||
        xml = parent.xml.find('{%s}BINVAL' % self.namespace)
 | 
			
		||||
        if xml is not None:
 | 
			
		||||
            return base64.b64decode(bytes(xml.text))
 | 
			
		||||
        return b''
 | 
			
		||||
 
 | 
			
		||||
@@ -123,7 +123,7 @@ class XEP_0054(BasePlugin):
 | 
			
		||||
        if iq['type'] == 'result':
 | 
			
		||||
            self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
 | 
			
		||||
            return
 | 
			
		||||
        elif iq['type'] == 'get':
 | 
			
		||||
        elif iq['type'] == 'get' and self.xmpp.is_component:
 | 
			
		||||
            vcard = self.api['get_vcard'](iq['from'].bare)
 | 
			
		||||
            if isinstance(vcard, Iq):
 | 
			
		||||
                vcard.send()
 | 
			
		||||
 
 | 
			
		||||
@@ -19,23 +19,27 @@ from slixmpp.exceptions import XMPPError
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResultIterator():
 | 
			
		||||
class ResultIterator:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    An iterator for Result Set Managment
 | 
			
		||||
    An iterator for Result Set Management
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, query, interface, results='substanzas', amount=10,
 | 
			
		||||
                       start=None, reverse=False):
 | 
			
		||||
                       start=None, reverse=False, recv_interface=None,
 | 
			
		||||
                       pre_cb=None, post_cb=None):
 | 
			
		||||
        """
 | 
			
		||||
        Arguments:
 | 
			
		||||
           query     -- The template query
 | 
			
		||||
           interface -- The substanza of the query, for example disco_items
 | 
			
		||||
           interface -- The substanza of the query to send, for example disco_items
 | 
			
		||||
           recv_interface -- The substanza of the query to receive, for example disco_items
 | 
			
		||||
           results   -- The query stanza's interface which provides a
 | 
			
		||||
                        countable list of query results.
 | 
			
		||||
           amount    -- The max amounts of items to request per iteration
 | 
			
		||||
           start     -- From which item id to start
 | 
			
		||||
           reverse   -- If True, page backwards through the results
 | 
			
		||||
           pre_cb    -- Callback to run before sending the stanza
 | 
			
		||||
           post_cb   -- Callback to run after receiving the reply
 | 
			
		||||
 | 
			
		||||
        Example:
 | 
			
		||||
           q = Iq()
 | 
			
		||||
@@ -49,17 +53,23 @@ class ResultIterator():
 | 
			
		||||
        self.amount = amount
 | 
			
		||||
        self.start = start
 | 
			
		||||
        self.interface = interface
 | 
			
		||||
        if recv_interface:
 | 
			
		||||
            self.recv_interface = recv_interface
 | 
			
		||||
        else:
 | 
			
		||||
            self.recv_interface = interface
 | 
			
		||||
        self.pre_cb = pre_cb
 | 
			
		||||
        self.post_cb = post_cb
 | 
			
		||||
        self.results = results
 | 
			
		||||
        self.reverse = reverse
 | 
			
		||||
        self._stop = False
 | 
			
		||||
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
    def __aiter__(self):
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    def __next__(self):
 | 
			
		||||
        return self.next()
 | 
			
		||||
    async def __anext__(self):
 | 
			
		||||
        return await self.next()
 | 
			
		||||
 | 
			
		||||
    def next(self):
 | 
			
		||||
    async def next(self):
 | 
			
		||||
        """
 | 
			
		||||
        Return the next page of results from a query.
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +78,7 @@ class ResultIterator():
 | 
			
		||||
              of items.
 | 
			
		||||
        """
 | 
			
		||||
        if self._stop:
 | 
			
		||||
            raise StopIteration
 | 
			
		||||
            raise StopAsyncIteration
 | 
			
		||||
        self.query[self.interface]['rsm']['before'] = self.reverse
 | 
			
		||||
        self.query['id'] = self.query.stream.new_id()
 | 
			
		||||
        self.query[self.interface]['rsm']['max'] = str(self.amount)
 | 
			
		||||
@@ -79,28 +89,32 @@ class ResultIterator():
 | 
			
		||||
            self.query[self.interface]['rsm']['after'] = self.start
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            r = self.query.send(block=True)
 | 
			
		||||
            if self.pre_cb:
 | 
			
		||||
                self.pre_cb(self.query)
 | 
			
		||||
            r = await self.query.send()
 | 
			
		||||
 | 
			
		||||
            if not r[self.interface]['rsm']['first'] and \
 | 
			
		||||
               not r[self.interface]['rsm']['last']:
 | 
			
		||||
                raise StopIteration
 | 
			
		||||
            if not r[self.recv_interface]['rsm']['first'] and \
 | 
			
		||||
               not r[self.recv_interface]['rsm']['last']:
 | 
			
		||||
                raise StopAsyncIteration
 | 
			
		||||
 | 
			
		||||
            if r[self.interface]['rsm']['count'] and \
 | 
			
		||||
               r[self.interface]['rsm']['first_index']:
 | 
			
		||||
                count = int(r[self.interface]['rsm']['count'])
 | 
			
		||||
                first = int(r[self.interface]['rsm']['first_index'])
 | 
			
		||||
                num_items = len(r[self.interface][self.results])
 | 
			
		||||
            if r[self.recv_interface]['rsm']['count'] and \
 | 
			
		||||
               r[self.recv_interface]['rsm']['first_index']:
 | 
			
		||||
                count = int(r[self.recv_interface]['rsm']['count'])
 | 
			
		||||
                first = int(r[self.recv_interface]['rsm']['first_index'])
 | 
			
		||||
                num_items = len(r[self.recv_interface][self.results])
 | 
			
		||||
                if first + num_items == count:
 | 
			
		||||
                    self._stop = True
 | 
			
		||||
 | 
			
		||||
            if self.reverse:
 | 
			
		||||
                self.start = r[self.interface]['rsm']['first']
 | 
			
		||||
                self.start = r[self.recv_interface]['rsm']['first']
 | 
			
		||||
            else:
 | 
			
		||||
                self.start = r[self.interface]['rsm']['last']
 | 
			
		||||
                self.start = r[self.recv_interface]['rsm']['last']
 | 
			
		||||
 | 
			
		||||
            if self.post_cb:
 | 
			
		||||
                self.post_cb(r)
 | 
			
		||||
            return r
 | 
			
		||||
        except XMPPError:
 | 
			
		||||
            raise StopIteration
 | 
			
		||||
            raise StopAsyncIteration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class XEP_0059(BasePlugin):
 | 
			
		||||
@@ -127,7 +141,8 @@ class XEP_0059(BasePlugin):
 | 
			
		||||
    def session_bind(self, jid):
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(Set.namespace)
 | 
			
		||||
 | 
			
		||||
    def iterate(self, stanza, interface, results='substanzas'):
 | 
			
		||||
    def iterate(self, stanza, interface, results='substanzas',
 | 
			
		||||
                recv_interface=None, pre_cb=None, post_cb=None):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new result set iterator for a given stanza query.
 | 
			
		||||
 | 
			
		||||
@@ -137,9 +152,23 @@ class XEP_0059(BasePlugin):
 | 
			
		||||
                         basic disco#items query.
 | 
			
		||||
            interface -- The name of the substanza to which the
 | 
			
		||||
                         result set management stanza should be
 | 
			
		||||
                         appended. For example, for disco#items queries
 | 
			
		||||
                         the interface 'disco_items' should be used.
 | 
			
		||||
                         appended in the query stanza. For example,
 | 
			
		||||
                         for disco#items queries the interface
 | 
			
		||||
                         'disco_items' should be used.
 | 
			
		||||
            recv_interface -- The name of the substanza from which the
 | 
			
		||||
                              result set management stanza should be
 | 
			
		||||
                              read in the result stanza. If unspecified,
 | 
			
		||||
                              it will be set to the same value as the
 | 
			
		||||
                              ``interface`` parameter.
 | 
			
		||||
            pre_cb    -- Callback to run before sending each stanza e.g.
 | 
			
		||||
                         setting the MAM queryid and starting a stanza
 | 
			
		||||
                         collector.
 | 
			
		||||
            post_cb   -- Callback to run after receiving each stanza e.g.
 | 
			
		||||
                         stopping a MAM stanza collector in order to
 | 
			
		||||
                         gather results.
 | 
			
		||||
            results   -- The name of the interface containing the
 | 
			
		||||
                         query results (typically just 'substanzas').
 | 
			
		||||
        """
 | 
			
		||||
        return ResultIterator(stanza, interface, results)
 | 
			
		||||
        return ResultIterator(stanza, interface, results,
 | 
			
		||||
                              recv_interface=recv_interface, pre_cb=pre_cb,
 | 
			
		||||
                              post_cb=post_cb)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
 | 
			
		||||
class Set(ElementBase):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0059 (Result Set Managment) can be used to manage the
 | 
			
		||||
    XEP-0059 (Result Set Management) can be used to manage the
 | 
			
		||||
    results of queries. For example, limiting the number of items
 | 
			
		||||
    per response or starting at certain positions.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -185,14 +185,14 @@ class XEP_0060(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        if config is not None:
 | 
			
		||||
            form_type = 'http://jabber.org/protocol/pubsub#node_config'
 | 
			
		||||
            if 'FORM_TYPE' in config['fields']:
 | 
			
		||||
            if 'FORM_TYPE' in config.get_fields():
 | 
			
		||||
                config.field['FORM_TYPE']['value'] = form_type
 | 
			
		||||
            else:
 | 
			
		||||
                config.add_field(var='FORM_TYPE',
 | 
			
		||||
                                 ftype='hidden',
 | 
			
		||||
                                 value=form_type)
 | 
			
		||||
            if ntype:
 | 
			
		||||
                if 'pubsub#node_type' in config['fields']:
 | 
			
		||||
                if 'pubsub#node_type' in config.get_fields():
 | 
			
		||||
                    config.field['pubsub#node_type']['value'] = ntype
 | 
			
		||||
                else:
 | 
			
		||||
                    config.add_field(var='pubsub#node_type', value=ntype)
 | 
			
		||||
 
 | 
			
		||||
@@ -82,9 +82,9 @@ class Item(ElementBase):
 | 
			
		||||
            self.xml.append(value)
 | 
			
		||||
 | 
			
		||||
    def get_payload(self):
 | 
			
		||||
        childs = list(self.xml)
 | 
			
		||||
        if len(childs) > 0:
 | 
			
		||||
            return childs[0]
 | 
			
		||||
        children = list(self.xml)
 | 
			
		||||
        if len(children) > 0:
 | 
			
		||||
            return children[0]
 | 
			
		||||
 | 
			
		||||
    def del_payload(self):
 | 
			
		||||
        for child in self.xml:
 | 
			
		||||
 
 | 
			
		||||
@@ -31,9 +31,9 @@ class EventItem(ElementBase):
 | 
			
		||||
        self.xml.append(value)
 | 
			
		||||
 | 
			
		||||
    def get_payload(self):
 | 
			
		||||
        childs = list(self.xml)
 | 
			
		||||
        if len(childs) > 0:
 | 
			
		||||
            return childs[0]
 | 
			
		||||
        children = list(self.xml)
 | 
			
		||||
        if len(children) > 0:
 | 
			
		||||
            return children[0]
 | 
			
		||||
 | 
			
		||||
    def del_payload(self):
 | 
			
		||||
        for child in self.xml:
 | 
			
		||||
 
 | 
			
		||||
@@ -55,17 +55,17 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
        """Returns the socket associated to the SID."""
 | 
			
		||||
        return self._sessions.get(sid, None)
 | 
			
		||||
 | 
			
		||||
    def handshake(self, to, ifrom=None, sid=None, timeout=None):
 | 
			
		||||
    async def handshake(self, to, ifrom=None, sid=None, timeout=None):
 | 
			
		||||
        """ Starts the handshake to establish the socks5 bytestreams
 | 
			
		||||
        connection.
 | 
			
		||||
        """
 | 
			
		||||
        if not self._proxies:
 | 
			
		||||
            self._proxies = yield from self.discover_proxies()
 | 
			
		||||
            self._proxies = await self.discover_proxies()
 | 
			
		||||
 | 
			
		||||
        if sid is None:
 | 
			
		||||
            sid = uuid4().hex
 | 
			
		||||
 | 
			
		||||
        used = yield from self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
 | 
			
		||||
        used = await self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
 | 
			
		||||
        proxy = used['socks']['streamhost_used']['jid']
 | 
			
		||||
 | 
			
		||||
        if proxy not in self._proxies:
 | 
			
		||||
@@ -73,16 +73,16 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self._sessions[sid] = (yield from self._connect_proxy(
 | 
			
		||||
            self._sessions[sid] = (await self._connect_proxy(
 | 
			
		||||
                    self._get_dest_sha1(sid, self.xmpp.boundjid, to),
 | 
			
		||||
                    self._proxies[proxy][0],
 | 
			
		||||
                    self._proxies[proxy][1]))[1]
 | 
			
		||||
        except socket.error:
 | 
			
		||||
            return None
 | 
			
		||||
        addr, port = yield from self._sessions[sid].connected
 | 
			
		||||
        addr, port = await self._sessions[sid].connected
 | 
			
		||||
 | 
			
		||||
        # Request that the proxy activate the session with the target.
 | 
			
		||||
        yield from self.activate(proxy, sid, to, timeout=timeout)
 | 
			
		||||
        await self.activate(proxy, sid, to, timeout=timeout)
 | 
			
		||||
        sock = self.get_socket(sid)
 | 
			
		||||
        self.xmpp.event('stream:%s:%s' % (sid, to), sock)
 | 
			
		||||
        return sock
 | 
			
		||||
@@ -104,7 +104,7 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
            iq['socks'].add_streamhost(proxy, host, port)
 | 
			
		||||
        return iq.send(timeout=timeout, callback=callback)
 | 
			
		||||
 | 
			
		||||
    def discover_proxies(self, jid=None, ifrom=None, timeout=None):
 | 
			
		||||
    async def discover_proxies(self, jid=None, ifrom=None, timeout=None):
 | 
			
		||||
        """Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
 | 
			
		||||
        if jid is None:
 | 
			
		||||
            if self.xmpp.is_component:
 | 
			
		||||
@@ -114,7 +114,7 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        discovered = set()
 | 
			
		||||
 | 
			
		||||
        disco_items = yield from self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
 | 
			
		||||
        disco_items = await self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
 | 
			
		||||
        disco_items = {item[0] for item in disco_items['disco_items']['items']}
 | 
			
		||||
 | 
			
		||||
        disco_info_futures = {}
 | 
			
		||||
@@ -123,7 +123,7 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        for item in disco_items:
 | 
			
		||||
            try:
 | 
			
		||||
                disco_info = yield from disco_info_futures[item]
 | 
			
		||||
                disco_info = await disco_info_futures[item]
 | 
			
		||||
            except XMPPError:
 | 
			
		||||
                continue
 | 
			
		||||
            else:
 | 
			
		||||
@@ -135,7 +135,7 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        for jid in discovered:
 | 
			
		||||
            try:
 | 
			
		||||
                addr = yield from self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
 | 
			
		||||
                addr = await self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
 | 
			
		||||
                self._proxies[jid] = (addr['socks']['streamhost']['host'],
 | 
			
		||||
                                      addr['socks']['streamhost']['port'])
 | 
			
		||||
            except XMPPError:
 | 
			
		||||
@@ -180,9 +180,8 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
                    streamhost['host'],
 | 
			
		||||
                    streamhost['port']))
 | 
			
		||||
 | 
			
		||||
        @asyncio.coroutine
 | 
			
		||||
        def gather(futures, iq, streamhosts):
 | 
			
		||||
            proxies = yield from asyncio.gather(*futures, return_exceptions=True)
 | 
			
		||||
        async def gather(futures, iq, streamhosts):
 | 
			
		||||
            proxies = await asyncio.gather(*futures, return_exceptions=True)
 | 
			
		||||
            for streamhost, proxy in zip(streamhosts, proxies):
 | 
			
		||||
                if isinstance(proxy, ValueError):
 | 
			
		||||
                    continue
 | 
			
		||||
@@ -192,7 +191,7 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
                proxy = proxy[1]
 | 
			
		||||
                # TODO: what if the future never happens?
 | 
			
		||||
                try:
 | 
			
		||||
                    addr, port = yield from proxy.connected
 | 
			
		||||
                    addr, port = await proxy.connected
 | 
			
		||||
                except socket.error:
 | 
			
		||||
                    log.exception('Socket error while connecting to the proxy.')
 | 
			
		||||
                    continue
 | 
			
		||||
@@ -213,7 +212,7 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
            self.xmpp.event('socks5_stream', conn)
 | 
			
		||||
            self.xmpp.event('stream:%s:%s' % (sid, requester), conn)
 | 
			
		||||
 | 
			
		||||
        asyncio.async(gather(proxy_futures, iq, streamhosts))
 | 
			
		||||
        asyncio.ensure_future(gather(proxy_futures, iq, streamhosts))
 | 
			
		||||
 | 
			
		||||
    def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None):
 | 
			
		||||
        """Activate the socks5 session that has been negotiated."""
 | 
			
		||||
@@ -231,7 +230,7 @@ class XEP_0065(BasePlugin):
 | 
			
		||||
                sock.close()
 | 
			
		||||
            except socket.error:
 | 
			
		||||
                pass
 | 
			
		||||
            # Though this should not be neccessary remove the closed session anyway
 | 
			
		||||
            # Though this should not be necessary remove the closed session anyway
 | 
			
		||||
            if sid in self._sessions:
 | 
			
		||||
                log.warn(('SOCKS5 session with sid = "%s" was not ' +
 | 
			
		||||
                          'removed from _sessions by sock.close()') % sid)
 | 
			
		||||
 
 | 
			
		||||
@@ -137,8 +137,8 @@ class Socks5Protocol(asyncio.Protocol):
 | 
			
		||||
    def resume_writing(self):
 | 
			
		||||
        self.paused.set_result(None)
 | 
			
		||||
 | 
			
		||||
    def write(self, data):
 | 
			
		||||
        yield from self.paused
 | 
			
		||||
    async def write(self, data):
 | 
			
		||||
        await self.paused
 | 
			
		||||
        self.transport.write(data)
 | 
			
		||||
 | 
			
		||||
    def _send_methods(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ class XEP_0077(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    def _force_stream_feature(self, stanza):
 | 
			
		||||
        if isinstance(stanza, StreamFeatures):
 | 
			
		||||
            if self.xmpp.use_tls or self.xmpp.use_ssl:
 | 
			
		||||
            if not self.xmpp.disable_starttls:
 | 
			
		||||
                if 'starttls' not in self.xmpp.features:
 | 
			
		||||
                    return stanza
 | 
			
		||||
                elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
 | 
			
		||||
 
 | 
			
		||||
@@ -130,7 +130,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
 | 
			
		||||
        sec = now.second
 | 
			
		||||
    if micro is None:
 | 
			
		||||
        micro = now.microsecond
 | 
			
		||||
    if offset is None:
 | 
			
		||||
    if offset in (None, 0):
 | 
			
		||||
        offset = tzutc()
 | 
			
		||||
    elif not isinstance(offset, dt.tzinfo):
 | 
			
		||||
        offset = tzoffset(None, offset)
 | 
			
		||||
@@ -177,7 +177,7 @@ def datetime(year=None, month=None, day=None, hour=None,
 | 
			
		||||
        sec = now.second
 | 
			
		||||
    if micro is None:
 | 
			
		||||
        micro = now.microsecond
 | 
			
		||||
    if offset is None:
 | 
			
		||||
    if offset in (None, 0):
 | 
			
		||||
        offset = tzutc()
 | 
			
		||||
    elif not isinstance(offset, dt.tzinfo):
 | 
			
		||||
        offset = tzoffset(None, offset)
 | 
			
		||||
 
 | 
			
		||||
@@ -65,9 +65,14 @@ class XEP_0092(BasePlugin):
 | 
			
		||||
            iq -- The Iq stanza containing the software version query.
 | 
			
		||||
        """
 | 
			
		||||
        iq = iq.reply()
 | 
			
		||||
        iq['software_version']['name'] = self.software_name
 | 
			
		||||
        iq['software_version']['version'] = self.version
 | 
			
		||||
        iq['software_version']['os'] = self.os
 | 
			
		||||
        if self.software_name:
 | 
			
		||||
            iq['software_version']['name'] = self.software_name
 | 
			
		||||
            iq['software_version']['version'] = self.version
 | 
			
		||||
            iq['software_version']['os'] = self.os
 | 
			
		||||
        else:
 | 
			
		||||
            iq.error()
 | 
			
		||||
            iq['error']['type'] = 'cancel'
 | 
			
		||||
            iq['error']['condition'] = 'service-unavailable'
 | 
			
		||||
        iq.send()
 | 
			
		||||
 | 
			
		||||
    def get_version(self, jid, ifrom=None, timeout=None, callback=None,
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ class XEP_0095(BasePlugin):
 | 
			
		||||
                extension='bad-profile',
 | 
			
		||||
                extension_ns=SI.namespace)
 | 
			
		||||
 | 
			
		||||
        neg = iq['si']['feature_neg']['form']['fields']
 | 
			
		||||
        neg = iq['si']['feature_neg']['form'].get_fields()
 | 
			
		||||
        options = neg['stream-method']['options'] or []
 | 
			
		||||
        methods = []
 | 
			
		||||
        for opt in options:
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ from slixmpp.stanza import StreamFeatures, Presence, Iq
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin, JID
 | 
			
		||||
from slixmpp.xmlstream.handler import Callback
 | 
			
		||||
from slixmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from slixmpp.util import MemoryCache
 | 
			
		||||
from slixmpp import asyncio
 | 
			
		||||
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
@@ -27,7 +28,7 @@ log = logging.getLogger(__name__)
 | 
			
		||||
class XEP_0115(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    XEP-0115: Entity Capabalities
 | 
			
		||||
    XEP-0115: Entity Capabilities
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    name = 'xep_0115'
 | 
			
		||||
@@ -37,7 +38,8 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
    default_config = {
 | 
			
		||||
        'hash': 'sha-1',
 | 
			
		||||
        'caps_node': None,
 | 
			
		||||
        'broadcast': True
 | 
			
		||||
        'broadcast': True,
 | 
			
		||||
        'cache': None,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
@@ -48,6 +50,9 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
        if self.caps_node is None:
 | 
			
		||||
            self.caps_node = 'http://slixmpp.com/ver/%s' % __version__
 | 
			
		||||
 | 
			
		||||
        if self.cache is None:
 | 
			
		||||
            self.cache = MemoryCache()
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Presence, stanza.Capabilities)
 | 
			
		||||
        register_stanza_plugin(StreamFeatures, stanza.Capabilities)
 | 
			
		||||
 | 
			
		||||
@@ -132,8 +137,7 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        self.xmpp.event('entity_caps', p)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _process_caps(self, pres):
 | 
			
		||||
    async def _process_caps(self, pres):
 | 
			
		||||
        if not pres['caps']['hash']:
 | 
			
		||||
            log.debug("Received unsupported legacy caps: %s, %s, %s",
 | 
			
		||||
                    pres['caps']['node'],
 | 
			
		||||
@@ -164,7 +168,7 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
        log.debug("New caps verification string: %s", ver)
 | 
			
		||||
        try:
 | 
			
		||||
            node = '%s#%s' % (pres['caps']['node'], ver)
 | 
			
		||||
            caps = yield from self.xmpp['xep_0030'].get_info(pres['from'], node,
 | 
			
		||||
            caps = await self.xmpp['xep_0030'].get_info(pres['from'], node,
 | 
			
		||||
                                                             coroutine=True)
 | 
			
		||||
 | 
			
		||||
            if isinstance(caps, Iq):
 | 
			
		||||
@@ -199,8 +203,8 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
                log.debug("Non form extension found, ignoring for caps")
 | 
			
		||||
                caps.xml.remove(stanza.xml)
 | 
			
		||||
                continue
 | 
			
		||||
            if 'FORM_TYPE' in stanza['fields']:
 | 
			
		||||
                f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
 | 
			
		||||
            if 'FORM_TYPE' in stanza.get_fields():
 | 
			
		||||
                f_type = tuple(stanza.get_fields()['FORM_TYPE']['value'])
 | 
			
		||||
                form_types.append(f_type)
 | 
			
		||||
                deduped_form_types.add(f_type)
 | 
			
		||||
                if len(form_types) != len(deduped_form_types):
 | 
			
		||||
@@ -214,7 +218,7 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
                        log.debug("Extra FORM_TYPE data, invalid for caps")
 | 
			
		||||
                        return False
 | 
			
		||||
 | 
			
		||||
                if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
 | 
			
		||||
                if stanza.get_fields()['FORM_TYPE']['type'] != 'hidden':
 | 
			
		||||
                    log.debug("Field FORM_TYPE type not 'hidden', " + \
 | 
			
		||||
                              "ignoring form for caps")
 | 
			
		||||
                    caps.xml.remove(stanza.xml)
 | 
			
		||||
@@ -253,7 +257,7 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        for stanza in info['substanzas']:
 | 
			
		||||
            if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
 | 
			
		||||
                if 'FORM_TYPE' in stanza['fields']:
 | 
			
		||||
                if 'FORM_TYPE' in stanza.get_fields():
 | 
			
		||||
                    f_type = stanza['values']['FORM_TYPE']
 | 
			
		||||
                    if len(f_type):
 | 
			
		||||
                        f_type = f_type[0]
 | 
			
		||||
@@ -265,11 +269,11 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
        for f_type in sorted_forms:
 | 
			
		||||
            for form in form_types[f_type]:
 | 
			
		||||
                S += '%s<' % f_type
 | 
			
		||||
                fields = sorted(form['fields'].keys())
 | 
			
		||||
                fields = sorted(form.get_fields().keys())
 | 
			
		||||
                fields.remove('FORM_TYPE')
 | 
			
		||||
                for field in fields:
 | 
			
		||||
                    S += '%s<' % field
 | 
			
		||||
                    vals = form['fields'][field].get_value(convert=False)
 | 
			
		||||
                    vals = form.get_fields()[field].get_value(convert=False)
 | 
			
		||||
                    if vals is None:
 | 
			
		||||
                        S += '<'
 | 
			
		||||
                    else:
 | 
			
		||||
@@ -280,10 +284,9 @@ class XEP_0115(BasePlugin):
 | 
			
		||||
        binary = hash(S.encode('utf8')).digest()
 | 
			
		||||
        return base64.b64encode(binary).decode('utf-8')
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def update_caps(self, jid=None, node=None, preserve=False):
 | 
			
		||||
    async def update_caps(self, jid=None, node=None, preserve=False):
 | 
			
		||||
        try:
 | 
			
		||||
            info = yield from self.xmpp['xep_0030'].get_info(jid, node, local=True)
 | 
			
		||||
            info = await self.xmpp['xep_0030'].get_info(jid, node, local=True)
 | 
			
		||||
            if isinstance(info, Iq):
 | 
			
		||||
                info = info['disco_info']
 | 
			
		||||
            ver = self.generate_verstring(info, self.hash)
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@ class StaticCaps(object):
 | 
			
		||||
        self.disco = self.xmpp['xep_0030']
 | 
			
		||||
        self.caps = self.xmpp['xep_0115']
 | 
			
		||||
        self.static = static
 | 
			
		||||
        self.ver_cache = {}
 | 
			
		||||
        self.jid_vers = {}
 | 
			
		||||
 | 
			
		||||
    def supports(self, jid, node, ifrom, data):
 | 
			
		||||
@@ -128,7 +127,7 @@ class StaticCaps(object):
 | 
			
		||||
        info = data.get('info', None)
 | 
			
		||||
        if not verstring or not info:
 | 
			
		||||
            return
 | 
			
		||||
        self.ver_cache[verstring] = info
 | 
			
		||||
        self.caps.cache.store(verstring, info)
 | 
			
		||||
 | 
			
		||||
    def assign_verstring(self, jid, node, ifrom, data):
 | 
			
		||||
        if isinstance(jid, JID):
 | 
			
		||||
@@ -139,4 +138,7 @@ class StaticCaps(object):
 | 
			
		||||
        return self.jid_vers.get(jid, None)
 | 
			
		||||
 | 
			
		||||
    def get_caps(self, jid, node, ifrom, data):
 | 
			
		||||
        return self.ver_cache.get(data.get('verstring', None), None)
 | 
			
		||||
        verstring = data.get('verstring', None)
 | 
			
		||||
        if verstring is None:
 | 
			
		||||
            return None
 | 
			
		||||
        return self.caps.cache.retrieve(verstring)
 | 
			
		||||
 
 | 
			
		||||
@@ -98,10 +98,9 @@ class XEP_0153(BasePlugin):
 | 
			
		||||
        first_future.add_done_callback(propagate_timeout_exception)
 | 
			
		||||
        return future
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _start(self, event):
 | 
			
		||||
    async def _start(self, event):
 | 
			
		||||
        try:
 | 
			
		||||
            vcard = yield from self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
 | 
			
		||||
            vcard = await self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
 | 
			
		||||
            data = vcard['vcard_temp']['PHOTO']['BINVAL']
 | 
			
		||||
            if not data:
 | 
			
		||||
                new_hash = ''
 | 
			
		||||
@@ -138,7 +137,11 @@ class XEP_0153(BasePlugin):
 | 
			
		||||
            if iq['type'] == 'error':
 | 
			
		||||
                log.debug('Could not retrieve vCard for %s', jid)
 | 
			
		||||
                return
 | 
			
		||||
            data = iq['vcard_temp']['PHOTO']['BINVAL']
 | 
			
		||||
            try:
 | 
			
		||||
                data = iq['vcard_temp']['PHOTO']['BINVAL']
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                log.debug('Invalid BINVAL in vCard’s PHOTO for %s:', jid, exc_info=True)
 | 
			
		||||
                data = None
 | 
			
		||||
            if not data:
 | 
			
		||||
                new_hash = ''
 | 
			
		||||
            else:
 | 
			
		||||
@@ -164,10 +167,7 @@ class XEP_0153(BasePlugin):
 | 
			
		||||
        data = pres['vcard_temp_update']['photo']
 | 
			
		||||
        if data is None:
 | 
			
		||||
            return
 | 
			
		||||
        elif data == '' or data != self.api['get_hash'](pres['from']):
 | 
			
		||||
            ifrom = pres['to'] if self.xmpp.is_component else None
 | 
			
		||||
            self.api['reset_hash'](pres['from'], ifrom=ifrom)
 | 
			
		||||
            self.xmpp.event('vcard_avatar_update', pres)
 | 
			
		||||
        self.xmpp.event('vcard_avatar_update', pres)
 | 
			
		||||
 | 
			
		||||
    # =================================================================
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,10 @@ class XEP_0163(BasePlugin):
 | 
			
		||||
        for ns in namespace:
 | 
			
		||||
            self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
 | 
			
		||||
                                              jid=jid)
 | 
			
		||||
        asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
 | 
			
		||||
        asyncio.ensure_future(
 | 
			
		||||
            self.xmpp['xep_0115'].update_caps(jid),
 | 
			
		||||
            loop=self.xmpp.loop,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def remove_interest(self, namespace, jid=None):
 | 
			
		||||
        """
 | 
			
		||||
@@ -81,7 +84,10 @@ class XEP_0163(BasePlugin):
 | 
			
		||||
        for ns in namespace:
 | 
			
		||||
            self.xmpp['xep_0030'].del_feature(jid=jid,
 | 
			
		||||
                                              feature='%s+notify' % namespace)
 | 
			
		||||
        asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
 | 
			
		||||
        asyncio.ensure_future(
 | 
			
		||||
            self.xmpp['xep_0115'].update_caps(jid),
 | 
			
		||||
            loop=self.xmpp.loop,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
 | 
			
		||||
                timeout_callback=None, callback=None, timeout=None):
 | 
			
		||||
 
 | 
			
		||||
@@ -174,8 +174,7 @@ class XEP_0198(BasePlugin):
 | 
			
		||||
        req = stanza.RequestAck(self.xmpp)
 | 
			
		||||
        self.xmpp.send_raw(str(req))
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _handle_sm_feature(self, features):
 | 
			
		||||
    async def _handle_sm_feature(self, features):
 | 
			
		||||
        """
 | 
			
		||||
        Enable or resume stream management.
 | 
			
		||||
 | 
			
		||||
@@ -203,7 +202,7 @@ class XEP_0198(BasePlugin):
 | 
			
		||||
                            MatchXPath(stanza.Enabled.tag_name()),
 | 
			
		||||
                            MatchXPath(stanza.Failed.tag_name())]))
 | 
			
		||||
                self.xmpp.register_handler(waiter)
 | 
			
		||||
                result = yield from waiter.wait()
 | 
			
		||||
                result = await waiter.wait()
 | 
			
		||||
        elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
 | 
			
		||||
            self.enabled = True
 | 
			
		||||
            resume = stanza.Resume(self.xmpp)
 | 
			
		||||
@@ -219,7 +218,7 @@ class XEP_0198(BasePlugin):
 | 
			
		||||
                        MatchXPath(stanza.Resumed.tag_name()),
 | 
			
		||||
                        MatchXPath(stanza.Failed.tag_name())]))
 | 
			
		||||
            self.xmpp.register_handler(waiter)
 | 
			
		||||
            result = yield from waiter.wait()
 | 
			
		||||
            result = await waiter.wait()
 | 
			
		||||
            if result is not None and result.name == 'resumed':
 | 
			
		||||
                return True
 | 
			
		||||
        return False
 | 
			
		||||
 
 | 
			
		||||
@@ -95,7 +95,10 @@ class XEP_0199(BasePlugin):
 | 
			
		||||
            self.timeout = timeout
 | 
			
		||||
 | 
			
		||||
        self.keepalive = True
 | 
			
		||||
        handler = lambda event=None: asyncio.ensure_future(self._keepalive(event))
 | 
			
		||||
        handler = lambda event=None: asyncio.ensure_future(
 | 
			
		||||
            self._keepalive(event),
 | 
			
		||||
            loop=self.xmpp.loop,
 | 
			
		||||
        )
 | 
			
		||||
        self.xmpp.schedule('Ping keepalive',
 | 
			
		||||
                           self.interval,
 | 
			
		||||
                           handler,
 | 
			
		||||
@@ -104,15 +107,14 @@ class XEP_0199(BasePlugin):
 | 
			
		||||
    def disable_keepalive(self, event=None):
 | 
			
		||||
        self.xmpp.cancel_schedule('Ping keepalive')
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _keepalive(self, event=None):
 | 
			
		||||
    async def _keepalive(self, event=None):
 | 
			
		||||
        log.debug("Keepalive ping...")
 | 
			
		||||
        try:
 | 
			
		||||
            rtt = yield from self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
 | 
			
		||||
            rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
 | 
			
		||||
        except IqTimeout:
 | 
			
		||||
            log.debug("Did not recieve ping back in time." + \
 | 
			
		||||
            log.debug("Did not receive ping back in time. " + \
 | 
			
		||||
                      "Requesting Reconnect.")
 | 
			
		||||
            self.xmpp.reconnect()
 | 
			
		||||
            self.xmpp.reconnect(0.0, "Ping timeout after %ds" % self.timeout)
 | 
			
		||||
        else:
 | 
			
		||||
            log.debug('Keepalive RTT: %s' % rtt)
 | 
			
		||||
 | 
			
		||||
@@ -145,8 +147,7 @@ class XEP_0199(BasePlugin):
 | 
			
		||||
        return iq.send(timeout=timeout, callback=callback,
 | 
			
		||||
                       timeout_callback=timeout_callback)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def ping(self, jid=None, ifrom=None, timeout=None):
 | 
			
		||||
    async def ping(self, jid=None, ifrom=None, timeout=None):
 | 
			
		||||
        """Send a ping request and calculate RTT.
 | 
			
		||||
        This is a coroutine.
 | 
			
		||||
 | 
			
		||||
@@ -174,7 +175,7 @@ class XEP_0199(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        log.debug('Pinging %s' % jid)
 | 
			
		||||
        try:
 | 
			
		||||
            yield from self.send_ping(jid, ifrom=ifrom, timeout=timeout)
 | 
			
		||||
            await self.send_ping(jid, ifrom=ifrom, timeout=timeout)
 | 
			
		||||
        except IqError as e:
 | 
			
		||||
            if own_host:
 | 
			
		||||
                rtt = time.time() - start
 | 
			
		||||
 
 | 
			
		||||
@@ -123,5 +123,5 @@ class EntityTime(ElementBase):
 | 
			
		||||
        if not isinstance(value, dt.datetime):
 | 
			
		||||
            date = xep_0082.parse(value)
 | 
			
		||||
        date = date.astimezone(tzutc())
 | 
			
		||||
        value = xep_0082.format_datetime(date)[:-1]
 | 
			
		||||
        value = xep_0082.format_datetime(date)
 | 
			
		||||
        self._set_sub_text('utc', value)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ class XEP_0202(BasePlugin):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        """Start the XEP-0203 plugin."""
 | 
			
		||||
        """Start the XEP-0202 plugin."""
 | 
			
		||||
 | 
			
		||||
        if not self.local_time:
 | 
			
		||||
            def default_local_time(jid):
 | 
			
		||||
 
 | 
			
		||||
@@ -73,11 +73,11 @@ class XEP_0222(BasePlugin):
 | 
			
		||||
                ftype='hidden',
 | 
			
		||||
                value='http://jabber.org/protocol/pubsub#publish-options')
 | 
			
		||||
 | 
			
		||||
        fields = options['fields']
 | 
			
		||||
        fields = options.get_fields()
 | 
			
		||||
        for field, value in self.profile.items():
 | 
			
		||||
            if field not in fields:
 | 
			
		||||
                options.add_field(var=field)
 | 
			
		||||
            options['fields'][field]['value'] = value
 | 
			
		||||
            options.get_fields()[field]['value'] = value
 | 
			
		||||
 | 
			
		||||
        return self.xmpp['xep_0163'].publish(stanza, node,
 | 
			
		||||
                options=options,
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ class XEP_0223(BasePlugin):
 | 
			
		||||
    dependencies = {'xep_0163', 'xep_0060', 'xep_0004'}
 | 
			
		||||
 | 
			
		||||
    profile = {'pubsub#persist_items': True,
 | 
			
		||||
               'pubsub#send_last_published_item': 'never'}
 | 
			
		||||
               'pubsub#access_model': 'whitelist'}
 | 
			
		||||
 | 
			
		||||
    def configure(self, node, ifrom=None, callback=None, timeout=None):
 | 
			
		||||
        """
 | 
			
		||||
@@ -78,7 +78,7 @@ class XEP_0223(BasePlugin):
 | 
			
		||||
        for field, value in self.profile.items():
 | 
			
		||||
            if field not in fields:
 | 
			
		||||
                options.add_field(var=field)
 | 
			
		||||
            options['fields'][field]['value'] = value
 | 
			
		||||
            options.get_fields()[field]['value'] = value
 | 
			
		||||
 | 
			
		||||
        return self.xmpp['xep_0163'].publish(stanza, node, options=options,
 | 
			
		||||
                                             ifrom=ifrom, callback=callback,
 | 
			
		||||
 
 | 
			
		||||
@@ -61,10 +61,12 @@ class XEP_0280(BasePlugin):
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:carbons:2')
 | 
			
		||||
 | 
			
		||||
    def _handle_carbon_received(self, msg):
 | 
			
		||||
        self.xmpp.event('carbon_received', msg)
 | 
			
		||||
        if msg['from'].bare == self.xmpp.boundjid.bare:
 | 
			
		||||
            self.xmpp.event('carbon_received', msg)
 | 
			
		||||
 | 
			
		||||
    def _handle_carbon_sent(self, msg):
 | 
			
		||||
        self.xmpp.event('carbon_sent', msg)
 | 
			
		||||
        if msg['from'].bare == self.xmpp.boundjid.bare:
 | 
			
		||||
            self.xmpp.event('carbon_sent', msg)
 | 
			
		||||
 | 
			
		||||
    def enable(self, ifrom=None, timeout=None, callback=None,
 | 
			
		||||
               timeout_callback=None):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								slixmpp/plugins/xep_0300/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								slixmpp/plugins/xep_0300/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2017 Emmanuel Gil Peyrot
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.xep_0300 import stanza
 | 
			
		||||
from slixmpp.plugins.xep_0300.stanza import Hash
 | 
			
		||||
from slixmpp.plugins.xep_0300.hash import XEP_0300
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(XEP_0300)
 | 
			
		||||
							
								
								
									
										87
									
								
								slixmpp/plugins/xep_0300/hash.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								slixmpp/plugins/xep_0300/hash.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2017 Emmanuel Gil Peyrot
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from base64 import b64encode
 | 
			
		||||
import hashlib
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
from slixmpp.plugins.xep_0300 import stanza, Hash
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class XEP_0300(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'xep_0300'
 | 
			
		||||
    description = 'XEP-0300: Use of Cryptographic Hash Functions in XMPP'
 | 
			
		||||
    dependencies = {'xep_0030'}
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
    default_config = {
 | 
			
		||||
        'block_size': 1024 * 1024,  # One MiB
 | 
			
		||||
        'preferded': 'sha-256',
 | 
			
		||||
        'enable_sha-1': False,
 | 
			
		||||
        'enable_sha-256': True,
 | 
			
		||||
        'enable_sha-512': True,
 | 
			
		||||
        'enable_sha3-256': True,
 | 
			
		||||
        'enable_sha3-512': True,
 | 
			
		||||
        'enable_BLAKE2b256': True,
 | 
			
		||||
        'enable_BLAKE2b512': True,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _hashlib_function = {
 | 
			
		||||
        'sha-1': hashlib.sha1,
 | 
			
		||||
        'sha-256': hashlib.sha256,
 | 
			
		||||
        'sha-512': hashlib.sha512,
 | 
			
		||||
        'sha3-256': lambda: hashlib.sha3_256(),
 | 
			
		||||
        'sha3-512': lambda: hashlib.sha3_512(),
 | 
			
		||||
        'BLAKE2b256': lambda: hashlib.blake2b(digest_size=32),
 | 
			
		||||
        'BLAKE2b512': lambda: hashlib.blake2b(digest_size=64),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        namespace = 'urn:xmpp:hash-function-text-names:%s'
 | 
			
		||||
        self.enabled_hashes = []
 | 
			
		||||
        for algo in self._hashlib_function:
 | 
			
		||||
            if getattr(self, 'enable_' + algo, False):
 | 
			
		||||
                # XXX: this is a hack for Python 3.5 or below, which
 | 
			
		||||
                # don’t support sha3 or blake2b…
 | 
			
		||||
                try:
 | 
			
		||||
                    self._hashlib_function[algo]()
 | 
			
		||||
                except AttributeError:
 | 
			
		||||
                    log.warn('Algorithm %s unavailable, disabling.', algo)
 | 
			
		||||
                else:
 | 
			
		||||
                    self.enabled_hashes.append(namespace % algo)
 | 
			
		||||
 | 
			
		||||
    def session_bind(self, jid):
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(Hash.namespace)
 | 
			
		||||
 | 
			
		||||
        for namespace in self.enabled_hashes:
 | 
			
		||||
            self.xmpp['xep_0030'].add_feature(namespace)
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        for namespace in self.enabled_hashes:
 | 
			
		||||
            self.xmpp['xep_0030'].del_feature(namespace)
 | 
			
		||||
 | 
			
		||||
        self.xmpp['xep_0030'].del_feature(feature=Hash.namespace)
 | 
			
		||||
 | 
			
		||||
    def compute_hash(self, filename, function=None):
 | 
			
		||||
        if function is None:
 | 
			
		||||
            function = self.preferred
 | 
			
		||||
        h = self._hashlib_function[function]()
 | 
			
		||||
        with open(filename, 'rb') as f:
 | 
			
		||||
            while True:
 | 
			
		||||
                block = f.read(self.block_size)
 | 
			
		||||
                if not block:
 | 
			
		||||
                    break
 | 
			
		||||
                h.update(block)
 | 
			
		||||
        hash_elem = Hash()
 | 
			
		||||
        hash_elem['algo'] = function
 | 
			
		||||
        hash_elem['value'] = b64encode(h.digest())
 | 
			
		||||
        return hash_elem
 | 
			
		||||
							
								
								
									
										35
									
								
								slixmpp/plugins/xep_0300/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								slixmpp/plugins/xep_0300/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2017 Emmanuel Gil Peyrot
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Hash(ElementBase):
 | 
			
		||||
    name = 'hash'
 | 
			
		||||
    namespace = 'urn:xmpp:hashes:2'
 | 
			
		||||
    plugin_attrib = 'hash'
 | 
			
		||||
    interfaces = {'algo', 'value'}
 | 
			
		||||
 | 
			
		||||
    allowed_algos = ['sha-1', 'sha-256', 'sha-512', 'sha3-256', 'sha3-512', 'BLAKE2b256', 'BLAKE2b512']
 | 
			
		||||
 | 
			
		||||
    def set_algo(self, value):
 | 
			
		||||
        if value in self.allowed_algos:
 | 
			
		||||
            self._set_attr('algo', value)
 | 
			
		||||
        elif value in [None, '']:
 | 
			
		||||
            self._del_attr('algo')
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError('Invalid algo: %s' % value)
 | 
			
		||||
 | 
			
		||||
    def get_value(self):
 | 
			
		||||
        return self.xml.text
 | 
			
		||||
 | 
			
		||||
    def set_value(self, value):
 | 
			
		||||
        self.xml.text = value
 | 
			
		||||
 | 
			
		||||
    def del_value(self):
 | 
			
		||||
        self.xml.text = ''
 | 
			
		||||
@@ -36,35 +36,58 @@ class XEP_0313(BasePlugin):
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.MAM)
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.Preferences)
 | 
			
		||||
        register_stanza_plugin(Message, stanza.Result)
 | 
			
		||||
        register_stanza_plugin(Message, stanza.Archived, iterable=True)
 | 
			
		||||
        register_stanza_plugin(Iq, stanza.Fin)
 | 
			
		||||
        register_stanza_plugin(stanza.Result, self.xmpp['xep_0297'].stanza.Forwarded)
 | 
			
		||||
        register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
 | 
			
		||||
        register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set)
 | 
			
		||||
 | 
			
		||||
    def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
 | 
			
		||||
                 timeout=None, callback=None, iterator=False):
 | 
			
		||||
                 timeout=None, callback=None, iterator=False, rsm=None):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        query_id = iq['id']
 | 
			
		||||
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        iq['type'] = 'set'
 | 
			
		||||
        iq['mam']['queryid'] = query_id
 | 
			
		||||
        iq['mam']['start'] = start
 | 
			
		||||
        iq['mam']['end'] = end
 | 
			
		||||
        iq['mam']['with'] = with_jid
 | 
			
		||||
        if rsm:
 | 
			
		||||
            for key, value in rsm.items():
 | 
			
		||||
                iq['mam']['rsm'][key] = str(value)
 | 
			
		||||
 | 
			
		||||
        cb_data = {}
 | 
			
		||||
        def pre_cb(query):
 | 
			
		||||
            query['mam']['queryid'] = query['id']
 | 
			
		||||
            collector = Collector(
 | 
			
		||||
                'MAM_Results_%s' % query_id,
 | 
			
		||||
                StanzaPath('message/mam_result@queryid=%s' % query['id']))
 | 
			
		||||
            self.xmpp.register_handler(collector)
 | 
			
		||||
            cb_data['collector'] = collector
 | 
			
		||||
 | 
			
		||||
        def post_cb(result):
 | 
			
		||||
            results = cb_data['collector'].stop()
 | 
			
		||||
            if result['type'] == 'result':
 | 
			
		||||
                result['mam']['results'] = results
 | 
			
		||||
 | 
			
		||||
        if iterator:
 | 
			
		||||
            return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results',
 | 
			
		||||
                                                 recv_interface='mam_fin',
 | 
			
		||||
                                                 pre_cb=pre_cb, post_cb=post_cb)
 | 
			
		||||
 | 
			
		||||
        collector = Collector(
 | 
			
		||||
            'MAM_Results_%s' % query_id,
 | 
			
		||||
            StanzaPath('message/mam_result@queryid=%s' % query_id))
 | 
			
		||||
        self.xmpp.register_handler(collector)
 | 
			
		||||
 | 
			
		||||
        if iterator:
 | 
			
		||||
            return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results')
 | 
			
		||||
        def wrapped_cb(iq):
 | 
			
		||||
            results = collector.stop()
 | 
			
		||||
            if iq['type'] == 'result':
 | 
			
		||||
                iq['mam']['results'] = results
 | 
			
		||||
            callback(iq)
 | 
			
		||||
            if callback:
 | 
			
		||||
                callback(iq)
 | 
			
		||||
 | 
			
		||||
        return iq.send(timeout=timeout, callback=wrapped_cb)
 | 
			
		||||
 | 
			
		||||
    def set_preferences(self, jid=None, default=None, always=None, never=None,
 | 
			
		||||
 
 | 
			
		||||
@@ -10,44 +10,76 @@ import datetime as dt
 | 
			
		||||
 | 
			
		||||
from slixmpp.jid import JID
 | 
			
		||||
from slixmpp.xmlstream import ElementBase, ET
 | 
			
		||||
from slixmpp.plugins import xep_0082
 | 
			
		||||
from slixmpp.plugins import xep_0082, xep_0004
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MAM(ElementBase):
 | 
			
		||||
    name = 'query'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:tmp'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:2'
 | 
			
		||||
    plugin_attrib = 'mam'
 | 
			
		||||
    interfaces = {'queryid', 'start', 'end', 'with', 'results'}
 | 
			
		||||
    sub_interfaces = {'start', 'end', 'with'}
 | 
			
		||||
 | 
			
		||||
    def setup(self, xml=None):
 | 
			
		||||
        ElementBase.setup(self, xml)
 | 
			
		||||
        self._form = xep_0004.stanza.Form()
 | 
			
		||||
        self._form['type'] = 'submit'
 | 
			
		||||
        field = self._form.add_field(var='FORM_TYPE', ftype='hidden',
 | 
			
		||||
                             value='urn:xmpp:mam:2')
 | 
			
		||||
        self.append(self._form)
 | 
			
		||||
        self._results = []
 | 
			
		||||
 | 
			
		||||
    def __get_fields(self):
 | 
			
		||||
        return self._form.get_fields()
 | 
			
		||||
 | 
			
		||||
    def get_start(self):
 | 
			
		||||
        timestamp = self._get_sub_text('start')
 | 
			
		||||
        return xep_0082.parse(timestamp)
 | 
			
		||||
        fields = self.__get_fields()
 | 
			
		||||
        field = fields.get('start')
 | 
			
		||||
        if field:
 | 
			
		||||
            return xep_0082.parse(field['value'])
 | 
			
		||||
 | 
			
		||||
    def set_start(self, value):
 | 
			
		||||
        if isinstance(value, dt.datetime):
 | 
			
		||||
            value = xep_0082.format_datetime(value)
 | 
			
		||||
        self._set_sub_text('start', value)
 | 
			
		||||
        fields = self.__get_fields()
 | 
			
		||||
        field = fields.get('start')
 | 
			
		||||
        if field:
 | 
			
		||||
            field['value'] = value
 | 
			
		||||
        else:
 | 
			
		||||
            field = self._form.add_field(var='start')
 | 
			
		||||
            field['value'] = value
 | 
			
		||||
 | 
			
		||||
    def get_end(self):
 | 
			
		||||
        timestamp = self._get_sub_text('end')
 | 
			
		||||
        return xep_0082.parse(timestamp)
 | 
			
		||||
        fields = self.__get_fields()
 | 
			
		||||
        field = fields.get('end')
 | 
			
		||||
        if field:
 | 
			
		||||
            return xep_0082.parse(field['value'])
 | 
			
		||||
 | 
			
		||||
    def set_end(self, value):
 | 
			
		||||
        if isinstance(value, dt.datetime):
 | 
			
		||||
            value = xep_0082.format_datetime(value)
 | 
			
		||||
        self._set_sub_text('end', value)
 | 
			
		||||
        fields = self.__get_fields()
 | 
			
		||||
        field = fields.get('end')
 | 
			
		||||
        if field:
 | 
			
		||||
            field['value'] = value
 | 
			
		||||
        else:
 | 
			
		||||
            field = self._form.add_field(var='end')
 | 
			
		||||
            field['value'] = value
 | 
			
		||||
 | 
			
		||||
    def get_with(self):
 | 
			
		||||
        return JID(self._get_sub_text('with'))
 | 
			
		||||
        fields = self.__get_fields()
 | 
			
		||||
        field = fields.get('with')
 | 
			
		||||
        if field:
 | 
			
		||||
            return JID(field['value'])
 | 
			
		||||
 | 
			
		||||
    def set_with(self, value):
 | 
			
		||||
        self._set_sub_text('with', str(value))
 | 
			
		||||
 | 
			
		||||
        fields = self.__get_fields()
 | 
			
		||||
        field = fields.get('with')
 | 
			
		||||
        if field:
 | 
			
		||||
            field['with'] = str(value)
 | 
			
		||||
        else:
 | 
			
		||||
            field = self._form.add_field(var='with')
 | 
			
		||||
            field['value'] = str(value)
 | 
			
		||||
    # The results interface is meant only as an easy
 | 
			
		||||
    # way to access the set of collected message responses
 | 
			
		||||
    # from the query.
 | 
			
		||||
@@ -64,7 +96,7 @@ class MAM(ElementBase):
 | 
			
		||||
 | 
			
		||||
class Preferences(ElementBase):
 | 
			
		||||
    name = 'prefs'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:tmp'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:2'
 | 
			
		||||
    plugin_attrib = 'mam_prefs'
 | 
			
		||||
    interfaces = {'default', 'always', 'never'}
 | 
			
		||||
    sub_interfaces = {'always', 'never'}
 | 
			
		||||
@@ -118,22 +150,13 @@ class Preferences(ElementBase):
 | 
			
		||||
            never.append(jid_xml)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Fin(ElementBase):
 | 
			
		||||
    name = 'fin'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:2'
 | 
			
		||||
    plugin_attrib = 'mam_fin'
 | 
			
		||||
 | 
			
		||||
class Result(ElementBase):
 | 
			
		||||
    name = 'result'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:tmp'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:2'
 | 
			
		||||
    plugin_attrib = 'mam_result'
 | 
			
		||||
    interfaces = {'queryid', 'id'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Archived(ElementBase):
 | 
			
		||||
    name = 'archived'
 | 
			
		||||
    namespace = 'urn:xmpp:mam:tmp'
 | 
			
		||||
    plugin_attrib = 'mam_archived'
 | 
			
		||||
    plugin_multi_attrib = 'mam_archives'
 | 
			
		||||
    interfaces = {'by', 'id'}
 | 
			
		||||
 | 
			
		||||
    def get_by(self):
 | 
			
		||||
        return JID(self._get_attr('by'))
 | 
			
		||||
 | 
			
		||||
    def set_by(self, value):
 | 
			
		||||
        return self._set_attr('by', str(value))
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from datetime import datetime, timedelta, timezone
 | 
			
		||||
 | 
			
		||||
from slixmpp.stanza import Presence
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
@@ -16,6 +16,10 @@ from slixmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from slixmpp.plugins.xep_0319 import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_local_timezone():
 | 
			
		||||
    return datetime.now(timezone.utc).astimezone().tzinfo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class XEP_0319(BasePlugin):
 | 
			
		||||
    name = 'xep_0319'
 | 
			
		||||
    description = 'XEP-0319: Last User Interaction in Presence'
 | 
			
		||||
@@ -47,10 +51,11 @@ class XEP_0319(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    def idle(self, jid=None, since=None):
 | 
			
		||||
        seconds = None
 | 
			
		||||
        timezone = get_local_timezone()
 | 
			
		||||
        if since is None:
 | 
			
		||||
            since = datetime.now()
 | 
			
		||||
            since = datetime.now(timezone)
 | 
			
		||||
        else:
 | 
			
		||||
            seconds = datetime.now() - since
 | 
			
		||||
            seconds = datetime.now(timezone) - since
 | 
			
		||||
        self.api['set_idle'](jid, None, None, since)
 | 
			
		||||
        self.xmpp['xep_0012'].set_last_activity(jid=jid, seconds=seconds)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -291,7 +291,7 @@ class XEP_0323(BasePlugin):
 | 
			
		||||
                request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600
 | 
			
		||||
                if request_delay_sec <= 0:
 | 
			
		||||
                    req_ok = False
 | 
			
		||||
                    error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat()
 | 
			
		||||
                    error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past (%s). Current time: %s" % (dt.isoformat(), dtnow.isoformat())
 | 
			
		||||
 | 
			
		||||
        if req_ok:
 | 
			
		||||
            session = self._new_session()
 | 
			
		||||
 
 | 
			
		||||
@@ -399,7 +399,7 @@ class XEP_0325(BasePlugin):
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not session in self.sessions:
 | 
			
		||||
            # This can happend if a session was deleted, like in a timeout. Just drop the data.
 | 
			
		||||
            # This can happen if a session was deleted, like in a timeout. Just drop the data.
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if result == "error":
 | 
			
		||||
@@ -457,7 +457,7 @@ class XEP_0325(BasePlugin):
 | 
			
		||||
        Arguments:
 | 
			
		||||
            from_jid        -- The jid of the requester
 | 
			
		||||
            to_jid          -- The jid of the device(s)
 | 
			
		||||
            callback        -- The callback function to call when data is availble.
 | 
			
		||||
            callback        -- The callback function to call when data is available.
 | 
			
		||||
 | 
			
		||||
                            The callback function must support the following arguments:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								slixmpp/plugins/xep_0335/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								slixmpp/plugins/xep_0335/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2018 Maxime “pep” Buquet
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.xep_0335 import stanza
 | 
			
		||||
from slixmpp.plugins.xep_0335.stanza import JSON_Container
 | 
			
		||||
from slixmpp.plugins.xep_0335.json_containers import XEP_0335
 | 
			
		||||
 | 
			
		||||
register_plugin(XEP_0335)
 | 
			
		||||
							
								
								
									
										23
									
								
								slixmpp/plugins/xep_0335/json_containers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								slixmpp/plugins/xep_0335/json_containers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2018 Maxime “pep” Buquet
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp import Message
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from slixmpp.plugins.xep_0335 import JSON_Container
 | 
			
		||||
from slixmpp.plugins.xep_0335 import stanza
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class XEP_0335(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'xep_0335'
 | 
			
		||||
    description = 'XEP-0335: JSON Containers'
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        register_stanza_plugin(Message, JSON_Container)
 | 
			
		||||
							
								
								
									
										28
									
								
								slixmpp/plugins/xep_0335/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								slixmpp/plugins/xep_0335/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2018 Maxime “pep” Buquet
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
from slixmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JSON_Container(ElementBase):
 | 
			
		||||
    name = 'json'
 | 
			
		||||
    plugin_attrib = 'json'
 | 
			
		||||
    namespace = 'urn:xmpp:json:0'
 | 
			
		||||
    interfaces = {'value'}
 | 
			
		||||
 | 
			
		||||
    def get_value(self):
 | 
			
		||||
        return json.loads(self.xml.text)
 | 
			
		||||
 | 
			
		||||
    def set_value(self, value):
 | 
			
		||||
        if not isinstance(value, str):
 | 
			
		||||
            value = json.dumps(value)
 | 
			
		||||
        self.xml.text = value
 | 
			
		||||
 | 
			
		||||
    def del_value(self):
 | 
			
		||||
        self.xml.text = ''
 | 
			
		||||
							
								
								
									
										14
									
								
								slixmpp/plugins/xep_0363/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								slixmpp/plugins/xep_0363/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
"""
 | 
			
		||||
    slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2018 Emmanuel Gil Peyrot
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.xep_0363.stanza import Request, Slot, Put, Get, Header
 | 
			
		||||
from slixmpp.plugins.xep_0363.http_upload import XEP_0363
 | 
			
		||||
 | 
			
		||||
register_plugin(XEP_0363)
 | 
			
		||||
							
								
								
									
										159
									
								
								slixmpp/plugins/xep_0363/http_upload.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								slixmpp/plugins/xep_0363/http_upload.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,159 @@
 | 
			
		||||
"""
 | 
			
		||||
    slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2018 Emmanuel Gil Peyrot
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import os.path
 | 
			
		||||
 | 
			
		||||
from aiohttp import ClientSession
 | 
			
		||||
from mimetypes import guess_type
 | 
			
		||||
 | 
			
		||||
from slixmpp import Iq, __version__
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin
 | 
			
		||||
from slixmpp.xmlstream.handler import Callback
 | 
			
		||||
from slixmpp.xmlstream.matcher import StanzaPath
 | 
			
		||||
from slixmpp.plugins.xep_0363 import stanza, Request, Slot, Put, Get, Header
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
class FileUploadError(Exception):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class UploadServiceNotFound(FileUploadError):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class FileTooBig(FileUploadError):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
class HTTPError(FileUploadError):
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return 'Could not upload file: %d (%s)' % (self.args[0], self.args[1])
 | 
			
		||||
 | 
			
		||||
class XEP_0363(BasePlugin):
 | 
			
		||||
    ''' This plugin only supports Python 3.5+ '''
 | 
			
		||||
 | 
			
		||||
    name = 'xep_0363'
 | 
			
		||||
    description = 'XEP-0363: HTTP File Upload'
 | 
			
		||||
    dependencies = {'xep_0030', 'xep_0128'}
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
    default_config = {
 | 
			
		||||
        'upload_service': None,
 | 
			
		||||
        'max_file_size': float('+inf'),
 | 
			
		||||
        'default_content_type': 'application/octet-stream',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        register_stanza_plugin(Iq, Request)
 | 
			
		||||
        register_stanza_plugin(Iq, Slot)
 | 
			
		||||
        register_stanza_plugin(Slot, Put)
 | 
			
		||||
        register_stanza_plugin(Slot, Get)
 | 
			
		||||
        register_stanza_plugin(Put, Header, iterable=True)
 | 
			
		||||
 | 
			
		||||
        self.xmpp.register_handler(
 | 
			
		||||
                Callback('HTTP Upload Request',
 | 
			
		||||
                         StanzaPath('iq@type=get/http_upload_request'),
 | 
			
		||||
                         self._handle_request))
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        self._http_session.close()
 | 
			
		||||
        self.xmpp.remove_handler('HTTP Upload Request')
 | 
			
		||||
        self.xmpp.remove_handler('HTTP Upload Slot')
 | 
			
		||||
        self.xmpp['xep_0030'].del_feature(feature=Request.namespace)
 | 
			
		||||
 | 
			
		||||
    def session_bind(self, jid):
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature(Request.namespace)
 | 
			
		||||
 | 
			
		||||
    def _handle_request(self, iq):
 | 
			
		||||
        self.xmpp.event('http_upload_request', iq)
 | 
			
		||||
 | 
			
		||||
    async def find_upload_service(self, domain=None, timeout=None):
 | 
			
		||||
        results = await self.xmpp['xep_0030'].get_info_from_domain(
 | 
			
		||||
            domain=domain, timeout=timeout)
 | 
			
		||||
 | 
			
		||||
        candidates = []
 | 
			
		||||
        for info in results:
 | 
			
		||||
            for identity in info['disco_info']['identities']:
 | 
			
		||||
                if identity[0] == 'store' and identity[1] == 'file':
 | 
			
		||||
                    candidates.append(info)
 | 
			
		||||
        for info in candidates:
 | 
			
		||||
            for feature in info['disco_info']['features']:
 | 
			
		||||
                if feature == Request.namespace:
 | 
			
		||||
                    return info
 | 
			
		||||
 | 
			
		||||
    def request_slot(self, jid, filename, size, content_type=None, ifrom=None,
 | 
			
		||||
                     timeout=None, callback=None, timeout_callback=None):
 | 
			
		||||
        iq = self.xmpp.Iq()
 | 
			
		||||
        iq['to'] = jid
 | 
			
		||||
        iq['from'] = ifrom
 | 
			
		||||
        iq['type'] = 'get'
 | 
			
		||||
        request = iq['http_upload_request']
 | 
			
		||||
        request['filename'] = filename
 | 
			
		||||
        request['size'] = str(size)
 | 
			
		||||
        request['content-type'] = content_type or self.default_content_type
 | 
			
		||||
        return iq.send(timeout=timeout, callback=callback,
 | 
			
		||||
                       timeout_callback=timeout_callback)
 | 
			
		||||
 | 
			
		||||
    async def upload_file(self, filename, size=None, content_type=None, *,
 | 
			
		||||
                          input_file=None, ifrom=None, domain=None, timeout=None,
 | 
			
		||||
                          callback=None, timeout_callback=None):
 | 
			
		||||
        ''' Helper function which does all of the uploading process. '''
 | 
			
		||||
        if self.upload_service is None:
 | 
			
		||||
            info_iq = await self.find_upload_service(
 | 
			
		||||
                domain=domain, timeout=timeout)
 | 
			
		||||
            if info_iq is None:
 | 
			
		||||
                raise UploadServiceNotFound()
 | 
			
		||||
            self.upload_service = info_iq['from']
 | 
			
		||||
            for form in info_iq['disco_info'].iterables:
 | 
			
		||||
                values = form['values']
 | 
			
		||||
                if values['FORM_TYPE'] == ['urn:xmpp:http:upload:0']:
 | 
			
		||||
                    try:
 | 
			
		||||
                        self.max_file_size = int(values['max-file-size'])
 | 
			
		||||
                    except (TypeError, ValueError):
 | 
			
		||||
                        log.error('Invalid max size received from HTTP File Upload service')
 | 
			
		||||
                        self.max_file_size = float('+inf')
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        if input_file is None:
 | 
			
		||||
            input_file = open(filename, 'rb')
 | 
			
		||||
 | 
			
		||||
        if size is None:
 | 
			
		||||
            size = input_file.seek(0, 2)
 | 
			
		||||
            input_file.seek(0)
 | 
			
		||||
 | 
			
		||||
        if size > self.max_file_size:
 | 
			
		||||
            raise FileTooBig()
 | 
			
		||||
 | 
			
		||||
        if content_type is None:
 | 
			
		||||
            content_type = guess_type(filename)[0]
 | 
			
		||||
            if content_type is None:
 | 
			
		||||
                content_type = self.default_content_type
 | 
			
		||||
 | 
			
		||||
        basename = os.path.basename(filename)
 | 
			
		||||
        slot_iq = await self.request_slot(self.upload_service, basename, size,
 | 
			
		||||
                                          content_type, ifrom, timeout,
 | 
			
		||||
                                          timeout_callback=timeout_callback)
 | 
			
		||||
        slot = slot_iq['http_upload_slot']
 | 
			
		||||
 | 
			
		||||
        headers = {
 | 
			
		||||
            'Content-Length': str(size),
 | 
			
		||||
            'Content-Type': content_type or self.default_content_type,
 | 
			
		||||
            **{header['name']: header['value'] for header in slot['put']['headers']}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # Do the actual upload here.
 | 
			
		||||
        async with ClientSession(headers={'User-Agent': 'slixmpp ' + __version__}) as session:
 | 
			
		||||
            response = await session.put(
 | 
			
		||||
                    slot['put']['url'],
 | 
			
		||||
                    data=input_file,
 | 
			
		||||
                    headers=headers,
 | 
			
		||||
                    timeout=timeout)
 | 
			
		||||
            if response.status >= 400:
 | 
			
		||||
                raise HTTPError(response.status, await response.text())
 | 
			
		||||
            log.info('Response code: %d (%s)', response.status, await response.text())
 | 
			
		||||
            response.close()
 | 
			
		||||
            return slot['get']['url']
 | 
			
		||||
							
								
								
									
										48
									
								
								slixmpp/plugins/xep_0363/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								slixmpp/plugins/xep_0363/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
"""
 | 
			
		||||
    slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2018 Emmanuel Gil Peyrot
 | 
			
		||||
    This file is part of slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.xmlstream import ElementBase
 | 
			
		||||
 | 
			
		||||
class Request(ElementBase):
 | 
			
		||||
    plugin_attrib = 'http_upload_request'
 | 
			
		||||
    name = 'request'
 | 
			
		||||
    namespace = 'urn:xmpp:http:upload:0'
 | 
			
		||||
    interfaces = {'filename', 'size', 'content-type'}
 | 
			
		||||
 | 
			
		||||
class Slot(ElementBase):
 | 
			
		||||
    plugin_attrib = 'http_upload_slot'
 | 
			
		||||
    name = 'slot'
 | 
			
		||||
    namespace = 'urn:xmpp:http:upload:0'
 | 
			
		||||
 | 
			
		||||
class Put(ElementBase):
 | 
			
		||||
    plugin_attrib = 'put'
 | 
			
		||||
    name = 'put'
 | 
			
		||||
    namespace = 'urn:xmpp:http:upload:0'
 | 
			
		||||
    interfaces = {'url'}
 | 
			
		||||
 | 
			
		||||
class Get(ElementBase):
 | 
			
		||||
    plugin_attrib = 'get'
 | 
			
		||||
    name = 'get'
 | 
			
		||||
    namespace = 'urn:xmpp:http:upload:0'
 | 
			
		||||
    interfaces = {'url'}
 | 
			
		||||
 | 
			
		||||
class Header(ElementBase):
 | 
			
		||||
    plugin_attrib = 'header'
 | 
			
		||||
    name = 'header'
 | 
			
		||||
    namespace = 'urn:xmpp:http:upload:0'
 | 
			
		||||
    plugin_multi_attrib = 'headers'
 | 
			
		||||
    interfaces = {'name', 'value'}
 | 
			
		||||
 | 
			
		||||
    def get_value(self):
 | 
			
		||||
        return self.xml.text
 | 
			
		||||
 | 
			
		||||
    def set_value(self, value):
 | 
			
		||||
        self.xml.text = value
 | 
			
		||||
 | 
			
		||||
    def del_value(self):
 | 
			
		||||
        self.xml.text = ''
 | 
			
		||||
@@ -49,15 +49,17 @@ class XEP_0380(BasePlugin):
 | 
			
		||||
 | 
			
		||||
        register_stanza_plugin(Message, Encryption)
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        self.xmpp.remove_handler('Chat State')
 | 
			
		||||
 | 
			
		||||
    def session_bind(self, jid):
 | 
			
		||||
        self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
 | 
			
		||||
 | 
			
		||||
    def has_eme(self, msg):
 | 
			
		||||
        return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None
 | 
			
		||||
 | 
			
		||||
    def add_eme(self, msg: Message, namespace: str) -> Message:
 | 
			
		||||
        msg['eme']['name'] = self.mechanisms[namespace]
 | 
			
		||||
        msg['eme']['namespace'] = namespace
 | 
			
		||||
        return msg
 | 
			
		||||
 | 
			
		||||
    def replace_body_with_eme(self, msg):
 | 
			
		||||
        eme = msg['eme']
 | 
			
		||||
        namespace = eme['namespace']
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								slixmpp/plugins/xep_0394/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								slixmpp/plugins/xep_0394/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.base import register_plugin
 | 
			
		||||
 | 
			
		||||
from slixmpp.plugins.xep_0394.stanza import Markup, Span, BlockCode, List, Li, BlockQuote
 | 
			
		||||
from slixmpp.plugins.xep_0394.markup import XEP_0394
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_plugin(XEP_0394)
 | 
			
		||||
							
								
								
									
										161
									
								
								slixmpp/plugins/xep_0394/markup.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								slixmpp/plugins/xep_0394/markup.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from slixmpp.stanza import Message
 | 
			
		||||
from slixmpp.plugins import BasePlugin
 | 
			
		||||
from slixmpp.xmlstream import register_stanza_plugin, ET, tostring
 | 
			
		||||
from slixmpp.plugins.xep_0394 import stanza, Markup, Span, BlockCode, List, Li, BlockQuote
 | 
			
		||||
from slixmpp.plugins.xep_0071 import XHTML_IM
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Start:
 | 
			
		||||
    def __init__(self, elem):
 | 
			
		||||
        self.elem = elem
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return 'Start(%s)' % self.elem
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class End:
 | 
			
		||||
    def __init__(self, elem):
 | 
			
		||||
        self.elem = elem
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return 'End(%s)' % self.elem
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class XEP_0394(BasePlugin):
 | 
			
		||||
 | 
			
		||||
    name = 'xep_0394'
 | 
			
		||||
    description = 'XEP-0394: Message Markup'
 | 
			
		||||
    dependencies = {'xep_0030', 'xep_0071'}
 | 
			
		||||
    stanza = stanza
 | 
			
		||||
 | 
			
		||||
    def plugin_init(self):
 | 
			
		||||
        register_stanza_plugin(Message, Markup)
 | 
			
		||||
 | 
			
		||||
    def session_bind(self, jid):
 | 
			
		||||
        self.xmpp['xep_0030'].add_feature(feature=Markup.namespace)
 | 
			
		||||
 | 
			
		||||
    def plugin_end(self):
 | 
			
		||||
        self.xmpp['xep_0030'].del_feature(feature=Markup.namespace)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _split_first_level(body, markup_elem):
 | 
			
		||||
        split_points = []
 | 
			
		||||
        elements = {}
 | 
			
		||||
        for markup in markup_elem['substanzas']:
 | 
			
		||||
            start = markup['start']
 | 
			
		||||
            end = markup['end']
 | 
			
		||||
            split_points.append(start)
 | 
			
		||||
            split_points.append(end)
 | 
			
		||||
            elements.setdefault(start, []).append(Start(markup))
 | 
			
		||||
            elements.setdefault(end, []).append(End(markup))
 | 
			
		||||
            if isinstance(markup, List):
 | 
			
		||||
                lis = markup['lis']
 | 
			
		||||
                for i, li in enumerate(lis):
 | 
			
		||||
                    start = li['start']
 | 
			
		||||
                    split_points.append(start)
 | 
			
		||||
                    li_end = lis[i + 1]['start'] if i < len(lis) - 1 else end
 | 
			
		||||
                    elements.setdefault(li_end, []).append(End(li))
 | 
			
		||||
                    elements.setdefault(start, []).append(Start(li))
 | 
			
		||||
        split_points = set(split_points)
 | 
			
		||||
        new_body = [[]]
 | 
			
		||||
        for i, letter in enumerate(body + '\x00'):
 | 
			
		||||
            if i in split_points:
 | 
			
		||||
                body_elements = []
 | 
			
		||||
                for elem in elements[i]:
 | 
			
		||||
                    body_elements.append(elem)
 | 
			
		||||
                new_body.append(body_elements)
 | 
			
		||||
                new_body.append([])
 | 
			
		||||
            new_body[-1].append(letter)
 | 
			
		||||
        new_body[-1] = new_body[-1][:-1]
 | 
			
		||||
        final = []
 | 
			
		||||
        for chunk in new_body:
 | 
			
		||||
            if not chunk:
 | 
			
		||||
                continue
 | 
			
		||||
            final.append(''.join(chunk) if isinstance(chunk[0], str) else chunk)
 | 
			
		||||
        return final
 | 
			
		||||
 | 
			
		||||
    def to_plain_text(self, body, markup_elem):
 | 
			
		||||
        chunks = self._split_first_level(body, markup_elem)
 | 
			
		||||
        final = []
 | 
			
		||||
        for chunk in chunks:
 | 
			
		||||
            if isinstance(chunk, str):
 | 
			
		||||
                final.append(chunk)
 | 
			
		||||
        return ''.join(final)
 | 
			
		||||
 | 
			
		||||
    def to_xhtml_im(self, body, markup_elem):
 | 
			
		||||
        chunks = self._split_first_level(body, markup_elem)
 | 
			
		||||
        final = []
 | 
			
		||||
        stack = []
 | 
			
		||||
        for chunk in chunks:
 | 
			
		||||
            if isinstance(chunk, str):
 | 
			
		||||
                chunk = (chunk.replace("&", '&')
 | 
			
		||||
                              .replace('<', '<')
 | 
			
		||||
                              .replace('>', '>')
 | 
			
		||||
                              .replace('"', '"')
 | 
			
		||||
                              .replace("'", ''')
 | 
			
		||||
                              .replace('\n', '<br/>'))
 | 
			
		||||
                final.append(chunk)
 | 
			
		||||
                continue
 | 
			
		||||
            num_end = 0
 | 
			
		||||
            for elem in chunk:
 | 
			
		||||
                if isinstance(elem, End):
 | 
			
		||||
                    num_end += 1
 | 
			
		||||
 | 
			
		||||
            for i in range(num_end):
 | 
			
		||||
                stack_top = stack.pop()
 | 
			
		||||
                for elem in chunk:
 | 
			
		||||
                    if not isinstance(elem, End):
 | 
			
		||||
                        continue
 | 
			
		||||
                    elem = elem.elem
 | 
			
		||||
                    if elem is stack_top:
 | 
			
		||||
                        if isinstance(elem, Span):
 | 
			
		||||
                            final.append('</span>')
 | 
			
		||||
                        elif isinstance(elem, BlockCode):
 | 
			
		||||
                            final.append('</code></pre>')
 | 
			
		||||
                        elif isinstance(elem, List):
 | 
			
		||||
                            final.append('</ul>')
 | 
			
		||||
                        elif isinstance(elem, Li):
 | 
			
		||||
                            final.append('</li>')
 | 
			
		||||
                        elif isinstance(elem, BlockQuote):
 | 
			
		||||
                            final.append('</blockquote>')
 | 
			
		||||
                        break
 | 
			
		||||
                else:
 | 
			
		||||
                    assert False
 | 
			
		||||
            for elem in chunk:
 | 
			
		||||
                if not isinstance(elem, Start):
 | 
			
		||||
                    continue
 | 
			
		||||
                elem = elem.elem
 | 
			
		||||
                stack.append(elem)
 | 
			
		||||
                if isinstance(elem, Span):
 | 
			
		||||
                    style = []
 | 
			
		||||
                    for type_ in elem['types']:
 | 
			
		||||
                        if type_ == 'emphasis':
 | 
			
		||||
                            style.append('font-style: italic;')
 | 
			
		||||
                        if type_ == 'code':
 | 
			
		||||
                            style.append('font-family: monospace;')
 | 
			
		||||
                        if type_ == 'deleted':
 | 
			
		||||
                            style.append('text-decoration: line-through;')
 | 
			
		||||
                    final.append("<span style='%s'>" % ' '.join(style))
 | 
			
		||||
                elif isinstance(elem, BlockCode):
 | 
			
		||||
                    final.append('<pre><code>')
 | 
			
		||||
                elif isinstance(elem, List):
 | 
			
		||||
                    final.append('<ul>')
 | 
			
		||||
                elif isinstance(elem, Li):
 | 
			
		||||
                    final.append('<li>')
 | 
			
		||||
                elif isinstance(elem, BlockQuote):
 | 
			
		||||
                    final.append('<blockquote>')
 | 
			
		||||
        p = "<p xmlns='http://www.w3.org/1999/xhtml'>%s</p>" % ''.join(final)
 | 
			
		||||
        p2 = ET.fromstring(p)
 | 
			
		||||
        print('coucou', p, tostring(p2))
 | 
			
		||||
        xhtml_im = XHTML_IM()
 | 
			
		||||
        xhtml_im['body'] = p2
 | 
			
		||||
        return xhtml_im
 | 
			
		||||
							
								
								
									
										123
									
								
								slixmpp/plugins/xep_0394/stanza.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								slixmpp/plugins/xep_0394/stanza.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
"""
 | 
			
		||||
    Slixmpp: The Slick XMPP Library
 | 
			
		||||
    Copyright (C) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
 | 
			
		||||
    This file is part of Slixmpp.
 | 
			
		||||
 | 
			
		||||
    See the file LICENSE for copying permission.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin, ET
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Markup(ElementBase):
 | 
			
		||||
    namespace = 'urn:xmpp:markup:0'
 | 
			
		||||
    name = 'markup'
 | 
			
		||||
    plugin_attrib = 'markup'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _FirstLevel(ElementBase):
 | 
			
		||||
    namespace = 'urn:xmpp:markup:0'
 | 
			
		||||
    interfaces = {'start', 'end'}
 | 
			
		||||
 | 
			
		||||
    def get_start(self):
 | 
			
		||||
        return int(self._get_attr('start'))
 | 
			
		||||
 | 
			
		||||
    def set_start(self, value):
 | 
			
		||||
        self._set_attr('start', '%d' % value)
 | 
			
		||||
 | 
			
		||||
    def get_end(self):
 | 
			
		||||
        return int(self._get_attr('end'))
 | 
			
		||||
 | 
			
		||||
    def set_end(self, value):
 | 
			
		||||
        self._set_attr('end', '%d' % value)
 | 
			
		||||
 | 
			
		||||
class Span(_FirstLevel):
 | 
			
		||||
    name = 'span'
 | 
			
		||||
    plugin_attrib = 'span'
 | 
			
		||||
    plugin_multi_attrib = 'spans'
 | 
			
		||||
    interfaces = {'start', 'end', 'types'}
 | 
			
		||||
 | 
			
		||||
    def get_types(self):
 | 
			
		||||
        types = []
 | 
			
		||||
        if self.xml.find('{urn:xmpp:markup:0}emphasis') is not None:
 | 
			
		||||
            types.append('emphasis')
 | 
			
		||||
        if self.xml.find('{urn:xmpp:markup:0}code') is not None:
 | 
			
		||||
            types.append('code')
 | 
			
		||||
        if self.xml.find('{urn:xmpp:markup:0}deleted') is not None:
 | 
			
		||||
            types.append('deleted')
 | 
			
		||||
        return types
 | 
			
		||||
 | 
			
		||||
    def set_types(self, value):
 | 
			
		||||
        del self['types']
 | 
			
		||||
        for type_ in value:
 | 
			
		||||
            if type_ == 'emphasis':
 | 
			
		||||
                self.xml.append(ET.Element('{urn:xmpp:markup:0}emphasis'))
 | 
			
		||||
            elif type_ == 'code':
 | 
			
		||||
                self.xml.append(ET.Element('{urn:xmpp:markup:0}code'))
 | 
			
		||||
            elif type_ == 'deleted':
 | 
			
		||||
                self.xml.append(ET.Element('{urn:xmpp:markup:0}deleted'))
 | 
			
		||||
 | 
			
		||||
    def det_types(self):
 | 
			
		||||
        for child in self.xml:
 | 
			
		||||
            self.xml.remove(child)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _SpanType(ElementBase):
 | 
			
		||||
    namespace = 'urn:xmpp:markup:0'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmphasisType(_SpanType):
 | 
			
		||||
    name = 'emphasis'
 | 
			
		||||
    plugin_attrib = 'emphasis'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CodeType(_SpanType):
 | 
			
		||||
    name = 'code'
 | 
			
		||||
    plugin_attrib = 'code'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeletedType(_SpanType):
 | 
			
		||||
    name = 'deleted'
 | 
			
		||||
    plugin_attrib = 'deleted'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BlockCode(_FirstLevel):
 | 
			
		||||
    name = 'bcode'
 | 
			
		||||
    plugin_attrib = 'bcode'
 | 
			
		||||
    plugin_multi_attrib = 'bcodes'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class List(_FirstLevel):
 | 
			
		||||
    name = 'list'
 | 
			
		||||
    plugin_attrib = 'list'
 | 
			
		||||
    plugin_multi_attrib = 'lists'
 | 
			
		||||
    interfaces = {'start', 'end', 'li'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Li(ElementBase):
 | 
			
		||||
    namespace = 'urn:xmpp:markup:0'
 | 
			
		||||
    name = 'li'
 | 
			
		||||
    plugin_attrib = 'li'
 | 
			
		||||
    plugin_multi_attrib = 'lis'
 | 
			
		||||
    interfaces = {'start'}
 | 
			
		||||
 | 
			
		||||
    def get_start(self):
 | 
			
		||||
        return int(self._get_attr('start'))
 | 
			
		||||
 | 
			
		||||
    def set_start(self, value):
 | 
			
		||||
        self._set_attr('start', '%d' % value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BlockQuote(_FirstLevel):
 | 
			
		||||
    name = 'bquote'
 | 
			
		||||
    plugin_attrib = 'bquote'
 | 
			
		||||
    plugin_multi_attrib = 'bquotes'
 | 
			
		||||
 | 
			
		||||
register_stanza_plugin(Markup, Span, iterable=True)
 | 
			
		||||
register_stanza_plugin(Markup, BlockCode, iterable=True)
 | 
			
		||||
register_stanza_plugin(Markup, List, iterable=True)
 | 
			
		||||
register_stanza_plugin(Markup, BlockQuote, iterable=True)
 | 
			
		||||
register_stanza_plugin(Span, EmphasisType)
 | 
			
		||||
register_stanza_plugin(Span, CodeType)
 | 
			
		||||
register_stanza_plugin(Span, DeletedType)
 | 
			
		||||
register_stanza_plugin(List, Li, iterable=True)
 | 
			
		||||
@@ -187,11 +187,24 @@ class Iq(RootStanza):
 | 
			
		||||
 | 
			
		||||
        future = asyncio.Future()
 | 
			
		||||
 | 
			
		||||
        # Prevents handlers from existing forever.
 | 
			
		||||
        if timeout is None:
 | 
			
		||||
            timeout = 120
 | 
			
		||||
 | 
			
		||||
        def callback_success(result):
 | 
			
		||||
            if result['type'] == 'error':
 | 
			
		||||
            type_ = result['type']
 | 
			
		||||
            if type_ == 'result':
 | 
			
		||||
                future.set_result(result)
 | 
			
		||||
            elif type_ == 'error':
 | 
			
		||||
                future.set_exception(IqError(result))
 | 
			
		||||
            else:
 | 
			
		||||
                future.set_result(result)
 | 
			
		||||
                # Most likely an iq addressed to ourself, rearm the callback.
 | 
			
		||||
                handler = constr(handler_name,
 | 
			
		||||
                                 matcher,
 | 
			
		||||
                                 callback_success,
 | 
			
		||||
                                 once=True)
 | 
			
		||||
                self.stream.register_handler(handler)
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            if timeout is not None:
 | 
			
		||||
                self.stream.cancel_schedule('IqTimeout_%s' % self['id'])
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user