Compare commits
177 Commits
sleek-merg
...
slix-1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
187c350805 | ||
|
|
96d1c26f90 | ||
|
|
46a90749f8 | ||
|
|
0c63a4bbda | ||
|
|
e4696e0471 | ||
|
|
8217dc5239 | ||
|
|
2586abc0d3 | ||
|
|
28f84ab3d9 | ||
|
|
813b45aded | ||
|
|
3a9b45e4f2 | ||
|
|
b8e091233e | ||
|
|
0edeefd977 | ||
|
|
6ba53cf1ff | ||
|
|
d7758eb7f4 | ||
|
|
125336aeee | ||
|
|
7cd1cf32ae | ||
|
|
d099e353a4 | ||
|
|
1e4a301c6e | ||
|
|
f53b12d227 | ||
|
|
e2562dcccf | ||
|
|
7b69ae3738 | ||
|
|
ab6df235d7 | ||
|
|
52cd8f4b22 | ||
|
|
e28318c271 | ||
|
|
39ee833c29 | ||
|
|
9019e2bc71 | ||
|
|
9208bf5bf1 | ||
|
|
f0f1698e46 | ||
|
|
eccd7f1c98 | ||
|
|
2587d82af8 | ||
|
|
7ea121b115 | ||
|
|
bb81fbbdfc | ||
|
|
1a00a08b7d | ||
|
|
90ea2a3411 | ||
|
|
8fc6814b6d | ||
|
|
ffced0ed9a | ||
|
|
e7248d9af9 | ||
|
|
6b1a04f59d | ||
|
|
4905407092 | ||
|
|
bd6ec10939 | ||
|
|
e15e6735f1 | ||
|
|
67afd6a462 | ||
|
|
2e2b97c53b | ||
|
|
a35df7fe1f | ||
|
|
fbc8562779 | ||
|
|
b549db959a | ||
|
|
d5188ac68a | ||
|
|
ada9444bf8 | ||
|
|
acc52fd935 | ||
|
|
1100ff1feb | ||
|
|
c17fc3a869 | ||
|
|
4dba697075 | ||
|
|
4305eddb4f | ||
|
|
c2dc44cfd1 | ||
|
|
5fc14de32e | ||
|
|
d245558fd5 | ||
|
|
9d45370e8a | ||
|
|
cc1cc61d36 | ||
|
|
c6740a4908 | ||
|
|
55114bcffe | ||
|
|
4fa5dedc47 | ||
|
|
5525ef2285 | ||
|
|
a7ac969215 | ||
|
|
329cb5a9f8 | ||
|
|
d9b47b33f5 | ||
|
|
3582ac9941 | ||
|
|
2a127a57a7 | ||
|
|
7059400020 | ||
|
|
0b14ef82d4 | ||
|
|
83953af53d | ||
|
|
110cf25c6d | ||
|
|
f2bf6072ec | ||
|
|
5f9abe2e0e | ||
|
|
ea65b672e7 | ||
|
|
93c705fb31 | ||
|
|
0724f623bb | ||
|
|
ffb2b6bc04 | ||
|
|
4a24f58be2 | ||
|
|
da14ce16ec | ||
|
|
18e5abb9dd | ||
|
|
1a75b76916 | ||
|
|
53b56899a0 | ||
|
|
a366482551 | ||
|
|
abcec1e2d3 | ||
|
|
eeab646bfa | ||
|
|
2c69144189 | ||
|
|
f54ebec654 | ||
|
|
2042e1a4d5 | ||
|
|
be14f0cc52 | ||
|
|
edd9199be8 | ||
|
|
bb094cc649 | ||
|
|
dbaa6ed952 | ||
|
|
8c94d894ab | ||
|
|
ffc7eac4dc | ||
|
|
555fd6d926 | ||
|
|
c024ac8f0b | ||
|
|
f00177c0cf | ||
|
|
224d7ae133 | ||
|
|
9b25a7cf77 | ||
|
|
7a908ac07b | ||
|
|
92901637ec | ||
|
|
3590b663ed | ||
|
|
a33bde9cc3 | ||
|
|
ac50fdccfc | ||
|
|
a0c6bf15e9 | ||
|
|
a8ac115310 | ||
|
|
1345b7c1d0 | ||
|
|
d60a652259 | ||
|
|
61a7cecb31 | ||
|
|
192b7e0349 | ||
|
|
80b60fc048 | ||
|
|
842157a6cc | ||
|
|
a63cc01482 | ||
|
|
1bbb6f3ff9 | ||
|
|
93894247a4 | ||
|
|
16bb5e2537 | ||
|
|
d19a6e05b2 | ||
|
|
86e85f9835 | ||
|
|
cc145d20b0 | ||
|
|
881d9040c4 | ||
|
|
1e77ea0944 | ||
|
|
140f0885b2 | ||
|
|
83f71a6610 | ||
|
|
271343a32d | ||
|
|
48857b0030 | ||
|
|
1fe7f5f4e6 | ||
|
|
81b7b2c190 | ||
|
|
460de7d301 | ||
|
|
69022c6db7 | ||
|
|
9044807121 | ||
|
|
24264d3a07 | ||
|
|
8bc70264ef | ||
|
|
c16b862200 | ||
|
|
a96f608469 | ||
|
|
e1f25604ec | ||
|
|
0fe057b5c3 | ||
|
|
be76dda21d | ||
|
|
ecd124dd06 | ||
|
|
4a8951c4ee | ||
|
|
8afba7de85 | ||
|
|
1ce42d3a2f | ||
|
|
2f4d811db4 | ||
|
|
61127f521d | ||
|
|
063e73c0d2 | ||
|
|
d261318e1a | ||
|
|
d33cc00fe9 | ||
|
|
27582f6fd2 | ||
|
|
e328ff4833 | ||
|
|
403462fdb8 | ||
|
|
f22d8e67b4 | ||
|
|
35f33f1614 | ||
|
|
c9f8ddff65 | ||
|
|
f5ae98aaf1 | ||
|
|
073e85381a | ||
|
|
afc939708f | ||
|
|
aabec8b993 | ||
|
|
e5e2fbb16b | ||
|
|
3dd379cdf1 | ||
|
|
a20582aba4 | ||
|
|
09cdbf1b76 | ||
|
|
ca306e7cec | ||
|
|
1bf34f7fe6 | ||
|
|
4144d60017 | ||
|
|
7265682a4d | ||
|
|
08c62a6bf1 | ||
|
|
d61f1cd035 | ||
|
|
1063feb33b | ||
|
|
79f3c1ac8f | ||
|
|
a5c03b763a | ||
|
|
3670d82f1c | ||
|
|
e94a73553d | ||
|
|
577fd71472 | ||
|
|
ef1c4368d0 | ||
|
|
48def71d0c | ||
|
|
c8c20fff71 | ||
|
|
75a18b5ffe | ||
|
|
ea3d39b50e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ slixmpp.egg-info/
|
||||
*~
|
||||
.baboon/
|
||||
.DS_STORE
|
||||
.idea/
|
||||
|
||||
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
install:
|
||||
- "pip install ."
|
||||
script: testall.py
|
||||
14
CONTRIBUTING.rst
Normal file
14
CONTRIBUTING.rst
Normal file
@@ -0,0 +1,14 @@
|
||||
Contributing to the Slixmpp project
|
||||
===================================
|
||||
|
||||
To contribute, the preferred way is to commit your changes on some
|
||||
publicly-available git repository (on a fork `on github
|
||||
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
|
||||
notify the developers with either:
|
||||
- a ticket `on the bug tracker <https://dev.poez.io/new>`_
|
||||
- a pull request on github
|
||||
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
|
||||
|
||||
Even though Slixmpp’s github repository is just a read-only mirror, we can
|
||||
still be notified of the pull requests and fetch your mirror manually to
|
||||
integrate your changes.
|
||||
21
README.rst
21
README.rst
@@ -8,6 +8,14 @@ Slixmpp's goals is to only rewrite the core of the library (the low level
|
||||
socket handling, the timers, the events dispatching) in order to remove all
|
||||
threads.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Slixmpp can make use of cython to improve performance on critical modules.
|
||||
To do that, **cython3** is necessary along with **libidn** headers.
|
||||
Otherwise, no compilation is needed. Building is done by running setup.py::
|
||||
|
||||
python3 setup.py build_ext --inplace
|
||||
|
||||
Documentation and Testing
|
||||
-------------------------
|
||||
@@ -94,8 +102,17 @@ Slixmpp projects::
|
||||
Slixmpp Credits
|
||||
---------------
|
||||
|
||||
**Maintainer of the slixmpp fork:** Florent Le Coz
|
||||
`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_,
|
||||
**Maintainers:**
|
||||
- Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_),
|
||||
- Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_),
|
||||
|
||||
**Contributors:**
|
||||
- Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_)
|
||||
- Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_)
|
||||
- Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_)
|
||||
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
|
||||
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
|
||||
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
|
||||
|
||||
Credits (SleekXMPP)
|
||||
-------------------
|
||||
|
||||
@@ -48,9 +48,9 @@ copyright = u'2011, Nathan Fritz, Lance Stout'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.0'
|
||||
version = '1.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.0'
|
||||
release = '1.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -259,8 +259,8 @@ Event Index
|
||||
|
||||
Signal that a connection to the XMPP server has been lost and the current
|
||||
stream session has ended. Currently equivalent to :term:`disconnected`, but
|
||||
future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
|
||||
will distinguish the two events.
|
||||
implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
|
||||
distinguish between the two events.
|
||||
|
||||
Plugins that maintain session-based state should clear themselves when
|
||||
this event is fired.
|
||||
|
||||
@@ -70,7 +70,7 @@ as well.
|
||||
class EchoBot(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(EchoBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
Handling Session Start
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -83,7 +83,7 @@ started. To do that, we will register an event handler for the :term:`session_st
|
||||
.. code-block:: python
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(EchoBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
|
||||
@@ -153,7 +153,7 @@ whenever a messsage is received.
|
||||
.. code-block:: python
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(EchoBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
self.add_event_handler('message', self.message)
|
||||
@@ -329,7 +329,7 @@ The Final Product
|
||||
-----------------
|
||||
|
||||
Here then is what the final result should look like after working through the guide above. The code
|
||||
can also be found in the Slixmpp `examples directory <http://github.com/fritzy/Slixmpp/tree/master/examples>`_.
|
||||
can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_.
|
||||
|
||||
.. compound::
|
||||
|
||||
|
||||
@@ -63,13 +63,13 @@ has been established:
|
||||
def start(self, event):
|
||||
self.get_roster()
|
||||
self.send_presence()
|
||||
self.plugin['xep_0045'].joinMUC(self.room,
|
||||
self.nick,
|
||||
wait=True)
|
||||
self.plugin['xep_0045'].join_muc(self.room,
|
||||
self.nick,
|
||||
wait=True)
|
||||
|
||||
Note that as in :ref:`echobot`, we need to include send an initial presence and request
|
||||
the roster. Next, we want to join the group chat, so we call the
|
||||
``joinMUC`` method of the MUC plugin.
|
||||
``join_muc`` method of the MUC plugin.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ for the JID that will receive our message, and the string content of the message
|
||||
class SendMsgBot(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password, recipient, msg):
|
||||
super(SendMsgBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.recipient = recipient
|
||||
self.msg = msg
|
||||
|
||||
@@ -61,7 +61,7 @@ operation using these stanzas without doing any complex operations such as
|
||||
checking an ACL, etc.
|
||||
|
||||
You may find it necessary at some point to revert a particular node or JID to
|
||||
using the default, static handlers. To do so, use the method ``make_static()``.
|
||||
using the default, static handlers. To do so, use the method ``restore_defaults()``.
|
||||
You may also elect to only convert a given set of actions instead.
|
||||
|
||||
Creating a Node Handler
|
||||
@@ -161,8 +161,8 @@ item itself, and the JID and node that will own the item.
|
||||
In this case, the owning JID and node are provided with the
|
||||
parameters ``ijid`` and ``node``.
|
||||
|
||||
Peforming Disco Queries
|
||||
-----------------------
|
||||
Performing Disco Queries
|
||||
------------------------
|
||||
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
|
||||
and their nodes for disco information. Since these methods are wrappers for
|
||||
sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``
|
||||
@@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
info = self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
ifrom='baz@mycomponent.example.com',
|
||||
block=True,
|
||||
timeout=30)
|
||||
info = yield from self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
ifrom='baz@mycomponent.example.com',
|
||||
timeout=30)
|
||||
|
||||
items = self['xep_0030'].get_info(jid='foo@example.com',
|
||||
node='bar',
|
||||
|
||||
@@ -160,9 +160,9 @@ if __name__ == '__main__':
|
||||
|
||||
myDevice = TheDevice(args.nodeid);
|
||||
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
|
||||
myDevice._add_field(name="Temperature", typename="numeric", unit="C")
|
||||
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
|
||||
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
|
||||
|
||||
xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10);
|
||||
xmpp.beClientOrServer(server=True)
|
||||
|
||||
@@ -68,7 +68,7 @@ class CommandBot(slixmpp.ClientXMPP):
|
||||
session. Additional, custom data may be saved
|
||||
here to persist across handler callbacks.
|
||||
"""
|
||||
form = self['xep_0004'].makeForm('form', 'Greeting')
|
||||
form = self['xep_0004'].make_form('form', 'Greeting')
|
||||
form['instructions'] = 'Send a custom greeting to a JID'
|
||||
form.addField(var='greeting',
|
||||
ftype='text-single',
|
||||
|
||||
@@ -94,7 +94,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
|
||||
# label="Your greeting" />
|
||||
# </x>
|
||||
|
||||
form = self['xep_0004'].makeForm(ftype='submit')
|
||||
form = self['xep_0004'].make_form(ftype='submit')
|
||||
form.addField(var='greeting',
|
||||
value=session['greeting'])
|
||||
|
||||
|
||||
100
examples/confirm_answer.py
Executable file
100
examples/confirm_answer.py
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp import asyncio
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AnswerConfirm(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic client demonstrating how to confirm or deny an HTTP request.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, trusted):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.add_event_handler("http_confirm", self.confirm)
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
def start(self, *args):
|
||||
self.make_presence().send()
|
||||
|
||||
def prompt(self, stanza):
|
||||
confirm = stanza['confirm']
|
||||
print('Received confirm request %s from %s to access %s using '
|
||||
'method %s' % (
|
||||
confirm['id'], stanza['from'], confirm['url'],
|
||||
confirm['method'])
|
||||
)
|
||||
result = input("Do you accept (y/N)? ")
|
||||
return 'y' == result.lower()
|
||||
|
||||
def confirm(self, stanza):
|
||||
if self.prompt(stanza):
|
||||
reply = stanza.reply()
|
||||
else:
|
||||
reply = stanza.reply()
|
||||
reply.enable('error')
|
||||
reply['error']['type'] = 'auth'
|
||||
reply['error']['code'] = '401'
|
||||
reply['error']['condition'] = 'not-authorized'
|
||||
reply.append(stanza['confirm'])
|
||||
reply.send()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.ERROR,
|
||||
default=logging.INFO)
|
||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.DEBUG,
|
||||
default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
|
||||
# Other options.
|
||||
parser.add_argument("-t", "--trusted", nargs='*',
|
||||
help="List of trusted JIDs")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
|
||||
xmpp = AnswerConfirm(args.jid, args.password, args.trusted)
|
||||
xmpp.register_plugin('xep_0070')
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
125
examples/confirm_ask.py
Executable file
125
examples/confirm_ask.py
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError, IqError
|
||||
from slixmpp import asyncio
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AskConfirm(slixmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic client asking an entity if they confirm the access to an HTTP URL.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, recipient, id, url, method):
|
||||
slixmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.recipient = recipient
|
||||
self.id = id
|
||||
self.url = url
|
||||
self.method = method
|
||||
|
||||
# Will be used to set the proper exit code.
|
||||
self.confirmed = asyncio.Future()
|
||||
|
||||
self.add_event_handler("session_start", self.start)
|
||||
self.add_event_handler("message", self.start)
|
||||
self.add_event_handler("http_confirm_message", self.confirm)
|
||||
|
||||
def confirm(self, message):
|
||||
print(message)
|
||||
if message['confirm']['id'] == self.id:
|
||||
if message['type'] == 'error':
|
||||
self.confirmed.set_result(False)
|
||||
else:
|
||||
self.confirmed.set_result(True)
|
||||
|
||||
@asyncio.coroutine
|
||||
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,
|
||||
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
|
||||
else:
|
||||
confirmed = True
|
||||
except IqError:
|
||||
confirmed = False
|
||||
if confirmed:
|
||||
print('Confirmed')
|
||||
else:
|
||||
print('Denied')
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.ERROR,
|
||||
default=logging.INFO)
|
||||
parser.add_argument("-d","--debug", help="set logging to DEBUG",
|
||||
action="store_const",
|
||||
dest="loglevel",
|
||||
const=logging.DEBUG,
|
||||
default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_argument("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
parser.add_argument("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
|
||||
# Other options.
|
||||
parser.add_argument("-r", "--recipient", required=True,
|
||||
help="Recipient JID")
|
||||
parser.add_argument("-i", "--id", required=True,
|
||||
help="id TODO")
|
||||
parser.add_argument("-u", "--url", required=True,
|
||||
help="URL the user tried to access")
|
||||
parser.add_argument("-m", "--method", required=True,
|
||||
help="HTTP method used")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=args.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if args.jid is None:
|
||||
args.jid = input("Username: ")
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
|
||||
xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id,
|
||||
args.url, args.method)
|
||||
xmpp.register_plugin('xep_0070')
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
xmpp.connect()
|
||||
xmpp.process(forever=False)
|
||||
sys.exit(0 if xmpp.confirmed else 1)
|
||||
@@ -76,10 +76,6 @@ class Disco(slixmpp.ClientXMPP):
|
||||
|
||||
try:
|
||||
if self.get in self.info_types:
|
||||
# By using block=True, the result stanza will be
|
||||
# returned. Execution will block until the reply is
|
||||
# received. Non-blocking options would be to listen
|
||||
# for the disco_info event, or passing a handler
|
||||
# function using the callback parameter.
|
||||
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node)
|
||||
|
||||
97
examples/http_over_xmpp.py
Normal file
97
examples/http_over_xmpp.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Implementation of HTTP over XMPP transport
|
||||
http://xmpp.org/extensions/xep-0332.html
|
||||
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp import ClientXMPP
|
||||
|
||||
from optparse import OptionParser
|
||||
import logging
|
||||
import getpass
|
||||
|
||||
|
||||
class HTTPOverXMPPClient(ClientXMPP):
|
||||
def __init__(self, jid, password):
|
||||
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
|
||||
)
|
||||
self.add_event_handler('http_request', self.http_request_received)
|
||||
self.add_event_handler('http_response', self.http_response_received)
|
||||
|
||||
def http_request_received(self, iq):
|
||||
pass
|
||||
|
||||
def http_response_received(self, iq):
|
||||
print('HTTP Response Received : %s' % iq)
|
||||
print('From : %s' % iq['from'])
|
||||
print('To : %s' % iq['to'])
|
||||
print('Type : %s' % iq['type'])
|
||||
print('Headers : %s' % iq['resp']['headers'])
|
||||
print('Code : %s' % iq['resp']['code'])
|
||||
print('Message : %s' % iq['resp']['message'])
|
||||
print('Data : %s' % iq['resp']['data'])
|
||||
|
||||
def session_start(self, event):
|
||||
# TODO: Fill in the blanks
|
||||
self['xep_0332'].send_request(
|
||||
to='?', method='?', resource='?', headers={}
|
||||
)
|
||||
self.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
#
|
||||
# NOTE: To run this example, fill up the blanks in session_start() and
|
||||
# use the following command.
|
||||
#
|
||||
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
|
||||
#
|
||||
|
||||
parser = OptionParser()
|
||||
|
||||
# Output verbosity options.
|
||||
parser.add_option(
|
||||
'-v', '--verbose', help='set logging to DEBUG', action='store_const',
|
||||
dest='loglevel', const=logging.DEBUG, default=logging.ERROR
|
||||
)
|
||||
|
||||
# JID and password options.
|
||||
parser.add_option('-J', '--jid', dest='jid', help='JID')
|
||||
parser.add_option('-P', '--password', dest='password', help='Password')
|
||||
|
||||
# XMPP server ip and port options.
|
||||
parser.add_option(
|
||||
'-i', '--ipaddr', dest='ipaddr',
|
||||
help='IP Address of the XMPP server', default=None
|
||||
)
|
||||
parser.add_option(
|
||||
'-p', '--port', dest='port',
|
||||
help='Port of the XMPP server', default=None
|
||||
)
|
||||
|
||||
opts, args = parser.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=opts.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
if opts.jid is None:
|
||||
opts.jid = input('Username: ')
|
||||
if opts.password is None:
|
||||
opts.password = getpass.getpass('Password: ')
|
||||
|
||||
xmpp = HTTPOverXMPPClient(opts.jid, opts.password)
|
||||
xmpp.connect()
|
||||
xmpp.process()
|
||||
|
||||
@@ -100,7 +100,7 @@ def on_session2(event):
|
||||
new_xmpp.update_roster(jid,
|
||||
name = item['name'],
|
||||
groups = item['groups'])
|
||||
new_xmpp.disconnect()
|
||||
new_xmpp.disconnect()
|
||||
new_xmpp.add_event_handler('session_start', on_session2)
|
||||
|
||||
new_xmpp.connect()
|
||||
|
||||
@@ -67,11 +67,11 @@ class MUCBot(slixmpp.ClientXMPP):
|
||||
"""
|
||||
self.get_roster()
|
||||
self.send_presence()
|
||||
self.plugin['xep_0045'].joinMUC(self.room,
|
||||
self.nick,
|
||||
# If a room password is needed, use:
|
||||
# password=the_room_password,
|
||||
wait=True)
|
||||
self.plugin['xep_0045'].join_muc(self.room,
|
||||
self.nick,
|
||||
# If a room password is needed, use:
|
||||
# password=the_room_password,
|
||||
wait=True)
|
||||
|
||||
def muc_message(self, msg):
|
||||
"""
|
||||
|
||||
@@ -15,7 +15,7 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password, server,
|
||||
node=None, action='nodes', data=''):
|
||||
super(PubsubClient, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.register_plugin('xep_0030')
|
||||
self.register_plugin('xep_0059')
|
||||
|
||||
@@ -14,7 +14,7 @@ from slixmpp.xmlstream.handler import Callback
|
||||
class PubsubEvents(slixmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(PubsubEvents, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.register_plugin('xep_0030')
|
||||
self.register_plugin('xep_0059')
|
||||
|
||||
@@ -59,7 +59,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
self.get_roster(callback=callback)
|
||||
yield from future
|
||||
except IqError as err:
|
||||
print('Error: %' % err.iq['error']['condition'])
|
||||
print('Error: %s' % err.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
print('Error: Request timed out')
|
||||
self.send_presence()
|
||||
|
||||
@@ -22,7 +22,7 @@ from slixmpp import ClientXMPP
|
||||
class LocationBot(ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(LocationBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
self.add_event_handler('user_location_publish',
|
||||
|
||||
@@ -17,7 +17,7 @@ from slixmpp import ClientXMPP
|
||||
class TuneBot(ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
super(TuneBot, self).__init__(jid, password)
|
||||
super().__init__(jid, password)
|
||||
|
||||
# Check for the current song every 5 seconds.
|
||||
self.schedule('Check Current Tune', 5, self._update_tune, repeat=True)
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
asyncio.sslproto._is_sslproto_available=lambda: False
|
||||
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())
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
:license: MIT, see LICENSE for more details
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from slixmpp import plugins, roster, stanza
|
||||
from slixmpp.api import APIRegistry
|
||||
@@ -21,8 +21,6 @@ from slixmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
from slixmpp.stanza import Message, Presence, Iq, StreamError
|
||||
from slixmpp.stanza.roster import Roster
|
||||
from slixmpp.stanza.nick import Nick
|
||||
from slixmpp.stanza.htmlim import HTMLIM
|
||||
|
||||
from slixmpp.xmlstream import XMLStream, JID
|
||||
from slixmpp.xmlstream import ET, register_stanza_plugin
|
||||
@@ -46,8 +44,8 @@ class BaseXMPP(XMLStream):
|
||||
is used during initialization.
|
||||
"""
|
||||
|
||||
def __init__(self, jid='', default_ns='jabber:client'):
|
||||
XMLStream.__init__(self)
|
||||
def __init__(self, jid='', default_ns='jabber:client', **kwargs):
|
||||
XMLStream.__init__(self, **kwargs)
|
||||
|
||||
self.default_ns = default_ns
|
||||
self.stream_ns = 'http://etherx.jabber.org/streams'
|
||||
@@ -71,7 +69,7 @@ class BaseXMPP(XMLStream):
|
||||
#: redirections that will be followed before quitting.
|
||||
self.max_redirects = 5
|
||||
|
||||
self.session_bind_event = threading.Event()
|
||||
self.session_bind_event = asyncio.Event()
|
||||
|
||||
#: A dictionary mapping plugin names to plugins.
|
||||
self.plugin = PluginManager(self)
|
||||
@@ -195,7 +193,6 @@ class BaseXMPP(XMLStream):
|
||||
|
||||
# Initialize a few default stanza plugins.
|
||||
register_stanza_plugin(Iq, Roster)
|
||||
register_stanza_plugin(Message, Nick)
|
||||
|
||||
def start_stream_handler(self, xml):
|
||||
"""Save the stream ID once the streams have been established.
|
||||
@@ -221,7 +218,7 @@ class BaseXMPP(XMLStream):
|
||||
self.plugin[name].post_init()
|
||||
self.plugin[name].post_inited = True
|
||||
|
||||
def register_plugin(self, plugin, pconfig={}, module=None):
|
||||
def register_plugin(self, plugin, pconfig=None, module=None):
|
||||
"""Register and configure a plugin for use in this stream.
|
||||
|
||||
:param plugin: The name of the plugin class. Plugin names must
|
||||
@@ -688,7 +685,6 @@ class BaseXMPP(XMLStream):
|
||||
self.address = (host, port)
|
||||
self.default_domain = host
|
||||
self.dns_records = None
|
||||
self.reconnect_delay = None
|
||||
self.reconnect()
|
||||
|
||||
def _handle_message(self, msg):
|
||||
@@ -754,6 +750,9 @@ class BaseXMPP(XMLStream):
|
||||
|
||||
Update the roster with presence information.
|
||||
"""
|
||||
if self.roster[presence['from']].ignore_updates:
|
||||
return
|
||||
|
||||
if not self.is_component and not presence['to'].bare:
|
||||
presence['to'] = self.boundjid
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
:license: MIT, see LICENSE for more details
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from slixmpp.stanza import StreamFeatures
|
||||
@@ -19,7 +20,7 @@ from slixmpp.basexmpp import BaseXMPP
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp.xmlstream import XMLStream
|
||||
from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.handler import Callback, CoroutineCallback
|
||||
|
||||
# Flag indicating if DNS SRV records are available for use.
|
||||
try:
|
||||
@@ -50,7 +51,6 @@ class ClientXMPP(BaseXMPP):
|
||||
|
||||
:param jid: The JID of the XMPP user account.
|
||||
:param password: The password for the XMPP user account.
|
||||
:param ssl: **Deprecated.**
|
||||
:param plugin_config: A dictionary of plugin configurations.
|
||||
:param plugin_whitelist: A list of approved plugins that
|
||||
will be loaded when calling
|
||||
@@ -58,9 +58,15 @@ class ClientXMPP(BaseXMPP):
|
||||
:param escape_quotes: **Deprecated.**
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
|
||||
escape_quotes=True, sasl_mech=None, lang='en'):
|
||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
||||
def __init__(self, jid, password, plugin_config=None,
|
||||
plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
|
||||
lang='en', **kwargs):
|
||||
if not plugin_whitelist:
|
||||
plugin_whitelist = []
|
||||
if not plugin_config:
|
||||
plugin_config = {}
|
||||
|
||||
BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs)
|
||||
|
||||
self.escape_quotes = escape_quotes
|
||||
self.plugin_config = plugin_config
|
||||
@@ -99,9 +105,9 @@ class ClientXMPP(BaseXMPP):
|
||||
self.register_stanza(StreamFeatures)
|
||||
|
||||
self.register_handler(
|
||||
Callback('Stream Features',
|
||||
MatchXPath('{%s}features' % self.stream_ns),
|
||||
self._handle_stream_features))
|
||||
CoroutineCallback('Stream Features',
|
||||
MatchXPath('{%s}features' % self.stream_ns),
|
||||
self._handle_stream_features))
|
||||
self.register_handler(
|
||||
Callback('Roster Update',
|
||||
StanzaPath('iq@type=set/roster'),
|
||||
@@ -135,8 +141,11 @@ class ClientXMPP(BaseXMPP):
|
||||
will be used.
|
||||
|
||||
:param address: A tuple containing the server's host and port.
|
||||
:param use_tls: Indicates if TLS should be used for the
|
||||
connection. Defaults to ``True``.
|
||||
:param force_starttls: Indicates that negotiation should be aborted
|
||||
if the server does not advertise support for
|
||||
STARTTLS. Defaults to ``True``.
|
||||
:param disable_starttls: Disables TLS for the connection.
|
||||
Defaults to ``False``.
|
||||
:param use_ssl: Indicates if the older SSL connection method
|
||||
should be used. Defaults to ``False``.
|
||||
"""
|
||||
@@ -244,6 +253,7 @@ class ClientXMPP(BaseXMPP):
|
||||
self.bindfail = False
|
||||
self.features = set()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _handle_stream_features(self, features):
|
||||
"""Process the received stream features.
|
||||
|
||||
@@ -252,7 +262,11 @@ class ClientXMPP(BaseXMPP):
|
||||
for order, name in self._stream_feature_order:
|
||||
if name in features['features']:
|
||||
handler, restart = self._stream_feature_handlers[name]
|
||||
if handler(features) and restart:
|
||||
if asyncio.iscoroutinefunction(handler):
|
||||
result = yield from handler(features)
|
||||
else:
|
||||
result = handler(features)
|
||||
if result and restart:
|
||||
# Don't continue if the feature requires
|
||||
# restarting the XML stream.
|
||||
return True
|
||||
|
||||
@@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP):
|
||||
Defaults to ``False``.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, secret, host=None, port=None,
|
||||
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
|
||||
def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
|
||||
|
||||
if not plugin_whitelist:
|
||||
plugin_whitelist = []
|
||||
if not plugin_config:
|
||||
plugin_config = {}
|
||||
|
||||
if use_jc_ns:
|
||||
default_ns = 'jabber:client'
|
||||
else:
|
||||
|
||||
@@ -77,7 +77,7 @@ class IqTimeout(XMPPError):
|
||||
"""
|
||||
|
||||
def __init__(self, iq):
|
||||
super(IqTimeout, self).__init__(
|
||||
super().__init__(
|
||||
condition='remote-server-timeout',
|
||||
etype='cancel')
|
||||
|
||||
@@ -94,7 +94,7 @@ class IqError(XMPPError):
|
||||
"""
|
||||
|
||||
def __init__(self, iq):
|
||||
super(IqError, self).__init__(
|
||||
super().__init__(
|
||||
condition=iq['error']['condition'],
|
||||
text=iq['error']['text'],
|
||||
etype=iq['error']['type'])
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_bind.stanza import Bind
|
||||
|
||||
|
||||
register_plugin(FeatureBind)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_bind = FeatureBind
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from slixmpp.jid import JID
|
||||
@@ -34,6 +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):
|
||||
"""
|
||||
Handle requesting a specific resource.
|
||||
@@ -49,7 +51,7 @@ class FeatureBind(BasePlugin):
|
||||
if self.xmpp.requested_jid.resource:
|
||||
iq['bind']['resource'] = self.xmpp.requested_jid.resource
|
||||
|
||||
iq.send(callback=self._on_bind_response)
|
||||
yield from iq.send(callback=self._on_bind_response)
|
||||
|
||||
def _on_bind_response(self, response):
|
||||
self.xmpp.boundjid = JID(response['bind']['jid'])
|
||||
|
||||
@@ -16,7 +16,3 @@ from slixmpp.features.feature_mechanisms.stanza import Failure
|
||||
|
||||
|
||||
register_plugin(FeatureMechanisms)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_mechanisms = FeatureMechanisms
|
||||
|
||||
@@ -190,14 +190,14 @@ class FeatureMechanisms(BasePlugin):
|
||||
except sasl.SASLCancelled:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
else:
|
||||
resp.send()
|
||||
|
||||
@@ -210,13 +210,13 @@ class FeatureMechanisms(BasePlugin):
|
||||
resp['value'] = self.mech.process(stanza['value'])
|
||||
except sasl.SASLCancelled:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except sasl.SASLFailed:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except sasl.SASLFailed:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
else:
|
||||
if resp.get_value() == '':
|
||||
resp.del_value()
|
||||
|
||||
@@ -29,7 +29,7 @@ class Mechanisms(ElementBase):
|
||||
"""
|
||||
"""
|
||||
results = []
|
||||
mechs = self.findall('{%s}mechanism' % self.namespace)
|
||||
mechs = self.xml.findall('{%s}mechanism' % self.namespace)
|
||||
if mechs:
|
||||
for mech in mechs:
|
||||
results.append(mech.text)
|
||||
@@ -47,7 +47,7 @@ class Mechanisms(ElementBase):
|
||||
def del_mechanisms(self):
|
||||
"""
|
||||
"""
|
||||
mechs = self.findall('{%s}mechanism' % self.namespace)
|
||||
mechs = self.xml.findall('{%s}mechanism' % self.namespace)
|
||||
if mechs:
|
||||
for mech in mechs:
|
||||
self.xml.remove(mech)
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_rosterver.stanza import RosterVer
|
||||
|
||||
|
||||
register_plugin(FeatureRosterVer)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_rosterver = FeatureRosterVer
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_session.stanza import Session
|
||||
|
||||
|
||||
register_plugin(FeatureSession)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_session = FeatureSession
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from slixmpp.stanza import Iq, StreamFeatures
|
||||
@@ -34,6 +35,7 @@ class FeatureSession(BasePlugin):
|
||||
register_stanza_plugin(Iq, stanza.Session)
|
||||
register_stanza_plugin(StreamFeatures, stanza.Session)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _handle_start_session(self, features):
|
||||
"""
|
||||
Handle the start of the session.
|
||||
@@ -44,7 +46,7 @@ class FeatureSession(BasePlugin):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq.enable('session')
|
||||
iq.send(callback=self._on_start_session_response)
|
||||
yield from iq.send(callback=self._on_start_session_response)
|
||||
|
||||
def _on_start_session_response(self, response):
|
||||
self.xmpp.features.add('session')
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.features.feature_starttls.stanza import *
|
||||
|
||||
|
||||
register_plugin(FeatureSTARTTLS)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
feature_starttls = FeatureSTARTTLS
|
||||
|
||||
@@ -208,16 +208,15 @@ def _format_jid(local=None, domain=None, resource=None):
|
||||
|
||||
:return: A full or bare JID string.
|
||||
"""
|
||||
result = []
|
||||
if domain is None:
|
||||
return ''
|
||||
if local is not None:
|
||||
result.append(local)
|
||||
result.append('@')
|
||||
if domain is not None:
|
||||
result.append(domain)
|
||||
result = local + '@' + domain
|
||||
else:
|
||||
result = domain
|
||||
if resource is not None:
|
||||
result.append('/')
|
||||
result.append(resource)
|
||||
return ''.join(result)
|
||||
result += '/' + resource
|
||||
return result
|
||||
|
||||
|
||||
class InvalidJID(ValueError):
|
||||
@@ -300,19 +299,23 @@ class JID:
|
||||
:raises InvalidJID:
|
||||
"""
|
||||
|
||||
__slots__ = ('_node', '_domain', '_resource')
|
||||
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
|
||||
|
||||
def __init__(self, jid=None):
|
||||
if not jid:
|
||||
self._node = None
|
||||
self._domain = None
|
||||
self._resource = None
|
||||
self._node = ''
|
||||
self._domain = ''
|
||||
self._resource = ''
|
||||
self._bare = ''
|
||||
self._full = ''
|
||||
return
|
||||
elif not isinstance(jid, JID):
|
||||
self._node, self._domain, self._resource = _parse_jid(jid)
|
||||
else:
|
||||
self._node = jid._node
|
||||
self._domain = jid._domain
|
||||
self._resource = jid._resource
|
||||
self._update_bare_full()
|
||||
|
||||
def unescape(self):
|
||||
"""Return an unescaped JID object.
|
||||
@@ -329,77 +332,94 @@ class JID:
|
||||
self._domain,
|
||||
self._resource)
|
||||
|
||||
def _update_bare_full(self):
|
||||
"""Format the given JID into a bare and a full JID.
|
||||
"""
|
||||
self._bare = (self._node + '@' + self._domain
|
||||
if self._node
|
||||
else self._domain)
|
||||
self._full = (self._bare + '/' + self._resource
|
||||
if self._resource
|
||||
else self._bare)
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
return self._node or ''
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
return self._node or ''
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return self._node or ''
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self._node or ''
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return self._domain or ''
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def server(self):
|
||||
return self._domain or ''
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
return self._domain or ''
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def resource(self):
|
||||
return self._resource or ''
|
||||
return self._resource
|
||||
|
||||
@property
|
||||
def bare(self):
|
||||
return _format_jid(self._node, self._domain)
|
||||
return self._bare
|
||||
|
||||
@property
|
||||
def full(self):
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
return self._full
|
||||
|
||||
@property
|
||||
def jid(self):
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
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):
|
||||
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):
|
||||
self._domain = _validate_domain(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@bare.setter
|
||||
def bare(self, value):
|
||||
@@ -407,26 +427,30 @@ class JID:
|
||||
assert not resource
|
||||
self._node = node
|
||||
self._domain = domain
|
||||
self._update_bare_full()
|
||||
|
||||
@resource.setter
|
||||
def resource(self, value):
|
||||
self._resource = _validate_resource(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@full.setter
|
||||
def full(self, value):
|
||||
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()
|
||||
|
||||
def __str__(self):
|
||||
"""Use the full JID as the string value."""
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
return self._full
|
||||
|
||||
def __repr__(self):
|
||||
"""Use the full JID as the representation."""
|
||||
return _format_jid(self._node, self._domain, self._resource)
|
||||
return self._full
|
||||
|
||||
# pylint: disable=W0212
|
||||
def __eq__(self, other):
|
||||
@@ -446,4 +470,4 @@ class JID:
|
||||
|
||||
def __hash__(self):
|
||||
"""Hash a JID based on the string version of its full JID."""
|
||||
return hash(_format_jid(self._node, self._domain, self._resource))
|
||||
return hash(self._full)
|
||||
|
||||
@@ -47,6 +47,7 @@ __all__ = [
|
||||
'xep_0108', # User Activity
|
||||
'xep_0115', # Entity Capabilities
|
||||
'xep_0118', # User Tune
|
||||
'xep_0122', # Data Forms Validation
|
||||
'xep_0128', # Extended Service Discovery
|
||||
'xep_0131', # Standard Headers and Internet Metadata
|
||||
'xep_0133', # Service Administration
|
||||
@@ -83,4 +84,5 @@ __all__ = [
|
||||
'xep_0319', # Last User Interaction in Presence
|
||||
'xep_0323', # IoT Systems Sensor Data
|
||||
'xep_0325', # IoT Systems Control
|
||||
'xep_0332', # HTTP Over XMPP Transport
|
||||
]
|
||||
|
||||
@@ -308,7 +308,7 @@ class BasePlugin(object):
|
||||
if key in self.default_config:
|
||||
self.config[key] = value
|
||||
else:
|
||||
super(BasePlugin, self).__setattr__(key, value)
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def _init(self):
|
||||
"""Initialize plugin state, such as registering event handlers.
|
||||
|
||||
47
slixmpp/plugins/google/auth/stanza.py
Normal file
47
slixmpp/plugins/google/auth/stanza.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 ElementBase, ET
|
||||
|
||||
|
||||
class GoogleAuth(ElementBase):
|
||||
name = 'auth'
|
||||
namespace = 'http://www.google.com/talk/protocol/auth'
|
||||
plugin_attrib = 'google'
|
||||
interfaces = set(['client_uses_full_bind_result', 'service'])
|
||||
|
||||
discovery_attr= '{%s}client-uses-full-bind-result' % namespace
|
||||
service_attr= '{%s}service' % namespace
|
||||
|
||||
def setup(self, xml):
|
||||
"""Don't create XML for the plugin."""
|
||||
self.xml = ET.Element('')
|
||||
|
||||
def get_client_uses_full_bind_result(self):
|
||||
return self.parent()._get_attr(self.discovery_attr) == 'true'
|
||||
|
||||
def set_client_uses_full_bind_result(self, value):
|
||||
if value in (True, 'true'):
|
||||
self.parent()._set_attr(self.discovery_attr, 'true')
|
||||
else:
|
||||
self.parent()._del_attr(self.discovery_attr)
|
||||
|
||||
def del_client_uses_full_bind_result(self):
|
||||
self.parent()._del_attr(self.discovery_attr)
|
||||
|
||||
def get_service(self):
|
||||
return self.parent()._get_attr(self.service_attr, '')
|
||||
|
||||
def set_service(self, value):
|
||||
if value:
|
||||
self.parent()._set_attr(self.service_attr, value)
|
||||
else:
|
||||
self.parent()._del_attr(self.service_attr)
|
||||
|
||||
def del_service(self):
|
||||
self.parent()._del_attr(self.service_attr)
|
||||
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
90
slixmpp/plugins/google/gmail/notifications.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from slixmpp.stanza import Iq
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import MatchXPath
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.google.gmail import stanza
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Gmail(BasePlugin):
|
||||
|
||||
"""
|
||||
Google: Gmail Notifications
|
||||
|
||||
Also see <https://developers.google.com/talk/jep_extensions/gmail>.
|
||||
"""
|
||||
|
||||
name = 'gmail'
|
||||
description = 'Google: Gmail Notifications'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, stanza.GmailQuery)
|
||||
register_stanza_plugin(Iq, stanza.MailBox)
|
||||
register_stanza_plugin(Iq, stanza.NewMail)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Gmail New Mail',
|
||||
MatchXPath('{%s}iq/{%s}%s' % (
|
||||
self.xmpp.default_ns,
|
||||
stanza.NewMail.namespace,
|
||||
stanza.NewMail.name)),
|
||||
self._handle_new_mail))
|
||||
|
||||
self._last_result_time = None
|
||||
self._last_result_tid = None
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Gmail New Mail')
|
||||
|
||||
def _handle_new_mail(self, iq):
|
||||
log.info('Gmail: New email!')
|
||||
iq.reply().send()
|
||||
self.xmpp.event('gmail_notification')
|
||||
|
||||
def check(self, timeout=None, callback=None):
|
||||
last_time = self._last_result_time
|
||||
last_tid = self._last_result_tid
|
||||
|
||||
callback = lambda iq: self._update_last_results(iq, callback)
|
||||
|
||||
return self.search(newer_time=last_time,
|
||||
newer_tid=last_tid,
|
||||
timeout=timeout,
|
||||
callback=callback)
|
||||
|
||||
def _update_last_results(self, iq, callback=None):
|
||||
self._last_result_time = iq['gmail_messages']['result_time']
|
||||
threads = iq['gmail_messages']['threads']
|
||||
if threads:
|
||||
self._last_result_tid = threads[0]['tid']
|
||||
if callback:
|
||||
callback(iq)
|
||||
|
||||
def search(self, query=None, newer_time=None, newer_tid=None,
|
||||
timeout=None, callback=None):
|
||||
if not query:
|
||||
log.info('Gmail: Checking for new email')
|
||||
else:
|
||||
log.info('Gmail: Searching for emails matching: "%s"', query)
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['to'] = self.xmpp.boundjid.bare
|
||||
iq['gmail']['search'] = query
|
||||
iq['gmail']['newer_than_time'] = newer_time
|
||||
iq['gmail']['newer_than_tid'] = newer_tid
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
59
slixmpp/plugins/google/nosave/stanza.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
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.jid import JID
|
||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
class NoSave(ElementBase):
|
||||
name = 'x'
|
||||
namespace = 'google:nosave'
|
||||
plugin_attrib = 'google_nosave'
|
||||
interfaces = set(['value'])
|
||||
|
||||
def get_value(self):
|
||||
return self._get_attr('value', '') == 'enabled'
|
||||
|
||||
def set_value(self, value):
|
||||
self._set_attr('value', 'enabled' if value else 'disabled')
|
||||
|
||||
|
||||
class NoSaveQuery(ElementBase):
|
||||
name = 'query'
|
||||
namespace = 'google:nosave'
|
||||
plugin_attrib = 'google_nosave'
|
||||
interfaces = set()
|
||||
|
||||
|
||||
class Item(ElementBase):
|
||||
name = 'item'
|
||||
namespace = 'google:nosave'
|
||||
plugin_attrib = 'item'
|
||||
plugin_multi_attrib = 'items'
|
||||
interfaces = set(['jid', 'source', 'value'])
|
||||
|
||||
def get_value(self):
|
||||
return self._get_attr('value', '') == 'enabled'
|
||||
|
||||
def set_value(self, value):
|
||||
self._set_attr('value', 'enabled' if value else 'disabled')
|
||||
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid', ''))
|
||||
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def get_source(self):
|
||||
return JID(self._get_attr('source', ''))
|
||||
|
||||
def set_source(self, value):
|
||||
self._set_attr('source', str(value))
|
||||
|
||||
|
||||
register_stanza_plugin(NoSaveQuery, Item)
|
||||
63
slixmpp/plugins/google/settings/settings.py
Normal file
63
slixmpp/plugins/google/settings/settings.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
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
|
||||
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.settings import stanza
|
||||
|
||||
|
||||
class GoogleSettings(BasePlugin):
|
||||
|
||||
"""
|
||||
Google: Gmail Notifications
|
||||
|
||||
Also see <https://developers.google.com/talk/jep_extensions/usersettings>.
|
||||
"""
|
||||
|
||||
name = 'google_settings'
|
||||
description = 'Google: User Settings'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, stanza.UserSettings)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Google Settings',
|
||||
StanzaPath('iq@type=set/google_settings'),
|
||||
self._handle_settings_change))
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Google Settings')
|
||||
|
||||
def get(self, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq.enable('google_settings')
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def update(self, settings, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq.enable('google_settings')
|
||||
|
||||
for setting, value in settings.items():
|
||||
iq['google_settings'][setting] = value
|
||||
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def _handle_settings_change(self, iq):
|
||||
reply = self.xmpp.Iq()
|
||||
reply['type'] = 'result'
|
||||
reply['id'] = iq['id']
|
||||
reply['to'] = iq['from']
|
||||
reply.send()
|
||||
self.xmpp.event('google_settings_change', iq)
|
||||
@@ -14,9 +14,3 @@ from slixmpp.plugins.xep_0004.dataforms import XEP_0004
|
||||
|
||||
|
||||
register_plugin(XEP_0004)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0004 = XEP_0004
|
||||
xep_0004.makeForm = xep_0004.make_form
|
||||
xep_0004.buildForm = xep_0004.build_form
|
||||
|
||||
@@ -13,8 +13,9 @@ class FormField(ElementBase):
|
||||
namespace = 'jabber:x:data'
|
||||
name = 'field'
|
||||
plugin_attrib = 'field'
|
||||
plugin_multi_attrib = 'fields'
|
||||
interfaces = set(('answer', 'desc', 'required', 'value',
|
||||
'options', 'label', 'type', 'var'))
|
||||
'label', 'type', 'var'))
|
||||
sub_interfaces = set(('desc',))
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
@@ -165,6 +166,7 @@ class FieldOption(ElementBase):
|
||||
plugin_attrib = 'option'
|
||||
interfaces = set(('label', 'value'))
|
||||
sub_interfaces = set(('value',))
|
||||
plugin_multi_attrib = 'options'
|
||||
|
||||
|
||||
FormField.addOption = FormField.add_option
|
||||
|
||||
@@ -10,6 +10,7 @@ import copy
|
||||
import logging
|
||||
|
||||
from collections import OrderedDict
|
||||
from slixmpp.thirdparty import OrderedSet
|
||||
|
||||
from slixmpp.xmlstream import ElementBase, ET
|
||||
from slixmpp.plugins.xep_0004.stanza import FormField
|
||||
@@ -22,8 +23,7 @@ class Form(ElementBase):
|
||||
namespace = 'jabber:x:data'
|
||||
name = 'x'
|
||||
plugin_attrib = 'form'
|
||||
interfaces = set(('fields', 'instructions', 'items',
|
||||
'reported', 'title', 'type', 'values'))
|
||||
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
|
||||
sub_interfaces = set(('title',))
|
||||
form_types = set(('cancel', 'form', 'result', 'submit'))
|
||||
|
||||
@@ -43,12 +43,12 @@ class Form(ElementBase):
|
||||
|
||||
@property
|
||||
def field(self):
|
||||
return self['fields']
|
||||
return self.get_fields()
|
||||
|
||||
def set_type(self, ftype):
|
||||
self._set_attr('type', ftype)
|
||||
if ftype == 'submit':
|
||||
fields = self['fields']
|
||||
fields = self.get_fields()
|
||||
for var in fields:
|
||||
field = fields[var]
|
||||
del field['type']
|
||||
@@ -74,24 +74,13 @@ class Form(ElementBase):
|
||||
field['desc'] = desc
|
||||
field['required'] = required
|
||||
if options is not None:
|
||||
field['options'] = options
|
||||
for option in options:
|
||||
field.add_option(**option)
|
||||
else:
|
||||
del field['type']
|
||||
self.append(field)
|
||||
return field
|
||||
|
||||
def getXML(self, type='submit'):
|
||||
self['type'] = type
|
||||
log.warning("Form.getXML() is deprecated API compatibility " + \
|
||||
"with plugins/old_0004.py")
|
||||
return self.xml
|
||||
|
||||
def fromXML(self, xml):
|
||||
log.warning("Form.fromXML() is deprecated API compatibility " + \
|
||||
"with plugins/old_0004.py")
|
||||
n = Form(xml=xml)
|
||||
return n
|
||||
|
||||
def add_item(self, values):
|
||||
itemXML = ET.Element('{%s}item' % self.namespace)
|
||||
self.xml.append(itemXML)
|
||||
@@ -151,7 +140,6 @@ class Form(ElementBase):
|
||||
return fields
|
||||
|
||||
def get_instructions(self):
|
||||
instructions = ''
|
||||
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
|
||||
return "\n".join([instXML.text for instXML in instsXML])
|
||||
|
||||
@@ -170,7 +158,7 @@ class Form(ElementBase):
|
||||
def get_reported(self):
|
||||
fields = OrderedDict()
|
||||
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
|
||||
FormField.namespace))
|
||||
FormField.namespace))
|
||||
for field in xml:
|
||||
field = FormField(xml=field)
|
||||
fields[field['var']] = field
|
||||
@@ -178,7 +166,7 @@ class Form(ElementBase):
|
||||
|
||||
def get_values(self):
|
||||
values = OrderedDict()
|
||||
fields = self['fields']
|
||||
fields = self.get_fields()
|
||||
for var in fields:
|
||||
values[var] = fields[var]['value']
|
||||
return values
|
||||
@@ -195,7 +183,14 @@ class Form(ElementBase):
|
||||
fields = fields.items()
|
||||
for var, field in fields:
|
||||
field['var'] = var
|
||||
self.add_field(**field)
|
||||
self.add_field(
|
||||
var=field.get('var'),
|
||||
label=field.get('label'),
|
||||
desc=field.get('desc'),
|
||||
required=field.get('required'),
|
||||
value=field.get('value'),
|
||||
options=field.get('options'),
|
||||
type=field.get('type'))
|
||||
|
||||
def set_instructions(self, instructions):
|
||||
del self['instructions']
|
||||
@@ -213,17 +208,33 @@ class Form(ElementBase):
|
||||
self.add_item(item)
|
||||
|
||||
def set_reported(self, reported):
|
||||
"""
|
||||
This either needs a dictionary of dictionaries or a dictionary of form fields.
|
||||
:param reported:
|
||||
:return:
|
||||
"""
|
||||
for var in reported:
|
||||
field = reported[var]
|
||||
field['var'] = var
|
||||
self.add_reported(var, **field)
|
||||
|
||||
if isinstance(field, dict):
|
||||
self.add_reported(**field)
|
||||
else:
|
||||
reported = self.xml.find('{%s}reported' % self.namespace)
|
||||
if reported is None:
|
||||
reported = ET.Element('{%s}reported' % self.namespace)
|
||||
self.xml.append(reported)
|
||||
|
||||
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
||||
reported.append(fieldXML)
|
||||
new_field = FormField(xml=fieldXML)
|
||||
new_field.values = field.values
|
||||
|
||||
def set_values(self, values):
|
||||
fields = self['fields']
|
||||
fields = self.get_fields()
|
||||
for field in values:
|
||||
if field not in fields:
|
||||
if field not in self.get_fields():
|
||||
fields[field] = self.add_field(var=field)
|
||||
fields[field]['value'] = values[field]
|
||||
self.get_fields()[field]['value'] = values[field]
|
||||
|
||||
def merge(self, other):
|
||||
new = copy.copy(self)
|
||||
|
||||
@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse
|
||||
|
||||
|
||||
register_plugin(XEP_0009)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0009 = XEP_0009
|
||||
|
||||
@@ -25,7 +25,7 @@ def fault2xml(fault):
|
||||
|
||||
def xml2fault(params):
|
||||
vals = []
|
||||
for value in params.findall('{%s}value' % _namespace):
|
||||
for value in params.xml.findall('{%s}value' % _namespace):
|
||||
vals.append(_xml2py(value))
|
||||
fault = dict()
|
||||
fault['code'] = vals[0]['faultCode']
|
||||
@@ -92,39 +92,40 @@ def _py2xml(*args):
|
||||
def xml2py(params):
|
||||
namespace = 'jabber:iq:rpc'
|
||||
vals = []
|
||||
for param in params.findall('{%s}param' % namespace):
|
||||
for param in params.xml.findall('{%s}param' % namespace):
|
||||
vals.append(_xml2py(param.find('{%s}value' % namespace)))
|
||||
return vals
|
||||
|
||||
def _xml2py(value):
|
||||
namespace = 'jabber:iq:rpc'
|
||||
if value.find('{%s}nil' % namespace) is not None:
|
||||
find_value = value.xml.find
|
||||
if find_value('{%s}nil' % namespace) is not None:
|
||||
return None
|
||||
if value.find('{%s}i4' % namespace) is not None:
|
||||
return int(value.find('{%s}i4' % namespace).text)
|
||||
if value.find('{%s}int' % namespace) is not None:
|
||||
return int(value.find('{%s}int' % namespace).text)
|
||||
if value.find('{%s}boolean' % namespace) is not None:
|
||||
return bool(int(value.find('{%s}boolean' % namespace).text))
|
||||
if value.find('{%s}string' % namespace) is not None:
|
||||
return value.find('{%s}string' % namespace).text
|
||||
if value.find('{%s}double' % namespace) is not None:
|
||||
return float(value.find('{%s}double' % namespace).text)
|
||||
if value.find('{%s}base64' % namespace) is not None:
|
||||
return rpcbase64(value.find('{%s}base64' % namespace).text.encode())
|
||||
if value.find('{%s}Base64' % namespace) is not None:
|
||||
if find_value('{%s}i4' % namespace) is not None:
|
||||
return int(find_value('{%s}i4' % namespace).text)
|
||||
if find_value('{%s}int' % namespace) is not None:
|
||||
return int(find_value('{%s}int' % namespace).text)
|
||||
if find_value('{%s}boolean' % namespace) is not None:
|
||||
return bool(int(find_value('{%s}boolean' % namespace).text))
|
||||
if find_value('{%s}string' % namespace) is not None:
|
||||
return find_value('{%s}string' % namespace).text
|
||||
if find_value('{%s}double' % namespace) is not None:
|
||||
return float(find_value('{%s}double' % namespace).text)
|
||||
if find_value('{%s}base64' % namespace) is not None:
|
||||
return rpcbase64(find_value('{%s}base64' % namespace).text.encode())
|
||||
if find_value('{%s}Base64' % namespace) is not None:
|
||||
# Older versions of XEP-0009 used Base64
|
||||
return rpcbase64(value.find('{%s}Base64' % namespace).text.encode())
|
||||
if value.find('{%s}dateTime.iso8601' % namespace) is not None:
|
||||
return rpctime(value.find('{%s}dateTime.iso8601' % namespace).text)
|
||||
if value.find('{%s}struct' % namespace) is not None:
|
||||
return rpcbase64(find_value('{%s}Base64' % namespace).text.encode())
|
||||
if find_value('{%s}dateTime.iso8601' % namespace) is not None:
|
||||
return rpctime(find_value('{%s}dateTime.iso8601' % namespace).text)
|
||||
if find_value('{%s}struct' % namespace) is not None:
|
||||
struct = {}
|
||||
for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace):
|
||||
for member in find_value('{%s}struct' % namespace).findall('{%s}member' % namespace):
|
||||
struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace))
|
||||
return struct
|
||||
if value.find('{%s}array' % namespace) is not None:
|
||||
if find_value('{%s}array' % namespace) is not None:
|
||||
array = []
|
||||
for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
|
||||
for val in find_value('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
|
||||
array.append(_xml2py(val))
|
||||
return array
|
||||
raise ValueError()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from binding import py2xml, xml2py, xml2fault, fault2xml
|
||||
from slixmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml
|
||||
from threading import RLock
|
||||
import abc
|
||||
import inspect
|
||||
@@ -18,6 +18,38 @@ import traceback
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def _isstr(obj):
|
||||
return isinstance(obj, str)
|
||||
|
||||
|
||||
# Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3.
|
||||
# This decorator is copied from 'six' (https://bitbucket.org/gutworth/six):
|
||||
#
|
||||
# Copyright (c) 2010-2015 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
def _add_metaclass(metaclass):
|
||||
def wrapper(cls):
|
||||
orig_vars = cls.__dict__.copy()
|
||||
slots = orig_vars.get('__slots__')
|
||||
if slots is not None:
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for slots_var in slots:
|
||||
orig_vars.pop(slots_var)
|
||||
orig_vars.pop('__dict__', None)
|
||||
orig_vars.pop('__weakref__', None)
|
||||
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||
return wrapper
|
||||
|
||||
def _intercept(method, name, public):
|
||||
def _resolver(instance, *args, **kwargs):
|
||||
log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
|
||||
@@ -68,7 +100,7 @@ def remote(function_argument, public = True):
|
||||
if hasattr(function_argument, '__call__'):
|
||||
return _intercept(function_argument, None, public)
|
||||
else:
|
||||
if not isinstance(function_argument, basestring):
|
||||
if not _isstr(function_argument):
|
||||
if not isinstance(function_argument, bool):
|
||||
raise Exception('Expected an RPC method name or visibility modifier!')
|
||||
else:
|
||||
@@ -131,7 +163,7 @@ class ACL:
|
||||
|
||||
@classmethod
|
||||
def _next_token(cls, expression, index):
|
||||
new_index = expression.find('*', index)
|
||||
new_index = expression.xml.find('*', index)
|
||||
if new_index == 0:
|
||||
return ''
|
||||
else:
|
||||
@@ -150,7 +182,7 @@ class ACL:
|
||||
#! print "[TOKEN] '%s'" % token
|
||||
size = len(token)
|
||||
if size > 0:
|
||||
token_index = value.find(token, position)
|
||||
token_index = value.xml.find(token, position)
|
||||
if token_index == -1:
|
||||
return False
|
||||
else:
|
||||
@@ -222,12 +254,11 @@ class TimeoutException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@_add_metaclass(abc.ABCMeta)
|
||||
class Callback(object):
|
||||
'''
|
||||
A base class for callback handlers.
|
||||
'''
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
|
||||
@abc.abstractproperty
|
||||
def set_value(self, value):
|
||||
@@ -291,7 +322,7 @@ class Future(Callback):
|
||||
self._event.set()
|
||||
|
||||
|
||||
|
||||
@_add_metaclass(abc.ABCMeta)
|
||||
class Endpoint(object):
|
||||
'''
|
||||
The Endpoint class is an abstract base class for all objects
|
||||
@@ -303,8 +334,6 @@ class Endpoint(object):
|
||||
which specifies which object an RPC call refers to. It is the
|
||||
first part in a RPC method name '<fqn>.<method>'.
|
||||
'''
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
|
||||
def __init__(self, session, target_jid):
|
||||
'''
|
||||
@@ -491,7 +520,7 @@ class RemoteSession(object):
|
||||
|
||||
def _find_key(self, dict, value):
|
||||
"""return the key of dictionary dic given the value"""
|
||||
search = [k for k, v in dict.iteritems() if v == value]
|
||||
search = [k for k, v in dict.items() if v == value]
|
||||
if len(search) == 0:
|
||||
return None
|
||||
else:
|
||||
@@ -547,7 +576,7 @@ class RemoteSession(object):
|
||||
result = handler_cls(*args, **kwargs)
|
||||
Endpoint.__init__(result, self, self._client.boundjid.full)
|
||||
method_dict = result.get_methods()
|
||||
for method_name, method in method_dict.iteritems():
|
||||
for method_name, method in method_dict.items():
|
||||
#!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
|
||||
self._register_call(result.FQN(), method, method_name)
|
||||
self._register_acl(result.FQN(), acl)
|
||||
@@ -569,11 +598,11 @@ class RemoteSession(object):
|
||||
self._register_callback(pid, callback)
|
||||
iq.send()
|
||||
|
||||
def close(self):
|
||||
def close(self, wait=False):
|
||||
'''
|
||||
Closes this session.
|
||||
'''
|
||||
self._client.disconnect(False)
|
||||
self._client.disconnect(wait=wait)
|
||||
self._session_close_callback()
|
||||
|
||||
def _on_jabber_rpc_method_call(self, iq):
|
||||
@@ -697,7 +726,8 @@ class Remote(object):
|
||||
if(client.boundjid.bare in cls._sessions):
|
||||
raise RemoteException("There already is a session associated with these credentials!")
|
||||
else:
|
||||
cls._sessions[client.boundjid.bare] = client;
|
||||
cls._sessions[client.boundjid.bare] = client
|
||||
|
||||
def _session_close_callback():
|
||||
with Remote._lock:
|
||||
del cls._sessions[client.boundjid.bare]
|
||||
|
||||
@@ -220,3 +220,4 @@ class XEP_0009(BasePlugin):
|
||||
def _extract_method(self, stanza):
|
||||
xml = ET.fromstring("%s" % stanza)
|
||||
return xml.find("./methodCall/methodName").text
|
||||
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0012.last_activity import XEP_0012
|
||||
|
||||
|
||||
register_plugin(XEP_0012)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0004 = XEP_0012
|
||||
|
||||
@@ -66,7 +66,7 @@ class XEP_0012(BasePlugin):
|
||||
self.del_last_activity(jid)
|
||||
|
||||
def start_uptime(self, status=None):
|
||||
self.set_last_activity(jid, 0, status)
|
||||
self.set_last_activity(None, 0, status)
|
||||
|
||||
def set_last_activity(self, jid=None, seconds=None, status=None):
|
||||
self.api['set_last_activity'](jid, args={
|
||||
|
||||
@@ -15,8 +15,3 @@ from slixmpp.plugins.xep_0030.disco import XEP_0030
|
||||
|
||||
|
||||
register_plugin(XEP_0030)
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0030 = XEP_0030
|
||||
XEP_0030.getInfo = XEP_0030.get_info
|
||||
XEP_0030.make_static = XEP_0030.restore_defaults
|
||||
|
||||
@@ -609,7 +609,7 @@ class XEP_0030(BasePlugin):
|
||||
"""
|
||||
self.api['del_features'](jid, node, None, kwargs)
|
||||
|
||||
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}):
|
||||
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None):
|
||||
"""
|
||||
Execute the most specific node handler for the given
|
||||
JID/node combination.
|
||||
@@ -620,6 +620,9 @@ class XEP_0030(BasePlugin):
|
||||
node -- The node requested.
|
||||
data -- Optional, custom data to pass to the handler.
|
||||
"""
|
||||
if not data:
|
||||
data = {}
|
||||
|
||||
return self.api[htype](jid, node, ifrom, data)
|
||||
|
||||
def _handle_disco_info(self, iq):
|
||||
|
||||
@@ -137,7 +137,7 @@ class DiscoInfo(ElementBase):
|
||||
identity = (category, itype, lang)
|
||||
if identity in self._identities:
|
||||
self._identities.remove(identity)
|
||||
for id_xml in self.findall('{%s}identity' % self.namespace):
|
||||
for id_xml in self.xml.findall('{%s}identity' % self.namespace):
|
||||
id = (id_xml.attrib['category'],
|
||||
id_xml.attrib['type'],
|
||||
id_xml.attrib.get('{%s}lang' % self.xml_ns, None))
|
||||
@@ -163,7 +163,7 @@ class DiscoInfo(ElementBase):
|
||||
identities = set()
|
||||
else:
|
||||
identities = []
|
||||
for id_xml in self.findall('{%s}identity' % self.namespace):
|
||||
for id_xml in self.xml.findall('{%s}identity' % self.namespace):
|
||||
xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None)
|
||||
if lang is None or xml_lang == lang:
|
||||
id = (id_xml.attrib['category'],
|
||||
@@ -205,7 +205,7 @@ class DiscoInfo(ElementBase):
|
||||
Arguments:
|
||||
lang -- Optional, standard xml:lang value.
|
||||
"""
|
||||
for id_xml in self.findall('{%s}identity' % self.namespace):
|
||||
for id_xml in self.xml.findall('{%s}identity' % self.namespace):
|
||||
if lang is None:
|
||||
self.xml.remove(id_xml)
|
||||
elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang:
|
||||
@@ -239,7 +239,7 @@ class DiscoInfo(ElementBase):
|
||||
"""
|
||||
if feature in self._features:
|
||||
self._features.remove(feature)
|
||||
for feature_xml in self.findall('{%s}feature' % self.namespace):
|
||||
for feature_xml in self.xml.findall('{%s}feature' % self.namespace):
|
||||
if feature_xml.attrib['var'] == feature:
|
||||
self.xml.remove(feature_xml)
|
||||
return True
|
||||
@@ -251,7 +251,7 @@ class DiscoInfo(ElementBase):
|
||||
features = set()
|
||||
else:
|
||||
features = []
|
||||
for feature_xml in self.findall('{%s}feature' % self.namespace):
|
||||
for feature_xml in self.xml.findall('{%s}feature' % self.namespace):
|
||||
if dedupe:
|
||||
features.add(feature_xml.attrib['var'])
|
||||
else:
|
||||
@@ -272,5 +272,5 @@ class DiscoInfo(ElementBase):
|
||||
def del_features(self):
|
||||
"""Remove all features."""
|
||||
self._features = set()
|
||||
for feature_xml in self.findall('{%s}feature' % self.namespace):
|
||||
for feature_xml in self.xml.findall('{%s}feature' % self.namespace):
|
||||
self.xml.remove(feature_xml)
|
||||
|
||||
@@ -95,7 +95,7 @@ class DiscoItems(ElementBase):
|
||||
node -- Optional extra identifying information.
|
||||
"""
|
||||
if (jid, node) in self._items:
|
||||
for item_xml in self.findall('{%s}item' % self.namespace):
|
||||
for item_xml in self.xml.findall('{%s}item' % self.namespace):
|
||||
item = (item_xml.attrib['jid'],
|
||||
item_xml.attrib.get('node', None))
|
||||
if item == (jid, node):
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from slixmpp import Iq
|
||||
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
|
||||
@@ -48,7 +47,6 @@ class StaticDisco(object):
|
||||
self.nodes = {}
|
||||
self.xmpp = xmpp
|
||||
self.disco = disco
|
||||
self.lock = threading.RLock()
|
||||
|
||||
def add_node(self, jid=None, node=None, ifrom=None):
|
||||
"""
|
||||
@@ -59,48 +57,45 @@ class StaticDisco(object):
|
||||
jid -- The JID that will own the new stanzas.
|
||||
node -- The node that will own the new stanzas.
|
||||
"""
|
||||
with self.lock:
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.full
|
||||
if node is None:
|
||||
node = ''
|
||||
if ifrom is None:
|
||||
ifrom = ''
|
||||
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
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.full
|
||||
if node is None:
|
||||
node = ''
|
||||
if ifrom is None:
|
||||
ifrom = ''
|
||||
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
|
||||
|
||||
def get_node(self, jid=None, node=None, ifrom=None):
|
||||
with self.lock:
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.full
|
||||
if node is None:
|
||||
node = ''
|
||||
if ifrom is None:
|
||||
ifrom = ''
|
||||
if isinstance(ifrom, JID):
|
||||
ifrom = ifrom.full
|
||||
if (jid, node, ifrom) not in self.nodes:
|
||||
self.add_node(jid, node, ifrom)
|
||||
return self.nodes[(jid, node, ifrom)]
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.full
|
||||
if node is None:
|
||||
node = ''
|
||||
if ifrom is None:
|
||||
ifrom = ''
|
||||
if isinstance(ifrom, JID):
|
||||
ifrom = ifrom.full
|
||||
if (jid, node, ifrom) not in self.nodes:
|
||||
self.add_node(jid, node, ifrom)
|
||||
return self.nodes[(jid, node, ifrom)]
|
||||
|
||||
def node_exists(self, jid=None, node=None, ifrom=None):
|
||||
with self.lock:
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.full
|
||||
if node is None:
|
||||
node = ''
|
||||
if ifrom is None:
|
||||
ifrom = ''
|
||||
if isinstance(ifrom, JID):
|
||||
ifrom = ifrom.full
|
||||
if (jid, node, ifrom) not in self.nodes:
|
||||
return False
|
||||
return True
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.full
|
||||
if node is None:
|
||||
node = ''
|
||||
if ifrom is None:
|
||||
ifrom = ''
|
||||
if isinstance(ifrom, JID):
|
||||
ifrom = ifrom.full
|
||||
if (jid, node, ifrom) not in self.nodes:
|
||||
return False
|
||||
return True
|
||||
|
||||
# =================================================================
|
||||
# Node Handlers
|
||||
@@ -199,14 +194,13 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
with self.lock:
|
||||
if not self.node_exists(jid, node):
|
||||
if not node:
|
||||
return DiscoInfo()
|
||||
else:
|
||||
raise XMPPError(condition='item-not-found')
|
||||
if not self.node_exists(jid, node):
|
||||
if not node:
|
||||
return DiscoInfo()
|
||||
else:
|
||||
return self.get_node(jid, node)['info']
|
||||
raise XMPPError(condition='item-not-found')
|
||||
else:
|
||||
return self.get_node(jid, node)['info']
|
||||
|
||||
def set_info(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -214,9 +208,8 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is a disco#info substanza.
|
||||
"""
|
||||
with self.lock:
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info'] = data
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info'] = data
|
||||
|
||||
def del_info(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -224,9 +217,8 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
with self.lock:
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['info'] = DiscoInfo()
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['info'] = DiscoInfo()
|
||||
|
||||
def get_items(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -234,14 +226,13 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
with self.lock:
|
||||
if not self.node_exists(jid, node):
|
||||
if not node:
|
||||
return DiscoItems()
|
||||
else:
|
||||
raise XMPPError(condition='item-not-found')
|
||||
if not self.node_exists(jid, node):
|
||||
if not node:
|
||||
return DiscoItems()
|
||||
else:
|
||||
return self.get_node(jid, node)['items']
|
||||
raise XMPPError(condition='item-not-found')
|
||||
else:
|
||||
return self.get_node(jid, node)['items']
|
||||
|
||||
def set_items(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -250,10 +241,9 @@ class StaticDisco(object):
|
||||
The data parameter may provide:
|
||||
items -- A set of items in tuple format.
|
||||
"""
|
||||
with self.lock:
|
||||
items = data.get('items', set())
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['items']['items'] = items
|
||||
items = data.get('items', set())
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['items']['items'] = items
|
||||
|
||||
def del_items(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -261,9 +251,8 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
with self.lock:
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['items'] = DiscoItems()
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['items'] = DiscoItems()
|
||||
|
||||
def add_identity(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -275,13 +264,12 @@ class StaticDisco(object):
|
||||
name -- Optional human readable name for this identity.
|
||||
lang -- Optional standard xml:lang value.
|
||||
"""
|
||||
with self.lock:
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info'].add_identity(
|
||||
data.get('category', ''),
|
||||
data.get('itype', ''),
|
||||
data.get('name', None),
|
||||
data.get('lang', None))
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info'].add_identity(
|
||||
data.get('category', ''),
|
||||
data.get('itype', ''),
|
||||
data.get('name', None),
|
||||
data.get('lang', None))
|
||||
|
||||
def set_identities(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -291,10 +279,9 @@ class StaticDisco(object):
|
||||
identities -- A list of identities in tuple form:
|
||||
(category, type, name, lang)
|
||||
"""
|
||||
with self.lock:
|
||||
identities = data.get('identities', set())
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info']['identities'] = identities
|
||||
identities = data.get('identities', set())
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info']['identities'] = identities
|
||||
|
||||
def del_identity(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -306,13 +293,12 @@ class StaticDisco(object):
|
||||
name -- Optional human readable name for this identity.
|
||||
lang -- Optional, standard xml:lang value.
|
||||
"""
|
||||
with self.lock:
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['info'].del_identity(
|
||||
data.get('category', ''),
|
||||
data.get('itype', ''),
|
||||
data.get('name', None),
|
||||
data.get('lang', None))
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['info'].del_identity(
|
||||
data.get('category', ''),
|
||||
data.get('itype', ''),
|
||||
data.get('name', None),
|
||||
data.get('lang', None))
|
||||
|
||||
def del_identities(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -320,9 +306,8 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
with self.lock:
|
||||
if self.node_exists(jid, node):
|
||||
del self.get_node(jid, node)['info']['identities']
|
||||
if self.node_exists(jid, node):
|
||||
del self.get_node(jid, node)['info']['identities']
|
||||
|
||||
def add_feature(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -331,10 +316,9 @@ class StaticDisco(object):
|
||||
The data parameter should include:
|
||||
feature -- The namespace of the supported feature.
|
||||
"""
|
||||
with self.lock:
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info'].add_feature(
|
||||
data.get('feature', ''))
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info'].add_feature(
|
||||
data.get('feature', ''))
|
||||
|
||||
def set_features(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -343,10 +327,9 @@ class StaticDisco(object):
|
||||
The data parameter should include:
|
||||
features -- The new set of supported features.
|
||||
"""
|
||||
with self.lock:
|
||||
features = data.get('features', set())
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info']['features'] = features
|
||||
features = data.get('features', set())
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['info']['features'] = features
|
||||
|
||||
def del_feature(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -355,10 +338,9 @@ class StaticDisco(object):
|
||||
The data parameter should include:
|
||||
feature -- The namespace of the removed feature.
|
||||
"""
|
||||
with self.lock:
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['info'].del_feature(
|
||||
data.get('feature', ''))
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['info'].del_feature(
|
||||
data.get('feature', ''))
|
||||
|
||||
def del_features(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -366,10 +348,9 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
with self.lock:
|
||||
if not self.node_exists(jid, node):
|
||||
return
|
||||
del self.get_node(jid, node)['info']['features']
|
||||
if not self.node_exists(jid, node):
|
||||
return
|
||||
del self.get_node(jid, node)['info']['features']
|
||||
|
||||
def add_item(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -381,12 +362,11 @@ class StaticDisco(object):
|
||||
non-addressable items.
|
||||
name -- Optional human readable name for the item.
|
||||
"""
|
||||
with self.lock:
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['items'].add_item(
|
||||
data.get('ijid', ''),
|
||||
node=data.get('inode', ''),
|
||||
name=data.get('name', ''))
|
||||
self.add_node(jid, node)
|
||||
self.get_node(jid, node)['items'].add_item(
|
||||
data.get('ijid', ''),
|
||||
node=data.get('inode', ''),
|
||||
name=data.get('name', ''))
|
||||
|
||||
def del_item(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -396,11 +376,10 @@ class StaticDisco(object):
|
||||
ijid -- JID of the item to remove.
|
||||
inode -- Optional extra identifying information.
|
||||
"""
|
||||
with self.lock:
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['items'].del_item(
|
||||
data.get('ijid', ''),
|
||||
node=data.get('inode', None))
|
||||
if self.node_exists(jid, node):
|
||||
self.get_node(jid, node)['items'].del_item(
|
||||
data.get('ijid', ''),
|
||||
node=data.get('inode', None))
|
||||
|
||||
def cache_info(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -410,12 +389,11 @@ class StaticDisco(object):
|
||||
containing the disco info to cache, or
|
||||
the disco#info substanza itself.
|
||||
"""
|
||||
with self.lock:
|
||||
if isinstance(data, Iq):
|
||||
data = data['disco_info']
|
||||
if isinstance(data, Iq):
|
||||
data = data['disco_info']
|
||||
|
||||
self.add_node(jid, node, ifrom)
|
||||
self.get_node(jid, node, ifrom)['info'] = data
|
||||
self.add_node(jid, node, ifrom)
|
||||
self.get_node(jid, node, ifrom)['info'] = data
|
||||
|
||||
def get_cached_info(self, jid, node, ifrom, data):
|
||||
"""
|
||||
@@ -423,8 +401,7 @@ class StaticDisco(object):
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
with self.lock:
|
||||
if not self.node_exists(jid, node, ifrom):
|
||||
return None
|
||||
else:
|
||||
return self.get_node(jid, node, ifrom)['info']
|
||||
if not self.node_exists(jid, node, ifrom):
|
||||
return None
|
||||
else:
|
||||
return self.get_node(jid, node, ifrom)['info']
|
||||
|
||||
@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0033.addresses import XEP_0033
|
||||
|
||||
|
||||
register_plugin(XEP_0033)
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0033 = XEP_0033
|
||||
Addresses.addAddress = Addresses.add_address
|
||||
|
||||
@@ -118,14 +118,8 @@ for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'):
|
||||
setattr(Addresses, "del_%s" % atype, del_multi)
|
||||
|
||||
# To retain backwards compatibility:
|
||||
setattr(Addresses, "get%s" % atype.title(), get_multi)
|
||||
setattr(Addresses, "set%s" % atype.title(), set_multi)
|
||||
setattr(Addresses, "del%s" % atype.title(), del_multi)
|
||||
if atype == 'all':
|
||||
Addresses.interfaces.add('addresses')
|
||||
setattr(Addresses, "getAddresses", get_multi)
|
||||
setattr(Addresses, "setAddresses", set_multi)
|
||||
setattr(Addresses, "delAddresses", del_multi)
|
||||
|
||||
|
||||
register_stanza_plugin(Addresses, Address, iterable=True)
|
||||
|
||||
@@ -29,82 +29,82 @@ class MUCPresence(ElementBase):
|
||||
affiliations = set(('', ))
|
||||
roles = set(('', ))
|
||||
|
||||
def getXMLItem(self):
|
||||
def get_xml_item(self):
|
||||
item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
|
||||
if item is None:
|
||||
item = ET.Element('{http://jabber.org/protocol/muc#user}item')
|
||||
self.xml.append(item)
|
||||
return item
|
||||
|
||||
def getAffiliation(self):
|
||||
def get_affiliation(self):
|
||||
#TODO if no affilation, set it to the default and return default
|
||||
item = self.getXMLItem()
|
||||
item = self.get_xml_item()
|
||||
return item.get('affiliation', '')
|
||||
|
||||
def setAffiliation(self, value):
|
||||
item = self.getXMLItem()
|
||||
def set_affiliation(self, value):
|
||||
item = self.get_xml_item()
|
||||
#TODO check for valid affiliation
|
||||
item.attrib['affiliation'] = value
|
||||
return self
|
||||
|
||||
def delAffiliation(self):
|
||||
item = self.getXMLItem()
|
||||
def del_affiliation(self):
|
||||
item = self.get_xml_item()
|
||||
#TODO set default affiliation
|
||||
if 'affiliation' in item.attrib: del item.attrib['affiliation']
|
||||
return self
|
||||
|
||||
def getJid(self):
|
||||
item = self.getXMLItem()
|
||||
def get_jid(self):
|
||||
item = self.get_xml_item()
|
||||
return JID(item.get('jid', ''))
|
||||
|
||||
def setJid(self, value):
|
||||
item = self.getXMLItem()
|
||||
def set_jid(self, value):
|
||||
item = self.get_xml_item()
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
item.attrib['jid'] = value
|
||||
return self
|
||||
|
||||
def delJid(self):
|
||||
item = self.getXMLItem()
|
||||
def del_jid(self):
|
||||
item = self.get_xml_item()
|
||||
if 'jid' in item.attrib: del item.attrib['jid']
|
||||
return self
|
||||
|
||||
def getRole(self):
|
||||
item = self.getXMLItem()
|
||||
def get_role(self):
|
||||
item = self.get_xml_item()
|
||||
#TODO get default role, set default role if none
|
||||
return item.get('role', '')
|
||||
|
||||
def setRole(self, value):
|
||||
item = self.getXMLItem()
|
||||
def set_role(self, value):
|
||||
item = self.get_xml_item()
|
||||
#TODO check for valid role
|
||||
item.attrib['role'] = value
|
||||
return self
|
||||
|
||||
def delRole(self):
|
||||
item = self.getXMLItem()
|
||||
def del_role(self):
|
||||
item = self.get_xml_item()
|
||||
#TODO set default role
|
||||
if 'role' in item.attrib: del item.attrib['role']
|
||||
return self
|
||||
|
||||
def getNick(self):
|
||||
def get_nick(self):
|
||||
return self.parent()['from'].resource
|
||||
|
||||
def getRoom(self):
|
||||
def get_room(self):
|
||||
return self.parent()['from'].bare
|
||||
|
||||
def setNick(self, value):
|
||||
def set_nick(self, value):
|
||||
log.warning("Cannot set nick through mucpresence plugin.")
|
||||
return self
|
||||
|
||||
def setRoom(self, value):
|
||||
def set_room(self, value):
|
||||
log.warning("Cannot set room through mucpresence plugin.")
|
||||
return self
|
||||
|
||||
def delNick(self):
|
||||
def del_nick(self):
|
||||
log.warning("Cannot delete nick through mucpresence plugin.")
|
||||
return self
|
||||
|
||||
def delRoom(self):
|
||||
def del_room(self):
|
||||
log.warning("Cannot delete room through mucpresence plugin.")
|
||||
return self
|
||||
|
||||
@@ -121,7 +121,7 @@ class XEP_0045(BasePlugin):
|
||||
|
||||
def plugin_init(self):
|
||||
self.rooms = {}
|
||||
self.ourNicks = {}
|
||||
self.our_nicks = {}
|
||||
self.xep = '0045'
|
||||
# load MUC support in presence stanzas
|
||||
register_stanza_plugin(Presence, MUCPresence)
|
||||
@@ -160,6 +160,7 @@ class XEP_0045(BasePlugin):
|
||||
got_online = False
|
||||
if pr['muc']['room'] not in self.rooms.keys():
|
||||
return
|
||||
self.xmpp.roster[pr['from']].ignore_updates = True
|
||||
entry = pr['muc'].get_stanza_values()
|
||||
entry['show'] = pr['show']
|
||||
entry['status'] = pr['status']
|
||||
@@ -200,28 +201,28 @@ class XEP_0045(BasePlugin):
|
||||
"""
|
||||
self.xmpp.event('groupchat_subject', msg)
|
||||
|
||||
def jidInRoom(self, room, jid):
|
||||
def jid_in_room(self, room, jid):
|
||||
for nick in self.rooms[room]:
|
||||
entry = self.rooms[room][nick]
|
||||
if entry is not None and entry['jid'].full == jid:
|
||||
return True
|
||||
return False
|
||||
|
||||
def getNick(self, room, jid):
|
||||
def get_nick(self, room, jid):
|
||||
for nick in self.rooms[room]:
|
||||
entry = self.rooms[room][nick]
|
||||
if entry is not None and entry['jid'].full == jid:
|
||||
return nick
|
||||
|
||||
def configureRoom(self, room, form=None, ifrom=None):
|
||||
def configure_room(self, room, form=None, ifrom=None):
|
||||
if form is None:
|
||||
form = self.getRoomConfig(room, ifrom=ifrom)
|
||||
form = self.get_room_config(room, ifrom=ifrom)
|
||||
iq = self.xmpp.make_iq_set()
|
||||
iq['to'] = room
|
||||
if ifrom is not None:
|
||||
iq['from'] = ifrom
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||
form = form.getXML('submit')
|
||||
form['type'] = 'submit'
|
||||
query.append(form)
|
||||
iq.append(query)
|
||||
# For now, swallow errors to preserve existing API
|
||||
@@ -233,7 +234,7 @@ class XEP_0045(BasePlugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
|
||||
def join_muc(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
|
||||
""" Join the specified room, requesting 'maxhistory' lines of history.
|
||||
"""
|
||||
stanza = self.xmpp.make_presence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
|
||||
@@ -257,7 +258,7 @@ class XEP_0045(BasePlugin):
|
||||
expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)})
|
||||
self.xmpp.send(stanza, expect)
|
||||
self.rooms[room] = {}
|
||||
self.ourNicks[room] = nick
|
||||
self.our_nicks[room] = nick
|
||||
|
||||
def destroy(self, room, reason='', altroom = '', ifrom=None):
|
||||
iq = self.xmpp.make_iq_set()
|
||||
@@ -282,7 +283,7 @@ class XEP_0045(BasePlugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
|
||||
def set_affiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
|
||||
""" Change room affiliation."""
|
||||
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||
raise TypeError
|
||||
@@ -304,7 +305,7 @@ class XEP_0045(BasePlugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def setRole(self, room, nick, role):
|
||||
def set_role(self, room, nick, role):
|
||||
""" Change role property of a nick in a room.
|
||||
Typically, roles are temporary (they last only as long as you are in the
|
||||
room), whereas affiliations are permanent (they last across groupchat
|
||||
@@ -336,7 +337,7 @@ class XEP_0045(BasePlugin):
|
||||
msg.append(x)
|
||||
self.xmpp.send(msg)
|
||||
|
||||
def leaveMUC(self, room, nick, msg='', pfrom=None):
|
||||
def leave_muc(self, room, nick, msg='', pfrom=None):
|
||||
""" Leave the specified room.
|
||||
"""
|
||||
if msg:
|
||||
@@ -345,7 +346,7 @@ class XEP_0045(BasePlugin):
|
||||
self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
|
||||
del self.rooms[room]
|
||||
|
||||
def getRoomConfig(self, room, ifrom=''):
|
||||
def get_room_config(self, room, ifrom=''):
|
||||
iq = self.xmpp.make_iq_get('http://jabber.org/protocol/muc#owner')
|
||||
iq['to'] = room
|
||||
iq['from'] = ifrom
|
||||
@@ -359,9 +360,9 @@ class XEP_0045(BasePlugin):
|
||||
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
|
||||
if form is None:
|
||||
raise ValueError
|
||||
return self.xmpp.plugin['xep_0004'].buildForm(form)
|
||||
return self.xmpp.plugin['xep_0004'].build_form(form)
|
||||
|
||||
def cancelConfig(self, room, ifrom=None):
|
||||
def cancel_config(self, room, ifrom=None):
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||
x = ET.Element('{jabber:x:data}x', type='cancel')
|
||||
query.append(x)
|
||||
@@ -370,39 +371,48 @@ class XEP_0045(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
iq.send()
|
||||
|
||||
def setRoomConfig(self, room, config, ifrom=''):
|
||||
def set_room_config(self, room, config, ifrom=''):
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||
x = config.getXML('submit')
|
||||
query.append(x)
|
||||
config['type'] = 'submit'
|
||||
query.append(config)
|
||||
iq = self.xmpp.make_iq_set(query)
|
||||
iq['to'] = room
|
||||
iq['from'] = ifrom
|
||||
iq.send()
|
||||
|
||||
def getJoinedRooms(self):
|
||||
def get_joined_rooms(self):
|
||||
return self.rooms.keys()
|
||||
|
||||
def getOurJidInRoom(self, roomJid):
|
||||
def get_our_jid_in_room(self, room_jid):
|
||||
""" Return the jid we're using in a room.
|
||||
"""
|
||||
return "%s/%s" % (roomJid, self.ourNicks[roomJid])
|
||||
return "%s/%s" % (room_jid, self.our_nicks[room_jid])
|
||||
|
||||
def getJidProperty(self, room, nick, jidProperty):
|
||||
def get_jid_property(self, room, nick, jid_property):
|
||||
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation'
|
||||
If not found, return None.
|
||||
"""
|
||||
if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
|
||||
return self.rooms[room][nick][jidProperty]
|
||||
if room in self.rooms and nick in self.rooms[room] and jid_property in self.rooms[room][nick]:
|
||||
return self.rooms[room][nick][jid_property]
|
||||
else:
|
||||
return None
|
||||
|
||||
def getRoster(self, room):
|
||||
def get_roster(self, room):
|
||||
""" Get the list of nicks in a room.
|
||||
"""
|
||||
if room not in self.rooms.keys():
|
||||
return None
|
||||
return self.rooms[room].keys()
|
||||
|
||||
def get_users_by_affiliation(cls, room, affiliation='member', ifrom=None):
|
||||
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||
raise TypeError
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
||||
item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation})
|
||||
query.append(item)
|
||||
iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get')
|
||||
iq.append(query)
|
||||
return iq.send()
|
||||
|
||||
|
||||
xep_0045 = XEP_0045
|
||||
register_plugin(XEP_0045)
|
||||
|
||||
@@ -15,7 +15,3 @@ from slixmpp.plugins.xep_0047.ibb import XEP_0047
|
||||
|
||||
|
||||
register_plugin(XEP_0047)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0047 = XEP_0047
|
||||
|
||||
@@ -59,7 +59,7 @@ class XEP_0048(BasePlugin):
|
||||
for conf in bookmarks['conferences']:
|
||||
if conf['autojoin']:
|
||||
log.debug('Auto joining %s as %s', conf['jid'], conf['nick'])
|
||||
self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'],
|
||||
self.xmpp['xep_0045'].join_muc(conf['jid'], conf['nick'],
|
||||
password=conf['password'])
|
||||
|
||||
def set_bookmarks(self, bookmarks, method=None, **iqargs):
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0050.adhoc import XEP_0050
|
||||
|
||||
|
||||
register_plugin(XEP_0050)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0050 = XEP_0050
|
||||
|
||||
@@ -94,7 +94,7 @@ class XEP_0050(BasePlugin):
|
||||
self._handle_command))
|
||||
|
||||
register_stanza_plugin(Iq, Command)
|
||||
register_stanza_plugin(Command, Form)
|
||||
register_stanza_plugin(Command, Form, iterable=True)
|
||||
|
||||
self.xmpp.add_event_handler('command_execute',
|
||||
self._handle_command_start)
|
||||
@@ -415,12 +415,26 @@ class XEP_0050(BasePlugin):
|
||||
|
||||
del self.sessions[sessionid]
|
||||
|
||||
payload = session['payload']
|
||||
if payload is None:
|
||||
payload = []
|
||||
if not isinstance(payload, list):
|
||||
payload = [payload]
|
||||
|
||||
for item in payload:
|
||||
register_stanza_plugin(Command, item.__class__, iterable=True)
|
||||
|
||||
iq = iq.reply()
|
||||
|
||||
iq['command']['node'] = node
|
||||
iq['command']['sessionid'] = sessionid
|
||||
iq['command']['actions'] = []
|
||||
iq['command']['status'] = 'completed'
|
||||
iq['command']['notes'] = session['notes']
|
||||
|
||||
for item in payload:
|
||||
iq['command'].append(item)
|
||||
|
||||
iq.send()
|
||||
else:
|
||||
raise XMPPError('item-not-found')
|
||||
|
||||
@@ -100,7 +100,7 @@ class Command(ElementBase):
|
||||
self.del_actions()
|
||||
if values:
|
||||
self._set_sub_text('{%s}actions' % self.namespace, '', True)
|
||||
actions = self.find('{%s}actions' % self.namespace)
|
||||
actions = self.xml.find('{%s}actions' % self.namespace)
|
||||
for val in values:
|
||||
if val in self.next_actions:
|
||||
action = ET.Element('{%s}%s' % (self.namespace, val))
|
||||
@@ -111,7 +111,7 @@ class Command(ElementBase):
|
||||
Return the set of allowable next actions.
|
||||
"""
|
||||
actions = set()
|
||||
actions_xml = self.find('{%s}actions' % self.namespace)
|
||||
actions_xml = self.xml.find('{%s}actions' % self.namespace)
|
||||
if actions_xml is not None:
|
||||
for action in self.next_actions:
|
||||
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
|
||||
@@ -136,7 +136,7 @@ class Command(ElementBase):
|
||||
('error', 'The command ran, but had errors')]
|
||||
"""
|
||||
notes = []
|
||||
notes_xml = self.findall('{%s}note' % self.namespace)
|
||||
notes_xml = self.xml.findall('{%s}note' % self.namespace)
|
||||
for note in notes_xml:
|
||||
notes.append((note.attrib.get('type', 'info'),
|
||||
note.text))
|
||||
@@ -167,7 +167,7 @@ class Command(ElementBase):
|
||||
"""
|
||||
Remove all notes associated with the command result.
|
||||
"""
|
||||
notes_xml = self.findall('{%s}note' % self.namespace)
|
||||
notes_xml = self.xml.findall('{%s}note' % self.namespace)
|
||||
for note in notes_xml:
|
||||
self.xml.remove(note)
|
||||
|
||||
|
||||
@@ -127,8 +127,9 @@ class Telephone(ElementBase):
|
||||
'ISDN', 'PCS', 'PREF'])
|
||||
|
||||
def setup(self, xml=None):
|
||||
super(Telephone, self).setup(xml=xml)
|
||||
self._set_sub_text('NUMBER', '', keep=True)
|
||||
super().setup(xml=xml)
|
||||
## this blanks out numbers received from server
|
||||
##self._set_sub_text('NUMBER', '', keep=True)
|
||||
|
||||
def set_number(self, value):
|
||||
self._set_sub_text('NUMBER', value, keep=True)
|
||||
|
||||
@@ -13,6 +13,3 @@ from slixmpp.plugins.xep_0059.rsm import ResultIterator, XEP_0059
|
||||
|
||||
|
||||
register_plugin(XEP_0059)
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0059 = XEP_0059
|
||||
|
||||
@@ -70,7 +70,7 @@ class Set(ElementBase):
|
||||
'count', 'index', 'last', 'max'))
|
||||
|
||||
def set_first_index(self, val):
|
||||
fi = self.find("{%s}first" % (self.namespace))
|
||||
fi = self.xml.find("{%s}first" % (self.namespace))
|
||||
if fi is not None:
|
||||
if val:
|
||||
fi.attrib['index'] = val
|
||||
@@ -82,7 +82,7 @@ class Set(ElementBase):
|
||||
self.xml.append(fi)
|
||||
|
||||
def get_first_index(self):
|
||||
fi = self.find("{%s}first" % (self.namespace))
|
||||
fi = self.xml.find("{%s}first" % (self.namespace))
|
||||
if fi is not None:
|
||||
return fi.attrib.get('index', '')
|
||||
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0060 import stanza
|
||||
|
||||
|
||||
register_plugin(XEP_0060)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0060 = XEP_0060
|
||||
|
||||
@@ -206,7 +206,7 @@ class Options(ElementBase):
|
||||
return form
|
||||
|
||||
def set_options(self, value):
|
||||
self.xml.append(value.getXML())
|
||||
self.xml.append(value)
|
||||
return self
|
||||
|
||||
def del_options(self):
|
||||
@@ -238,7 +238,7 @@ class PublishOptions(ElementBase):
|
||||
if value is None:
|
||||
self.del_publish_options()
|
||||
else:
|
||||
self.xml.append(value.getXML())
|
||||
self.xml.append(value)
|
||||
return self
|
||||
|
||||
def del_publish_options(self):
|
||||
|
||||
@@ -251,7 +251,6 @@ class XEP_0065(BasePlugin):
|
||||
host : The hostname or the IP of the proxy. <str>
|
||||
port : The port of the proxy. <str> or <int>
|
||||
"""
|
||||
|
||||
factory = lambda: Socks5Protocol(dest, 0, self.xmpp.event)
|
||||
return self.xmpp.loop.create_connection(factory, proxy, proxy_port)
|
||||
|
||||
|
||||
@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0066.oob import XEP_0066
|
||||
|
||||
|
||||
register_plugin(XEP_0066)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0066 = XEP_0066
|
||||
|
||||
15
slixmpp/plugins/xep_0070/__init__.py
Normal file
15
slixmpp/plugins/xep_0070/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 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_0070.stanza import Confirm
|
||||
from slixmpp.plugins.xep_0070.confirm import XEP_0070
|
||||
|
||||
|
||||
register_plugin(XEP_0070)
|
||||
86
slixmpp/plugins/xep_0070/confirm.py
Normal file
86
slixmpp/plugins/xep_0070/confirm.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from slixmpp.plugins import BasePlugin, register_plugin
|
||||
from slixmpp import future_wrapper, Iq, Message
|
||||
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
|
||||
from slixmpp.jid import JID
|
||||
from slixmpp.xmlstream import JID, register_stanza_plugin
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.plugins.xep_0070 import stanza, Confirm
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XEP_0070(BasePlugin):
|
||||
|
||||
"""
|
||||
XEP-0070 Verifying HTTP Requests via XMPP
|
||||
"""
|
||||
|
||||
name = 'xep_0070'
|
||||
description = 'XEP-0070: Verifying HTTP Requests via XMPP'
|
||||
dependencies = {'xep_0030'}
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Iq, Confirm)
|
||||
register_stanza_plugin(Message, Confirm)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Confirm',
|
||||
StanzaPath('iq@type=get/confirm'),
|
||||
self._handle_iq_confirm))
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Confirm',
|
||||
StanzaPath('message/confirm'),
|
||||
self._handle_message_confirm))
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Confirm')
|
||||
self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/http-auth')
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/http-auth')
|
||||
|
||||
@future_wrapper
|
||||
def ask_confirm(self, jid, id, url, method, *, ifrom=None, message=None):
|
||||
jid = JID(jid)
|
||||
if jid.resource:
|
||||
stanza = self.xmpp.Iq()
|
||||
stanza['type'] = 'get'
|
||||
else:
|
||||
stanza = self.xmpp.Message()
|
||||
stanza['thread'] = uuid4().hex
|
||||
stanza['from'] = ifrom
|
||||
stanza['to'] = jid
|
||||
stanza['confirm']['id'] = id
|
||||
stanza['confirm']['url'] = url
|
||||
stanza['confirm']['method'] = method
|
||||
if not jid.resource:
|
||||
if message is not None:
|
||||
stanza['body'] = message.format(id=id, url=url, method=method)
|
||||
stanza.send()
|
||||
return stanza
|
||||
else:
|
||||
return stanza.send()
|
||||
|
||||
def _handle_iq_confirm(self, iq):
|
||||
self.xmpp.event('http_confirm_iq', iq)
|
||||
self.xmpp.event('http_confirm', iq)
|
||||
|
||||
def _handle_message_confirm(self, message):
|
||||
self.xmpp.event('http_confirm_message', message)
|
||||
self.xmpp.event('http_confirm', message)
|
||||
17
slixmpp/plugins/xep_0070/stanza.py
Normal file
17
slixmpp/plugins/xep_0070/stanza.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2015 Emmanuel Gil Peyrot
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class Confirm(ElementBase):
|
||||
|
||||
name = 'confirm'
|
||||
namespace = 'http://jabber.org/protocol/http-auth'
|
||||
plugin_attrib = 'confirm'
|
||||
interfaces = {'id', 'url', 'method'}
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0077.register import XEP_0077
|
||||
|
||||
|
||||
register_plugin(XEP_0077)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0077 = XEP_0077
|
||||
|
||||
@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0078.legacyauth import XEP_0078
|
||||
|
||||
|
||||
register_plugin(XEP_0078)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0078 = XEP_0078
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0085.chat_states import XEP_0085
|
||||
|
||||
|
||||
register_plugin(XEP_0085)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0085 = XEP_0085
|
||||
|
||||
@@ -50,7 +50,7 @@ class ChatState(ElementBase):
|
||||
def get_chat_state(self):
|
||||
parent = self.parent()
|
||||
for state in self.states:
|
||||
state_xml = parent.find('{%s}%s' % (self.namespace, state))
|
||||
state_xml = parent.xml.find('{%s}%s' % (self.namespace, state))
|
||||
if state_xml is not None:
|
||||
self.xml = state_xml
|
||||
return state
|
||||
@@ -68,7 +68,7 @@ class ChatState(ElementBase):
|
||||
def del_chat_state(self):
|
||||
parent = self.parent()
|
||||
for state in self.states:
|
||||
state_xml = parent.find('{%s}%s' % (self.namespace, state))
|
||||
state_xml = parent.xml.find('{%s}%s' % (self.namespace, state))
|
||||
if state_xml is not None:
|
||||
self.xml = ET.Element('')
|
||||
parent.xml.remove(state_xml)
|
||||
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0086.legacy_error import XEP_0086
|
||||
|
||||
|
||||
register_plugin(XEP_0086)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0086 = XEP_0086
|
||||
|
||||
@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0092.version import XEP_0092
|
||||
|
||||
|
||||
register_plugin(XEP_0092)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0092 = XEP_0092
|
||||
|
||||
@@ -47,6 +47,7 @@ class XEP_0096(BasePlugin):
|
||||
data['size'] = size
|
||||
data['date'] = date
|
||||
data['desc'] = desc
|
||||
data['hash'] = hash
|
||||
if allow_ranged:
|
||||
data.enable('range')
|
||||
|
||||
|
||||
@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0115.caps import XEP_0115
|
||||
|
||||
|
||||
register_plugin(XEP_0115)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0115 = XEP_0115
|
||||
|
||||
@@ -124,23 +124,19 @@ class StaticCaps(object):
|
||||
return None
|
||||
|
||||
def cache_caps(self, jid, node, ifrom, data):
|
||||
with self.static.lock:
|
||||
verstring = data.get('verstring', None)
|
||||
info = data.get('info', None)
|
||||
if not verstring or not info:
|
||||
return
|
||||
self.ver_cache[verstring] = info
|
||||
verstring = data.get('verstring', None)
|
||||
info = data.get('info', None)
|
||||
if not verstring or not info:
|
||||
return
|
||||
self.ver_cache[verstring] = info
|
||||
|
||||
def assign_verstring(self, jid, node, ifrom, data):
|
||||
with self.static.lock:
|
||||
if isinstance(jid, JID):
|
||||
jid = jid.full
|
||||
self.jid_vers[jid] = data.get('verstring', None)
|
||||
if isinstance(jid, JID):
|
||||
jid = jid.full
|
||||
self.jid_vers[jid] = data.get('verstring', None)
|
||||
|
||||
def get_verstring(self, jid, node, ifrom, data):
|
||||
with self.static.lock:
|
||||
return self.jid_vers.get(jid, None)
|
||||
return self.jid_vers.get(jid, None)
|
||||
|
||||
def get_caps(self, jid, node, ifrom, data):
|
||||
with self.static.lock:
|
||||
return self.ver_cache.get(data.get('verstring', None), None)
|
||||
return self.ver_cache.get(data.get('verstring', None), None)
|
||||
|
||||
7
slixmpp/plugins/xep_0122/__init__.py
Normal file
7
slixmpp/plugins/xep_0122/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
from slixmpp.plugins.xep_0122.stanza import FormValidation
|
||||
from slixmpp.plugins.xep_0122.data_validation import XEP_0122
|
||||
|
||||
|
||||
register_plugin(XEP_0122)
|
||||
19
slixmpp/plugins/xep_0122/data_validation.py
Normal file
19
slixmpp/plugins/xep_0122/data_validation.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.xep_0004 import stanza
|
||||
from slixmpp.plugins.xep_0004.stanza import FormField
|
||||
from slixmpp.plugins.xep_0122.stanza import FormValidation
|
||||
|
||||
|
||||
class XEP_0122(BasePlugin):
|
||||
"""
|
||||
XEP-0122: Data Forms
|
||||
"""
|
||||
|
||||
name = 'xep_0122'
|
||||
description = 'XEP-0122: Data Forms Validation'
|
||||
dependencies = set(['xep_0004'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(FormField, FormValidation)
|
||||
93
slixmpp/plugins/xep_0122/stanza.py
Normal file
93
slixmpp/plugins/xep_0122/stanza.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from slixmpp.xmlstream import ElementBase, ET
|
||||
|
||||
|
||||
class FormValidation(ElementBase):
|
||||
"""
|
||||
Validation values for form fields.
|
||||
|
||||
Example:
|
||||
|
||||
<field var='evt.date' type='text-single' label='Event Date/Time'>
|
||||
<validate xmlns='http://jabber.org/protocol/xdata-validate'
|
||||
datatype='xs:dateTime'/>
|
||||
<value>2003-10-06T11:22:00-07:00</value>
|
||||
</field>
|
||||
|
||||
Questions:
|
||||
Should this look at the datatype value and convert the range values as appropriate?
|
||||
Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype?
|
||||
"""
|
||||
|
||||
namespace = 'http://jabber.org/protocol/xdata-validate'
|
||||
name = 'validate'
|
||||
plugin_attrib = 'validate'
|
||||
interfaces = {'datatype', 'basic', 'open', 'range', 'regex', }
|
||||
sub_interfaces = {'basic', 'open', 'range', 'regex', }
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def _add_field(self, name):
|
||||
self.remove_all()
|
||||
item_xml = ET.Element('{%s}%s' % (self.namespace, name))
|
||||
self.xml.append(item_xml)
|
||||
return item_xml
|
||||
|
||||
def set_basic(self, value):
|
||||
if value:
|
||||
self._add_field('basic')
|
||||
else:
|
||||
del self['basic']
|
||||
|
||||
def set_open(self, value):
|
||||
if value:
|
||||
self._add_field('open')
|
||||
else:
|
||||
del self['open']
|
||||
|
||||
def set_regex(self, regex):
|
||||
if regex:
|
||||
_regex = self._add_field('regex')
|
||||
_regex.text = regex
|
||||
else:
|
||||
del self['regex']
|
||||
|
||||
def set_range(self, value, minimum=None, maximum=None):
|
||||
if value:
|
||||
_range = self._add_field('range')
|
||||
_range.attrib['min'] = str(minimum)
|
||||
_range.attrib['max'] = str(maximum)
|
||||
else:
|
||||
del self['range']
|
||||
|
||||
def remove_all(self, except_tag=None):
|
||||
for a in self.sub_interfaces:
|
||||
if a != except_tag:
|
||||
del self[a]
|
||||
|
||||
def get_basic(self):
|
||||
present = self.xml.find('{%s}basic' % self.namespace)
|
||||
return present is not None
|
||||
|
||||
def get_open(self):
|
||||
present = self.xml.find('{%s}open' % self.namespace)
|
||||
return present is not None
|
||||
|
||||
def get_regex(self):
|
||||
present = self.xml.find('{%s}regex' % self.namespace)
|
||||
if present is not None:
|
||||
return present.text
|
||||
|
||||
return False
|
||||
|
||||
def get_range(self):
|
||||
present = self.xml.find('{%s}range' % self.namespace)
|
||||
if present is not None:
|
||||
attributes = present.attrib
|
||||
return_value = dict()
|
||||
if 'min' in attributes:
|
||||
return_value['minimum'] = attributes['min']
|
||||
if 'max' in attributes:
|
||||
return_value['maximum'] = attributes['max']
|
||||
return return_value
|
||||
|
||||
return False
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0128.extended_disco import XEP_0128
|
||||
|
||||
|
||||
register_plugin(XEP_0128)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0128 = XEP_0128
|
||||
|
||||
145
slixmpp/plugins/xep_0138.py
Normal file
145
slixmpp/plugins/xep_0138.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import zlib
|
||||
|
||||
|
||||
from slixmpp.stanza import StreamFeatures
|
||||
from slixmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase
|
||||
from slixmpp.xmlstream.matcher import *
|
||||
from slixmpp.xmlstream.handler import *
|
||||
from slixmpp.plugins import BasePlugin, register_plugin
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Compression(ElementBase):
|
||||
name = 'compression'
|
||||
namespace = 'http://jabber.org/features/compress'
|
||||
interfaces = set(('methods',))
|
||||
plugin_attrib = 'compression'
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
|
||||
def get_methods(self):
|
||||
methods = []
|
||||
for method in self.xml.findall('{%s}method' % self.namespace):
|
||||
methods.append(method.text)
|
||||
return methods
|
||||
|
||||
|
||||
class Compress(StanzaBase):
|
||||
name = 'compress'
|
||||
namespace = 'http://jabber.org/protocol/compress'
|
||||
interfaces = set(('method',))
|
||||
sub_interfaces = interfaces
|
||||
plugin_attrib = 'compress'
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
|
||||
def setup(self, xml):
|
||||
StanzaBase.setup(self, xml)
|
||||
self.xml.tag = self.tag_name()
|
||||
|
||||
|
||||
class Compressed(StanzaBase):
|
||||
name = 'compressed'
|
||||
namespace = 'http://jabber.org/protocol/compress'
|
||||
interfaces = set()
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
|
||||
def setup(self, xml):
|
||||
StanzaBase.setup(self, xml)
|
||||
self.xml.tag = self.tag_name()
|
||||
|
||||
|
||||
|
||||
|
||||
class ZlibSocket(object):
|
||||
|
||||
def __init__(self, socketobj):
|
||||
self.__socket = socketobj
|
||||
self.compressor = zlib.compressobj()
|
||||
self.decompressor = zlib.decompressobj(zlib.MAX_WBITS)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__socket, name)
|
||||
|
||||
def send(self, data):
|
||||
sentlen = len(data)
|
||||
data = self.compressor.compress(data)
|
||||
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
|
||||
log.debug(b'>>> (compressed)' + (data.encode("hex")))
|
||||
#return self.__socket.send(data)
|
||||
sentactuallen = self.__socket.send(data)
|
||||
assert(sentactuallen == len(data))
|
||||
|
||||
return sentlen
|
||||
|
||||
def recv(self, *args, **kwargs):
|
||||
data = self.__socket.recv(*args, **kwargs)
|
||||
log.debug(b'<<< (compressed)' + data.encode("hex"))
|
||||
return self.decompressor.decompress(self.decompressor.unconsumed_tail + data)
|
||||
|
||||
|
||||
class XEP_0138(BasePlugin):
|
||||
"""
|
||||
XEP-0138: Compression
|
||||
"""
|
||||
name = "xep_0138"
|
||||
description = "XEP-0138: Compression"
|
||||
dependencies = set(["xep_0030"])
|
||||
|
||||
def plugin_init(self):
|
||||
self.xep = '0138'
|
||||
self.description = 'Stream Compression (Generic)'
|
||||
|
||||
self.compression_methods = {'zlib': True}
|
||||
|
||||
register_stanza_plugin(StreamFeatures, Compression)
|
||||
self.xmpp.register_stanza(Compress)
|
||||
self.xmpp.register_stanza(Compressed)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Compressed',
|
||||
StanzaPath('compressed'),
|
||||
self._handle_compressed,
|
||||
instream=True))
|
||||
|
||||
self.xmpp.register_feature('compression',
|
||||
self._handle_compression,
|
||||
restart=True,
|
||||
order=self.config.get('order', 5))
|
||||
|
||||
def register_compression_method(self, name, handler):
|
||||
self.compression_methods[name] = handler
|
||||
|
||||
def _handle_compression(self, features):
|
||||
for method in features['compression']['methods']:
|
||||
if method in self.compression_methods:
|
||||
log.info('Attempting to use %s compression' % method)
|
||||
c = Compress(self.xmpp)
|
||||
c['method'] = method
|
||||
c.send(now=True)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _handle_compressed(self, stanza):
|
||||
self.xmpp.features.add('compression')
|
||||
log.debug('Stream Compressed!')
|
||||
compressed_socket = ZlibSocket(self.xmpp.socket)
|
||||
self.xmpp.set_socket(compressed_socket)
|
||||
raise RestartStream()
|
||||
|
||||
def _handle_failure(self, stanza):
|
||||
pass
|
||||
|
||||
xep_0138 = XEP_0138
|
||||
register_plugin(XEP_0138)
|
||||
@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0184.receipt import XEP_0184
|
||||
|
||||
|
||||
register_plugin(XEP_0184)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0184 = XEP_0184
|
||||
|
||||
@@ -32,7 +32,7 @@ class Request(ElementBase):
|
||||
|
||||
def get_request_receipt(self):
|
||||
parent = self.parent()
|
||||
if parent.find("{%s}request" % self.namespace) is not None:
|
||||
if parent.xml.find("{%s}request" % self.namespace) is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -63,7 +63,7 @@ class Received(ElementBase):
|
||||
|
||||
def get_receipt(self):
|
||||
parent = self.parent()
|
||||
xml = parent.find("{%s}received" % self.namespace)
|
||||
xml = parent.xml.find("{%s}received" % self.namespace)
|
||||
if xml is not None:
|
||||
return xml.attrib.get('id', '')
|
||||
return ''
|
||||
|
||||
@@ -33,7 +33,7 @@ class XEP_0186(BasePlugin):
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq.enable('invisible')
|
||||
iq.send(callback=callback, timeout=timeout)
|
||||
return iq.send(callback=callback, timeout=timeout)
|
||||
|
||||
def set_visible(self, ifrom=None, callback=None,
|
||||
timeout=None):
|
||||
@@ -41,4 +41,4 @@ class XEP_0186(BasePlugin):
|
||||
iq['type'] = 'set'
|
||||
iq['from'] = ifrom
|
||||
iq.enable('visible')
|
||||
iq.send(callback=callback, timeout=timeout)
|
||||
return iq.send(callback=callback, timeout=timeout)
|
||||
|
||||
@@ -99,7 +99,7 @@ class StreamManagement(ElementBase):
|
||||
interfaces = set(['required', 'optional'])
|
||||
|
||||
def get_required(self):
|
||||
return self.find('{%s}required' % self.namespace) is not None
|
||||
return self.xml.find('{%s}required' % self.namespace) is not None
|
||||
|
||||
def set_required(self, val):
|
||||
self.del_required()
|
||||
@@ -110,7 +110,7 @@ class StreamManagement(ElementBase):
|
||||
self._del_sub('required')
|
||||
|
||||
def get_optional(self):
|
||||
return self.find('{%s}optional' % self.namespace) is not None
|
||||
return self.xml.find('{%s}optional' % self.namespace) is not None
|
||||
|
||||
def set_optional(self, val):
|
||||
self.del_optional()
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import threading
|
||||
import collections
|
||||
|
||||
from slixmpp.stanza import Message, Presence, Iq, StreamFeatures
|
||||
@@ -70,15 +70,10 @@ class XEP_0198(BasePlugin):
|
||||
return
|
||||
|
||||
self.window_counter = self.window
|
||||
self.window_counter_lock = threading.Lock()
|
||||
|
||||
self.enabled = threading.Event()
|
||||
self.enabled = False
|
||||
self.unacked_queue = collections.deque()
|
||||
|
||||
self.seq_lock = threading.Lock()
|
||||
self.handled_lock = threading.Lock()
|
||||
self.ack_lock = threading.Lock()
|
||||
|
||||
register_stanza_plugin(StreamFeatures, stanza.StreamManagement)
|
||||
self.xmpp.register_stanza(stanza.Enable)
|
||||
self.xmpp.register_stanza(stanza.Enabled)
|
||||
@@ -161,7 +156,7 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
def session_end(self, event):
|
||||
"""Reset stream management state."""
|
||||
self.enabled.clear()
|
||||
self.enabled = False
|
||||
self.unacked_queue.clear()
|
||||
self.sm_id = None
|
||||
self.handled = 0
|
||||
@@ -171,15 +166,15 @@ class XEP_0198(BasePlugin):
|
||||
def send_ack(self):
|
||||
"""Send the current ack count to the server."""
|
||||
ack = stanza.Ack(self.xmpp)
|
||||
with self.handled_lock:
|
||||
ack['h'] = self.handled
|
||||
ack['h'] = self.handled
|
||||
self.xmpp.send_raw(str(ack))
|
||||
|
||||
def request_ack(self, e=None):
|
||||
"""Request an ack from the server."""
|
||||
req = stanza.RequestAck(self.xmpp)
|
||||
self.xmpp.send_queue.put(str(req))
|
||||
self.xmpp.send_raw(str(req))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _handle_sm_feature(self, features):
|
||||
"""
|
||||
Enable or resume stream management.
|
||||
@@ -196,13 +191,21 @@ class XEP_0198(BasePlugin):
|
||||
return False
|
||||
if not self.sm_id:
|
||||
if 'bind' in self.xmpp.features:
|
||||
self.enabled.set()
|
||||
enable = stanza.Enable(self.xmpp)
|
||||
enable['resume'] = self.allow_resume
|
||||
enable.send()
|
||||
self.enabled = True
|
||||
self.handled = 0
|
||||
elif self.sm_id and self.allow_resume:
|
||||
self.enabled.set()
|
||||
self.unacked_queue.clear()
|
||||
|
||||
waiter = Waiter('enabled_or_failed',
|
||||
MatchMany([
|
||||
MatchXPath(stanza.Enabled.tag_name()),
|
||||
MatchXPath(stanza.Failed.tag_name())]))
|
||||
self.xmpp.register_handler(waiter)
|
||||
result = yield from 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)
|
||||
resume['h'] = self.handled
|
||||
resume['previd'] = self.sm_id
|
||||
@@ -216,7 +219,7 @@ class XEP_0198(BasePlugin):
|
||||
MatchXPath(stanza.Resumed.tag_name()),
|
||||
MatchXPath(stanza.Failed.tag_name())]))
|
||||
self.xmpp.register_handler(waiter)
|
||||
result = waiter.wait()
|
||||
result = yield from waiter.wait()
|
||||
if result is not None and result.name == 'resumed':
|
||||
return True
|
||||
return False
|
||||
@@ -250,7 +253,7 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
Raises an :term:`sm_failed` event.
|
||||
"""
|
||||
self.enabled.clear()
|
||||
self.enabled = False
|
||||
self.unacked_queue.clear()
|
||||
self.xmpp.event('sm_failed', stanza)
|
||||
|
||||
@@ -262,21 +265,24 @@ class XEP_0198(BasePlugin):
|
||||
if ack['h'] == self.last_ack:
|
||||
return
|
||||
|
||||
with self.ack_lock:
|
||||
num_acked = (ack['h'] - self.last_ack) % MAX_SEQ
|
||||
num_unacked = len(self.unacked_queue)
|
||||
log.debug("Ack: %s, Last Ack: %s, " + \
|
||||
"Unacked: %s, Num Acked: %s, " + \
|
||||
"Remaining: %s",
|
||||
ack['h'],
|
||||
self.last_ack,
|
||||
num_unacked,
|
||||
num_acked,
|
||||
num_unacked - num_acked)
|
||||
for x in range(num_acked):
|
||||
seq, stanza = self.unacked_queue.popleft()
|
||||
self.xmpp.event('stanza_acked', stanza)
|
||||
self.last_ack = ack['h']
|
||||
num_acked = (ack['h'] - self.last_ack) % MAX_SEQ
|
||||
num_unacked = len(self.unacked_queue)
|
||||
log.debug("Ack: %s, Last Ack: %s, " + \
|
||||
"Unacked: %s, Num Acked: %s, " + \
|
||||
"Remaining: %s",
|
||||
ack['h'],
|
||||
self.last_ack,
|
||||
num_unacked,
|
||||
num_acked,
|
||||
num_unacked - num_acked)
|
||||
if num_acked > len(self.unacked_queue) or num_acked < 0:
|
||||
log.error('Inconsistent sequence numbers from the server,'
|
||||
' ignoring and replacing ours with them.')
|
||||
num_acked = len(self.unacked_queue)
|
||||
for x in range(num_acked):
|
||||
seq, stanza = self.unacked_queue.popleft()
|
||||
self.xmpp.event('stanza_acked', stanza)
|
||||
self.last_ack = ack['h']
|
||||
|
||||
def _handle_request_ack(self, req):
|
||||
"""Handle an ack request by sending an ack."""
|
||||
@@ -284,30 +290,27 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
def _handle_incoming(self, stanza):
|
||||
"""Increment the handled counter for each inbound stanza."""
|
||||
if not self.enabled.is_set():
|
||||
if not self.enabled:
|
||||
return stanza
|
||||
|
||||
if isinstance(stanza, (Message, Presence, Iq)):
|
||||
with self.handled_lock:
|
||||
# Sequence numbers are mod 2^32
|
||||
self.handled = (self.handled + 1) % MAX_SEQ
|
||||
# Sequence numbers are mod 2^32
|
||||
self.handled = (self.handled + 1) % MAX_SEQ
|
||||
return stanza
|
||||
|
||||
def _handle_outgoing(self, stanza):
|
||||
"""Store outgoing stanzas in a queue to be acked."""
|
||||
if not self.enabled.is_set():
|
||||
if not self.enabled:
|
||||
return stanza
|
||||
|
||||
if isinstance(stanza, (Message, Presence, Iq)):
|
||||
seq = None
|
||||
with self.seq_lock:
|
||||
# Sequence numbers are mod 2^32
|
||||
self.seq = (self.seq + 1) % MAX_SEQ
|
||||
seq = self.seq
|
||||
# Sequence numbers are mod 2^32
|
||||
self.seq = (self.seq + 1) % MAX_SEQ
|
||||
seq = self.seq
|
||||
self.unacked_queue.append((seq, stanza))
|
||||
with self.window_counter_lock:
|
||||
self.window_counter -= 1
|
||||
if self.window_counter == 0:
|
||||
self.window_counter = self.window
|
||||
self.request_ack()
|
||||
self.window_counter -= 1
|
||||
if self.window_counter == 0:
|
||||
self.window_counter = self.window
|
||||
self.request_ack()
|
||||
return stanza
|
||||
|
||||
@@ -13,8 +13,3 @@ from slixmpp.plugins.xep_0199.ping import XEP_0199
|
||||
|
||||
|
||||
register_plugin(XEP_0199)
|
||||
|
||||
|
||||
# Backwards compatibility for names
|
||||
xep_0199 = XEP_0199
|
||||
xep_0199.sendPing = xep_0199.send_ping
|
||||
|
||||
@@ -95,18 +95,20 @@ class XEP_0199(BasePlugin):
|
||||
self.timeout = timeout
|
||||
|
||||
self.keepalive = True
|
||||
handler = lambda event=None: asyncio.ensure_future(self._keepalive(event))
|
||||
self.xmpp.schedule('Ping keepalive',
|
||||
self.interval,
|
||||
self._keepalive,
|
||||
handler,
|
||||
repeat=True)
|
||||
|
||||
def disable_keepalive(self, event=None):
|
||||
self.xmpp.cancel_schedule('Ping keepalive')
|
||||
|
||||
@asyncio.coroutine
|
||||
def _keepalive(self, event=None):
|
||||
log.debug("Keepalive ping...")
|
||||
try:
|
||||
rtt = self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
|
||||
rtt = yield from self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
|
||||
except IqTimeout:
|
||||
log.debug("Did not recieve ping back in time." + \
|
||||
"Requesting Reconnect.")
|
||||
@@ -172,8 +174,7 @@ class XEP_0199(BasePlugin):
|
||||
|
||||
log.debug('Pinging %s' % jid)
|
||||
try:
|
||||
yield from self.send_ping(jid, ifrom=ifrom, timeout=timeout,
|
||||
coroutine=True)
|
||||
yield from self.send_ping(jid, ifrom=ifrom, timeout=timeout)
|
||||
except IqError as e:
|
||||
if own_host:
|
||||
rtt = time.time() - start
|
||||
|
||||
@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0202.time import XEP_0202
|
||||
|
||||
|
||||
register_plugin(XEP_0202)
|
||||
|
||||
|
||||
# Retain some backwards compatibility
|
||||
xep_0202 = XEP_0202
|
||||
|
||||
@@ -96,3 +96,4 @@ class XEP_0202(BasePlugin):
|
||||
iq['from'] = ifrom
|
||||
iq.enable('entity_time')
|
||||
return iq.send(**iqargs)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user